-
Notifications
You must be signed in to change notification settings - Fork 5
Lecture 02
You should always have on mind that with great power comes great responsibility.
In Ruby code can be separated into files. To load code from file use require
require "somefile.extension"
File with extension [rb, so, o, dll, bundle, jar] can be addressed without the extension. Having a file called somefile.rb these two are equivalent
require "somefile"
require "somefile.rb"
The file, if it is not absolute path, will be looked from paths contained in
Every line in Ruby is an expression. That means that every line of Ruby is just executable code. There is a top level context that is just simple instance of Object (required files are executed in this context).
For example:
class A
puts "Hello world"
end
will print "Hello world" and define new class A. We can put executable code anywhere in Ruby. And everything is just an execution.
Ruby behaves in a way that every code is executable - is an expression. By definition an expression should have some return value - the result of it's execution. Let's take a look at this example
class A
end
What is the result of such an expression? You might say "we defined class A". But it is not. The fact we defined a class is only an effect of the expression but not a result ... result is a value returned from the expression itself.
var = nil
puts var.inspect
# => nil
var = class A
end
puts var.inspect
# => nil
The value have not changed .... there is no resulting value. Wrong again. The resulting value was simply nil, the same value as in the variable, but it was returned.
var = nil
puts var.inspect
# => nil
var = class A
self
end
puts var.inspect
# => A
In this example we get a return value. As we can see, it is a class A. How is that possible? The last line of a method is return value. Do you remember? Basically we can generalize to last line of an expression is it's return value.
As said before, everything in 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. This way programmer 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"
People unfamiliar with Ruby might ask, why is the precedent example valid and the answer is simple:
classes are objects
everything in Ruby is an object ... even a class. Don't you believe me? Try this example:
class A
def self.call
"called"
end
end
class B
end
def B.call
"called"
end
C = Class.new
class C
def self.call
"called"
end
end
D = Class.new
def D.call
"called"
end
A.call # => "called"
B.call # => "called"
C.call # => "called"
D.call # => "called"
In the example we defined 4 classes, with class method call that return string "called" all those 4 techniques are equivalent and you can mix them freely.
There we can see, that expression
class A
end
is nothing else than a simple way to access the object that represents the class.
One class can inherit from another. Ruby has only single-class inheritance - you can not inherit from multiple classes.
class A
def call
"called"
end
end
class B < A
end
C = Class.new(B)
B.new.call
# => "called"
C.new.call
# => "called"
When a class needs to inherit from multiple classes, Ruby allows as to mix in multiple Modules. Methods of these modules are then available to the class.
module Methods
def call
"called"
end
end
class A
include Methods
end
A.new.call
# => "called"
As everything else in Ruby even methods are objects of class Method.
Ruby has powerful tools to introspect object. One of the cases is the list of methods of an objects. The list can be obtained simple by calling method methods.
class A
def call
end
end
A.new.methods
# => array of methods
What's this good for? Let's continue.
Sometimes it might be useful to pass around only a method instead of the whole object. Ruby lets you extract a method for later use.
class A
def call(arg1)
self
end
end
meth = A.new.method(:call)
# => #<Method: A#call>
This example shows, that we "extracted" method call from class A. The method is bound to the instance of class A - the method will be evaluated in the context of the object. The method can be executed by calling call method with appropriate arguments.
meth.call("some string")
# => #<A:some_number>
From the example is obvious, that the method is executed in the context of the object.
Because Ruby is very dynamic language, we can not be always sure what type of argument we receive. Usually the programmer does not care what class the argument is, but whether the argument response to some method. This is called Duck typing technique - we do not care what the object is, we only care whether it behaves as we expect.
class A
def call
end
end
a = A.new
a.respond_to?(:call)
# => true
a.respond_to?(:wtf)
# => false
class A
def call
end
end
A.new.call
This example shows how to call a method, but there is one big "but". We have to know the name of the methods beforehand ... in the time we write the code. What if we do not the method name and we need to call it. Do not be surprised, this is very common use-case in Ruby. The two call to methods call are identical.
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. This way a developer is allowed to call event protected or private methods.
The way to define methods shown before is not the only one. We can also define in a more programmatically 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.
Class.define_method is private
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"
To create an object of some class it is used the method new of respective class.
class Dog
end
dog = Dog.new
When talking about classes we defined new methods this way
A = Class.new
def A.call
end
and we said that this is possible because class are object of the class Class. But, what that means really? Let's take a look at this diagram
+---------+ +-...
| | |
BasicObject-----|-->(BasicObject)-------|-...
^ | ^ |
| | | |
Object---------|----->(Object)---------|-...
^ | ^ |
| | | |
+-------+ | +--------+ |
| | | | | |
| Module-|---------|--->(Module)-|-...
| ^ | | ^ |
| | | | | |
| Class-|---------|---->(Class)-|-...
| ^ | | ^ |
| +---+ | +----+
| |
obj--->OtherClass---------->(OtherClass)-----------...
http://blog.madebydna.com/all/code/2011/06/24/eigenclasses-demystified.html