Sonntag, 27. April 2014

Return something meaningful! ... Ruby methods.

The response of a message should always be appropriate. And that means if a method promises to do something meaningful, its response should be accordingly.
For example:
class Person
  attr_accessor :born_at
  def adult?
    unless born_at.nil?
      (born_at.year - Date.current.year) > 18
    end
  end
end
and trying it in IRB:
Person.new.adult?
=> nil
Well, since a nil object behaves like a false object in a condition, it works in most cases as expected. But apart from potentially breaking codes relying on that response, it is breaking the promise to return a meaningful result (either true or false) That nasty response behaviour can be refactored to:
class Person
  attr_accessor :born_at
  def adult?
    return false if born_at.nil?
    (born_at.year - Date.current.year) > 18
  end
end
So nil oftentimes is not a appropriate response.
Meaningful responses not only refer to getter methods, but also to setter methods.
The example:
class Stack
  def initialize
    @store = Array.new
  end

  def push element
    @store << element
  end

  def size
    @store.size
  end
end
in IRB again:
stack = Stack.new
stack.push 42
=> [42]
Why should the setter method push return an array, which is an internal data structure? That is not meaningful. More useful is to return self instead:
class Stack
  def initialize
    @store = Array.new
  end

  def push element
    @store << element
    self
  end

  def size
    @store.size
  end
end
so that the result does not reveal internal and dull data structures. Instead the result can be used for chaining (read Chain your Ruby methods!), like:
stack = Stack.new
stack.push(42).size
=> 1
Think about the result of EVERY method.

Supported by Ruby 1.9.3

Sonntag, 20. April 2014

Share your Ruby logic with modules!

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:
  1. Modularized features (means concise code blocks)
  2. Maintainable code
  3. Avoids the downsides of inheritance

Supported by Ruby 2.1.1 and Ruby on Rails 3.2.17

Sonntag, 13. April 2014

Soft delete your ActiveRecord!

Some models should not be deleted, but deactivated for very comprehensible reasons:
  1. Restoring the record
  2. Documenting the record in pre deletion points in time
  3. Statistics
The migration for adding the new boolean 'active' column to the Person model:
rails g migration add_active_to_people active:boolean
and running rails db:migrate. The model:
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
The model expects the boolean active flag to be set with nothing else than true or false.
Furthermore every new person record will be initialized with true in a callback before validating it. That means if nothing else was set, the instance method activate is called, which does exactly set the flag to true by default.
A named scope appends a selection to the finder chain, returning all active people. A usage example:
Person.active
If you are not familiar with ActiveRecord scopes read 'Scope the model!'.
The second instance method deactivate deactivates the record. In fact it prepares soft deletion by setting the 'active' flag to false like:
Person.find(1).deactivate
and touching he database:
Person.find(1).deactivate.save
which will generate the SQL on a MariaDB system:
UPDATE 'people' SET 'active' = 0, WHERE 'people'.'id' = 1
Well that could be wrapped behind:
def deactivate!
  deactivate.save!
end
Another reasonable data type for soft deletion columns is datetime like deleted_at. I would suggest using it, when the timestamp of the deletion has to be documented as well.

Supported by Ruby 2.1.1 and Ruby on Rails 3.2.17

Sonntag, 6. April 2014

Scope the model!

The Ruby on Rails ORM ActiveRecord encapsulates all the inconvenient SQL stuff which keeps the code maintainable and secure (think of SQL injection).
For example the model:
class Person < ActiveRecord::Base
  attr_accessible :name, :birthday
end
and the PeopleController#index lists the first 10 kids:
class PeopleController < ApplicationController
  def index
    @people = if params[:page] and params[:page] > 0
      Person.where(:birthday => 18.year.ago..Time.current).limit(10).offset(params[:page] * 10).order(:name)
    else
      Person.where(:birthday => 18.year.ago..Time.current).order(:name)
    end
  end
end
The query does not limits the result, if no params[:page] was sent. Of course the generated query works, but the code is awkward. In addition new filter requirements will shake it up totally.
Before going on refactoring, some words how the ActiveRecord finder statement works.
The entire finder statement consists of four query methods (ActiveRecord::QueryMethods) 'where', 'order', 'limit' and 'offset', each returning a chain object, but which are only chained together to a SQL statement and fired finally. And that is important to know. In fact the query methods are only fragments and are combineable, but do not touch the database until the last method was called.
The refactoring starts with extending the model with some scopes:
class Person < ActiveRecord::Base
  attr_accessible :name, :birthday
  default_scope order(:name)
  scope :kids, where(:birthday => 18.year.ago..Time.current)

  def self.range per_page=10, page=nil
    return scoped if page.nil? or page <= 0
    limit(per_page).offset(page * per_page)
  end
end
The default scope always orders by the people name.
Using the first named scope 'kids' appends the condition searching for alle people beeing younger than 18 years old. A scope is just a static class method.
And that is why the second named scope 'range' is one. The default value for the 'per_page' parameter is 10 and 'page' can even be nil. If that is the case, a blank scope object is returned, preventing the chain to be polluted.
Right, the model is fatter, because there was logic added, but the controller is skinnier:
class PeopleController < ApplicationController
  def index
    @people = Person.kids.range(10, params[:page])
  end
end
You should scope you ActiveRecord queries, because:
The chains and their logic are reuseable
The chains can be combined
The query finders themselves are more readable
The logic is encapsulated and therefore better maintainable (think of having people queries spread all over your application and you have to change the default order)
Further articles of interest:

Supported by Ruby 2.1.1 and Ruby on Rails 3.2.17