In Ruby everything is an expression - it executes and returns some value, the result of it’s execution. Let’s take a look at a simple example
5 + 2 # => 7
"hello" # => "hello"
"a" if true # => "a"
"a" if false # => nil
class A; end # => ???What is the result of the last expression? It could be obvious - "we defined class A". But not really. The fact we have defined a class is only an effect of the expression but not the result, result is a value returned from the expression itself.
class A; end # => nilThe result is simply nil. It’s similar to the expression "a" if false above. The condition is evaluated as false
and there is no else so there is nothing to return, so the result is nil. In this case a class was defined, but
there was nothing to be return, as the body of the class was empty, so the result is nil. Let’s modify the example to
return a value
class A; 1; end # => 1
class A; 1; "hello"; end # => "hello"
class A; self; end # => ABefore it was said that the return value of a method is the result of it’s last expression. And it’s obvious that in this example it’s very similar, and it can be generalized for all structures, even though sometimes it may not be obvious on the first sight.
the return value is the the result of the last expression
In Ruby everything is executed in some context. This context is know as current object and is always represented
by self.
self.class # => Object
class B
self
end
# => Class
class A
def call
self
end
end
A.new.call # => #<A:some number>Unlike most languages, Ruby classes are open for modifications. Developers can modify behavior of classes defined by
frameworks or Ruby itself. This technique is called Monkey patching.
class Clazz
def call
"A"
end
end
class Clazz
def call
"B"
end
end
Clazz.new.call # => "B"Let’s start with a definition
classes are instances of class Class
and as mentioned many times before, everything in Ruby is an object … even a class.
class A
def self.call
"A"
end
end
B = Class.new
def B.call
"called"
end
A.call # => "called"
B.call # => "called"
A.object_id # => [some number]
B.object_id # => [some number]
A.class # => Class
B.class # => ClassClass A was defined using the class keyword and then a class method was defined. Class B was created by creating new
instance of the Class class and the object was assigned to constant B. As both of those classes are objects, it’s
possible to check it’s class and the ID of the object.
In Ruby classes can inherit from each other, though Ruby has only single-class inheritance - it’s not possible to inherit from multiple classes, only from one.
class A
def call
"called"
end
end
class B < A
end
C = Class.new(B)
B.new.call # => "called"
C.new.call # => "called"When some class needs to inherit from multiple classes, it’s not possible, but Ruby provides a workaround through mixins. It is possible to include many Modules into a class the methods defined in those modules will become part of the lookup path as if they were defined in the class.
module Methods
def call
"called"
end
end
class A
include Methods
end
A.new.call # => "called"Ruby allows many introspections on classes and many other objects.
There is method name defined on a class that returns the name of the current class.
Array.name # => "Array"
[].class.name # => "Array"It’s possible to list methods of an object
class A
def call
end
end
A.new.methods # => array of methodsAs everything else in Ruby even methods are instances of class Method.
Sometimes it is useful to pass around only a method instead of the whole object. Ruby allows extraction of a method for later usege.
class A
def call(arg1)
self
end
end
meth = A.new.method(:call) # => #<Method: A#call>In the example method call from class A was "extracted". The method is still bound to the instance of class A and
the method will be evaluated in the context of the object (self will be the instance). The method can be executed
by calling the call method with appropriate arguments.
meth.call("some string") # => #<A:some_number>Because Ruby is a very dynamic language, it’s not possible to know in advance what kind of arguments will be received. In most cases the developer should not care what class the argument is, but whether the argument responds to a method.
Do not care what the object is, only care whether it behaves as expected.
This technique is called Duck typing.
class A
def call
end
end
a = A.new
a.respond_to?(:call) # => true
a.respond_to?(:wtf) # => falseLet’s define a class with a method, create an instance and call the method.
class A
def call
end
end
A.new.callThe method is called, but the develeoper had to know the name of the method beforehand … in the time the code is written. What if the method name is not known and there has to be some method called. Do not be surprised, this is very common use-case in Ruby.
class A
def call(arg1)
end
end
a = A.new
a.call("some string")
a.send(:call, "some string")Well, not so identical. When you use the send method on an object, you effectively bypass the access modifiers.
Ruby has three access levels public is default, protected and private.
class A
def public_method
end
protected
def protected_method
end
private
def private_method
end
end
a = A.new
a.public_method # => nil
a.protected_method # => NoMethodError: protected method `protected_method' called ...
a.private_method # => NoMethodError: private method `private_method' called ...
a.send(:public_method) # => nil
a.send(:protected_method) # => nil
a.send(:private_method) # => nilThe way to define methods using the def keyword shown before is not the only one. It’s also possible to define
method in a more dynamic way. It makes sense. We can inspect methods of an object, we can extract methods of an object
and also call methods of an object in a dynamic way. To dynamically define a method use the define_method method of
a class, however
Class.define_method is private
To get around this obstacle, it’s possible to use the send method and bypass the access modifier.
class A
end
a = A.new
logic = Proc.new do
"data"
end
A.send(:define_method, :some_method_name, logic)
a.some_method_name # => "data"Every object can define special method_missing method that is called whenever there is a call to undefined method
on that object.
class A
def method_missing(name, *args, &block)
puts "method #{name} called with args #{args.inspect}"
end
end
A.new.something("a") # => method something called with args ["a"]Objects complement classes in a way that
objects define state and classes define behavior
Behavior id defined as a class, then an object is created for that class to hold the state. Every object has to be of some class.
To create an object of a class there is the new method on respective class.
class Dog
end
dog = Dog.newIn the example above many methods were defined in simple or more fancy styles. But let’s get back to the core and try to define a method
class A
def call
end
endhere we use def keyword to define method call. Where will def define the method? The answer is simple and complex
def defines methods into the nearest class
So in the previous example the nearest class is A. That is obvious from next example where the current context is returned and inspected
var = class A; self; end
var.class # => Class
var.name # => "A"OK, so the the current context is a Class and thus is’t obvious that the nearest class is this class. Now let’s try to define a class method
class A
def self.call
"string"
end
endWhere will Ruby define the method now?? It is a bit more complicated. To understand this, we have to explain something else first.
To understand how Ruby works, we have to understand what eigenclasses are. Let’s start with simple definition
every object in Ruby has it's own eigenclass => an instance of Class
|
Note
|
eigen means "it’s own" in German |
Why is this important? Because, however the eigenclasses are basically invisible to developers, they take an important
part in method lookups.
When Ruby is trying to look up a method, it follows a basic chain (will be described a bit later). Important is, that
before the class the object is linked to, there is the one more class - object’s eigenclass. Every single object in Ruby
has it’s own eigenclass and because Classes are object as well, eigenclasses has their own eigenclasses as well.
The closest class to an object is not it's class but it's eigenclass.
Back to the example we were talking about
class A
def self.call
"string"
end
endto see it more clearly we can rewrite this example identically as
class A
end
def A.call
"string"
endthese two expressions are identical. To understand why it is important to understand this
class A
end
scope = class A
self
end
A == scope # => truebut back to the original question … where is the method going to be defined? In the context of the instance of the class A. The important part is the instance of. What is the closest class to an instance (object)? As stated above it’s its eigenclass. Now you might have guessed that from implementation point of view
there are no class methods in Ruby
What would be called a class method is only an instance method defined on the eigenclass associated with object representing the class.
So eigenclass is some stealth object that we can not see? Not really. Ruby has ways to access eigenclasses
eigenclass = class << some_object
self
end
eigenclass = some_object.singleton_classnow that we can access eigenclasses, let’s see how we could define "class methods" (instance methods in the eigenclass).
class A
def self.call
"called"
end
end
class B
class << self
def call
"called"
end
end
end
D = Class.new
class << D
def call
"called"
end
endall those examples are identical.
Now that you know where and how are methods defined, lets see how methods are looked up. Let’s see how the class hierarchy looks for class
SomeClass -> Class -> Module -> Object -> BasicObject
and for objects
object -> SomeClass -> Object -> BasicObject
though in real it is a bit more complex as seen in this picture
Eigenclasses are not visible as classes of objects.
o1 = Object.new
def o1.meth
"string"
end
o1.meth # => "string"
o1.class # => Object
o2 = Object.new
o2.meth # => undefined method `meth`
o2.class # => ObjectThis example shows that having two instances of same objects. Both can behave differently. Because in the case of o1 the method is stored in the eigenclass, that is not accessible by o2.
Eigenclasses are used when a specific behavior of an object is expected.