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

Load associations to one level while conditionally sideloading associations in Active model serializers #927

Closed
vamsi-krishna-E0029 opened this issue May 23, 2015 · 16 comments
Labels

Comments

@vamsi-krishna-E0029
Copy link

This ain't an issue frankly speaking. May be a tailored feature request. Tried my best to get this working but in vain.
Using AMS version 0.8.3.
I created a base_serializer.rb like this and extended the same.

class BaseSerializer < ActiveModel::Serializer
  def include_associations!
    if @options[:embed]
      embed = @options[:embed].split(',').map{|item| item.strip.to_sym}
      embed.each do |assoc|
        include! assoc if _associations.keys.include?(assoc)
      end
    end
  end
end

class EventSerializer < BaseSerializer
  attributes :id, :name
  has_many :organizers, serializer: OrganizerSerializer
  has_many :participants, serializer: ParticipantSerializer
end

class OrganizerSerializer < BaseSerializer
  attributes :id, :name
  has_many :related, serializer: RelatedSerializer
end

class ParticipantSerializer < BaseSerializer
  attributes :id, :name
  has_many :related, serializer: RelatedSerializer
end

class RelatedSerializer < BaseSerializer
  attributes :id, :name
  has_many :something, serializer: SomethingSerializer
end

and the index method in EventsController is written as

# GET /events?embed=organizers,participants
  def index
      @events = Event.all
      render json: @events, embed: params[:embed]
  end

With this I can get the :id and :name of events, organizers and participants. But, I want the attributes of related association as well. I don't need details of something serializer. I want to go till this level for each association. How can I achieve that?

@groyoh
Copy link
Member

groyoh commented May 27, 2015

I'm not sure if this would work but have you tried embed=organizers,participants,related ?
I might have another solution but I need to try it first.

@vamsi-krishna-E0029
Copy link
Author

Thanks @groyoh That works. I never thought of trying like that. But, when I query for events. I would just know that organizers and participants are the associations for that model. Is there any way where I can fetch the association of association objects which are included?

For eg: May be I confused you a lot...

Before I iterate,

  embed = @options[:embed].split(',').map{|item| item.strip.to_sym}
  embed.each do |assoc|
    include! assoc if _associations.keys.include?(assoc)
  end

So, in my case the embed variable would have organizers,participants. Can I know the associations of organizers and participants (related) before hand so that I can add them in the embed variable when I iterate?

I was able to get the associations when I iterate. But, I need them before iterations.

  embed = @options[:embed].split(',').map{|item| item.strip.to_sym}
  embed.each do |assoc|
    assoc_obj = include! assoc if _associations.keys.include?(assoc)
    # This gives me the required associations which need to be added. But, I need it before iteration.
    p assoc_obj.first._associations.keys.inspect if assoc_obj.present?
  end

Thanks once again for the suggested solution. That helped me move one step closer.

@groyoh
Copy link
Member

groyoh commented May 28, 2015

Once again I'm not sure about the solution, but you could try:

embed = @options[:embed].split(',').map{|item| item.strip.to_sym}
embed.each do |name|
  next unless _associations.key?(name)
  assoc_serializer = _associations[name].options[:serializer]
  embed = @options[:embed]
  @options[:embed] = assoc_serializer._associations.keys.join(",")
  include! name
  @options[:embed] = embed
end

But you need to make sure there is no recursive relationship or you might get an infinite loop.
Do you need it for only one level e.g. organizers and participants or do you also need to get the associations of related?

@vamsi-krishna-E0029
Copy link
Author

I don't need associations of RelatedSerializer. I just need attributes of RelatedSerializer.

The [:serializer] doesn't seem to work. Getting the following error.

undefined method `[]' for (subclass of ActiveModel::Serializer::Associations::HasMany):Class

@groyoh
Copy link
Member

groyoh commented May 28, 2015

I have something in mind then, but I'll try it later and tell you if it works.

@groyoh
Copy link
Member

groyoh commented May 28, 2015

My bad, I made a typo. I updated the snippet.

@groyoh
Copy link
Member

groyoh commented May 28, 2015

Here is my final solution, I tested it and it work as expected:

class BaseSerializer < ActiveModel::Serializer
  def include_associations!
    @options[:embed_level] ||= 1
    return unless @options.key?(:embed) && @options[:embed_level] != 0
    embed = @options[:embed].split(',').map{|item| item.strip.to_sym}
    embed.each do |assoc|
      next unless _associations.key?(assoc)
      assoc_serializer = _associations[assoc].options[:serializer]
      embed = @options[:embed]
      embed_level = @options[:embed_level]
      @options[:embed_level] -= 1
      @options[:embed] = assoc_serializer._associations.keys.join(",")
      include! assoc
      @options[:embed] = embed
      @options[:embed_level] = embed_level
    end
  end
end

then in your controller:

render json: @events, embed: "organizers,participants", embed_level: 2

Then you can change the embed_level option to set how deep it should load associations.

@vamsi-krishna-E0029
Copy link
Author

This is perfect @groyoh . Thanks a lot. May be I'm asking for too much now. It is a problem from my end actually. the assoc_serializer might be nil at times where we don't specify any serializer option to associations at times. As of now I added a present? check for assoc_serializer. What can I do in those cases? Do we have any option to fetch the serializer when no serializer option is provided?

Say something like

has_many :roles

This association actually uses the RoleSerializer by default way. I don't specify any serializer option here.

class BaseSerializer < ActiveModel::Serializer
  def include_associations!
    @options[:embed_level] ||= 1
    return unless @options.key?(:embed) && @options[:embed_level] != 0
    embed = @options[:embed].split(',').map{|item| item.strip.to_sym}
    embed.each do |assoc|
      next unless _associations.key?(assoc)
      assoc_serializer = _associations[assoc].options[:serializer]
      embed = @options[:embed]
      embed_level = @options[:embed_level]
      @options[:embed_level] -= 1
      @options[:embed] = assoc_serializer._associations.keys.join(",") if assoc_serializer.present?
      include! assoc
      @options[:embed] = embed
      @options[:embed_level] = embed_level
    end
  end
end

Well, with this solution you actually made my day...

@groyoh
Copy link
Member

groyoh commented May 29, 2015

Do your models inherit from ActiveModel::Base?

@vamsi-krishna-E0029
Copy link
Author

I think you meant ActiveRecord::Base. If thats the case yes. My db models inherit from ActiveRecord::Base and serializers from ActiveModel::Serializer

@groyoh
Copy link
Member

groyoh commented May 29, 2015

Yes that's what I meant, typo again 😄. Try:

class BaseSerializer < ActiveModel::Serializer
    def include_associations!
      @options[:embed_level] ||= 0
      return unless @options.key?(:embed) && @options[:embed_level] != 0
      embed = @options[:embed].split(',').map{|item| item.strip.to_sym}
      embed.each do |assoc|
        next unless _associations.key?(assoc)
        assoc_serializer = serializer_for(assoc)
        next unless assoc_serializer
        embed = @options[:embed]
        embed_level = @options[:embed_level]
        @options[:embed_level] -= 1
        @options[:embed] = assoc_serializer._associations.keys.join(",")
        include! assoc
        @options[:embed_level] = embed_level
      end
    end

    def serializer_for(assoc)
      serializer = _associations[assoc].options[:serializer]
      return serializer if serializer
      assoc_value = send(assoc)
      Array(assoc_value).first.active_model_serializer
    end
  end
end

@vamsi-krishna-E0029
Copy link
Author

Thanks again...I actually tried out this at first.

    def serializer_for(assoc)
      serializer = _associations[assoc].options[:serializer]
      return serializer if serializer
      assoc.to_s.classify.concat("Serializer").constantize
    end

which one would you prefer? I guess both should work.

@groyoh
Copy link
Member

groyoh commented May 29, 2015

Take yours if that works for you! 😉 And please close the issue if it's all good.

@vamsi-krishna-E0029
Copy link
Author

Ha Ha...LOL... Thanks a lot @groyoh That was really a timely help...

@joaomdmoura
Copy link
Member

Great! I'm glad to see that you guys figured it out! I'm closing it for now! bte @ovamsikrishna give a try to 0.10.x version ;) and try to use JSON API 😄

@beauby
Copy link
Contributor

beauby commented Sep 23, 2015

Since #1158 / #1127 you can do stuff like include: '*.*.*' (which exactly includes 3 levels of nested relationships) in 0.10.x.

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

No branches or pull requests

4 participants