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

Fieldset support in Attributes/JSON adapters #2223

Merged
merged 5 commits into from
Apr 23, 2019

Conversation

bf4
Copy link
Member

@bf4 bf4 commented Nov 20, 2017

Supersedes stale PR #2100
Closes: #2100, #2278
Addresses partially: #2273

begin
object.class.model_name.to_s.underscore
rescue ArgumentError
'anonymous_object'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some of the tests use anonymous classes.. not sure how real world this is, but I figure giving it a descriptive fallback name is a good compromise

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For serialized POROs (i.e. whose class does not understand model_name message), does this imply json_key resolves to 'anonymous_object'? Or do I miss something?

Rationale:

  • with :attributes adapter there is no root;
  • _type is not required for AMS v0.9.x serializers;
  • PORO classes do not respond to model_name:
NoMethodError:
  undefined method `model_name' for OpenStruct:Class
# /Users/user/.rvm/gems/ruby-2.3.1@gemset1/gems/active_model_serializers-0.10.7/
  lib/active_model/serializer.rb:374:in `json_key' 
  • rescue ArgumentError won't catch NoMethodError.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, since this version of AMS describe what's a valid PORO, and even provides a linter for it, then it's reasonable to assume that's what you're using...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like AMS should rescue the NoMethodError and raise saying "no explicit root given, could not infer root from object" with maybe a link for more info.

The argument error is fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        begin
          object.class.model_name.to_s.underscore
        rescue ArgumentError
          'anonymous_object'
        rescue NoMethodError
          raise 'no explicit root given; could not infer root from object.'

something like this? also can we provide the PORO link in the msg?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bf4 would you like to have another look at this, please?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wasifhossain that seems fine, though maybe raise a different error

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please point me out which error class can we use for the case :) do you mean creating a custom class?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wasifhossain I was thinking we could just define one. Maybe

UnknownSerializerRootError = Class.new(NoMethodError)

?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks fine to me. will do this. thank you!

def serializable_hash(options = nil)
options = serialization_options(options)
options[:fields] ||= instance_options[:fields]
serialized_hash = serializer.serializable_hash(instance_options, options, self)

self.class.transform_key_casing!(serialized_hash, instance_options)
end

def fields_to_fieldset(fields)
Copy link
Member Author

@bf4 bf4 Nov 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because, as per docs/general/fields.md, we have a different syntax for fieldset in JSON adapter vs. JSON:API

The test usage in #2100 is [:title, {comments: [:body]}. I'm not sure if we ever considered the fields option for relationships

However, I'd accept just turning an array into an implicit fieldset, but otherwise require it to be jsonapi-like.

In other words, I have no idea at this time if the below code is introducing new or desired behavior. Needs thought

and in a specific controller, you want to return access_token only, fields will help you:

class AnonymousController < ApplicationController
  def create
    render json: User.create(activation_state: 'anonymous'), fields: [:access_token], status: 201
  end
end

Note that this is only valid for the json and attributes adapter. For the json_api adapter, you would use

render json: @user, fields: { users: [:access_token] }

Where users is the JSONAPI type.

@@ -357,6 +357,9 @@ def associations(include_directive = ActiveModelSerializers.default_include_dire
def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
adapter_options ||= {}
options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
if (fieldset = adapter_options[:fieldset])
options[:fields] = fieldset.fields_for(json_key)
end
Copy link
Member Author

@bf4 bf4 Nov 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, this seemed to be the right place for this, code-wise. I think it's because the serializer has some responsibility for serializing associations

@@ -1,13 +1,32 @@
module ActiveModelSerializers
module Adapter
class Attributes < Base
def initialize(*)
super
instance_options[:fieldset] ||= ActiveModel::Serializer::Fieldset.new(fields_to_fieldset(instance_options.delete(:fields)))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the fieldset for the adapter is set at init. The fields the adapter is told to use is passed to serializable_hash. Not sure how good a distinction this is, but it's also how the json:api adapter works

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the JSON Adapter delegates to Attributes here, this is the only necessary adapter change

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

honestly, this particular comment answered my long-standing question for this PR 😄 clever work 👏

Copy link

@cintamani cintamani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM! Shall we rebase it against the latest 0-10-stable and go for a merge?

@wasifhossain
Copy link
Member

@bf4 very good explanations for each set of changes 👍

@krzysiek1507
Copy link
Contributor

@bf any update on this?

else fail ArgumentError, "Unknown conversion of fields to fieldset: '#{field.inspect}' in '#{fields.inspect}'"
end
end
relationship_fields.merge(serializer.json_key.to_sym => resource_fields)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could please change it either to merge! or

relationship_fields[serializer.json_key.to_sym] = resource_fields
relationship_fields

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. I wonder why I didn't mutate a local var we own and mutate elsewhere

@wasifhossain you're welcome to take ownership of this PR if you'd like

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bf4 thanks for the offer. I will rebase it as @cintamani suggested earlier as well as make this small fix (prefer using merge!) before merging this PR. do you have any suggestion, pls?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only real suggestion is to bear in mind once merged we need to support it.

I didn't merge it in part due to lack of confidence it was the right change.

I don't like to add features without someone actually trying it out.

@wasifhossain wasifhossain force-pushed the fields_support_in_all_adapters branch from e527977 to 6a8994f Compare February 20, 2019 21:04
@wasifhossain wasifhossain force-pushed the fields_support_in_all_adapters branch from e300ce7 to d796a0e Compare March 21, 2019 19:54
@johan-smits
Copy link

@wasifhossain It works as expected indeed 😄. A 👍 from my side.

@wasifhossain
Copy link
Member

that's great news @johan-smits! Thanks very much for your valuable feedback 👍

will look forward to some more feedback from production usage of this PR in coming weeks and then merge it.

@johan-smits
Copy link

@wasifhossain we are using the branch in acceptance tomorrow. Without negative feedback the coming week it is ok.

@johan-smits
Copy link

No negative feedback from my side, looking forward to the new release!

@wasifhossain
Copy link
Member

great feedback! @johan-smits I will work in this weekend to merge it. thanks

@johan-smits
Copy link

@wasifhossain no pressure but no merge done yet?

@wasifhossain
Copy link
Member

@johan-smits honestly I was running under a bit of pressure in past weeks. but looking forward to taking care of this in coming weekend. appreciate your patience 👍 thanks!

@wasifhossain wasifhossain force-pushed the fields_support_in_all_adapters branch from d796a0e to c77fdda Compare April 23, 2019 09:18
@wasifhossain
Copy link
Member

so we are all green on travis. let's merge it 🚢

@wasifhossain wasifhossain merged commit 3419a01 into 0-10-stable Apr 23, 2019
@wasifhossain
Copy link
Member

@johan-smits now you can use this feature by pointing your Gemfile to 0-10-stable branch until the next gem version (guessing 0.10.10) is published

@wasifhossain wasifhossain deleted the fields_support_in_all_adapters branch April 23, 2019 10:01
@johan-smits
Copy link

@wasifhossain testing...

@flvrone
Copy link

flvrone commented Jul 29, 2019

Guys, I think we are rescuing wrong error here:

In my case when I work with PORO's, it actually raises

NoMethodError:
       undefined method `model_name' for Money:Class

And it totally makes sense, isn't it? :)

P.S. I didn't have any issues with it on version 0.10.9. But it looks like that model_name call was there all the time.

@wasifhossain
Copy link
Member

@FunkyloverOne here is a good thread for you to follow: #2223 (comment)

dmann added a commit to dmann/duke-data-service that referenced this pull request Sep 19, 2019
AMS expects serialized models to behave like an
ActiveModel::Model now:
rails-api/active_model_serializers#2223
dmann added a commit to dmann/duke-data-service that referenced this pull request Sep 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants