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

Passing serializer options for associations #331

Closed
grk opened this issue Jun 12, 2013 · 38 comments
Closed

Passing serializer options for associations #331

grk opened this issue Jun 12, 2013 · 38 comments
Labels

Comments

@grk
Copy link

grk commented Jun 12, 2013

Is there any way to pass serializer options to associations?

I looked at the code, and it looks like it was planned, but is not "connected" yet.

ActiveModel::Serializer::Association takes the third argument for serializer_options, but I don't see it used anywhere. Perhaps getting them from options[:serializer_options] would be a good idea?

Would you accept a pull request with that change?

@spastorino
Copy link
Contributor

I'd like to see how real world code would look like before that.
Show me how are you planning to use it and let's see.
Thanks!

@grk
Copy link
Author

grk commented Jun 12, 2013

I'm including some details depending on params. Looks like this:

class FooSerializer < ActiveModel::Serializer
  has_one :slow_to_serialize

  def include_slow_to_serialize?
    @options[:with_slow_to_serialize]
  end
end

Which works fine when I'm serializing an array of Foos. But when I have Bar:

class BarSerializer < ActiveModel::Serializer
  has_one :foo
end

and I serialize an array of Bars, it would be nice to be able to control including slow_to_serialize.

I created a second serializer inheriting from FooSerializer that has include_slow_to_serialize? always set to true, but it would be nice to be able to control it in BarSerializer's has_one.

@spastorino
Copy link
Contributor

But that's not related with ActiveModel::Serializer::Association.

And you can already do what you say, just pass with_slow_to_serialize option to the serializer when you create it.

@grk
Copy link
Author

grk commented Jun 12, 2013

OK, maybe I wasn't clear. I want to render json: @bars and have it include slow_to_serialize in foos. I also want to be able to render json: @foos, with_slow_to_serialize: params[:with_slow_to_serialize].

From what I understand, I have these options to do it right now:

  • create a FooWithSlowSerializer < FooSerializer and hardcode include_slow_to_serialize? to return true. Then, in BarSerializer, use has_one :foo, serializer: FooWithSlowSerializer
  • change include_slow_to_serialize? to be true by default, and disable it in the controller when param is not present

Ideally, I'd like to change BarSerializer to have: has_one :foo, serializer_options: {with_slow_to_serialize: true)

Does this make sense?

@bpardee
Copy link

bpardee commented Jun 20, 2013

+1

Maybe I'm going about this wrong but I find that I sometimes get in a recursive situation so I would like to do something like the following:

class UserSerializer < ActiveModel::Serializer
  attributes :id, :name
  has_many :friends, :serializer => UserSerializer, :dont_embed_friends => true

  def include_associations!
    include! :friends unless @options[:dont_embed_friends]
  end
end

@oliverjesse
Copy link

+1

1 similar comment
@HungYuHei
Copy link

+1

@jtomaszewski
Copy link

+1 ! Such feature is a must for serializers. It'll make handling circular references much easier.

Currently I have to create a lot of serializer classes with repeated code / huge inheritance to make this work, when there are a lot of many-to-many associations.

@2468ben
Copy link

2468ben commented Sep 18, 2013

+1

@cryo28
Copy link

cryo28 commented Sep 20, 2013

I would be great to have this feature available. Otherwise I similar cases I end up building unneccessary inherited serializers to refine some serialization options.

@2468ben
Copy link

2468ben commented Sep 20, 2013

Until they support passing options this way, there is a way to do it, but not as pretty:

instead of using an association:

class ParentSerializer < ActiveModel::Serializer
  has_many :kids, serializer: ParentSerializer
end

you have to define it as an attribute, and then call the serialization yourself:

class ParentSerializer < ActiveModel::Serializer
  attribute :kids

  def kids
    kid_options = @options.except(:option_you_dont_want)
    object.kids.map{ |comment| 
      ParentSerializer.new(comment, kid_options).serializable_hash
    }
  end
end

Hope that makes sense and helps for now.

@cryo28
Copy link

cryo28 commented Sep 20, 2013

Well. I piggy backed on the fact that association serializer is instantiated with the same options as the base serializer

class BaseSerializer < ActiveModel::Serializer
   has_one :association

  def initialize(*args)
    super
    options[:extended_mode] = true
  end
end

class AssociationSerializer < ActiveModel::Serializer
  attribute :attr1

  def include_attr1
    options[:extended_mode]
  end
end

@mateusmaso
Copy link

+1 for it.. I think there also must be a way to configure which attributes/methods do I need given a serializer (mostly to avoid circular references and/or improve speed)

@dgilperez
Copy link

+1

@aaronchi
Copy link

+1 for options to serializers

@spastorino
Copy link
Contributor

Can you guys check if master code allows you to do what you want?.
And if doesn't work give me a complete script to understand what are you trying to do.

@aaronchi
Copy link

I don't see an option to do this right now... a simple example would be:

class ContactSerializer < ActiveModel::Serializer

  attributes :id, :first_name, :last_name, :email, :avatar

  filter(keys)
    keys.delete(:avatar) unless options[:with_avatar]
    keys
  end

end

Then call

ContactSerializer.new(contact)

or

ContactSerializer.new(contact, serializer_options: {with_avatar: true})

@aaronchi
Copy link

You can get this functionality by modifying the initialize function in a base serializer... Should be easy to add to the code if desired:

class BaseSerializer < ActiveModel::Serializer

  def initialize(object, options={})
    super
    @options = options[:serializer_options] || {}
  end

  attr_accessor :options

end

@lastobelus
Copy link

I think this should all be hidden behind the scope object. I made a pull request that passes the scope object through to association serializers: #433

@bpardee
Copy link

bpardee commented Dec 30, 2013

I've upgraded to the latest master and it seems to work so my vote is to close this issue.

@spastorino
Copy link
Contributor

Yep this should be working. In any case comment back and I will reopen.

@stevenharman
Copy link

So scope is passed through, but what about other options? For instance, I need :url_options available in all serializers so that I can use URL helpers to generate URLs.

class ApplicationController < ActionController::Base

  def default_serializer_options
    { url_options: url_options }.merge(super)
  end

end

class ApplicationSerializer < ActiveModel::Serializer
  include Rails.application.routes.url_helpers

  def self.links(&block)
    @links = block if block_given?
    @links || Proc.new {}
  end

  def url_options
    options[:url_options]
  end

  def link(rel, opts = {})
    links[rel] = opts[:href]
  end

  def filter(keys)
    self.instance_eval(&self.class.links)
    keys << :links unless links.empty?
    keys
  end

  private

  def links
    @links ||= {}
  end
end

class BeerSerializer < ApplicationSerializer
  attributes :id, :name

  links do
    link :self, href: beer_url(object)
  end
end

I'm guessing I should open a new Issue for this?

@spastorino
Copy link
Contributor

You can access options using options method. So options[:url_options] should work

@stevenharman
Copy link

@spastorino In the example code I posted, note that I do use options[:url_options] in the ApplicationSerializer#url_options, and that works as expected for the initial serializer. However, the options hash for nested (i.e., has_one or has_many) serializers is empty.

As far as I can tell (via Serializer#serialize), and per #433, only the :scope is passed to associated serializers. Changing the above linked line to

association.build_serializer(object, options.merge(scope: scope)).serializable_object

However, that might be a bit heavy-handed, as @lastobelus indicated in his above comment.

@stevenharman
Copy link

To clarify - I'm talking about the options passed to an instance of an associated serializer, not the options you give when declaring the serializer via a has_one/has_many. That is, given example code above, plus the following:

class BrewerySerializer < ApplicationSerializer
  attributes :id, :name, :website

  has_many :beers, random_option: :foo

  links do
    link :self, href: brewery_url(object) #=> /brewery/123
    link :beers, href: brewery_beers_url(object) #=> /brewery/123/beers
  end
end

A BrewerySerializer will have the default_serializer_options passed to it via the ApplicationController. And then BeerSerializers will be created by Serializer#serializefor the associated Beers, but the options given to the BrewerySerializer (those :url_options coming from default_serializer_options) won't be passed along. However, the random_option: :foo option, from the has_many declaration will be passed along.

@mmun
Copy link

mmun commented Jan 2, 2014

@2468ben Do you know if anything has changed here? I think it would be useful to be able to specify per-association serializer options. Here is some pseudocode (highlighted) that illustrates how it might work:

https://gist.github.com/mmun/3646b88d4cb2f13a256b#file-2-blog_serializer-rb-L5-L9

spastorino added a commit that referenced this issue Jan 2, 2014
@spastorino
Copy link
Contributor

I've committed this ^^^ to allow passing options for now but is not yet defined if that's the way it should be using it. We may move to something like contexts and we would allow passing scope and context only to associations. Then you would be able to do the dance passing whatever you like inside context all the way down.

@mmun
Copy link

mmun commented Jan 2, 2014

@spastorino Wow, awesome response time. I look forward to seeing how this progresses. I can try helping if needed.

@2468ben
Copy link

2468ben commented Jan 3, 2014

@mmun @spastorino That'll help out, thank you. I haven't migrated to 0.9 because the README mentioned that there would still be heavy changes (including caching) in the near future for . Are you still doing a gut renovation for 1.0, or is the current approach (minus caching) going to stay pretty close? I'm happy to wait.

@spastorino
Copy link
Contributor

We have a roadmap here https://www.pivotaltracker.com/s/projects/978898
Functionality will be pretty close to current one + possibly caching & JSONAPI compliance

@spastorino
Copy link
Contributor

I've reverted the commit that allowed you to pass options around. I think that's a bad idea.
You can pass things around using scope for now. But I will think about this a bit better. We may come up with a context option or something like that.

@aaronchi
Copy link

aaronchi commented Jan 3, 2014

:( I was using that

@sandstrom
Copy link

One use-case for passing things around to associated serializers would be API versioning.

We use this to include the version, and can then adjust the serialization output based on the client. If such info could be passed onto associated serializers (via options/scope/context/something) one could easily build basic api versioning into the serializer.

def default_serializer_options
  client_version = request.headers['X-Client-Version'] # may be nil
  { :current_user => current_user, :client_version => client_version }
end

Note that we are not on master, but an older branch, so exact syntax may differ.
For major API changes it would probably be more sense with name-spaced classes.

@aaronchi
Copy link

Would love to see options return. The recommendation by blakewatters in this thread seems great for versioning though:

#144

@gnapse
Copy link

gnapse commented Nov 3, 2015

Hi, sorry to resurface this discussion, but I'm not sure if this feature was finally provided, and if so, how to use it. Or else any workarounds. Any updates on this?

@oliverjesse
Copy link

afaict everyone has stopped using ams and gone back to jbuilder :( too bad, i like the ams interface way better

Jesse Sanford

On Nov 3, 2015, at 14:58, Ernesto García notifications@github.com wrote:

Hi, sorry to resurface this discussion, but I'm not sure if this feature was finally provided, and if so, how to use it. Or else any workarounds. Any updates on this?


Reply to this email directly or view it on GitHub.

@voltechs
Copy link

Not sure what poll you're referring to. Can you cite your source @oliverjesse? We for one are still using AMS...

I'd also like the 411 on how to use this feature, if it exists.

@jameswilliamiii
Copy link

You can use the options to pass along the info to the association. An example would be:

class PlantSerializer < ActiveModel::Serializer
  has_one :plant_type

  def initialize(object, options={})
    super
    # This options[:parent] will get passed through to the plant_type
    options[:parent] = object.parent
  end

end

class PlantTypeSerializer < ActiveModel::Serializer

  def my_method
    # I am able to access the previously set data using @options
    if @options[:parent]
      # do something special based on @options[:parent]
    end
  end

end

There is probably an elegant solution, but this is how I am solving the problem for now.

@rails-api rails-api locked and limited conversation to collaborators Dec 10, 2015
@bf4 bf4 added the V: 0.9.x label Dec 10, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests