I'm using the JSON API adapter with ember-data and I'm having a small compatibility issue.
The new JSON API adapter currently does not transform the attribute keys in any way. I have an attribute with an underscore in my model, it will be left untouched. ember-data/jsonapi looks up attribute names with dash-separated words. This means that these two adapters cannot be used together without changing one of them.
The jsonapi spec does not specify that words must be dash-separated, but it does recommend it (https://github.com/rails-api/active_model_serializers/pull/534#issuecomment-116296477, reference). ember-data follows this recommendation (https://github.com/emberjs/data/issues/3455).
It would be nice to have a way to use the adapter in the recommended way.
Should this transformation be handled specifically by the jsonapi adapter?
Or should AMS provide a general key transformation option (this feature dropped on 0.10.x: https://github.com/rails-api/active_model_serializers/pull/534)?
EDIT:
My current workaround was to define a base serializer, from which the actual serializers inherit:
class BaseSerializer < ActiveModel::Serializer
def attributes *args
Hash[super.map do |key, value|
[key.to_s.dasherize.to_sym, value]
end]
end
end
:+1: I'm having this issue also. @hugopeixoto - did you find a way around this for the time being?
IMO - Ember Data should handle both cases, however....
AMS should provide a dasherize option also - as the spec recommends it. Rails is all about underscores, so we'd have to do something like this:
attributes :'full-name'
def full-name
object.full_name
end
end
Not sure if this would even parse...
@hhff: There's a :key option in the #attribute (singular) method which you could use:
attribute :full_name, key: "full-name"
I noticed that I forgot to add my current fix to the issue. I have edited the original post with a potential workaround, if anyone runs into this problem.
thanks @hugopeixoto ! seems like this should also be a global config, however...
@hhff ED does, if you're using the AMS adapter...
Thanks @bf4, thinking more about the new JSONAPI adapter, as AMS adapter is being removed from the official ember data package in favour of it. The suggestions in the above ember data thread are fine tho.
I do think ams should handle this transform for us tho.
Yeah, though we'd probably want it to be optional since people might still use AMS without a JS frontend and Rails-world still prefers underscores to dashes or camelcase.
Thanks @bf4, thinking more about the new JSONAPI adapter, as AMS adapter is being removed from the official ember data package
@hhff is it? :sweat_smile: I should stay more tuned into this!
We are kind moving away from global configs, but is seems a good case.
In the order hand, I checked with the JSON API folks and it is indeed the recommended. It seems to me it should be the default format, but I need to give it some thought.
Anyway, I agree with @hhff we should handle this.
<3
On Thu, 2 Jul 2015 at 7:40 am João Moura [email protected] wrote:
Thanks @bf4 https://github.com/bf4, thinking more about the new JSONAPI
adapter, as AMS adapter is being removed from the official ember data
package@hhff https://github.com/hhff is it? [image: :sweat_smile:] I should
stay more tuned into this!We are kind moving away from global configs, but is seems a good case.
In the order hand, I checked with the JSON API folks and it is indeed the
recommended. It seems to me it should be the default format, but I need to
give it some thought.Anyway, I agree with @hhff https://github.com/hhff we should handle
this.—
Reply to this email directly or view it on GitHub
https://github.com/rails-api/active_model_serializers/issues/974#issuecomment-117915155
.
I was thinking a bit more about this.
In my case - I'm happy for AMS to _not_ handle this. AMS has no control of serializing attributes on the way back into the application - so it means that there has to be two layers of serialization / deserialization / normalization, whatever you want to call it, just to dasherize keys.
IMO, the cleaner way to handle this is on the client (Ember makes this trivial). The Rails API "line" should be neat and clean - it should consume and deliver the same format that's in the DB wherever possible, and for Rails, dashes are a no-go.
@hhff FYI, once we finish it, there will be no need of two layers anymore, AMS will do the magic. :smile:
I partially agree with you, but for me is more about follow JSON API conventions, if this is what ppl expect when using JSON API, the adapter should provide it. But in the other hand, as you said, dashes are a no-go for Rails. Still not sure about it.
I'm in the same scenario. Using Ember CLI and the JSON API adapter. I've also configured active_model_serializers to use the JSON API format. i.e. setting ActiveModel::Serializer.config.adapter = :json_api in a Rails initializer.
I understand Rails is all about underscores. But if ASM is set to :json_api shouldn't it follow the convention of JSON API?
At http://jsonapi.org/format/#document-resource-object-attributes search for first-name. The JSON API format dictates that attributes be delimited by dashes (-). So even if Rails users expect underscores (which is still fine) the output should still be in dashes.
For now, the solution suggested by the OP is a workaround:
def attributes *args
Hash[super.map do |key, value|
[key.to_s.dasherize.to_sym, value]
end]
end
It's goofy that the JSON API spec recommends hyphenating names, especially when this is a disallowed character for names in most languages.
I definitely do think it's right to follow the spec recommendation though. AMS should default to hyphenated names, with a global option to customize the transformation. Even though, yes, Ruby and Rails use the convention of snake case, the data exchange format shouldn't be restricted to that.
@hugopeixoto so, the https://github.com/ember-data/active-model-adapter has been extracted from ember data, but sounds like it expects ams 0.9. It might be a good idea for us to coordinate with that repo...
@bf4 That repo is for the "vanilla" AMS format, not for the jsonapi (http://jsonapi.org) format.
The jsonapi format adapter is still in the ember-data main repo.
@hugopeixoto just wondering, wouldn't the fix be also needed for the relationship names? I am currently trying to use JSONapi and fear that this might bite me.
@piotrpalek you're probably right. I'm not using relationships at the moment, so I haven't run into this. Relationship keys seem to be dasherized in ember-data as well.
Relationship keys come from Serializer.each_association, so the workaround wouldn't work, as it only fixes Serializer.attributes.
I suppose one could override each_association in BaseSerializer as well:
(I have not tried this code, so beware)
class BaseSerializer < ActiveModel::Serializer
def transform_key key
key.to_s.dasherize.to_sym
end
def attributes *args
Hash[super.map do |key, value|
[transform_key(key), value]
end]
end
def each_association &block
super do |key, association, opts|
if block_given?
block.call transform_key(key), association, opts
end
end
end
end
Another potential workaround, temporarily on the ember-data side:
import DS from 'ember-data';
// export default DS.ActiveModelSerializer;
export default DS.JSONAPISerializer.extend({
/**
@method keyForAttribute
@param {String} key
@param {String} method
@return {String} normalized key
*/
keyForAttribute: function (key) {
return key;
},
/**
@method keyForRelationship
@param {String} key
@param {String} typeClass
@param {String} method
@return {String} normalized key
*/
keyForRelationship: function (key) {
return key;
},
});
I placed this in my ember app's app/serializers/application.js.
Obviously, this is not ideal in the long term.
Ember Data has exposed those hooks on purpose to account for cases like this. It's an elegant solution to the problem and IMO can be considered "the" solution here, as its normalizing both the input to Ember and the output back to Rails. Rails doesn't have to know anything about keys in this case.
Tony, not sure how you're declaring keys in Ember Data models, but if you're using camel case (that's the Ember/JS way), you should use
return Ember.string.underscore(key);
Thanks @hhff and @tpitale. This workaround worked for me with the following changes/additions:
import DS from 'ember-data';
// export default DS.ActiveModelSerializer;
export default DS.JSONAPISerializer.extend({
/**
@method keyForAttribute
@param {String} key
@param {String} method
@return {String} normalized key
*/
keyForAttribute: function (key) {
return Ember.String.underscore(key);
},
/**
@method keyForRelationship
@param {String} key
@param {String} typeClass
@param {String} method
@return {String} normalized key
*/
keyForRelationship: function (key) {
return Ember.String.underscore(key);
},
});
App.ApplicationAdapter = DS.ActiveModelAdapter.extend({
pathForType: function(type) {
var underscored = Ember.String.underscore(type);
return Ember.String.pluralize(underscored);
}
});
It looks like the JSON API spec is indifferent between dashes and underscores. If the spec is indifferent then I think it'd be best to provide an option wherever it's implemented - AMS _and_ ED.
Btw, @hugopeixoto's code doesn't go far enough as it doesn't cover type values. I dasherized the value if the key == :type in attributes, but hadn't figured out where to change the type value in the relationships hash before finding the Ember Data solution above.
Thanks again everyone!
@joaomdmoura would be great to have some labels for common themes in issues: resource keys, serialization outside of a controller, testing, custom adapter, JSON API compliance, assocation issues, db queries, active record models, non-active record models (poros), upgrading, deserialization, proposing new features, asking questions (mailng list? json api forum?), refactoring, usage, who's using it, ... and in CONTRIBUTING recommend PRs/issues/commenters help identify the theme
I just looked up a bunch of testing-related issues to reference in https://github.com/rails-api/active_model_serializers/issues/1101
Thanks @bf4 I'll try to figure it out some new tags that we might use in order to make it easier
btw everyone interest on this, would be awesome to have your opinion on #1029
+1
IMO this should be fixed on rail's side, and not on ember's. JSON API's format requires it to be dasherized instead of underscored. Yes, ember is flexible and lets you fix this, but there might be other frameworks that arent as flexible.
Not required, recommended
Sure, you know what I mean ;)
We should assume that other parsers, adapters, serializers, etc follow recommendations.
@joaomdmoura :+1: on what #1029 is aiming to achieve. Especially love that the default for JSON API would be dasherized.
FYI, @hugopeixoto's workarounds mentioned in this issue breaks on association keys with today's release of v0.10.0.rc3. (#each_association went away in rc3.) I'm personally looking forward to #1029 being completed and released. Rolling back to rc2 in the meantime...
@mminkoff Thank you for that code snippet; I was able to use standard AMS without defining :key for every attribute!
I'm using AMS v0.10.0.rc3 with Rails 4.2.1 and Ember 0.13.
since we have various work arounds (ruby side and javascript side), I'm going to close this issue.
Just as a recap (because this is what I'm doing)
I've made the following modifications on the ember side to work with rails 4 and AMS:
// serializers/application.js
// this allows for ember's JSONAPISerializer to accept underscored attributes (like what rails / ams outputs)
import Ember from 'ember';
import DS from 'ember-data';
var underscore = Ember.String.underscore;
export default DS.JSONAPISerializer.extend({
keyForAttribute: function(attr) {
return underscore(attr);
},
keyForRelationship: function(rawKey) {
return underscore(rawKey);
}
});
// adapters/application.js
// this changes the URLs ember builds from dasherized-ness to underscored_ness
import DS from 'ember-data';
import ENV from "../config/environment";
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';
export default DS.JSONAPIAdapter.extend(DataAdapterMixin, {
namespace: 'api',
host: ENV.host,
authorizer: 'authorizer:application',
pathForType: function(type) {
let underscored = Ember.String.underscore(type);
return Ember.String.pluralize(underscored);
}
});
A guide for rails 5 can be found here http://emberigniter.com/modern-bridge-ember-and-rails-5-with-json-api/
The options brought up in this issue about customizing format on the AMS side are brought up in other issues. :-)
Docs pr? Howto code and links?
B mobile phone
On Oct 25, 2015, at 12:57 PM, L. Preston Sego III [email protected] wrote:
since we have various work arounds (ruby side and javascript side), I'm going to close this issue.
Just as a recap (because this is what I'm doing)
I've made the following modifications on the ember side to work with rails 4 and AMS:
// serializers/application.js
// this allows for ember's JSONAPISerializer to accept underscored attributes (like what rails / ams outputs)
import Ember from 'ember';
import DS from 'ember-data';
var underscore = Ember.String.underscore;export default DS.JSONAPISerializer.extend({
keyForAttribute: function(attr) {
return underscore(attr);
},keyForRelationship: function(rawKey) {
return underscore(rawKey);
}
});// adapters/application.js
// this changes the URLs ember builds from dasherized-ness to underscored_ness
import DS from 'ember-data';
import ENV from "../config/environment";
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';export default DS.JSONAPIAdapter.extend(DataAdapterMixin, {
namespace: 'api',
host: ENV.host,
authorizer: 'authorizer:application',pathForType: function(type) {
let underscored = Ember.String.underscore(type);
return Ember.String.pluralize(underscored);
}
});A guide for rails 5 can be found here http://emberigniter.com/modern-bridge-ember-and-rails-5-with-json-api/
—
Reply to this email directly or view it on GitHub.
An alternative Rails side solution, I took the answer to this post for creating a json params parser transform:
http://stackoverflow.com/a/30557924/80050
.. and modified it to work with JSONAPI mime type giving me this (for Rails 4):
# File: config/initializers/jsonapi_param_key_transform.rb
# Transform JSONAPI request param keys from dasherized to
# Rails-conventional snake_case:
Rails.application.config.middleware.swap(
::ActionDispatch::ParamsParser, ::ActionDispatch::ParamsParser,
::Mime::Type.lookup('application/vnd.api+json') => Proc.new { |raw_post|
# Borrowed from action_dispatch/middleware/params_parser.rb except for
# data.deep_transform_keys!(&:underscore) :
data = ::ActiveSupport::JSON.decode(raw_post)
data = {:_json => data} unless data.is_a?(::Hash)
data = ::ActionDispatch::Request::Utils.deep_munge(data)
# Transform camelCase param keys to snake_case:
data.deep_transform_keys!(&:underscore)
data.with_indifferent_access
}
)
You can now set it globally,
config/active_model_serializers.rb:
ActiveModel::Serializer.config.adapter = ActiveModel::Serializer::Adapter::JsonApi
ActiveModelSerializers.config.key_transform = :underscore
https://github.com/rails-api/active_model_serializers/blob/master/docs/general/key_transforms.md
Most helpful comment
You can now set it globally,
config/active_model_serializers.rb:
ActiveModel::Serializer.config.adapter = ActiveModel::Serializer::Adapter::JsonApi
ActiveModelSerializers.config.key_transform = :underscore
https://github.com/rails-api/active_model_serializers/blob/master/docs/general/key_transforms.md