17
votes

I'm building an API using ActiveModel::Serializers. What is the best way to sideload data conditionally using params?

So I can make requests like GET /api/customers:

"customers": {
   "first_name": "Bill",
   "last_name": "Gates"
}

And GET /api/customers?embed=address,note

"customers": {
   "first_name": "Bill",
   "last_name": "Gates"
},
"address: {
   "street": "abc"
},
"note": {
   "body": "Banned"
}

Something like that depending on the params. I know ActiveModel::Serializers has the include_[ASSOCIATION]? syntax but how can I use it efficiently from my controllers?


This is my current solution, but it's not neat:

customer_serializer.rb:

def include_address?
  !options[:embed].nil? && options[:embed].include?(:address)
end

application_controller.rb:

def embed_resources(resources = [])
  params[:embed].split(',').map { |x| resources << x.to_sym } if params[:embed]
  resources
end

customers_controller.rb:

def show
  respond_with @customer, embed: embed_resources
end

Must be an easier way?

3

3 Answers

8
votes

I'm also looking for an effective and clean way to do this.

I found a solution but it's not pretty.

In my BaseController/ApplicationController I added this method:

serialization_scope :params

So the scope is now the params Hash and I can use it in the include_[ASSOCIATION]? methods of my serializers.

def include_associations?
    if scope[:embed]
        embed = scope[:embed].split(',') 
        return true if embed.include?('associations')
    end
end

I don't like this method because if I need to use the scope for something else like the current_user to conditionally return data if it's an admin for instance.

But this solution can work in some cases.

UPDATE

You can pass view_context instead of directly pass the params.

And you can delegate in your Serializer to keep the params name instead of scope.

in your ApplicationController:

serialization_scope :view_context

in your serializer:

delegate :params, to: :scope

And voila you can use params[:embed] in the include_[ASSOCIATION]? methods of your serializers.

2
votes

I have yet another solution based off of your answers because I wanted similar functionality. According to the documentation if one wants lower level control of association serialization, they can override include_associations!.

For example:

def include_associations!
    if scope[:embed]
        include! :addresses, {embed: :ids, include: true}
    else
        include! :addresses, {embed: :ids}
    end
end
1
votes

Very helpful to know about include_associations!, thanks! Noting that with the active_model_serializers gem (version 0.8.3) you can use @options to set context in the controller. For example, if in the controller you call

render json: customer, include_addresses: true

then in CustomerSerializer:

has_many :addresses
def include_associations!
  if @options[:include_addresses]
    include! :addresses
  end
end

then the addresses will be serialized. If you render with include_addresses set to false, they won't be. With newer versions of active_model_serializers, use serialization_options rather than @options.