54

How do I render an attribute only if some condition is true?

For example, I want to render User's token attribute on create action.

livibetter
  • 19,832
  • 3
  • 42
  • 42
Dan
  • 1,274
  • 2
  • 15
  • 32

6 Answers6

86

In the latest version (0.10.x), you can also do it this way:

class EntitySerializer < ActiveModel::Serializer
  attributes :id, :created_at, :updated_at
  attribute :conditional_attr, if: :condition?

  def condition?
    #condition code goes here
  end
end

For example:

class UserSerializer < ActiveModel::Serializer
  attributes :id, :username, :name, :email, :created_at, :updated_at
  attribute :auth_token, if: :auth_token?

  def created_at
    object.created_at.to_i
  end

  def updated_at
    object.updated_at.to_i
  end

  def auth_token?
    true if object.auth_token
  end
end

EDIT (Suggested by Joe Essey) :

This method does not work with latest version (0.10)

With the version 0.8 it is even simpler. You don't have to use the if: :condition?. Instead you can use the following convention to achieve the same result.

class EntitySerializer < ActiveModel::Serializer
  attributes :id, :created_at, :updated_at
  attribute :conditional_attr

  def include_conditional_attr?
    #condition code goes here
  end
end

The example above would look like this.

class UserSerializer < ActiveModel::Serializer
  attributes :id, :username, :name, :email, :created_at, :updated_at
  attribute :auth_token

  def created_at
    object.created_at.to_i
  end

  def updated_at
    object.updated_at.to_i
  end

  def include_auth_token?
    true if object.auth_token
  end
end

See 0.8 documentation for more details.

Dennis
  • 56,821
  • 26
  • 143
  • 139
Abdelhakim AKODADI
  • 1,556
  • 14
  • 22
  • 2
    The library has been updated. You no longer need `attribute :foo, if: :bar`. Simply name a function for each attribute you wish to conditionally show like: `include_foo?` and do the bool check there. It's in the docs that are linked in the answer. – Joe Essey Mar 07 '18 at 19:39
  • Thanks @JoeEssey. I added your suggestion. – Abdelhakim AKODADI Mar 09 '18 at 14:56
  • 6
    The include_foo? method no longer appears to be valid as of 0.10 – RonLugge Mar 26 '18 at 22:48
  • @RonLugge I think it is still valid. See documentation right [here](https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/serializers.md#attributes). – Abdelhakim AKODADI Mar 29 '18 at 22:12
  • 3
    Sorry, to be clear, the automatic `include_foo?` methods appear to have been removed -- you still have the ability to add an `if: foo?` parameter to the attribute method. – RonLugge Mar 30 '18 at 18:11
  • not sure about `With the version 0.8 it is even simpler`, coz what about case `not include foo` ? in 0.10 it looks so simpler – Alex Strizhak Jun 07 '19 at 16:13
  • @AbdelhakimAKODADI just take a lookt a case: for not include for few conditions 0.10: `attributes :fields, if mutli_conditions`, `if mutli_condition` etc. 0.8: `include_fields do mutli_conditions` alias_method :include_fields, include_fields_2 etc. – Alex Strizhak Jun 18 '19 at 05:54
  • Would be nice if this answer was updated, the automatic include methods no longer work in newer versions – Joel Blum Sep 22 '21 at 15:04
  • @JoelBlum It's been a while since I used RoR, so I'm not up-to-date with the newer versions. You can go ahead and update – Abdelhakim AKODADI Sep 26 '21 at 01:27
52

you can override the attributes method, here is a simple example:

class Foo < ActiveModel::Serializer

  attributes :id

  def attributes(*args)
    hash = super
    hash[:last_name] = 'Bob' unless object.persisted?
    hash
  end

end
apneadiving
  • 114,565
  • 26
  • 219
  • 213
  • 1
    This is fantastic for adding dynamic attributes. – bennick Jan 19 '17 at 00:53
  • I definitely prefer this method. It makes adding a bunch of conditionals a lot more concise because you don't have to have a method for each attribute (which are incredibly redundant anyway). – Chris Cirefice May 24 '17 at 18:20
5

You could start by setting a condition on the serializers 'initialize' method. This condition can be passed from wherever else in your code, included in the options hash that 'initialize' accepts as second argument:

class SomeCustomSerializer < ActiveModel::Serializer

  attributes :id, :attr1, :conditional_attr2, :conditional_attr2

  def initialize(object, options={})
    @condition = options[:condition].present? && options[:condition]
    super(object, options)
  end

  def attributes(*args)
    return super unless @condition  #get all the attributes
    attributes_to_remove = [:conditional_attr2, :conditional_attr2]
    filtered = super.except(*attributes_to_remove)
    filtered
  end
end

In this case attr1 would always be passed, while the conditional attributes would be hidden if the condition is true.

You would get the result of this custom serialization wherever else in your code as follows:

custom_serialized_object = SomeCustomSerializer.new(object_to_serialize, {:condition => true})

I hope this was useful!

froskos
  • 494
  • 5
  • 10
2

Serializer options were merged into ActiveModel Serializers and now are available (since 0.10).

Dan
  • 1,274
  • 2
  • 15
  • 32
  • 3
    Unfortunately, the discussion you linked to doesn't provide any useful description of where to find those options or how to use them. – RonLugge Mar 26 '18 at 22:46
1

Override is a good idea, but if you use the super the attributes will be calculated before you remove what you want. If it does not make difference to you, ok, but when it does, you can use it:

def attributes(options={})
  attributes =
    if options[:fields]
      self.class._attributes & options[:fields]
    else
      self.class._attributes.dup
    end

  attributes.delete_if {|attr| attr == :attribute_name } if condition

  attributes.each_with_object({}) do |name, hash|
    unless self.class._fragmented
      hash[name] = send(name)
    else
      hash[name] = self.class._fragmented.public_send(name)
    end
  end
end

ps: v0.10.0.rc3

Passalini
  • 101
  • 6
1

Here is how you can pass parameters directly to the serializer instance and show or hide attributes based on these parameters in the serializer declaration.

It also works with parent-child serializers.

Controller or parent serializer:

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

Serializer with conditions:

class PublicLocationSerializer < ActiveModel::Serializer
  attributes :id, :latitude, :longitude, :title

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

end
Tim Kozak
  • 4,026
  • 39
  • 44