I came across this while working with multiple somewhat similar user models. Originally, I had chosen to use Single Table Inheritance (STI), but opted to split the models because of increasing orthogonality. Luckily, Rails offers concerns which allows for multiple models to easily share functionality without having to use inheritance!
Sometimes, however, models sharing the same concern may need slightly different behavior. In my case, all 3 models had both a phone number field, though for one model in particular (the Client), the presence of
phone_number was not required.
The trick was to use class_attribute in the ‘included’ section of the concern, and then in the Client model, add
self.phone_number_optional = true.
# app/models/concerns/phonable.rb module Phonable extend ActiveSupport::Concern included do class_attribute :phone_number_optional phony_normalize :phone_number, default_country_code: 'US' validates :phone_number, presence: true, unless: :phone_number_optional validates :phone_number, phony_plausible: true end end
# app/models/client.rb class Client < ApplicationRecord include Phonable self.phone_number_optional = true end
The easiest thing was to use the inverse, hence
phone_number_optional instead of
phone_number_required, as the default of the
class_attribute would be nil and therefore falsey.
Remember to test both options when testing the concern, as the concern supports both cases. If you’re just testing the model that uses the concern, then you simply have to test for whatever case the model requires.
I found this to be a pretty clean solution to this somewhat messy issue, at least cleaner than duplication or throwing a lambda to evaluate a class literal into the validation conditional.