Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON API adapter word separator issue #974

Closed
hugopeixoto opened this issue Jun 28, 2015 · 33 comments
Closed

JSON API adapter word separator issue #974

hugopeixoto opened this issue Jun 28, 2015 · 33 comments
Assignees

Comments

@hugopeixoto
Copy link

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 (#534 (comment), reference). ember-data follows this recommendation (emberjs/data#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: #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
@hhff
Copy link

hhff commented Jun 30, 2015

👍 I'm having this issue also. @hugopeixoto - did you find a way around this for the time being?

@hhff
Copy link

hhff commented Jun 30, 2015

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...

@hugopeixoto
Copy link
Author

@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.

@hhff
Copy link

hhff commented Jun 30, 2015

thanks @hugopeixoto ! seems like this should also be a global config, however...

@bf4
Copy link
Member

bf4 commented Jul 1, 2015

@hhff ED does, if you're using the AMS adapter...

@hhff
Copy link

hhff commented Jul 1, 2015

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.

@bf4
Copy link
Member

bf4 commented Jul 1, 2015

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.

@joaomdmoura
Copy link
Member

Thanks @bf4, thinking more about the new JSONAPI adapter, as AMS adapter is being removed from the official ember data package

@hhff is it? 😅 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.

@hhff
Copy link

hhff commented Jul 2, 2015

<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: 😅] 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
#974 (comment)
.

@bf4
Copy link
Member

bf4 commented Jul 2, 2015

Additional references: #507 #699

@hhff
Copy link

hhff commented Jul 3, 2015

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.

@joaomdmoura
Copy link
Member

@hhff FYI, once we finish it, there will be no need of two layers anymore, AMS will do the magic. 😄

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.

@krzkrzkrz
Copy link

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

@zaeleus
Copy link

zaeleus commented Jul 7, 2015

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.

@bf4
Copy link
Member

bf4 commented Jul 7, 2015

@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...

@hugopeixoto
Copy link
Author

@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.

@piotrpalek
Copy link

@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.

@hugopeixoto
Copy link
Author

@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

@tpitale
Copy link
Contributor

tpitale commented Jul 12, 2015

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.

@hhff
Copy link

hhff commented Jul 12, 2015

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);

@mminkoff
Copy link

Thanks @hhff and @tpitale. This workaround worked for me with the following changes/additions:

  1. String needs to be capitalized in @hhff's code above. Here's the complete code:
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);
  },
});
  1. unless your API URLs are dasherized, you'll need to tell Ember Data to make them underscored in app/adapters/application.js:
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!

@bf4
Copy link
Member

bf4 commented Aug 30, 2015

@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 #1101

jumski added a commit to jumski/blog-backend that referenced this issue Sep 4, 2015
jumski added a commit to jumski/blog-backend that referenced this issue Sep 7, 2015
@joaomdmoura
Copy link
Member

Thanks @bf4 I'll try to figure it out some new tags that we might use in order to make it easier

@joaomdmoura
Copy link
Member

btw everyone interest on this, would be awesome to have your opinion on #1029

@joaomdmoura joaomdmoura self-assigned this Sep 7, 2015
@ghost
Copy link

ghost commented Sep 12, 2015

+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.

@bf4
Copy link
Member

bf4 commented Sep 13, 2015

Not required, recommended

@ghost
Copy link

ghost commented Sep 13, 2015

Sure, you know what I mean ;)

We should assume that other parsers, adapters, serializers, etc follow recommendations.

@chrisdpeters
Copy link
Contributor

@joaomdmoura 👍 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...

@asselstine
Copy link

@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.

@NullVoxPopuli
Copy link
Contributor

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. :-)

@bf4
Copy link
Member

bf4 commented Oct 26, 2015

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.

robinboening added a commit to robinboening/cashbox_client that referenced this issue Jan 23, 2016
..instead of dashes in attributes as well as relations
Further information, see this: rails-api/active_model_serializers#974
@gregmalcolm
Copy link

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
  }
)

@ghost
Copy link

ghost commented Aug 31, 2016

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests