I found the behavior of keyword def in ruby is really confusing! At least, really confusing to me! In most case, we use def in class context, then it defines a instance method on specific class.
Use def in class
1
2
3
4
5
6
7
8
9
classFoo
deffoo
:foo
end
$context = self
end
Foo.new.foo.should == :foo
$context.should == Foo
Besides the typical usage, we can also use def in block.
Use def in class_eval block
1
2
3
4
5
6
7
8
9
10
11
classFoo;end
Foo.class_eval do
deffoo
:foo
end
$context = self
end
Foo.new.foo.should == :foo
$context.should == Foo
This previous piece of code works as we reopened the class Foo, and add a new method to it. It is also not hard to understand.
The fact that really surprised me is in the following code:
Use def in instance_eval block
1
2
3
4
5
6
7
8
9
10
11
classFoo;end
Foo.instance_eval do
deffoo
:foo
end
$context = self
end
Foo.foo.should == :foo# Method foo goes into the Foo class itself rather than Foo's instance!
$context.should == Foo
Here we can found that method foo goes into the Foo class itself, rather than Foo‘s instance! But the $context is still Foo class!
So in a word, calling def foo in instance_eval block is equivalent to calling ‘def self.foo’ in class_eval block, even though the context of both block are the class itself. So we can figure out that keyword def works different than method define_method and define_singleton_method, since it doesn’t depend on self, but the two methods does!
To me it is kind of hard to understand. and confusing. And I think it is not a good design! Ruby is different to other Java or C#, ruby uses methods on class to take place of the keywords in other languages, such as public, protected and private. In most of the language, they are keywords. But in ruby they are actually the class methods of Class. This design is good, because it is kind of enabled the developer to extend the “keyword” they can use! But at the same time, this design melted the boundary between customizable methods and predefined keywords, so people won’t pay much attention to the difference of the two. So it is important to keep the consistency between methods and keyword behaviors. But def breaks the consistency, so it is confusing!
Look the following code:
def vs define_method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
definition_block = Proc.new do
deffoo
:foo
end
define_method :bardo
:bar
end
end
classA;end
classB;end
A.class_eval &definition_block
B.instance_eval &definition_block
Comparing class A and class B, we can find that they are different, even they are defined with exactly same block!
To me, “Eigenclass” is a weird name. Here is the definition of “Eigenclass” from wikipedia:
A hidden class associated with each specific instance of another class.
“Eigen” is a Dutch word, which means “own” or “one’s own”. So “Eigenclass” means the class that class owned by the instance itself.
To open the eigenclass of the object, Ruby provide the following way:
Open Eigenclass
1
2
3
4
5
6
7
foo = Foo.new
class << foo
# do something with the eigenclass of foo
end
Since the in most cases, the purpose that we open a eigenclass is to define singleton methods on specific object. So Ruby provide an easy way to define the singleton method on specific instance:
Shorten saying
1
2
3
4
5
6
7
foo = Foo.new
deffoo.some_method
# do something
end
Since “static method” or “class method” is actually the singleton method of a specific class. So this statement is usually used to declare the “class method”.
Besides this simpler statment, we also can open the eigenclass of the class to achieve the same result. We can write this:
Open eigenclass of the class
1
2
3
4
5
6
7
8
9
10
11
classFoo
class << self
# define class methods
end
# define instance methods
end
Since we’re in the class block, so the “self” indicates the Foo class instance. So we can use class << self; end to open the eigenclass of the class.
Today, I pair with Ma Wei to refactor a piece of pre-existed code. We try to eliminate some “static methods” (in fact, there is no real static method in ruby, I use this term to describe the methods that only depends on its parameters other than any instance variables).
The code is like this:
Recruiter.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
classRecruiter
defapprove!candidates
Candidate.transaction do
candidates.each do|candidate|
candidate.status.approve!
end
end
end
defreject!candidates
Candidate.transaction do
candidates.each do|candidate|
candidate.status.reject!
end
end
end
defrevoke!candidates
Candidate.transaction do
candidates.each do|candidate|
candidate.status.revoke!
end
end
end
# ...
# Some other methods similar
end
As you can see the class Recruiter is used as a host for the methods that manipulate the array of candidates, which is a strong bad smell . So we decide to move these methods to their context class.
In Java or C#, the solution to this smell is quite obvious, which could be announced as “Standard Answers”:
Mark all methods static.
Create a new class named CandiateCollection.
Change the type of candidates to CandidateCollection.
Mark all methods non-static, and move it to CandidateCollection class. If you use Resharper or IntelliJ enterprise version, then the tool can even do this for you.
But in ruby world, or even in dynamic language world, we don’t like to create so many classes, especially these “strong-typed collection”. I wish I could inject these domain related methods to the array instance when necessary, which is known as “singleton methods” in ruby. To achieve this, I might need the code like this:
Singleton Methods
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
defwrap_arrayarray
defarray.approve!
# ...
end
defarray.reject!
# ...
end
# ...
# Some other methods similar
array
end
With the help of this wrap_array method, we can dynamic inject the method into the array like this:
This is cool, but still not cool enough. We still have problems:
All the business logic is included in the wrap method. It is hard to maintain.
Where should we declare this wrap method? In class Array or another “static class”?
The answer to the 1st question is easy, our solution is encapsulate these logics into a module:
Module CandidateCollection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
moduleCandidateCollection
defapprove!
# ...
end
defreject!
# ...
end
defrevoke!
# ...
end
# ...
# Some other methods similar
end
end
By encapsulate the logic into a module, then we can extract it into a single file, so the logic could be organized in the way as we want. Now we need to solve the second problem and reuse the module we just created.
To achieve this, we wrote the following code:
Array.to_candidate_collection
1
2
3
4
5
6
7
8
classArray
defto_candidate_collection
class << self
include CandidateCollection
end
self
end
end
In the code, we re-opened the class Array, and define a new method called to_candidate_collection, which is used to inject domain methods into a generic array. So we can have the following code:
But soon, we realize that is pattern is really powerful and should be able to be reused easily. So we decide to move on. We want to_candiate_collection be more generic, so we can dynamically inject any module, not just CandidateCollection. So we wrote the following code:
The code looks cool, but failed to run. The reason is that we opened the meta class of the instance, which means we enter another level of context, so the parameter module is no longer visible. To solve this problem, we need to flatten the context by using closure. So we modified the code as following:
dynamic_inject version 2
1
2
3
4
5
6
7
8
9
classArray
defdynamic_injectmodule
metaclass = class << self;self; end
metaclass.class_eval do
includemodule
end
self
end
end
The code metaclass = class << self; self; end is very tricky, we use this statement to get the meta class of the array instance. Then we call class_eval on meta class, which then mixed-in the module we want.
Now the code is looked nice. We can dynamically inject any module into “Array” instance. Wait a minute, why only “Array”? We’d like to have this capability on any object! Ok, that’s easy, let’s move the method to Kernel module, which is mixed-in by Object class.
dynamic_inject version 3
1
2
3
4
5
6
7
8
9
moduleKernel
defdynamic_injectmodule
metaclass = class << self;self; end
metaclass.class_eval do
includemodule
end
self
end
end
Now we can say the code looks beautiful.
NOTICE: Have you noticed that we have a self expression at the end of the dynamic_inject method as return value. This statement is quite important! Since we will get “undefined method error” when calling Candidate.scoped_by_id(candidate_ids).dynamic_inject(CandidateCollection).approve! if we missed this statement. We spent almost 1 hour to figure out this stupid mistake. It is really a stupid but expensive mistake!
Instead of these tricky ways, for Ruby 1.9+, it is okay to use extend method to replace the tricky code. The extend method is the official way to do “dyanamic inject” as described before.