You are currently browsing the category archive for the 'Uncategorized' category.
In studying the inheritance of methods I came across what I consider a surprising behavior of modules included into classes.
First, when you include a module into a class, the module methods are available in the class and they also propagate down to subclasses. This is reflected in the fact that the module is added to the ancestors of both the including class and all subclasses (be the defined before or after the include).
class LateIntoClassTest < Test::Unit::TestCase
class A
end
class B < A
end
module LateInClass
end
class A
include LateInClass
end
class C < A
end
def test_including_a_module_into_a_superclass_adds_to_ancestors
# LateInClass is added to A
assert_equal [A, LateInClass, Object, Kernel], A.ancestors
# LateInClass is added to B
assert_equal [B, A, LateInClass, Object, Kernel], B.ancestors
# LateInClass is added to C
assert_equal [C, A, LateInClass, Object, Kernel], C.ancestors
end
end
What is surprising (at least to me), is that the same is not true when you include a module into an included module. I thought modules were kind of like superclasses; if you add to a module then you add to everything that uses the module. Not so.
Here you can see LateInModule is not added to classes that already include A. By contrast, classes defined after the ‘late’ include will add LateInModule to their ancestors.
class LateIntoModuleTest < Test::Unit::TestCase
module A
end
class B
include A
end
module LateInModule
end
module A
include LateInModule
end
class C
include A
end
def test_including_into_an_included_module_DOES_NOT_add_to_ancestors
# LateInModule is added to A
assert_equal [A, LateInModule], A.ancestors
# LateInModule is missing from B
assert_equal [B, A, Object, Kernel], B.ancestors
# LateInModule is added to C
assert_equal [C, A, LateInModule, Object, Kernel], C.ancestors
end
end
You might take from this the lesson that modules are not superclasses, they are collections of methods poured into a class by include. But that isn’t the full story either. After all, you can modify a module and still have those changes propagate into an including class.
class LateModuleModificationTest < Test::Unit::TestCase
module A
end
class B
include A
end
module A
def late_method; true; end
end
def test_included_modules_MAY_be_modified
assert_equal true, B.new.late_method
end
end
I find it tricky to express this behavior descriptively, even though the cause is clear; modules only add to ancestors when first included in a class.
Update: for those who are interested, Redmine has a feature request regarding how modules get added to ancestors.
I’ve been trying to speed up the command line response of tap by judiciously loading only what needs be loaded up front. This script has proven quite helpful… it’s a profiler for require/load. Simply add the requires you want to profile at the end of the script and run it from the command line:
% ruby profile_load_time.rb
================================================================================
Require/Load Profile (time in ms)
* Load times > 0.5 ms
- duplicate requires
================================================================================
* 21.6: yaml
* 0.5: stringio
0.2: yaml/error
* 2.1: yaml/syck
* 0.7: syck
* 1.2: yaml/basenode
0.3: yaml/ypath
0.3: yaml/tag
0.3: yaml/stream
0.3: yaml/constants
* 15.9: yaml/rubytypes
* 11.8: date
* 1.2: rational
* 4.0: date/format
- 0.1: rational
* 0.9: yaml/types
The output flags requires that take longer than 0.5 ms, and requires that occur multiple times. Long requires are often good candidates for autoload… if you want to have YAML available but feel 22 ms is too long to wait up front:
autoload(:YAML, 'yaml')
Then the file will be required the first time YAML gets used, if at all.
