Skip to content

Instantly share code, notes, and snippets.

@chriskillpack
Created November 4, 2012 20:42
Show Gist options
  • Save chriskillpack/4013683 to your computer and use it in GitHub Desktop.
Save chriskillpack/4013683 to your computer and use it in GitHub Desktop.
Exploring Ruby modules
# My explorations into ruby modules and instance variables.
module TestModule
def tm_foo
@bar = 1
end
def self.hello
'world'
end
end
class TestClass
include TestModule
def tc_foo
@bar
end
end
# When a class includes a module the modules instance methods are included
# directly onto the class, so instance variables in these methods are instance
# methods on the class.
tc = TestClass.new
tc.tc_foo # => nil
tc.tm_foo # => 1
tc.tc_foo # => 1
tc.instance_variable_get('@bar') # => 1
# What about the module's class methods?
tc.tm_set_class_var # NoMethodError, inaccessible
# Instead, module class methods behave like the namespaced static methods:
TestModule.hello # => 'world'
# So that's instance methods, is it possible to add class methods via a module?
# Yes, but using the extend keyword instead:
class TestClassExtend
extend TestModule
end
TestClassExtend.tm_foo # => 1
TestClassExtend.instance_variable_get('@foo') # => 1
TestClassExtend.new.tm_foo # NoMethodError
# So the include keyword adds instance methods, and the extend keyword adds
# class methods, what if I wanted to add a mixture?
# Let's try the obvious:
class TestClassIncludeExtend
include TestModule
extend TestModule
end
TestClassExtend.tm_foo # => 1
TestClassExtend.new.tm_foo # => 1
# It works, but it's not very useful - why add the same methods as both class
# and instance. What if I want to add some methods as instance methods and
# others as class methods. We could split into separate modules:
module TestInstanceMethods
def tim_hello
"world"
end
end
module TestClassMethods
def tcm_greeting
"hello there!"
end
end
class TestClassSplitModule
include TestInstanceMethods
extend TestClassMethods
end
TestClassSplitModule.tcm_greeting # => "hello there!"
TestClassSplitModule.new.tim_hello # => "world"
# Hooray that works, but maintain two separate files is a bit of a pain. We
# can do better by making use of the handy included module function. If this
# class method is defined then Ruby will call it when a module is included:
module MyImprovementModule
def tim_hello
"world"
end
module ClassMethods
def tcm_greeting
"hello there!"
end
end
# klass is the class of the receiving class, which in the example below will
# be TestImprovedModule.
def self.included(klass)
klass.extend ClassMethods
end
end
class TestImprovedModule
include MyImprovementModule
end
TestImprovedModule.tcm_greeting # => "hello there!"
TestImprovedModule.new.tim_hello # => "world"
# When I first learned the pattern showed in the MyImprovementModule it brought
# home the dynamism of Ruby. include and extend are not priviledged keywords
# that get special treatment from the language in order to work their magic.
# Instead they are functions that can be applied dynamically as the situation
# requires.
module TestModule
def self.tm_set_class_var
@delta = 1
end
def self.delta
@delta
end
end
TestModule.delta # => nil
TestModule.tm_set_class_var # => 1
TestModule.delta # => 1
TestModule.instance_variable_get('@delta') # => 1
# This implies that modules can have their own state which is 'private' from the
# class. Very useful in avoiding naming collisions with instance variables
# in the receiving class. Write that one down.
# These modules instance variables, are they accessible from the receiving
# class?
TestModule.instance_variable_get('@delta') # => 1
tc = TestClass.new
tc.instance_variable_get('@delta') # => nil, so no.
# What about module instance variables initialized in the module definition?
module TestModuleTwo
@cat = 2
end
class TestClassTwo
include TestModuleTwo
def cat
@cat
end
end
TestClassTwo.new.cat # => nil
# Hmmm, weird. What happened?
# The answer, it's all about self. Each class has a set of instance variables
# and where they are stored depends on the value of self at the time of
# evaluation.
# Let's redefine TestModuleTwo, but this time annotate the value of self.
module TestModuleTwo
@cat = 2 # self is the module
def set_cow
@cow = 3 # self is the receiving class, because we don't evaluate this
# until we invoke the function.
end
def cow
@cow # again, self is the receiving class.
end
def self.cat
@cat
end
end
# Let's try again:
class TestClassTwo
include TestModuleTwo
end
tc = TestClassTwo.new
tc.instance_variable_get('@cat') # => nil
tc.cow # => nil
tc.set_cow # => 3
TestModuleTwo.cat # => 2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment