24 September 2014

While wandering lost through the strange land of Rails code I stumbled upon some many bits of code. One particular bit that crossed my path was the /railties/lib/rails/generators/migration.rb code.

migration.rb
require 'active_support/concern'
require 'rails/generators/actions/create_migration'
module Rails
  module Generators
    # Holds common methods for migrations. It assumes that migrations has the
    # [0-9]*_name format and can be used by another frameworks (like Sequel)
    # just by implementing the next migration version method.
    module Migration
      extend ActiveSupport::Concern
      attr_reader :migration_number, :migration_file_name, :migration_class_name
      module ClassMethods
        def migration_lookup_at(dirname) #:nodoc:
          Dir.glob("#{dirname}/[0-9]*_*.rb")
        end
        def migration_exists?(dirname, file_name) #:nodoc:
          migration_lookup_at(dirname).grep(/d+_#{file_name}.rb$/).first
        end
##########code omitted here#################

    end
  end
end

Having never seen “ClassMethods” module in use, I decided to investigate. It turns out that this is a design pattern prevalent in Rails. Confused I decided to investigate using code examples. First we need to understand Class and Instance methods. Class methods are methods that belong to the class and are not available to an object instantiated from the class. Instance methods are just the opposite. They are methods that belong to an instantiated object but not to the class. The following code will illustrate this point.

ruby/language/include-extend$ ruby expected.rb

language/include-extend/expected.rb
class  Expected
  #Putting self in front of the method name makes it a class method
  def self.class_method
    puts "Expected.class_method succeeded!"
  end

  def instance_method
    puts "expected.instance_method succeeded!"
  end

end

puts ""
puts "Expected Code"
puts "Begin using class"
puts "--------------------------------------"
begin
  puts Expected.class_method
  puts Expected.instance_method
rescue
  puts "Expected.instance_method failed!"
  puts ""
end

puts "Begin using instantiated object"
puts "--------------------------------------"
expected = Expected.new

begin
  puts expected.instance_method
  puts expected.class_method
rescue
  puts "expected.class_method failed!"
end

Which when run gives us the following results

run expected.rb in command line
bash-4.2$ ruby expected.rb 

Expected Code
Begin using class
--------------------------------------
Expected.class_method succeeded!

Expected.instance_method failed!

Begin using instantiated object
--------------------------------------
expected.instance_method succeeded!

expected.class_method failed!

Having the basics under our belt let us take another step and talk about include and extend. Include and extend are used to include Modules into classes and other modules. Include “includes” instance methods and extends “includes” class methods. It can actually get quite a bit more complicated than this simple explanation. We will come back to this at the end.

Using include and extend we can write the following:

language/include-extend/using-module.rb
module UsingModule
  module InstanceMethods
    def instance_method
      puts "using_class.instance_method worked!"
    end
  end

  module ClassMethods
    def class_method
      puts "UsingClass.class_method worked!"
    end
  end
end


class UsingClass
  include UsingModule::InstanceMethods
  extend UsingModule::ClassMethods

end

puts ""
puts "Using Module Code"
puts "Begin using class instance"
puts "-------------------------------"
using_class = UsingClass.new()

begin
  puts using_class.instance_method
  puts using_class.class_method
rescue
  puts "using_class.class_method failed!"
end

puts ""
puts "Begin using class"
puts "-------------------------------"

begin
  puts UsingClass.class_method
  puts UsingClass.instance_method
rescue
  puts "UsingClass.instance_method failed!"
end

Which gives us similar results as expected.rb.

run using-module.rb in command line
bash-4.2$ ruby using-module.rb 

Using Module Code
Begin using class instance
-------------------------------
using_class.instance_method worked!

using_class.class_method failed!

Begin using class
-------------------------------
UsingClass.class_method worked!

UsingClass.instance_method failed!

Using-module.rb while an interesting experiment isn’t what you will likely see. You are more likely to see the use of only a ClassMethods sub module along with included class method as shown next.

language/include-extend/confusing-code.rb
module ConfusingModule
  def self.included(base)
#    This commented out code is also seen.
#    The results are the same as the line below it.
#    base.send :extend, ClassMethods
    base.extend(ClassMethods)
  end

  def instance_method
    puts "using_class.instance_method worked!"
  end

  module ClassMethods
    def class_method
      puts "ConfusingClass.class_method worked!"
    end
  end
end


class ConfusingClass
  include ConfusingModule

end

puts ""
puts "Confusing Code"
puts "Begin using class instance"
puts "-------------------------------"
confusing_class = ConfusingClass.new()

begin
  puts confusing_class.instance_method
  puts confusing_class.class_method
rescue
  puts "confusing_class.class_method failed!"
end

puts ""
puts "Begin using class"
puts "-------------------------------"

begin
  puts ConfusingClass.class_method
  puts ConfusingClass.instance_method
rescue
  puts "ConfusingClass.instance_method failed!"
end

Which once again will produce similar results.

Terminal window in mysecurity directory

bash-4.2$ ruby confusing-code.rb 

Confusing Code
Begin using class instance
-------------------------------
using_class.instance_method worked!

confusing_class.class_method failed!

Begin using class
-------------------------------
ConfusingClass.class_method worked!

ConfusingClass.instance_method failed!

Clear as mud? Good! I highly recommend playing with this code to get a more intuitive grasp of how it works. We now proceed to how Rails does all of this with hidden magic. It does this with ActiveSupport::Concern. This next code example requires you to have the ActiveSupport gem installed.

language/include-extend/concerned-code.rb

`` require ‘active_support’

module ConcernedModule extend ActiveSupport::Concern

def instance_method puts “concerned_class.instance_method worked!” end

module ClassMethods def class_method puts “ConcernedClass.class_method worked!” end end end

class ConcernedClass include ConcernedModule

end

puts "" puts “Concerned Code” puts “Begin concerned class instance” puts “——————————-” concerned_class = ConcernedClass.new()

begin puts concerned_class.instance_method puts concerned_class.class_method rescue puts “concerned_class.class_method failed!” end

puts "" puts “Begin concerned class” puts “——————————-”

begin puts ConcernedClass.class_method puts ConcernedClass.instance_method rescue puts “ConcernedClass.instance_method failed!” end

</pre>

Code which once again produces similar results.

<span class="terminalCaption">Terminal window in mysecurity directory</span><pre class="terminal">

bash-4.2$ ruby concerned-code.rb

Concerned Code Begin concerned class instance

concerned_class.instance_method worked!

concerned_class.class_method failed!

Begin concerned class

ConcernedClass.class_method worked!

ConcernedClass.instance_method failed!

</pre>

ActiveSupport::Concern, among other things,  searches for a ClassMethods sub module and extends it.  This brings us to where we can understand a bit more of Rails code and the Ruby magick it employs.  Finally lets look at one last piece of code that can confuse the use of extend.  What happens if we call extend on an object?

<span class="codeCaption"><a href="https://github.com/ChrisLynch42/ruby/blob/master/language/include-extend/extend-confusion.rb">language/include-extend/extend-confusion.rb</a>b</span><pre class="prettyprint">

module MoreConfusionModule

def instance_method puts “more_confusion_class.instance_method worked!” end

module ClassMethods def class_method puts “MoreConfusionClass.class_method worked!” end end end

module Diabolical module ClassMethods def confuse puts “Haha i am not a class method!” end end end

class MoreConfusionClass include MoreConfusionModule

end

puts "" puts “MoreConfusion Code” puts “Begin more_confusion class instance” puts “——————————-” more_confusion_class = MoreConfusionClass.new()

more_confusion_class.extend(Diabolical::ClassMethods)

begin puts more_confusion_class.instance_method puts more_confusion_class.class_method rescue puts “more_confusion_class.class_method failed!” end

puts "" puts “Begin more_confusion class” puts “——————————-”

begin puts MoreConfusionClass.class_method puts MoreConfusionClass.instance_method rescue puts “MoreConfusionClass.instance_method failed!” end

puts “——————————-” puts more_confusion_class.confuse

</pre>

The results of running this class may be somewhat suprising.

<span class="terminalCaption">Terminal window in mysecurity directory</span><pre class="terminal">

bash-4.2$ ruby extend-confusion.rb

MoreConfusion Code Begin more_confusion class instance

more_confusion_class.instance_method worked!

more_confusion_class.class_method failed!

Begin more_confusion class

MoreConfusionClass.instance_method failed!

Haha i am not a class method!

</pre>

The confuse method defined in Diabolical::ClassMethods mocks us!  Extend assigns the methods to the object which is calling it.  You do remember that in Ruby classes are also objects?  So if you call extend from an object it will assign the methods to the object not the class.

Below are links to sources the first of which I found to be a fantastic resource.

<ul>
<li><a href="http://engineering.appfolio.com/2013/06/17/ruby-mixins-activesupportconcern/">http://engineering.appfolio.com/2013/06/17/ruby-mixins-activesupportconcern/</a>
<li><a href="http://yehudakatz.com/2009/11/12/better-ruby-idioms/">http://yehudakatz.com/2009/11/12/better-ruby-idioms/</a>
<li><a href="http://api.rubyonrails.org/classes/ActiveSupport/Concern.html">http://api.rubyonrails.org/classes/ActiveSupport/Concern.html</a>
</ul>



Less Is More ~ Older posts are available in the archive.