Value object management for Rails Active Records.
Override getters and setters using custom value object to dry active records from flooding logic.
Tested with Rails 5.
Add this line to your application's Gemfile :
gem 'value_object', git: '[email protected]:Snapp-FidMe/rails-value-object.git'
And then execute :
$ bundle
Uniqueness validator will blow cast type error. Add this in initializer :
ActiveRecord::Base.connection.class.send(:define_method, :type_cast) do |value, column = nil|
if value.is_a?(ValueObject::Base)
super(value.value, column)
else
super(value, column)
end
end
ActiveRecord::Base.connection.class.send(:define_method, :quote) do |value|
if value.is_a?(ValueObject::Base)
super(value.value)
else
super(value)
end
end
Considering model User migrated with :
class CreateUsers < ActiveRecord::Migration[5.1]
def change
create_table :users do |t|
t.datetime :birthdate
t.string :paypal_id
t.string :email
end
end
end
We can create an email value object :
class Email < ValueObject::Base
end
And use it in our model :
class User < ApplicationRecord
extend ValueObject::Accessor
value_object_accessor :email
end
user = User.new(email: '[email protected]')
user.email #=> value object
We can give directly a value object as attribute :
user = User.new(email: Email.new('[email protected]'))
user.email #=> value object
Accessor use a value object name equal to targeted attribute. We can user another value object type :
class User < ApplicationRecord
extend ValueObject::Accessor
value_object_accessor :email
value_object_accessor :paypal_id, Email
end
user = User.new(paypal_id: '[email protected]')
user.paypal_id #=> Email value object
Email.new(nil).nil? #=> true
Email.new(nil).present? #=> false
Email.new('').blank? #=> true
Email.new('').present? #=> false
Email.new('[email protected]').present? #=> true
Email.new('[email protected]') == Email.new('[email protected]') #=> true
Email.new('[email protected]') <=> Email.new('[email protected]') #=> 0
Email.new('[email protected]').value #=> '[email protected]'
Email.new('[email protected]').to_s #=> '[email protected]'
Email.new('[email protected]').to_json #=> "\"[email protected]\""
Email.new('[email protected]').as_json #=> '[email protected]'
Use custom rails validation when needed.
class Email < ValueObject::Base
FORMAT = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validator
def valid?
value.present? && value.match(FORMAT).present?
end
end
class User < ApplicationRecord
extend ValueObject::Accessor
value_object_accessor :email
validates :email, presence: true, email: { allow_nil: true }
end
user = User.new(email: '[email protected]')
user.valid? #=> true
user = User.new(email: 'wrong')
user.valid? #=> false, error message at email: :invalid
Can user validator DSL to give specific validation :
class Birthdate < ValueObject::Base
validator name: 'MajorityValidator', with: ->(record, attribute, value) do
record.errors.add(:birthdate, :too_young) if value.present? && value.younger_than(18)?
end
def younger_than(age)?
value > Time.zone.now - age.years
end
end
class User < ApplicationRecord
extend ValueObject::Accessor
value_object_accessor :birthdate
validates :birthdate, majority: true
end
user = User.new(birthdate: Time.zone.now - 19.years)
user.valid? #=> true
user = User.new(birthdate: Time.zone.now - 17.years)
user.valid? #=> false, error message at birthdate: :too_young
- method_missing redirect method to inner value
- custom validator options to make simpler
- explicit exception when validator override already existing validator
The gem is available as open source under the terms of the MIT License.