Dasherize properties for JSONAPI - ember.js

I have this model in my drf backend:
class Product:
price_range = ...
I am using EmberData with the JSONApi serializer. I have just found out that JSON API requires dasherized properties. So I need to tell drf:
JSON_API_FORMAT_KEYS = 'dasherize'
And the property gets serialized in the JSON as:
price-range
Then, EmberData does its dance, and I get the Ember model property:
import DS from 'ember-data';
export default DS.Model.extend({
...
priceRange: DS.attr('number'),
...
});
(the old RESTSerializer was expecting priceRange in the JSON, if I recall properly)
So, we go from price_range -> price-range -> priceRange (which is pretty crazy if you ask me). I found all this by trial and error. For drf the corresponding settings are documented here.
Where is this documented for JSONApi, EmberData and Ember? I would like to make sure I have really understood this, and that this is also the case for relationships. The related drf config setting would be:
JSON_API_FORMAT_RELATION_KEYS = 'dasherize'

In the blog post for the Ember-Data 1.13 release the Ember data team writes:
JSONSerializer and RESTSerializer have been refactored and streamlined
to return JSON API payloads.
This means that moving forward Ember Data expects dasherized names in line with JSON API. See the JSON API recommendation for dashes in between words of member names:
Member names SHOULD contain only the characters "a-z" (U+0061 to
U+007A), "0-9" (U+0030 to U+0039), and the hyphen minus (U+002D
HYPHEN-MINUS, "-") as separator between multiple words.
And see some of the example JSON on the JSON API home page:
-"attributes": {
"first-name": "Dan",
"last-name": "Gebhardt",
"twitter": "dgeb"
},
As far as the models are concerned, the most recent documentation for Ember 2.2.0 states:
In Ember Data the convention is to camelize attribute names on a model
And the example model given with mutli-world attribute names is as expected:
export default DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
isPersonOfTheYear: DS.attr('boolean')
});
While there definitely has been churn in the recommended naming conventions, I expect that at this point most of these changes are behind us. The core team has taken notice. I believe in the move to standardize around JSON API so that inter-operable and re-usable tooling can be built, but unfortunately that move came along with these changes.
In summary: Use dasherized names in your JSON and camelized names in your models.
Edit:
It seems that while the Ember Data team went with the JSON API recommendation regarding property names in JSON payloads, it is just that - a recommendation. See this discussion on GitHub regarding the dasherized naming convention for JSON API.

Related

Ember 2 simple polymorphic relations

I have a notes model that I want to attach to one of two other models, customers and suppliers.
In my database I have a foreignType and foreignId field that holds the type and the corresponding ID for the customer or supplier, something like
notes: { {id: 1, body:'bar',foreignType:'customer',foreignId:100},
{id: 2, body:'foo',foreignType:'supplier',foreignId:100}
}
That is, a note can be attached to a customer or a supplier.
The convention seems to be that the field be called noteType?
I have seen a tutorial where the related type was nested in the JSON, rather then being at the root.
My ember models look like this:
//pods/note/model.js
export default DS.Model.extend({
//...
body: DS.attr('string'),
foreign: DS.belongsTo('noteable',{polymorphic:true})
});
//pods/noteable/model.js (is there a better/conventional place to put this file?)
export default DS.Model.extend({
notes: DS.hasMany('note')
});
//pods/customer/model.js
import Noteable from '../noteable/model';
export default Noteable.extend({ //derived from Noteable class
name: DS.attr('string'),
//...
});
//pods/supplier/model.js
// similar to customer
// sample incoming JSON
//
{"customer":{"id":2,"name":"Foobar INC",...},
"contacts":
[{"id":1757,"foreignType": "customer","foreignId":2,...},
{"id":1753,"foreignType": "customer","foreignId":2,...},
...],
...
"todos":
[{"id":1,"foreignType":"customer","foreignId":2,"description":"test todo"}],
"notes":
[{"id":1,"foreignType":"customer","foreignId":2,"body":"Some customer note "}]
}
How to set this up correctly, i.e. what does Ember expect?
My notes aren't attaching correctly to the customer model. They show up in the Data tab of the Ember Inspector, but the notes list of any customer is empty.
I can see several possibilities:
extend customer/supplier from DS.Model and have a property notes: belongsTo('noteable'), that would mean the belongsTo in notes isn't polymorphic, as there wouldn't be any derived classes, only Noteable itself. Not sure if ember (data) can deal with this nesting correctly.
extend from Noteable. what if I want to have other things like addresses or contacts, that can be related to customer or supplier?
create duplicate models like customernote/suppliernote, customercontact/ suppliercontact, customer/supplier/employee address. And have the backend return the filtered table/model name depending on the endpoint. I don't like to repeat myself though ....
Ember : 2.2.0
Ember Data : 2.2.1
I love how Ember doc has explained polymorphism here - https://guides.emberjs.com/v2.13.0/models/relationships/#toc_polymorphism
So, first you need to have a 'type' which will define the model to be used (your data calls it foreignType)
Next, your note model will be the polymorphic model (similar to paymentMethod model in the example above). Let me know in the comment if you need more clarification, but I think if you follow the given example, it'll be very clear.

override ember-data's url conventions

In my ember-app I have this model:
//models/photo.js
export default DS.Model.extend({
image: DS.attr(),
title: DS.attr('string'),
caption: DS.attr('string'),
published: DS.attr('boolean')
});
Then I have to fetch data for it from a REST api; in particular the photo model should be fecthed from 2 endpoints in this way:
route /aaa --> should fetch from /photos endpoint that returns an array of all photos;
this is easy:
var photos = this.store.findAll('photo')
and ember-data will automatically call the /photos endpoint, since this follows its conventions;
route /bbb --> should fetch from /feed; the /feed REST api endpoint returns also an array of photos (same model as above) but filtered in a certain way;
in any case the it returns an array of photos objects, like the /photos endpoint;
but in this case it is not possible to do
var photos = this.store.findAll('photos')
as in the /aaa route, since doing this will tell ember-data to fetch from /photos endopoint;
How can I fetch the same model from different endpoints in different routes?
(I use ember 2.2.0 with ember-data 2.2.1)
You can't do that unless you're doing a plain ol ajax call and push the payload to store or patch the adapter. I wouldn't recommend messing with ember data and its adapter as it will definitely come back to bite you. What you could do is pass in a parameter and change the logic in the API controller to do a if/else check on that param and send the relevant data that you need.
Route 1 - this.store.query('photo', { type: 'png' })
Route 2 - this.store.query('photo', { type: 'jpeg' })
The short answer is you can't because adapters are per model, not per route.
To get around this, however, you can use a plain ajax call paired with store.push/store.pushPayload to load the data into the store.
See the store documentation for further details.

How to use Ember Data with Nested Resources

My application backend has several resources. A model is exposed for each resource.
The entry point to all other resources is through the User model. What I mean is, given a User we can find BlogPost. Given a BlogPost we can find Comments etc.
In Ember terminology, we could say:
User hasMany BlogPost
BlogPost hasMany Comment
Comment belongsTo BlogPost
By backend exposes REST APIs of the form:
GET /api/v1/users/1
GET /api/v1/users/1/blog_posts/1
GET /api/v1/users/1/blog_posts/1/comments/1
I'm trying to figure out how to use Ember Data to fetch Comment belonging to a certain BlogPost belonging to a certain User.
From what I see, if I define a typical Ember model for Comment:
App.Comment = DS.Model.extend({
...
blogPost: DS.belongsTo('App.BlogPost')
});
and in the CommentRoute I have the following:
var CommentRoute = MessageRoute.extend({
model: function(params) {
this.store.find('comment')
},
The request is sent to:
/api/v1/comments
I don't even know where to begin in order for Ember Data to use urls of the form:
GET /api/v1/users/1/blog_posts/1/comments/1
I've seen several similar questions asked (see links below), but haven't seen a definitive answer to any of them. Most of them are almost a year old when ember-data, perhaps, did not have such functionality (or so it is claimed in some of these threads).
I ask again to confirm whether ember-data has such functionality or not.
Similar Questions:
Ember Data nested Models
Canonical way to load nested resources
Deep nested routes
The best way to handle it is with links. If you don't want to do it like that, it is far from supported, and difficult to hack in (the pipeline just doesn't easily pass the information around). Personally I'd recommend rolling your own adapter in that case (Ember without Ember Data).
App.Foo = DS.Model.extend({
name: DS.attr('string'),
bars : DS.hasMany('bar', {async:true})
});
App.Bar = DS.Model.extend({
foo: DS.belongsTo('foo'),
});
json:
{
id: 1,
name: "bill",
links: {
bars: '/foo/1/bars'
}
}
Example: http://emberjs.jsbin.com/OxIDiVU/971/edit

How to access Ember Model from the Route in Ember CLI

Perhaps doing this with regular Ember application having all the code sitting in app.js would have been much easier for me. But since I'm using Ember CLI I'm having trouble accessing my model in my Route. I'm still trying to learn how to use Ember CLI so please help me out.
As I just want to fire AJAX calls and get the data to render on my UI, I downloaded and added Ember Model library to my project. I don't see a need of using Ember Data. This is the Ember Model documentation I'm referring: https://github.com/ebryn/ember-model#example-usage. With that said, here's my directory structure that Ember CLI proposed:
|-app
|-controllers
| |-customers.js
|-models
| |-customers.js
|-routes
| |-customers.js
|-templates
| |-customers.hbs
|-app.js
|-index.html
|-main.js
|-router.js
This is much simpler representation of the actual project structure that I have just to focus on the problem. As proposed in Ember Model documentation I added following code to my Customers model (model\customers.js):
export default Ember.Model.extend({
nameid: attr(),
firstname: attr(),
middlename: attr(),
lastname: attr(),
prefixname: attr(),
suffixname: attr()
});
this.url = "http://restapi/api/customers";
this.adapter = Ember.RESTAdapter.create();
Notice that I had to do the "export default" instead of "App.Customers = Ember.Model.extend...". This is the Ember CLI convention. So when I try to access the model I created in my Customers Route I get error "Error while loading route: ReferenceError: App is not defined"..
Customers Route code:
export default Ember.Route.extend({
model: function () {
App.Customers.find();
},
actions: {
addnew: function(){
//logic of saving edited customer
alert('customer created!');
}
}
});
I tried this.model() - Returns an object of type supperWrapper and this.modelFor() - Returns null.
Please suggest how to get an handle of my model in its route so that I can perform CRUD operations provided out-of-the-box by Ember Model.
Thanks!
I suggest you change the file name of the model to singular e.g. customer.js.
If you want to access the model class within the route file you have to import the model. Since Ember CLI uses ES6 module syntax you can't / shouldn't access anything directly on the App object. This should be done via import statements or Ember internally via the resolver.
import Customer from "../models/customer";
Now you can use it in the model hook. There is also another error in your example code, you have to return the promise from the find call.
model: function () {
return Customer.find();
},
I'm curious why you picked Ember Model over Ember Data, because for this example you would need less code with Ember Data and it would be more like the Ember way AFAIK.

Preload nested data with ember-data

In my Ember app, I have a nested hasMany relationships using ember-data like so workout->exercise->set.
My API has nested JSON instead of sideloaded JSON, so to fetch an existing workout I use store.find('workout', id) and override extractSingle.
My problem is when building a new workout I need to prepopulate it with exercises and sets based on a workout plan that a user is following. On the server side, I just have a /new controller action that prepopulates everything and renders a template.
Now that I'm moving to Ember I need the same functionality, but can't seem to make it work. The first thing I tried was to use Ember.$.getJSON to call a custom API endpoint in conjunction with pushPayload. This doesn't work however, because pushPayload bypasses extractSingle which means I can't convert my nested JSON into side-loaded JSON.
The prepopulation logic is very complicated so I'd prefer not to duplicated it client side and retrieve it from the API. Any other ideas on how I could accomplish this using ember-data?
For anyone trying to load embedded relationships with EmberData, check out EmberData's EmbeddedRecordsMixin.
Using Ember-CLI, you will need to create a serializer for the model with embedded relationships and add the mixin in the serializer like this:
// app/serializers/my-example-model.js
import DS from 'ember-data';
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
Then, declare an attrs hash and include each embedded relationship with a hash for each, like this:
attrs: {
conference: { embedded: 'always' },
organizationType: { embedded: 'always' }
}
});
In my example above, conference and organizationType are both embedded in the myExampleModel model for both serializing and deserializing. That means that when I use this serializer over a RESTful API, I will get the embedded objects and I need to put the embedded objects.
The EmbeddedRecordsMixin has several options regarding each relationship, as in you have separate settings for serializing and deserializing. you can specify ids, records, or neither. embedded: always is shorthand for:
{
serialize: 'records',
deserialize: 'records'
}
It's all documented here with better examples here:
http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html.