HiveBrain v1.2.0
Get Started
← Back to all entries
patternrubyMinor

Classes to serialize departments and their employees

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
employeesserializedepartmentsclassesandtheir

Problem

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?

Solution

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

Code Snippets

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
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
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

Context

StackExchange Code Review Q#37305, answer score: 2

Revisions (0)

No revisions yet.