Search code examples
ruby-on-railsactive-model-serializers

How to limit the active_model_serializers attributes depending on a specific field without having to put the `if:` option on every attribute?


I have a model that can have various types that will affect the fields being used. I'd like to remove the unused fields from the output. Currently, this works:

attribute :field1, if: :is_type1?
attribute :field2, if: :is_type1?
attribute :field3, if: :is_type2?
attribute :field4 # always shown

The thing is I have a lot of attributes and this can become tedious and hard to maintain especially when a field can be used by some but not all of the types. I know that maybe the modelization could be changed to have multiple models and thus multiple serializers but this not something I cannot afford right now so I'm looking for another solution.

I imagined 2 ways of doing this: either having some kind of block to define attributes like:

given -> { object.type == 1 } do
  attributes :field1, :field2
end
given -> { object.type == 2 } do
  attributes :field3
end
attribute :field4

Or using some generic method in if: everywhere. For that matter, I already had defined in my model the fields that apply to each type:

def allowed_attr
  allowed_attr = {
    field1: false,
    field2: false,
    field3: false
  }

  case type
  when 1
    allowed_attr[:field1] = true
    allowed_attr[:field2] = true
  when 2
    allowed_attr[:field3] = true
  end
  allowed_attr
end

What i would like to do in my serializer then is something like that:

attribute :field1, if: -> { allowed(attribute_name) }
attribute :field2, if: -> { allowed(attribute_name) }
attribute :field3, if: -> { allowed(attribute_name) }
attribute :field4, if: -> { allowed(attribute_name) }

def allowed(attr)
  object.allowed_attr[attr] != false # allows everything that is not precisely set to false
end

The thing is I don't seem to be able to access the field currently being tested (attribute_name)

Is there some way to do what I wish that I have overlooked ?

EDIT

Here's the solution I used based on Omnigazer's answer.

[
  :field1
  [:field1_attr, :field1],
  [:field2, Proc.new { object.field1.present? }]
].each do |attr|
  if attr.kind_of? Array
    name = attr.shift

    if attr[0].kind_of? Symbol
      allowed = attr.shift
    end

    if attr[0].kind_of? Proc
      cond = attr.shift
    end
  end

  name    ||= attr
  allowed ||= name
  cond    ||= Proc.new { true }

  attribute name, if: -> { instance_eval(&cond) && object.allowed_attribute?(allowed) }
end

Solution

  • You could do something like this:

    [:field1, :field2, :field3, :field4].each do |attribute_name|
        attribute attribute_name, if: -> { allowed(attribute_name) }
    end