Duplication of enum across two models

Posted on

Problem

In my Rails app, I have two classes each with the same enum for status. Code climate is complaining about duplication.

class Enrolment < ActiveRecord::Base
  has_many :enrolment_presentations
  has_many :presentations, through: :enrolment_presentations

  enum status: {
    'Pending' => 1,
    'Suspended' => 2,
    'Cancelled' => 3,
    'Pass' => 4,
    'Fail' => 5,
    'DPR' => 6,
    'Unknown' => 7,
    'Enrolled' => 8
  }
end


class EnrolmentPresentations < ActiveRecord::Base
  belongs_to :enrolment
  belongs_to :presentation

  enum status: {
    'Pending' => 1,
    'Suspended' => 2,
    'Cancelled' => 3,
    'Pass' => 4,
    'Fail' => 5,
    'DPR' => 6,
    'Unknown' => 7,
    'Enrolled' => 8
  }
end

How would you go about removing this duplication?

Solution

ActiveRecord enums “are exposed through a class method with the pluralized attribute name”, so I recommend the following code. However, I would highly recommend making the enums symbols instead of strings for compatibility. Also, enrollment is spelled with two l’s. Note that you can control the capitalization of DPR using inflections.

class Enrollment < ActiveRecord::Base
  has_many :enrollment_presentations
  has_many :presentations, through: :enrollment_presentations

  enum status: {
    pending: 1,
    suspended: 2,
    cancelled: 3,
    pass: 4,
    fail: 5,
    dpr: 6,
    unknown: 7,
    enrolled: 8
  }
end

class EnrollmentPresentations < ActiveRecord::Base
  belongs_to :enrollment
  belongs_to :presentation

  enum status: Enrollment.statuses
end

In Rails 6, you can put the enum in a module in concerns and include it in your models.

model/concerns/statusable.rb

module Statusable
    extend ActiveSupport::Concern

    included do
        enum status: {
            pending: 1,
            suspended: 2,
            cancelled: 3,
            pass: 4,
            fail: 5,
            dpr: 6,
            unknown: 7,
            enrolled: 8
        }
    end
end

model/enrollment.rb

class Enrolment < ApplicationRecord
    include Statusable
    has_many :enrolment_presentations
    has_many :presentations, through: :enrolment_presentations
end

Forgive my ignorance, but isn’t ActiveRecord::Base#enum just a method? Therefore, to help reduce duplication (but not eliminate it), you could do something like this:

STATUS_FLAGS = {
  'Pending' => 1,
  'Suspended' => 2,
  'Cancelled' => 3,
  'Pass' => 4,
  'Fail' => 5,
  'DPR' => 6,
  'Unknown' => 7,
  'Enrolled' => 8
}

class Enrolment < ActiveRecord::Base
  has_many :enrolment_presentations
  has_many :presentations, through: :enrolment_presentations

  enum status: STATUS_FLAGS
end


class EnrolmentPresentations < ActiveRecord::Base
  belongs_to :enrolment
  belongs_to :presentation

  enum status: STATUS_FLAGS
end

This, of course, doesn’t eliminate duplication. One way around that might be to use a module as a mixin (though I’m not sure how that would work with ActiveRecord) or use singular inheritance to create a class that extends ActiveRecord::Base with the enum status, and then inherit from that – though I have to admit it feels hacky extending Active Records through inheritance.

As a stylistic note, it appears that the convention for #enum is to use lowercase names rather than PascalCase.

Leave a Reply

Your email address will not be published. Required fields are marked *