Charly's Tech Blog

home

Rails refactoring exercise

02 Oct 2008

I started my blog after reading jay field's post on the underuse of modules and 'design pattern in ruby' book. Mainly because i thought it would be interesting, as a learning process, to explore the silent power of modules.

So knowing Rails was doing a heavy use of alias_method_chain as it's AOP trick, i did a shallow dive in ActiveRecord's code to see how it could be transformed in the MultiInheritance-like-style of modules.

require "rubygems"
require "uninclude"

module Core
  def save; "saved" end
end

module Dirty
  def save; "clean + " + super end
end

module Transaction
  def save; "transaction + " + super end
end

module Validation
  def save;  "valid + " + super end
end

class Base
  include Core
  include Dirty
  include Transaction
  include Validation
end

# PLUGIN adding more validation
module MyValidation
  def save
    "more validation + " + super
  end
end

# PLUGIN overiding Transaction
module MyTransaction
  def save
    Base.send :uninclude, Transaction
    body = "my transaction! + " + super
    Base.send :include, Transaction
    body
  end
end

# Our everyday model
class A < Base
  include MyTransaction
end

class B < Base; end

class C < Base
  include MyValidation
end

a = A.new
b = B.new
c = C.new
puts a.save #=> my transaction! + valid + clean + saved
puts b.save #=> transaction + valid + clean + saved
puts c.save #=> more validation + transaction + valid + clean + saved

UPDATE I published the post unfinished not thinking it would attract much attention without annoucement : so sorry for the lack of explanation.

So...

module MyTransaction
  def save
    Transaction.instance_eval do
      @@save = instance_method(:save)
      remove_method :save
    end

    body = "my transaction! + " + super

    Transaction.module_eval do
      define_method :save do
        @@save.bind(self).call
      end
    end

    return body
  end
end

Another solution would be to use the callstack to intercept the presence of MyTransaction#save and tell Transaction to do a transparent super.