5
\$\begingroup\$

I have many small but similar classes like these:

class DepartmentSerializer < Serializer
  self.root = false
  attributes :id, :name
  has_many :employees, serializer: EmployeeSerializer
  has_one :location, serializer: LocationNameSerializer
end

class DepartmentWithManagersSerializer < Serializer
  self.root = false
  attributes :id, :name
  has_many :primary_managers, serializer: ManagerSerializer
  has_many :secondary_managers, serializer: ManagerSerializer
end

class DepartmentNameSerializer < Serializer
  self.root = false
  attributes :id, :name
end

class DepartmentWithEmployeesSerializer < Serializer
  self.root = false
  attributes :id, :name
  has_many :employees, serializer: EmployeeNameSerializer
end

As you can see they're all just variations on a theme. There is a potential here for a combinatorial explosion of classes if there are enough use cases. Should I do something like ActiveRecord's find methods which allow you to create methods like .find_by_name_and_manager_and_location?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Using inheritance, first you can clean up your structure as following:

class DepartmentNameSerializer < Serializer
  self.root = false
  attributes :id, :name
end

class DepartmentSerializer < DepartmentNameSerializer
  has_many :employees, serializer: EmployeeSerializer
  has_one :location, serializer: LocationNameSerializer
end

class DepartmentWithManagersSerializer < DepartmentNameSerializer
  has_many :primary_managers, serializer: ManagerSerializer
  has_many :secondary_managers, serializer: ManagerSerializer
end

class DepartmentWithEmployeesSerializer < DepartmentNameSerializer
  has_many :employees, serializer: EmployeeNameSerializer
end

There is little you can do to really enhance your code any further in terms of readability and reusability if you want to separate all serializer contexts into separate classes like this.

If you are using ActiveModel::Serializers which I think you do, since 0.8.0 you can use ActiveRecord-Serialization-like :except and :only to add some serialization context, so you could refactor your structure to work like the following:

class DepartmentSerializer < Serializer
  self.root = false
  attributes :id, :name, :employee_names, :location_names
  # it is afaik not possible to pass serialization context to the other serializers 
  # via association
  # has_many :employee_names, serializer: EmployeeSerializer, only: :name
  # has_one :location_names, serializer: LocationSerializer, only: :name
  has_many :employees, serializer: EmployeeSerializer
  has_many :primary_managers, serializer: ManagerSerializer
  has_many :secondary_managers, serializer: ManagerSerializer

  def employee_names
    EmployeeSerializer.new(object.employees, only: :name) # + root: false if EmployeeSerializer uses a root
  end

  def location_names
    LocationSerializer.new(object.locations, only: :name) # + root: false if LocationSerializer uses a root
  end
end

And your controllers can call the DepartmentSerializer like this:

class DepartmentController < ApplicationController # or other

  def some_context_with_managers
    render json: @department, except: %i(employees employee_names location_names)
  end

  # ... and so on
end
\$\endgroup\$
1
  • \$\begingroup\$ You're right that I'm using ActiveModel::Serializers. I didn't know about the :except and :only options. Thanks for the tip and the great answer! \$\endgroup\$ Dec 15, 2013 at 22:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.