14 July 2017

Metaprogramming = code that produces code

Learn by example:

Express declaration of finders and accessors

class Purchase < ActiveRecord::Base
  validates :status,
    :presence => true,
    :inclusion => { :in => %w(in_progress submitted approved shipped received canceled) }

  # Status Finders
  scope :all_in_progress, where(:status => "in_progress")
  scope :all_submitted, where(:status => "submitted")
  scope :all_approved, where(:status => "approved")
  scope :all_shipped, where(:status => "shipped")
  scope :all_received, where(:status => "received")
  scope :all_canceled, where(:status => "canceled")

  # Status Accessors
  def in_progress?
    status == "in_progress"
  end
  def submitted?
    status == "submitted"
  end
  def approved?
    status == "approved"
  end
  def shipped?
    status == "shipped"
  end
  def received?
    status == "received"
  end
  def canceled?
    status == "canceled"
  end
end

Metaprogramming

class Purchase < ActiveRecord::Base
  STATUSES = %w(in_progress submitted approved shipped received)

  validates_presence_of :status
  validates_inclusion_of :status, :in => STATUSES
  
  # Status Finders
  class << self
    STATUSES.each do |status_name|
      define_method "all_#{status_name}"
        where(:status => status_name)
      end
    end
  end

  # Status Accessors
  STATUSES.each do |status_name|
    define_method "#{status_name}?"
    status == status_name
    end
  end
end

Extending classes

# lib/extensions/statuses.rb
class ActiveRecord::Base
  def self.has_statuses(*status_names)
    validates :status,
      :presence => true,
      :inclusion => { :in => status_names }

    # Status Finders
    status_names.each do |status_name|
      scope "all_#{status_name}", where(:status => status_name)
    end

    # Status accessors
    status_names.each do |status_name|
      define_method "#{status_name}?" do
        status == status_name
      end
    end
  end
end

class Purchase < ActiveRecord::Base
  has_statuses :in_progress, :submitted, :approved, :shipped,
  :received
end