One of the widespread design patterns is composition. The Gang of Four already defined:
"Favor composition over inheritance". Ruby offers a great way to achieve composition by modules.
The original class from the "
Soft delete your ActiveRecord" example:
class Person < ActiveRecord::Base
attr_accessible :name
validates :active, :inclusion => { :in => [true, false] }
before_validation :activate,
:on => :create,
:if => Proc.new {|r| r.active.nil? }
def self.active
where :active => true
end
def activate
self.active = true
self
end
def deactivate
self.active = false
self
end
end
can be refactored to include the soft deletion feature by composition. Starting with the module structure in the new created file lib/activerecord_extentions.rb:
module ActiveRecordExtensions
module Deactivateable
extend ActiveSupport::Concern
self.included do
end
end
end
The module
Deactivateable was scoped by
ActiveRecordExtensions to be more clear about its intention.
Using the Ruby on Rails ActiveSupport::Concern module helps to handle module dependencies gracefully.
The
self.
included block contains all the stuff, that has to be done immediately when the module was included. Until now it contains not a bit logic, but can already be included:
class Person < ActiveRecord::Base
include ActiveRecordExtensions::Deactivateable
attr_accessible :name
validates :active, :inclusion => { :in => [true, false] }
before_validation :activate,
:on => :create,
:if => Proc.new {|r| r.active.nil? }
def self.active
where :active => true
end
def activate
self.active = true
self
end
def deactivate
self.active = false
self
end
end
Extracting the instance methods and the putting them into the module:
module ActiveRecordExtensions
module Deactivateable
extend ActiveSupport::Concern
self.included do
end
def activate
self.active = true
self
end
def deactivate
self.active = false
self
end
end
end
The second step is to move the validator and the default setter on creation into the
self.
included block:
module ActiveRecordExtensions
module Deactivateable
extend ActiveSupport::Concern
self.included do
validates :active, :inclusion => { :in => [true, false] }
before_validation :activate,
:on => :create,
:if => Proc.new {|r| r.active.nil? }
end
def activate
self.active = true
self
end
def deactivate
self.active = false
self
end
end
end
Both the validator and the callback are processed right at including time on the target.
At least the class method is moved into the class scope of the prospectively enriched class:
module ActiveRecordExtensions
module Deactivateable
extend ActiveSupport::Concern
self.included do
validates :active, :inclusion => { :in => [true, false] }
before_validation :activate,
:on => :create,
:if => Proc.new {|r| r.active.nil? }
end
def activate
self.active = true
self
end
def deactivate
self.active = false
self
end
module ClassMethods
def active
where :active => true
end
end
end
end
Finally, let's take a look at the target class
Person after moving the soft delete feature into the module:
class Person < ActiveRecord::Base
include ActiveRecordExtensions::Deactivateable
attr_accessible :name
end
and benefit from it by sharing it with other classes:
class Task < ActiveRecord::Base
include ActiveRecordExtensions::Deactivateable
end
The benefits are:
- Modularized features (means concise code blocks)
- Maintainable code
- Avoids the downsides of inheritance
Supported by Ruby 2.1.1 and Ruby on Rails 3.2.17