The problem arises from the fact that I have some data in backend which when presented has to be formed in different way than how they are stored, or at least this is my conception after reading some guides and docs about emberjs so I want to know what would an ember profi do.
To be concrete, on the backend I have a data structure which presents days (with full date like 2014-07-16) and have a bunch of data attached to each day.
On the client however I need to present the data structured under years, weeks and then day.
So I setup some nested route like this:
WorkTime.Router.map(function() {
this.resource('year', {
path: '/:year'
},
function() {
this.resource('week', {
path: '/:week'
},
function() {
this.resource('day', {
path: '/:day'
},
function() {
});
});
});
});
This implies the right structure that I need for the sake of presentation. Now the question is how should I approach toward needed models?
One solution that I can think of is to define the models for year, week and day. The first two are not stored on server (as they are really presentation only) and day has some computed attributes (like year and weeknumber) which then gives the right binding to year and week models. This however means that each time application starts, has to receive the days' records, and generate local records for year and week.
Im not sure if this is even supported in ember.
Is this a proper approach? would an ember profi do it?
Just to note: I'm using ember 1.6.0 with ember data 1.0.0 beta 8.
I have a data structure which presents days (with full date like 2014-07-16) and have a bunch of data attached to each day
Let's say we are storing log entries. I would only have one log DS model, one server API /logs and one Ember Router & Controller. Server will return records for all dates in one large list (each day entry is a separate object).
Then you can narrow your search for specific period of time by adding some filter refinement on a controller, or, even better by using Query Params (please note that this is still beta).
After you have loaded correct entries you can display them, grouping by day. Have a look at this answer regarding grouping results: Ember.js Grouping Results
— From discussion on Ember Forum http://discuss.emberjs.com/t/help-me-to-get-the-right-mindset/5919
Related
I'm new to ember and exploring its capabilities by building a small module. I came across a scenario where I need to update the ember model content synchronously. The ember-data model contains an array of objects as contents.
I was hoping to perform a few tasks as follows
Perform an array content reorder - for the sake of simplicity we
can assume swapping the first and last item.
Append a record
without a network call
Delete a record without a network call.
Doing these should automatically sync the data bindings/computed props
My data model after a peekAll call contains 10 records(shown below) on which I need to perform the above operations.
My model is as shown below
export default Model.extend({
testId: attr('number'),
name: attr('string')
});
What is the right approach to update the content record? Could someone please suggest how to proceed?
This looks to me like the results of running something like let arr = await store.findAll('test-model'), is that correct? This is probably a PromiseArray and you can access the data as a Javascript Array by calling arr.slice() on it. This will let you do normal array operations, though performing a content re-order doesn't really make much sense in this scenario. I assume you were using it as an example.
For adding and removing records without a network call you can do that by going back to the store and this is what is covered in the docs, you don't need to act on this Object you're looking at.
Adding a new record:
let testModel = store.createRecord('test-model', {
name: 'Lorem ipsum'
});
testModel.save(); //until you do this no network data will be sent
Removing a record:
let testModel = store.peekRecord('testModel', 1); //to get a record with ID of 1
testModel.deleteRecord();
testModel.save(); //until you run save no network is sent
Once you've taken action like this on the store the data structure you posted above may be updated to contain the new data depending on how you accessed it originally. You can also re-fetch data from the store which will now know about your adding a deleting of models (even though you haven't saved it back to the server yet)
If you haven't saved yet and you re-do a peekRecord you'll need to filter out any deleted records from the results.
let undeletedModels = this.store.peekAll('test-model').filter(m => !m.isDeleted);
At the moment, when an article is added to the store, my view is not updated when I use store.query(), filtering server side, in my route but it's updated when I use store.findAll() with filtering client side.
With findAll, filtering client side
//route.js
model() {
return this.get('store').findAll('article');
}
//controller.js
articleSorted: computed.filterBy('model', 'isPublished', true),
and with query filtering server side
//route.js
model() {
return this.get('store').query('article', { q: 'isPublished' }),
}
The fact is that findAll is reloading and query is not.
I've found this but did not understand
https://github.com/emberjs/ember.js/issues/15256
thanks for the question. I'll try to answer it the best I can but it would seem like some more documentation should be added to the Ember Guides to explain this situation 🤔
Essentially this.store.findAll() and this.store.query() do two very different things. findAll() is designed to be a representation of all of the entities (articles in your case) so it makes sense that the result will automatically update as the store finds more articles it should care about. It does this because it doesn't return an array of articles, it returns a DS.RecordArray that will automatically update.
query() on the other hand is designed to ask the backend every time what it expects the result to be, and you are usually passing a number of parameters to the query() call that the backend is using to find or filter results. It would be impossible for the frontend to know exactly how the backend interprets these query parameters so it is not possible for it to "auto-update" when a new article is added that would satisfy the same query.
Does that make sense? Would you like me to go into any more detail?
When using store.query to fetch data from the server, the view can still be auto-updated with new client-created store data before it's saved to the server, by using a "live" record array for it.
While data from store.query isn't live, data from store.peekAll is, so you can query first but then leverage store.peekAll for display. You can query before setting your model to the peeked data, or keep your query as the model but use some other property of peeked data for display. The important part is to ensure the query is resolved before peeking at the store.
Example based on the current code in your question:
// route.js
beforeModel() {
// using return ensures this hook waits for the promise to resolve before moving on
return this.store.query('article', { q: 'isPublished' });
}
model() {
// Queried server data should now be available to peek at locally,
// so we can set the model to a live version of it. Don't append filterBy here,
// otherwise it will no longer be live.
return this.store.peekAll('article');
}
// controller.js
// seemingly redundant filter since using query, but needed if there are other records
// in the store that shouldn't be displayed, and is recomputed when
// a record is added or removed from the store-based model
articleSorted: filterBy('model', 'isPublished', true) // filterBy imported from '#ember/object/computed'
// template.hbs
{{#each articleSorted as |article|}}
{{!-- this displayed list should update as new records are added to the store --}}
{{article}}
{{/each}}
Note that after a new record is saved to the server, the query can be updated via its update method or via a route refresh. This will re-run the query and get the updated results from the server. If the query is the model, that would look like model.update(). If it was saved to someOtherProperty, then someOtherProperty.update(). In either case, route.refresh() could be used instead to re-run all route hooks.
Some specific comments/examples that I think are helpful:
https://github.com/emberjs/ember.js/issues/15256#issuecomment-302894768
https://github.com/emberjs/ember.js/issues/15256#issuecomment-302906077
https://github.com/pouchdb-community/ember-pouch/issues/232#issuecomment-428927114
I have an array controller ActivitiesController which is responsible for managing a days worth of activities (versus the full set). I've set the model property of the ActivitiesRoute to the following:
return this.store.find('activity', {on_date: params.on_date});
This does pull back the appropriate records and I can see them all within the Ember debugger but for some reason these records aren't available to the template (activities.hbs). That was baffling to me but in reality that isn't the final solution anyway (i had just expected it to work).
What I really want to do is have the controller's content manage an array of local Activity records that have been filtered to the specified date. This filtered array then will periodically update based on asynchronous calls to an update query: find('activity', {on_date: this.get('on_date'), since: this.get('since');.
I hope that description makes sense. I've looked at other queries on SO and there were some promising starts but the answers all seemed to be dated and I could find anything that helped me crack this nut.
In summary, I want to:
Have the controller's content be an active filter on the local Activity records that correspond to a given date.
Be able to asynchronously push additional records onto the model which will automagically show on the controller (assuming they're for the appropriate date).
As an aside, I'd be particularly interested to know why my current model hook isn't available the activities template.
------------- UPDATE -------------
I have tried this in my model hook:
return this.store.filter('activity',function(activity){
var itemDate = new Date(activity.get('start_time'));
itemDate = moment(itemDate).format('YYYY-MM-DD');
return itemDate === params.on_date;
});
This should be inline with #Bradley's suggestion. I needed to truncate the time component so I added that logic using the moment.js library. This doesn't throw any errors but also doesn't pull any records from the server by itself so I added the following line of code into the model hook:
this.set('activities', this.store.find('activity', {on_date: params.on_date}));
Using the debugger I can see that the ActivityRoute has an activities property that is set as a DS.PromiseArray and I can see that the activities property has a length of 15 which is the number of activities for that day.
It also looks like the currentModel of ActivityRoute is set to a DS.FilteredRecordArray and it too has the appropriate 15 records in it but my handlebars template still has no ability to iterate over it. I assume this is because the content property of the ActivityController is not set. Why is this step not done by Ember in this situation? Do I need to force this or are there gremlins in the system that need teasing out?
For local filtering you are looking for the DS.Store#filter function.
return this.store.filter('activity', function(activity) {
activity.get('on_date') === params.on_date
});
This returns a DS.FilteredRecordArray that live-updates with new/updated records
We recently made a shift from relational (MySQL) to NoSQL (couchbase). Basically its a back-end for social mobile game. We were facing a lot of problems scaling our backend to handle increasing number of users. When using MySQL loading a user took a lot of time as there were a lot of joins between multiple tables. We saw a huge improvement after moving to couchbase specially when loading data as most of it is kept in a single document.
On the downside, couchbase also seems to have a lot of limitations as far as querying is concerned. Couchbase alternative to SQL query is views. While we managed to handle most of our queries using map-reduce, we are really having a hard time figuring out how to handle time based queries. e.g. we need to filter users based on timestamp attribute. We only need a user in view if time is less than current time:
if(user.time < new Date().getTime() / 1000)
What happens is that once a user's time is set to some future time, it gets exempted from this view which is the desired behavior but it never gets added back to view unless we update it - a document only gets re-indexed in view when its updated.
Our solution right now is to load first x user documents and then check time in our application. Sorting is done on user.time attribute so we get those users who's time is less than or near to current time. But I am not sure if this is actually going to work in live environment. Ideally we would like to avoid these type of checks at application level.
Also there are times e.g. match making when we need to check multiple time based attributes. Our current strategy doesn't work in such cases and we frequently get documents from view which do not pass these checks when done in application. I would really appreciate if someone who has already tackled similar problems could share their experiences. Thanks in advance.
Update:
We tried using range queries which works for only one key. Like I said in most cases we have multiple time based keys meaning multiple ranges which does not work.
If you use Date().getTime() inside a view function, you'll always get the time when that view was indexed, just as you said "it never gets added back to view unless we update it".
There are two ways:
Bad way (don't do this in production). Query views with stale=false param. That will cause view to update before it will return results. But view indexing is slow process, especially if you have > 1 milllion records.
Good way. Use range requests. You just need to emit your date in map function as a key or a part of complex key and use that range request. You can see one example here or here (also if you want to use DateTime in couchbase this example will be more usefull). Or just look to my example below:
I.e. you will have docs like:
doc = {
"id"=1,
"type"="doctype",
"timestamp"=123456, //document update or creation time
"data"="lalala"
}
For those docs map function will look like:
map = function(){
if (doc.type === "doctype"){
emit(doc.timestamp,null);
}
}
And now to get recently "updated" docs you need to query this view with params:
startKey="dateTimeNowFromApp"
endKey="{}"
descending=true
Note that startKey and endKey are swapped, because I used descending order. Here is also a link to documnetation about key types that couchbase supports.
Also I've found a link to a question that can also help.
I am building a dashboard application for an accounting department.
The user will select a month, and see all the companies that meet certain criteria in that month. The routes will be
/:month_id a summary page
/:month_id/companies more details about each company
/:month_id/companies/:company_id all details about a single company
Ideally I'd have a month model which hasMany company models. The only problem is, companies will show up in multiple months.
Correct me if I'm wrong, but doesn't this mean that if a company is already in the store, when a new month is requested, it will take that company's data from the store instead of from the server? The data will be different for each month, so this wouldn't work for me.
I suppose in this application each company's id is really their normal, integer id plus the selected month. So one way around this would be to give each company an id like '15-Mar-2013'.
Alternatively, I could just pass each month's companies data through raw and do transformations on the plain array. The problem with this, though, is that I'll have to calculate aggregates on companies (in addition to the entire month), and it would be very nice to keep those two things separate. If I had a completely separate Company model, I could just go to town within the model:
App.Company = DS.Model.extend({
month: DS.belongsTo('App.Month'),
name: DS.attr('string'),
contracts: DS.hasMany('App.Contract'),
totalRevenue: function() {
return this.get('contracts')
.filterProperty('type', 'newSetup')
.getEach('feeChange').reduce(function(accum, item) {
return accum + item;
}, 0);
}.property('contracts.#each.feeChange'),
...additional aggregation functions
});
What do you think?
It doesn't make any sense to me that months own companies, which is what your month[1]->[N]company relationship indicates.
What you want to say is that you're looking for all companies that have certain criteria that occur within a month. The month chosen is just part of the search criteria.