I have declared a properties in a Mirage factory as below and using Dependent attributes as found in the docs.
price() {
return faker.finance.amount(100000, null, 0);
},
priceDisplay() {
return '$' + this.price;
}
When I do a patch to update price I expect priceDisplay to update as well like a computed property however this is not the case.
Does anyone know if this is possible or is this a Mirage limitation?
Mirage factory are meant to generate testing data:
Factories are classes that help you organize your data-creation logic, making it easier to define different server states during development or within tests.
The factory is only run once by server.create('foo') or server.createList('foo', 10) to create initial data for the records. This helps you to avoid code duplication in tests and scenarios. But it's not a model representing that record.
Actually Mirage does not support something like computed properties out of the box. But you could achieve it by customizing the serializer used. Overriding the serialize method should do the trick:
// serializers/product.js
import { JSONAPISerializer } from 'ember-cli-mirage';
export default JSONAPISerializer.extend({
// This is how to call super, as Mirage borrows [Backbone's implementation of extend](http://backbonejs.org/#Model-extend)
let json = Serializer.prototype.serialize.apply(this, arguments);
json.priceDisplay = '$' + json.price;
return json;
});
But from your example given I would question if returning a formatted string from the API is the right approach. Formatting data should be a concern of the client in my opinion. Otherwise you will quickly run into limitations if you need to support localization or require different formats in your client.
Related
I set up a simple Ember Twiddle to show you my error that is occurring when trying to update a model.
It's considerable that I'm using ember-cli-mirage for mocking the data.
According to the docs, I created a shorthand route that should handle the PUT request.
It does, but with the error: Your handler for the url /api/shops/1 threw an error: Cannot convert undefined or null to object
When using the JSONAPISerializer, everything is working with shorthands (mirage/config.js) and I'm able to update models, but in my case I have to use the RESTSerializer with serialized IDs in the responses.
The request payload when I'm sending the model's attrs are without Id at the end of the property name, f.e.:
// attrs object in PUT request
{
name: "Shop 1",
city: "1" // belongsTo relationship,
}
Now Mirage is trying to find those properties on the respective database model that has to be updated, but cannot find it, because in the database it's cityId and not just city...
I also found this issue report and it’s working, but I was hoping I could avoid something like this. As far as I can remember, in previous versions of ember-cli-mirage (v0.1.x) it was also not needed to override the normalize method in the serializer to be able to make use of the RestSerializer with serializedIds…
My question is:
Is there a way to stick to shorthand route handlers only, or do I really have to write a helper or other custom solution only because I have to use the RestSerializer?
That would be really sad, but at least I would know then.
Thanks for your support!
Short answer: it looks like you need the custom serializer for now until the bug fix for it is merged.
Long answer: that issue looks to be an issue that occurred in the 0.2 -> 0.3 upgrade for Mirage, likely because of underlying DB changes made in Mirage. It'll probably get fixed, but for now you'll need to work around it.
On my route im requesting via ember-data some records. Lets say the model-type is 'item'.
model: function(){
return this.get('store').find('item');
}
now ive got a component named 'my-foo' which should use the records to do something with the data. Therefore Im calling the component like that:
{{my-foo myItems=model}}
in my routes template. In the components js part, Im trying to get the myItems-field and iterate over them.
this.get('myItems').forEach(...);
Unfortunalety its not clear for me if the model i want to overgive to the component is an collection from records or just a single record (since on some routes the model is the result of store.find('item') on other store.find('item', 23424).
How can I check what kind of data arrives in the component.
(Also Im wondering what kind of object is it since im using ember-data. Is it a DS.recordarray or a promise or something else at this time?)
I can see two solutions to the problem:
Making component aware of the form that model receives
Checking and/or adjusting data type in component (in my opinion better default scenario)
As for making component aware - you could go with 2 approaches. Either differentiate in a way how your component take arguments, so there could be:
{{my-foo myItems=model}} - when you expect to receive multiple items
{{my-foo item=model}} - when you expect to receive single one
And then work accordingly further on, or - the second approach - is to actually split component (while extracting shared part to a different structure) so you would have my-foo for single items and my-foo-array for multiple.
Advantage of this approach is that you don't deal with what-if-multiple logic, that might grow to something unmanagable later on, yet usage of it is dependant on project requirements.
As for checking and/or adjusting - you already have data in, so could make assumption that your data is dirty and sanitize it using computed property. Below example, where single item is wrapped into an array.
export default Ember.Component.extend({
sanitizedItems: Ember.computed('items', function() {
var items = this.get('items');
if(!Array.isArray(items)) {
return [items];
} else {
return items;
}
})
});
Since you're using Ember.Data, depending on your setup, you might get a promise instead of object/array. In this case, you might want to resolve promise using this.get('items').then(function(items) { ... }) before doing sanitization, yet the idea behind is exactly the same.
You can check full example: Gist, Twiddle
Let's say that I have an Ember.Data query I'd like to make:
this.store.find('items', {itemIds: [1,2,3]});
By default, Ember.Data creates a URL that looks like this:
items?itemIds%5B%5D=1&itemIds%5B%5D=2&itemIds%5B%5D=3
But the REST api I am connecting to wants it in this format:
items?itemIds=1&itemIds=2&itemIds=3
How do I achieve this adaptation?
Extend the RESTAdapter and override the ajax method and create the URL that you want to use based on the circumstances.
App.ItemsAdapter = DS.RESTAdapter.extend({
ajax: function(url, type, options){
if(myCircumstance){
var data = options.data;
delete options.data;
url = url + ......;
}
return this._super(url, type, options);
}
});
REST Adapter implementation: https://github.com/emberjs/data/blob/v1.0.0-beta.16.1/packages/ember-data/lib/adapters/rest-adapter.js
From what I see looking on the ember data code, you'd have to overwrite the RestAdapter's findQuery or ajax method, see http://emberjs.com/api/data/classes/DS.RESTAdapter.html#method_findMany (see https://github.com/emberjs/data/blob/v1.0.0-beta.16.1/packages/ember-data/lib/adapters/rest-adapter.js). Both are private, but the store expects the findQuery to be there (https://github.com/emberjs/data/blob/v1.0.0-beta.16.1/packages/ember-data/lib/system/store/finders.js#L137), so I wouldn't expect this behaviour to change soon.
If you use this for production, you'd better open a bug report to have this or something similar exposed as a public hook, as I cannot see one being there thus far.
#Kingpin2k's answer is the right direction, but the solution is even simpler. To create the query params, Ember Data simply yields the data object, wrapped in the options object, to the jQuery.ajax function.
Knowing that, we just need another query param serializer. By default, jQuery will serialize arrays the way TS described. You can change the way of serialization by overriding the $.param method. But luckily we don't even have too, because the $.param function has a second argument called traditional serialization. If set to true, the serialization will be like TS wanted*.
The jQuery.Ajax function also has the traditional flag to use the traditional style of param serialization. Combining this facts, you just need to set this flag yourself:
DS.RESTAdapter.extend({
ajax(url, type, options) {
if (options) {
options.traditional = true;
}
return this._super(...arguments);
}
});
P.S. If you use the JSONAPIAdapter, the trick is the same, because the JSONAPIAdapter extends the RESTAdapter.
*If you need another serialization, you need to override the $.param.
I have a backend resource that contains user activities and in the application I would like to present activities based on a single day's worth of activities. I have an ArrayController called ActivitiesController defined in the router like this:
this.resource('activities', { path: '/activities/:by_date' }, function() {
this.route('new');
});
The REST API provides the following GET method:
GET /activities/[by_date]
So far this looks pretty symmetrical and achievable but I'm running into two problems:
Parameterized array find. Typically a parameterized route would be serviced by a ObjectController but in this case the by_date parameter simply reduces/filters the array of activities but it's still an array that's returned. I'm not sure how to structure this in the model hook in the ActivitiesRoute so that its effectively doing a "findAll" rather than expecting a singular resultset.
Since functionality. As there is a reasonable network cost in bringing back these arrays of activities I would like to minimize this as much as possible and the REST API supports this by allowing for a since parameter to be passed along with the date of the last request. This way the server simply responds with a 304 code if no records have been updated since the last call and if there are new records only the new records are returned. Is there anyway to get this "out of the box" with ember-data? Does this require building a custom Adaptor? If so, are there any open source solutions that are available?
p.s. I was thinking that part of the answer to #2 might be to incorporate Alex Speller's Query Parameters: http://discuss.emberjs.com/t/query-string-support-in-ember-router/1962/48
What does your route's model hook look like? I am thinking something like this should work:
model: function(params) {
return this.store.find('activity', { by_date: params.by_date });
}
The cloudant RESTful API is fairly simple but doesn't match the way ember-data expects things to be. How can I customize or create an Adapter that deals with these issues...
In my specific case I only want to load records from one of several secondary indexes (ie. MapReduce fnctions).
The URL for this is below, where [name] and [view] would change depending on user selection or the route I am in.
https://[username].cloudant.com/[db_name]/_design/[name]/_view/[view]
Looking at the ember-data source there doesn't seem to be an easy way of defining URLs like this. I took a look at findQuery and it expects to send any variables through as url params not as part of the actual URL itself.
Am I missing something? Is there an obvious way of dealing with this?
Then the data comes back in a completely different format, is there a way to tell ember what this format is?
Thanks!
I had similar problem where URL's are dynamic. I ended up creating my own adapater by extending DS.RESTAdapter and overriding the default buildURL method. For example:
App.MyAdapter = DS.RESTAdapter.extend({
buildURL: function(record, suffix) {
var username, db_name, name, view;
// Do your magic and fill the variables
return 'https://'+username+'.cloudant.com/'+db_name+'/_design/'+name+'/_view/'+view;
}
});
I ended up also defining my own find, findAll, findQuery, createRecord, updateRecord, deleteRecord etc. methods as I had to pass more variables to buildURL method.
If returning data is in different format then you can also write your own serializer by extending DS.JSONSerializer and define your own extraction methods extract, extractMany etc.
You should evaluate how well your API follows the data format required by ember/data RESTAdapter. If it is very different then it's maybe better to use some other component for communication like ember-model, ember-restless, emu etc, as ember-data is not very flexible (see this blog post). You can also write your own ajax queries directly from routes model hooks without using ember-data or other components at all. It is not very hard to do that.