Skip to content

Lecture 02

Marek Jelen edited this page Oct 4, 2011 · 21 revisions

Basics

Responsibility

You should always have on mind that with great power comes great responsibility.

Separating code into files

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 $: variable. $: is Array and user is free to add it's own paths or delete others. Required files are tracked in variable $". If a path is already in $", than it will not be required again. $" is also Array and user is free to modify it.

Execution

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.

Return statements

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.

Context

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>

Class

Open classes

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" 

What is a class?

People unfamiliar with Ruby might ask, why is the precedent example valid and the answer is simple:

classes are instances of class Class

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.

Inheritance

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"

Mixins

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"

Class name

Ruby let us get the name of class by calling method name of the class.

Array.name
# => "Array"

[].class.name
# => "Array"

Methods

As everything else in Ruby even methods are objects of class Method.

Inspecting methods

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.

Extracting methods

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.

Checking method existence

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

Dynamic method calling

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.

Defining methods programmatically

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"

Objects

What is an object? Let's simplify it ... and show you as a contrast to classes

objects define state x classes define behavior

Object are complements to classes. We define some behavior as a class, then creating an object of the class that holds some state. Every object has to be of some class.

Creating new object

To create an object of some class it is used the method new of respective class.

class Dog
end

dog = Dog.new

Defining methods

Previously we defined many methods in simple and fancy styles. But let's get back to the core and try to define a method

class A
  def call
  end
end

here we use def keyword to define method. Where will def define the method? The answer is simple and complex

def defines method into the nearest class

So in the previous example the nearest class is A. That is obvious from next example when we inspect the self inside the class.

var = class A; self; end

var.class
# => Class
var.name
# => "A"

A.new.call
# => "string"

Now let's try to define a class method.

class A
  def self.call
    "string"
  end
end

Where will Ruby define the method now?? It is a bit more complicated. To understand this, we have to explain something else first.

Metaclass

To understand how Ruby works, we have to understand what metaclasses are. Let's start with simple definition

every object in Ruby has it's own metaclass => an instance of Class

Why is this important? Because, however the metaclasses is basically invisible to Ruby, it takes an important part in lookup paths.

When Ruby looks up a method a program is calling, it follows a basic chain (we will talk about it a bit later). Important is, that before the class the object inherits from, there is the object's metaclass. Now it is important that the closest class to an object is not it's class but it's metaclass.

So, back to the example we were talking about

class A
  def self.call
    "string"
  end
end

to see it more clearly we can rewrite this example identically as

class A
end

def A.call
  "string"
end

these two expressions are identical. To understand why it is important to understand this

class A
end

scope = class A
  self
end

A == scope
# => true

but cack to the original quieting ... where are we trying to define the method? In the context of the instance of the class A. The important part is the instance of ... what is the closest class to instance (object)? As stated above ... it's metaclasses. From this point you should see that

there are no class methods in Ruby

What could be called a class method is only an instance method defined on the metaclass associated with object that represents the class itself.

So metaclass is some stealth object that we can not see? Not really. Ruby has concept to access metaclasses

metaclass = class << some_object
	self
end

now that we can access metaclasses, let's see how we could define "class methods" (instance methods of metaclass)

class A
  def self.call
    "called"
  end
end

class B
  class << self
    def call
      "called"
    end
  end
end

class C
end

class << C
  def call
    "called"
  end
end

D = Class.new
class << D
  def call
    "called"
  end
end

E = Class.new
def E.call
  "called"
end

F = Class.new
class F
  class << self
    def call
      "called"
    end
  end
end

all those examples are identical.

Method lookups

Now that you know where and how are methods defined, lets see how methods are looked up. First let's see how the tree looks for a class

SomeClass -> Class -> Module -> Object -> BasicObject

and for objects

object -> metaclass -> SomeClass -> Object -> BasicObject

to see it in a more complex manner, let's take a look at this (diagram)[http://blog.madebydna.com/images/posts/entire_method_lookup.gif] from (Made by DNA)[http://blog.madebydna.com/all/code/2011/06/24/eigenclasses-demystified.html]

Wait, wait!! What is a metaclass? Good question. Let's dig it out. Let me first state some basic statements

Back to the example. As said many times before ... class is an object of class Class. So we could generalize it, let me show you this example

# Once again
A = Class.new
def A.call
end

# we could generalize as
o = Object.new
def o.some_method
end

So what are we doing? We define a method some_method on an object. What? You said before that object define state and classes define behavior. How could we define method on an object? You are right, we did not.

The example defines a method on a metaclass of the object. This metaclass lies between the object itself and the class, however it is invisible to Ruby. Let me show you

There can be seen that we have two objects (representation of state) of the same class (behavior), but they both behave differently. That's because the lookup path of the method meth looks like this

some_object -> methods of metaclass of some_object -> methods of the class of some_object (some_object.class)

some_object inherits from it's metaclass than from it's class ....

To access metaclass we have spacial construct

class << some_object
end

and now let's take a look at the stealth metaclass

o1 = Object.new

metaclass = class << o1; self; end

metaclass.class
# => Class

let's take a look at this example

class A
  self
end
# => A
o1 = Object.new

def o1.meth
  "string"
end

o1.meth
# => "string"

o1.class
# => Object

o2 = Object.new

o2.meth
# => undefined method `meth`

o2.class
# => Object

This example shows that having two instances of same objects. Both can behave differently.

Clone this wiki locally