Active_model_serializers: Pass options into raw array serializer call

Created on 9 Jun 2015  路  16Comments  路  Source: rails-api/active_model_serializers

I'm trying to serialize a collection of objects using the Array Serializer syntax.

ActiveModel::ArraySerializer.new(all, start_date: start_date, end_date: end_date, each_serializer: UserSerializer).to_json

I want to pass the start_date and end_date options so I can access it through serializer_options[:start_date] and serializer_options[:end_date]. When I puts out the serializer options though they are empty. I have serializer_options working when I use it through the render json call like this,

render json: @users, start_date: start_date, end_date: end_date

Most helpful comment

+1. I want to pass some params to seriallizer
in comment_seriallizer.rb

  def initialize(object, options)
    super
    @current_user_id = options[:current_user_id]
  end

controller

With single it work perfect

CommentSerializer.new(::Comment.first, {current_user_id: current_user.try(:id)})

But with array it not work.

ActiveModel::ArraySerializer.new(comments, current_user_id: current_user.try(:id), each_serializer: CommentSerializer)

puts options in function initialize => {:scope=>nil, :key_format=>nil, :only=>nil, :except=>nil, :polymorphic=>false, :namespace=>nil}

All 16 comments

Please see https://github.com/rails-api/active_model_serializers/blob/master/lib/action_controller/serialization.rb#L38-L48 and perhaps http://www.benjaminfleischer.com/2015/06/02/understanding-rails-model-serializers/ for how AMS works

I'd recommend against calling ArraySerializer directly, in general. It's not the intended usage, AFAIK.

What are you trying to do that you want to pass start_data/end_date in the ArraySerializer? Maybe I can help you reach your goal?

If you want transient attributes on your serialized models, you can just add attr_accessors for that

I have a filterable/sortable table I'm getting json data served into from my api. I want to make it so they can export that data to csv. I have to do the csv stuff all on the backend so it is accessible via urls and not just the front end. At first I replicated the serializer with a to_csv method but I kept adding fields so I decided to serialize the data and then turn it into a csv so when I add a column I don't have to add it in two places.

The tables have date range input so I have to pass in dates form params to the serializer to get the right efficiency/productivity data. So those same params passed into the serializer for rendering the json to the page needs to get passed to the call to the json in the csv method.

Does all that make sense? I'm more than open to an alternate solution and unfortunately couldn't come up with one.

You want to generate a CSV form the JSON? That sounds like using a CSV renderer e.g. https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_controller/metal/renderers.rb#L66-L128

It might help if you gave an example input and the desired output.

Here's some of my index controller code,

app/controllers/user_controller.rb

def index
  @users = User.where(labor_enabled: true)

  if params[:start_date].blank?
    start_date = User::DEFAULT_START_DATE
  else
    start_date = Time.parse(params[:start_date])
  end

  if params[:end_date].blank?
    end_date = User::DEFAULT_END_DATE
  else
    end_date = Time.parse(params[:end_date])
  end

  unless params[:search_term].blank?
    @users = @users.where("lower(users.firstname) LIKE ? or lower(users.lastname) LIKE ?", "%#{params[:search_term].downcase}%", "%#{params[:search_term].downcase}%")
  end

  @users.to_csv(start_date, end_date)

  respond_to do |format|
    format.json {render json: @users, start_date: start_date, end_date: end_date, meta: {startDate: start_date, endDate: end_date, role_list: Role.select([:id, :name]).map {|e| [e.id, e.name]}, language_list: User::LANGUAGE_LIST, labor_task_list: LaborTask.where("maintenance is not true").select([:id, :name, :photo_file_name]).map {|e| {id: e.id, name: e.name, photo_file_name: e.photo_file_name}}}}
    format.csv { send_data @users.to_csv(start_date, end_date) }
  end
end

So I need that to_csv method to use the same serializer logic as the json serializer. Right now I'm kicking it to a to_csv method, which is doing this,

app/models/user.rb

def self.to_csv(start_date=Time.now.midnight, end_date=Time.now)
  csv = []
  JSON.parse(ActiveModel::ArraySerializer.new(all, start_date: start_date, end_date: end_date, each_serializer: UserSerializer).to_json).each do |user|
    csv << self::METRICS_TABLE_HEADERS.keys.map { |thing| user[thing]}
  end
end

But the start_date and end_date aren't getting received by the serializer like they are in the json call.

Here's the serializer.

app/serializers/user_serializer.rb

class UserSerializer < ActiveModel::Serializer
  attributes :id, :delay_time, :current_task, :current_login, :firstname, :lastname, :efficiency, :hours, :photo_file_name, :daily_efficiency

  def daily_efficiency
    object.efficiency(Time.now.midnight, Time.now)
  end

  def efficiency
    user_stats[:efficiency].round(2)
  end

  def hours
    user_stats[:hours].round(2)
  end

  def user_stats
    puts '==============='
    ap serialization_options
    puts '==============='
    @stats ||= object.user_stats(serialization_options[:start_date], serialization_options[:end_date])
  end

  def current_login
    object.current_task ? object.current_task.name : 'Not Logged In'
  end

  def delay_time
    object.delay_time(serialization_options[:start_date], serialization_options[:end_date])
  end
end

+1. I want to pass some params to seriallizer
in comment_seriallizer.rb

  def initialize(object, options)
    super
    @current_user_id = options[:current_user_id]
  end

controller

With single it work perfect

CommentSerializer.new(::Comment.first, {current_user_id: current_user.try(:id)})

But with array it not work.

ActiveModel::ArraySerializer.new(comments, current_user_id: current_user.try(:id), each_serializer: CommentSerializer)

puts options in function initialize => {:scope=>nil, :key_format=>nil, :only=>nil, :except=>nil, :polymorphic=>false, :namespace=>nil}

Hey ppl, just read the thread :smile:
What version of AMS are you using? If not 0.10.x scope might help you into passing parameters to serializer.

yeap. scope save my day :D thanks @joaomdmoura

Btw would be awesome if we could replicate this issue on StackOverflow to help others :smile:

I'm already using scope for current_user but I have to pass in start and end dates. So what is happening is there is a table of data and there's a start and end date filter. Once they change the dates it hits the api again with the new dates and needs to get the new data in json. What I'm doing is passing the params through the serialization options which works great but I'm trying to figure out how to pass the options in so I can get csv. And what I'm doing to make csv is I have a model method that takes the json serializer and then takes the processed result and converts it to csv.

@bf4 If I add a render of csv will render csv: @users just work with the other serializer? Or is there more to this?

What version of AMS are you using? Which versions of Rails/Ruby?

@bf4 .9-stable and I'm using rails 3.2.17. There were a lot of ways options were handled in various versions of AMS, and .9-stable allows you to pass things as serialization_options.

@jbhatab Have you tried to use .merge to merge the data you want, after serialization, with the dates you need

@joaomdmoura I have a unique scenario with this where I need to pass the params into the serializer for it to correctly serialize it. I could use attr_accessor but it makes a lot more sense to pass params from the front end to the serializer and then output the data based on the params.

In my case I have a date range filter and when I change the dates I need the serializer to output different efficiency and production values.

@jbhatab unfortunately I don't believe that we have a feature that would allow you to easily do this.
There are some workarounds, but nothing graceful, like pass the date together with the user as scope.
It just seems an edge case for me. :pensive:

It's totally fine. I really couldn't think of many other scenarios besides this but I ended up just making custom ruby objects.

Might be something to think about for 1.0 but I'm good!

Really appreciate the input.

Here is how you can pass parameters from the parent serializer and show or hide attributes based on these parameters in the child serializer.

Parent serializer:

class LocationSharesSerializer < ActiveModel::Serializer
  attributes :id, :locations, :show_title, :show_address

  def locations
    ActiveModelSerializers::SerializableResource.new(object.locations, {
      each_serializer: PublicLocationSerializer,
      params: { 
        show_title: object.show_title
      },
    })
  end

end

Child serializer

class PublicLocationSerializer < ActiveModel::Serializer
  attributes :id, :latitude, :longitude, :title, :directions, :description, :address, :tags, :created_at, :updated_at, :photos

  def title
    object.title if @instance_options[:params][:show_title]
  end

end
Was this page helpful?
0 / 5 - 0 ratings