How to send an array to a store to initialize in store with Vuex? - store

I am building a front-end application and I started to use vuex framework with vue front-end library. I have a state and I want to fill it by initial values, sending values with an array. My question is, How can I initialize state ? Do I need to follow action-mutation path for that ? Or just directly manipulate state as a start up only

A typical store is like this:
const store = new Vuex.Store({
state: {
foo: [],
bar: 1
}
});
Just initialize the values there inside the state property.
However, if you want to initialize your state repeatedly, you can do it with a mutation:
const store = new Vuex.Store({
state: {
foo: [],
bar: 1
},
mutations: {
initialization(state) {
state.foo = [];
state.bar = 1;
}
}
});

Related

How do I select specific column data to be displayed in my bookshelf model belongsTo relationship in Nodejs?

This is a contrived example of what I would like to do:
Suppose I have a database of teams and players:
team:
->id
->color
->rank
->division
player:
->id
->team_id
->number
->SECRET
And the following bookshelf models:
var Base = require('./base');
const Player = Base.Model.extend(
{
tableName: "players",
},
nonsecretdata: function() {
return this.belongsTo('Team')
},
{
fields: {
id: Base.Model.types.integer,
team_id: Base.Model.types.integer,
number: Base.Model.types.integer,
SECRET: Base.Model.types.string,
}
}
);
module.exports = Base.model('Player', Player);
And
var Base = require('./base');
const Team = Base.Model.extend(
{
tableName: "teams",
},
{
fields: {
id: Base.Model.types.integer,
color: Base.Model.types.string,
rank: Base.Model.types.integer,
division: Base.Model.types.string,
}
}
);
module.exports = Base.model('Team', Team);
My question is, how can I limit the scope of player such that SECRET is not grabbed by calls to join player and team with callback nonsecretdata?
I am new to Bookshelf so if any other information is needed, please let me know. Thank you
++++++++++
Edit: Do I need to create a separate model?
The only way to do this using bookshelf would be to delete the individual fields from the object after fetching the entire model.
A potentially better solution for this use case would be to define a custom Data Access Object class that uses a SQL query for the information that would like to be obtained and then use that DOA instead of using bookshelf. That way the SQL code is still abstracted away from the code that is requesting the information and the SECRET or any other potential sensitive information that is added to the table will not be included in the fetch.

Passing a value from database to vue component and assigning it to a variable

I am trying to pass a value from my database and then assign that value to a variable in my Vue Component. This successfully gets the data from the database however i get error when assigning that value to a variable in the component:
"TypeError: Cannot read property 'data' of undefined"
Vue Component:
import TransactionsService from '#/services/TransactionsService'
export default {
components: {
},
data(){
return {
transactions: null,
product: null,
amount: null
}
},
async mounted() {
try{
this.transactions = (await TransactionsService.index()).data.transactions
for( transaction in transactions){
this.amount = transaction.amount
}
console.log(amount)
this.userId = this.$store.state.user.priviledge
} catch(error){
this.error = error.response.data.message
}
}
}
I want to assign the value at transaction.amount to the variable amount
In that for ... in loop, transaction is the item's array index, not the item itself. Here is a more common loop:
// forEach
transactions.forEach(transaction => {
this.amount = transaction.amount
})
This loop will work but still doesn't make sense because you will only keep setting this.amount to the next transaction amount and overwriting the last one. If you intended to add them up you could use:
this.amount += transaction.amount
(Note: It's a good practice to use any other loop type with arrays because index order isn't guaranteed with for ... in. Alternatives are forEach, for, or for ... of)

How to implement a selector in easy: search for Meteor, using React instead of Blaze

I'm trying to follow the documentation and examples to add a server-side selector to a search function in my Meteor app, implemented using the Easy Search plugin. The end goal is to ensure that only documents the user has permission to see are returned by searching.
I can see a selector working in the Leaderboard example, but I can't get it to work in my code.
Versions:
Meteor 1.7.0.1
easy:search#2.2.1
easysearch:components#2.2.2
easysearch:core#2.2.2
I modified the Meteor 'todos' example app to demonstrate the problem, and my demo code is in a repo.
NOTE! to demonstrate the problem, you need to create an account in the demo app, then create a list and make it private. This add the 'userId' field to the list.
Then you can search for the name of the list, by typing in the search box near the top of the main section; search results are written to the browser console.
The first problem is that if I copy the code from the example in the documentation, I see a server error 'searchObject is not defined:
copied from docs, causes an error: imports/api/lists/lists.js
export const MyIndex = new Index({
'collection': Lists,
'fields': ['name'],
engine: new MongoDBEngine({
selector(searchDefinition, options, aggregation) {
// retrieve the default selector
const selector = this.defaultConfiguration()
.selector(searchObject, options, aggregation)
// options.search.userId contains the userId of the logged in user
selector.userId = options.search.userId
return selector
},
}),
});
It seems there is an error in the docs.
Working instead from the leaderboard example, the code below runs but intermittently returns no results. For example if I have a list called "My list", and I type the search term 's', sometimes the list is returned from the search and sometimes it is not. If I use the MiniMongo engine it all works perfectly.
index selector {"$or":[{"name":{"$regex":".*my.*","$options":"i"}}],"userId":"Wtrr5FRHhkKuAcrLZ"}
client and server: imports/api/lists/lists.js
export const MyIndex = new Index({
'collection': Lists,
'fields': ['name'],
'engine': new MongoDBEngine({
selector: function (searchObject, options, aggregation) {
let selector = this.defaultConfiguration().selector(searchObject, options, aggregation);
selector.userId = options.search.userId;
console.log('index selector', JSON.stringify(selector));
return selector;
}
}),
permission: () => {
return true;
},
});
client: imports/ui/components/lists-show.js
Template.Lists_show.events({
'keyup #search'(event) {
console.log('search for ', event.target.value);
const cursor = MyIndex.search(event.target.value);
console.log('count',cursor.count());
console.log('results', cursor.fetch());
},
});
client: imports/ui/components/lists-show.html
<input id="search" type="text" placeholder="search..." />
Edit: I think the problem is that while the Minimongo engine runs on the client, the MongoDBEngine runs on the server and there are timing issues with the results. The docs show using Tracker.autorun, but that's not a natural fit with my React / Redux app. I'll post an answer if I manage to figure something out - I can't be the only person trying to do something like this.
I got it working in my React / Redux / Meteor app. Things to note:
The cursor MyIndex.search(searchTerm) is a reactive data source - you can't just use it as a return value. When searching on the client with MiniMongo this isn't an issue, but it's important when you use MongoDBEngine to search on the server, because it's asynchronous. In React you can wrap the cursor in withTracker to pass data to the component reactively. In Blaze you would use autorun.tracker. This is shown in the docs but not explained, and it took me a while to understand what was happening.
The docs have an error in the selector example, easily corrected but it's confusing if you have other problems in your code.
With MongoDBEngine, 'permission' must be specified - it does not default to 'true'. Without it, you will see no results.
Writing out the default selector object to the console let me see how it's constructed, and then create a new selector that returns MyDocs that are either public or created by the user.
My code is below. In case it helps anybody else, I've shown how to search on tags also, which are objects with a name property stored in a collection Tags. Each MyDoc has a 'tags' property which is an array of tag ids. The selector first searches the Tags collection to find tags whose name matches the search term, then selects docs in MyDocs with the ids of those tags in their doc.tags array.
There may be a better way to find the search term, or to structure the Tags search, but this is what I could get working.
On server and client:
import { Index, MongoDBEngine } from 'meteor/easy:search';
export const MyDocs = new Mongo.Collection('mydocs');
export const Tags = new Mongo.Collection('tags');
export const MyIndex = new Index({
'collection': MyDocs,
'fields': ['name'],
'engine': new MongoDBEngine({
'selector': function (searchObject, options, aggregation) {
const selector = this.defaultConfiguration().selector(searchObject, options, aggregation);
console.log('default selector', selector); // this searches on name only
// find docs by tag as well as by name
const searchTerm = searchObject.name;
const matchingTags = Tags.find({ 'name': { '$regex': searchTerm } }).fetch();
const matchingTagIds = matchingTags.map((tag) => tag._id);
selector.$or.push({ 'tags': { '$in': matchingTagIds } });
const newSelector = {
'$and': [
{
'$or': [
{ 'isPublic': { '$eq': true } },
{ 'createdBy': options.search.userId },
],
},
{
'$or': selector.$or,
},
],
};
return newSelector;
},
'fields': (searchObject, options) => ({
'_id': 1,
'createdBy': 1,
'name': 1,
}),
'sort': () => ({ 'name': 1 }),
}),
'permission': () => true,
});
React component in client only code:
import React from 'react';
import { connect } from 'react-redux';
import { withTracker } from 'meteor/react-meteor-data';
import PropTypes from 'prop-types';
import store from '../modules/store';
import {
getSearchTerm,
searchStart,
} from '../modules/search'; // contains Redux actions and partial store for search
import { MyIndex } from '../../modules/collection';
function Search(props) {
// functional React component that contains the search box
...
const onChange = (value) => {
clearTimeout(global.searchTimeout);
if (value.length >= 2) {
// user has entered a search term
// of at least 2 characters
// wait until they stop typing
global.searchTimeout = setTimeout(() => {
dispatch(searchStart(value)); // Redux action which sets the searchTerm in Redux state
}, 500);
}
};
...
// the component returns html which calls onChange when the user types in the search input
// and a list which displays the search results, accessed in props.searchResults
}
const Tracker = withTracker(({ dispatch }) => {
// searchTerm is saved in Redux state.
const state = store.getState();
const searchTerm = getSearchTerm(state); // Redux function to get searchTerm out of Redux state
let results = [];
if (searchTerm) {
const cursor = MyIndex.search(searchTerm); // search is a reactive data source
results = cursor.fetch();
console.log('*** cursor count', cursor.count());
return {
'searchResults': results,
};
})(Search);
export default connect()(Tracker);

Cannot read property 'map' of undefined with promise all

I was initially just running one query in node.js but I now need two sets of data so I ran two queries and used Promise.all like this:
Promise.all([products, subcats]);
res.status(200).json({
products,
subcats
});
In React I have:
class ProductList extends Component {
state = {
products: []
};
async componentDidMount() {
const catslug = this.props.match.params.catslug;
const { data: products } = await getCatProducts(catslug);
this.setState({ products: products });
}
When I was only running the one query I was running this without any issue:
this.state.products.map(product => (
Now because I have the 2 queries I need to change it to:
this.state.products.products.map(product => (
But as soon as I do that I get the error:
Cannot read property 'map' of undefined
So, I changed it to this and now it works with no errors:
{this.state.products.products &&
this.state.products.products.map(product => (
My question is why did it work before without having to put in the ... && bit but now I have to use it with the Promise.all in node.js?
It's because of the initial shape of your state
state = {
products: []
};
You see this.state.products is already defined as an array, so it can be mapped over (despite being empty). However this.products.products is NOT an array, it is undefined, so trying to map will return the error you are seeing. The line you have entered
{this.state.products.products &&
this.state.products.products.map(product => (
checks that the array exists before attempting to map it (which wont evaluate to true until your async code finishes, and then the array is defined).
An alternative fix would be to set your initial state shape to match your final state shape. i.e
state ={
products:{
products:[]
}
};
Or if you don't want the nested products property you can change
async componentDidMount() {
const catslug = this.props.match.params.catslug;
const { data } = await getCatProducts(catslug);
this.setState({ products: data.products });
}
and return to your old this.state.products.map()
The issue is in the following line:
const { data: products } = await getCatProducts(catslug);
If I understand correctly, when you were sending one value, you were sending it like:
res.status(200).json(products); // an array
But now you are sending an object, which further contains 2 arrays products and subcats.
What you have 2 do is add below changes to make it work:
const obj = await getCatProducts(catslug);
const products = obj.products
const subcats = obj.subcats

loopbackjs: Attach a model to different datasources

I have defined several models that use a Datasource "db" (mysql) for my environment.
Is there any way to have several datasources attached to those models, so I would be able to perform REST operations to different databases?
i.e:
GET /api/Things?ds="db"
GET /api/Things?ds="anotherdb"
GET /api/Things (will use default ds)
As #superkhau pointed above, each LoopBack Model can be attached to a single data-source only.
You can create (subclass) a new model for each datasource you want to use. Then you can either expose these per-datasource models via unique REST URLs, or you can implement a wrapper model that will dispatch methods to the correct datasource-specific model.
In my example, I'll show how to expose per-datasource models for a Car model that is attached to db and anotherdb. The Car model is defined in the usual way via common/models/car.json and common/models/car.js.
Now you need to define per-datasource models:
// common/models/car-db.js
{
"name": "Car-db",
"base": "Car",
"http": {
"path": "/cars:db"
}
}
// common/models/car-anotherdb.js
{
"name": "Car-anotherdb",
"base": "Car",
"http": {
"path": "/cars:anotherdb"
}
}
// server/model-config.json
{
"Car": {
"dataSource": "default"
},
"Car-db": {
"dataSource": "db"
},
"Car-anotherdb": {
"dataSource": "anotherdb"
}
}
Now you have the following URLs available:
GET /api/Cars:db
GET /api/Cars:anotherdb
GET /api/Cars
The solution outlined above has two limitations: you have to define a new model for each datasource and the datasource cannot be selected using a query parameter.
To fix that, you need a different approach. I'll again assume there is a Car model already defined.
Now you need to create a "dispatcher".
// common/models/car-dispatcher.json
{
"name": "CarDispatcher",
"base": "Model", //< important!
"http": {
"path": "/cars"
}
}
// common/models/car-dispatcher.js
var loopback = require('loopback').PersistedModel;
module.exports = function(CarDispatcher) {
Car.find = function(ds, filter, cb) {
var model = this.findModelForDataSource(ds);
model.find(filter, cb);
};
// a modified copy of remoting metadata from loopback/lib/persisted-model.js
Car.remoteMethod('find', {
isStatic: true,
description: 'Find all instances of the model matched by filter from the data source',
accessType: 'READ',
accepts: [
{arg: 'ds', type: 'string', description: 'Name of the datasource to use' },
{arg: 'filter', type: 'object', description: 'Filter defining fields, where, orderBy, offset, and limit'}
],
returns: {arg: 'data', type: [typeName], root: true},
http: {verb: 'get', path: '/'}
});
// TODO: repeat the above for all methods you want to expose this way
Car.findModelForDataSource = function(ds) {
var app = this.app;
var ds = ds && app.dataSources[ds] || app.dataSources.default;
var modelName = this.modelName + '-' + ds;
var model = loopback.findModel(modelName);
if (!model) {
model = loopback.createModel(
modelName,
{},
{ base: this.modelName });
}
return model;
};
};
The final bit is to remove Car and use CarDispatcher in the model config:
// server/model-config.json
{
"CarDispatcher": {
dataSource: null,
public: true
}
}
By default, you can only attach data sources on a per-model basis. Meaning you can attach each model to a different data source via datasources.json.
For your use case, you will to add a remote hook to each endpoint you want for multiple data sources. In your remote hook, you will do something like:
...
var ds1 = Model.app.dataSources.ds1;
var ds2 = Model.app.dataSources.ds2;
//some logic to pick a data source
if (context.req.params...
...
See http://docs.strongloop.com/display/LB/Remote+hooks for more info.
For anyone still looking for a working answer to this, the solution for switching databases on the fly was to write a middleware script that examined the request path and then created a new DataSource connector, passing in a variable based on the req.path variable. For example, if the request path is /orders, then "orders" as a string would be saved in a variable, then we attached a new Datasource, passing in that variable for "orders". Here's the complete working code.
'use strict';
const DataSource = require('loopback-datasource-juggler').DataSource;
const app = require('../server.js');
module.exports = function() {
return function datasourceSelector(req, res, next) {
// Check if the API request path contains one of our models.
// We could use app.models() here, but that would also include
// models we don't want.
let $models = ['offers', 'orders', 'prducts'];
// $path expects to be 'offers', 'orders', 'prducts'.
let $path = req.path.toLowerCase().split("/")[1];
// Run our function if the request path is equal to one of
// our models, but not if it also includes 'count'. We don't
// want to run this twice unnecessarily.
if (($models.includes($path, 0)) && !(req.path.includes('count'))) {
// The angular customer-select form adds a true value
// to the selected property of only one customer model.
// So we search the customers for that 'selected' = true.
let customers = app.models.Customer;
// Customers.find() returns a Promise, so we need to get
// our selected customer from the results.
customers.find({"where": {"selected": true}}).then(function(result){
// Called if the operation succeeds.
let customerDb = result[0].name;
// Log the selected customer and the timestamp
// it was selected. Needed for debugging and optimization.
let date = new Date;
console.log(customerDb, $path+req.path, date);
// Use the existing veracore datasource config
// since we can use its environment variables.
let settings = app.dataSources.Veracore.settings;
// Clear out the veracore options array since that
// prevents us from changing databases.
settings.options = null;
// Add the selected customer to the new database value.
settings.database = customerDb;
try {
let dataSource = new DataSource(settings);
// Attach our models to the new database selection.
app.models.Offer.attachTo(dataSource);
app.models.Order.attachTo(dataSource);
app.models.Prduct.attachTo(dataSource);
} catch(err) {
console.error(err);
}
})
// Called if the customers.find() promise fails.
.catch(function(err){
console.error(err);
});
}
else {
// We need a better solution for paths like '/orders/count'.
console.log(req.path + ' was passed to datasourceSelector().');
}
next();
};
};

Resources