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

Support nested associations for Json and Attributes adapters + Refactor Attributes adapter #1127

Conversation

NullVoxPopuli
Copy link
Contributor

  • Refactor of Attributes adapter
  • Make Json and Attributes adapter handle nested serialization via adapter include option

Note: the serializer-level include option has been dropped for now (i.e. doing has_many :comments, include: 'author, posts' is still not possible).

@NullVoxPopuli
Copy link
Contributor Author

Rebased as of now

@@ -7,37 +7,13 @@ class Json < Adapter
def serializable_hash(options = nil)
options ||= {}
if serializer.respond_to?(:each)
@result = serializer.map { |s| FlattenJson.new(s).serializable_hash(options) }
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason for not using a local variable for result here?

Copy link
Member

Choose a reason for hiding this comment

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

I think because a superclass or subclass may use @result, e.g. FlattenJson currently does

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, result is very generic. hash might not be the best, but at least it's slightly more precise (and given the method name is serializable_hash it does make sense).

Copy link
Contributor

Choose a reason for hiding this comment

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

@bf4 Well, in this case better not rename it at all and keep @hash.

Copy link
Member

Choose a reason for hiding this comment

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

re @result.... at least until #1117 is merged.. it's bad OO in any case, for sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't like the way multiple methods were modifying the same instance variable. :-/

Copy link
Contributor

Choose a reason for hiding this comment

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

@NullVoxPopuli I agree on that, neither do I. But my question was why not get rid of the instance variable altogether and simply use a local variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh OK. Sorry. Yeah, I'd prefer that. I'll take care of that in my next push

@NullVoxPopuli
Copy link
Contributor Author

Nested Associations Usage

Associations defined on a serializer (via has_many, belongs_to, etc) may optionally include the associations' associations by using the include key in the association definition.

Example:

BlogSerializer < ActiveModel::Serializer
  attributes :id, :title

  has_many :posts, include: { author: [:bio], comments: [:author]] }
end

This will provide nested json for each post that includes the post's author and comments as well as associations nested on both the author and each comment.

resulting json may look something like the following:

{
  blog: {
    id: 1,
    title: 'My First Blog',
    posts: [
      {
        title: 'There and back again',
        author: {
          id: 1
          name: 'Bilbo Baggins',
          bio: {
            age: 111,
            location: 'Hobbiton, The Shire'
          }
        },
        comments: [
          {  
             id: 1
             text: 'Great read! A+++ Would read again',
             author: {
               id: 2,
               name: 'Frodo Baggins'
             } 
          }
        ]
      }
    ]
  }
}

Nested associations must each be explicitly provided, or only the attributes will be rendered (no associations)

Example:

BlogSerializer < ActiveModel::Serializer
  attributes :id, :title

  has_many :posts, include: :author
end

Would result in this:

{
  blog: {
    id: 1,
    title: 'My First Blog',
    posts: [
      {
        title: 'There and back again',
        author: {
          id: 1
          name: 'Bilbo Baggins'
        }
      }
    ]
  }
}

Defining the include options

The simplest include of one nested association

has_many :posts, include: :author

Because posts could have multiple associations, a list of a association names may be provided

has_many :posts, include: [:author]
# or
has_many :posts, include: [:author, :comments]

These two are also equivalent, and include the author of each comment on each post

has_many :posts, include: [:author, comments: :author]
# or 
has_many :posts include: [:author, comments: [:author]]

@willcosgrove
Copy link

How difficult would it be to port this over to the :flatten_json adapter? I've got a use case where that would be great to have. If it's difficult or unwanted, it wouldn't be hard to grab the value of the lone key in the resulting hash from the :json adapter. So no big deal if that's not in the cards, just curious.

Also great work with this 👍 can't wait to use it!

@NullVoxPopuli
Copy link
Contributor Author

Would that just function the same as json, but without the root?

I think it would be better to wait until a few PRs are merged, cause I think something is changing with FlattenJson

@willcosgrove
Copy link

Would that just function the same as json, but without the root?

Yeah, that's what I was thinking.

I think it would be better to wait until a few PRs are merged, cause I think something is changing with FlattenJson

Good to know. I'm currently dependent on the way FlattenJson is working. So I should probably start using the Json adapter, and just remove the root manually...

Thanks!

@NullVoxPopuli
Copy link
Contributor Author

Updated include syntax translator to only use a hash of hashes.
Related to #1131

end

# TODO: remove usage of result instance variable from other flows / tests
@result = serialized_hash
Copy link
Contributor

Choose a reason for hiding this comment

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

Where exactly is @Result used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think a super class or something -- tests unrelated to my stuff fail if I get rid of it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Faire enough. I think @bf4 issued a PR to remove that dependency, but let's leave it like that for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, if his gets merged first, I'll change this

@NullVoxPopuli
Copy link
Contributor Author

this is the failed build. for some reason a gem didn't install correctly: https://travis-ci.org/rails-api/active_model_serializers/jobs/79579984

@joaomdmoura
Copy link
Member

Hey @NullVoxPopuli finally checking this for real! Look awesome and great! There are some room for improvement but most of thing were already mentioned.

I merged the Adapter PR from Benjamin so you can already rebase yours and update it to use any new feature he might have added that would help you.

After review this I had some ideas, I don't think it need to be implemented on this PR but it's something we would need on 0.10.x. About this specifically:

As I mentioned on the comment above, right now we have an include options on render that handles nested association on json-api. This PR implements a include option on the association declaration that also handles nested associations.
Would be awesome if we could use both includes in both adapters, so you can handle nested association on json-api and json/flatten_json with the same way enabling you to define it on the serializer os specify on a render call.

@NullVoxPopuli
Copy link
Contributor Author

Thanks for the comments! I'll work on those soon!

@@ -1,47 +1,128 @@
class ActiveModel::Serializer::Adapter::Json < ActiveModel::Serializer::Adapter
extend ActiveSupport::Autoload
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in master, the indentation is incorrect. :-\

I'll rebase. I just forgot :-(

Copy link
Member

Choose a reason for hiding this comment

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

(reason is it makes the diff easier to read, and we'll hopefully go back to a more standard class definition when we make Adapter a module and Adapter::Base the superclass

Copy link
Member

Choose a reason for hiding this comment

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

It's like that in master on purpose.. to make the diffs easier to read.. previous comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

aight, I'll fix

Copy link
Member

Choose a reason for hiding this comment

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

I totally agree it looks weird, but it makes the diff so much easier, and I do intend to get it back to sane soon enough

@beauby beauby assigned NullVoxPopuli and unassigned beauby Sep 21, 2015
beauby added a commit that referenced this pull request Sep 21, 2015
…ns-for-json-adapter

Support nested associations for Json and Attributes adapters + Refactor Attributes adapter
@beauby beauby merged commit 606e2ae into rails-api:master Sep 21, 2015
@yoda
Copy link

yoda commented Oct 2, 2015

Thanks heaps for this

@beauby
Copy link
Contributor

beauby commented Oct 2, 2015

@philipgiuliani Good question. @NullVoxPopuli?

@NullVoxPopuli
Copy link
Contributor Author

That is a good question. I haven't done anything with the cache, so I am not sure.

@kalinchuk
Copy link

Any way to include all associations by default like it was in previous versions?

@NullVoxPopuli
Copy link
Contributor Author

include: '*' :-)

@NullVoxPopuli
Copy link
Contributor Author

oh... default

uhh. nope

@bf4
Copy link
Member

bf4 commented Apr 29, 2016

There's an open issue and a stale pr to add default includes. But let's not discuss that here :)

@pinglamb
Copy link

pinglamb commented May 3, 2016

Thanks for the PR. Just realize that include: '**' will include associations of associations recursively.

@cgmckeever
Copy link
Contributor

@pinglamb Nice find!!!! That solves one of my issues. Still cant get the polymorphic RELATION_id RELATION_type of appear in the data ... may need to open up an issue when I have some time .. again, nice find!

@plicjo
Copy link

plicjo commented Jun 15, 2016

The first post says "the serializer-level include option has been dropped for now", which is true!

The provided example a few posts down is implemented at the serializer level, which doesn't work.

In order to use the stuff in this commit, you'll need to apply the include at the render level: render @posts, include: [author: :bio]

Hopefully this helps someone in the future.

@steverob
Copy link

steverob commented Jul 19, 2016

Thanks @plicjo :)

Is there a way to set include: '**' as the default across the app? It bit me when I was upgrading too and we're using this stuff extensively. I dont want to go through all my serializers and find out where the nested stuff is being used in the front end and update my controller endpoints :(

Update - found it :)

ActiveModelSerializers.config.default_includes = "**"

@brendon
Copy link
Contributor

brendon commented Sep 19, 2016

Is there a way to have unlimited recursion? In my case it's a tree structure with no chance of circular dependency, but arbitrary depth that I can't know in advance.

@NullVoxPopuli
Copy link
Contributor Author

there is include: '**'

@brendon
Copy link
Contributor

brendon commented Sep 19, 2016

Thanks @NullVoxPopuli, lol is that inspired by Dir.glob? :) I'll update my doc PR to mention it.

@brendon
Copy link
Contributor

brendon commented Sep 20, 2016

With regard to setting this globally. I've added the following to an initialiser file in a Rails project. It runs but seems to be ignored:

ActiveModelSerializers.config.default_includes = "**"

Is there a better way to set the default?

@NullVoxPopuli
Copy link
Contributor Author

what adapter are you using?

@brendon
Copy link
Contributor

brendon commented Sep 20, 2016

The default: :attributes

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

Successfully merging this pull request may close these issues.