by Diego Mónaco
nil.class # => NilClass
nil.object_id # => 4
false.class # => FalseClass
false.object_id # => 0
true.class # => TrueClass
true.object_id # => 2
3.class # => Fixnum
3.object_id # => 7
Foo = Class.new
Foo.class # => Class
Foo.object_id # => 75217210
The following snippets are equivalent:
class Foo
def foo
end
end
Foo = Class.new do
def foo
end
end
Re opening classes
class Foo
def method_one
end
end
Foo.instance_methods(false) # => [:method_one]
class Foo
def method_two
end
end
Foo.instance_methods(false) # => [:method_one, :method_two]
Re opening NilClass
class NilClass
def wtf!
puts 'this is crazy'
end
end
nil.wtf! # => this is crazy
Re opening Class
class Class
def chatty_new
puts "Hi, I'm about to create a new object!"
new
end
end
class Bar; end
class Foo; end
Bar.chatty_new # => Hi, I'm about to create a new object!
# => #<Bar:0x909cd0c>
Foo.chatty_new # => Hi, I'm about to create a new object!
# => #<Foo:0x924c8c8>
Don't trust me let's ask ruby
class Foo; end
Foo.is_a? Module # => true
Class.superclass # => Module
Class.instance_methods(false) # => [:allocate, :new, :superclass]
module M; end
M.respond_to? :new # => false
A different 'self' for each context
# Top Level Context
puts self # => main
class Foo
# Class Body Context
puts self # => Foo
module M
# Module Body context
puts self # => Foo::M
end
def a_method
# Method Body Context
puts self
another_method
end
def another_method
puts self
end
end
Foo.new.a_method # => #<Foo:0x471004>
# => #<Foo:0x471004>
Foo.new.a_method # => #<Foo:0x34c566>
# => #<Foo:0x34c566>
Instance variable ownership
class Foo
@foo_iv = "Foo_iv"
def initialize(iv_value)
@foo_iv = iv_value
end
end
Foo.instance_variable_get :@foo_iv # => "Foo_iv"
Foo.new("foo_1_iv").instance_variable_get :@foo_iv # => "foo_1_iv"
Foo.new("foo_2_iv").instance_variable_get :@foo_iv # => "foo_2_iv"
Instance variables in the top level context
# Top Level context
@main_iv = "main_iv"
m = self # => main
m.instance_variable_get :@main_iv # => "main_iv"
Sending messages
"abcde".public_send(:upcase) # => ABCDE
"abcde".public_send(:chomp, "de") # => abc
Breaking encapsulation
class Foo
private
def a_private_method
"you should not be calling me from the ouside world"
end
end
foo = Foo.new
foo.send(:a_private_method) # => you should not be calling me from the ouside world
Method Lookup Rule:
Go one step to the right into the receiver's class, and then up the ancestors chain, until you find the method
Riding the ancestors chain with 'super'
module M
def hello
puts 'Hi from M'
end
end
class Bar
include M
def hello
puts 'Hi from Bar'
super
end
end
class Foo < Bar
def hello
puts 'Hi from Foo'
super
end
end
foo = Foo.new
foo.hello # Hi from Foo
# Hi from Bar
# Hi from M
foo.class.ancestors # => [Foo, Bar, M, Object, Kernel, BasicObject]
[Foo, Bar].each { |c| c.send(:remove_method, :hello) }
foo.hello # => Hi from M
modules are searched in reverse orden of inclusion
module M; end
module N; end
class A
include M
include N
end
A.ancestors # => [A, N, M, Object, Kernel, BasicObject]
Defining singleton methods
o = Object.new
def o.bar
"Hi from o's singleton class"
end
puts o.bar # => Hi from o's singleton class
x = Object.new
x.bar # => NoMethodError: undefined method `bar' for #<Object:0x909cd0c>
class << x
def bar
"Hi from x's singleton class"
end
end
x.bar # => Hi from x's singleton class
The Eigenclass superclass
class A
end
a = A.new
a.singleton_class.superclass # => A
Including a module in a singleton class
module N
def baz
puts 'Hi from N'
end
end
z = Object.new
class << z
include N
end
z.baz # => 'Hi from N'
class methods: ordinary methods defined in the 'singleton class' of the given class instance
class A
def self.one
'one'
end
def A.two
'two'
end
class << self
def three
'three'
end
end
end
A.one # => one
A.two # => two
A.three # => three
A.singleton_class.instance_methods(false) # => [:one, :two, :three]
A.singleton_class.ancestors # => [Class, Module, Object, Kernel, BasicObject]
Class methods inheritance
class A
def self.a_class_method
"Hi from A"
end
end
B = Class.new(A)
B.a_class_method # => Hi from A
Here is a tongue-twister:
"The superclass of the eigenclass of a regular object is the object’s class"
"The superclass of the eigenclass of a class object is the eigenclass of the class’s superclass"
Class methods inheritance
class Module
def foo
puts 'Hi from Module'
end
end
class Class
def foo
puts 'Hi from Class'
super
end
end
class Object
def self.foo
puts 'Hi from Object eigenclass'
super
end
end
class A
def self.foo
puts 'Hi from A eigenclass'
super
end
end
class B < A
def self.foo
puts 'Hi from B eigenclass'
super
end
end
B.foo # => Hi from B eigenclass
# => Hi from A eigenclass
# => Hi from Object eigenclass
# => Hi from Class
# => Hi from Module
What happened?
klass = B.singleton_class
while klass.superclass
puts klass
klass = klass.superclass
end
#<Class:B> B eigenclass
#<Class:A> A eigenclass
#<Class:Object> Object eigenclass
#<Class:BasicObject> BasicObject eigenclass
Class
Module
Object
Wait, wtf is metaprogramming!
Metaprogramming: "code that writes code"
Euclidean meta-geometry
module Geometry
FORMULAS = {
triangle: {perimeter: 'l1+l2+l3', area: 'b*h/2'},
rectangle: {perimeter: 'l1*2+l2*2', area: 'l1*l2'},
circle: {perimeter: '2*3.14*r', area: '3.14*r*r'}
}
end
Geometry::FORMULAS[:triangle][:perimeter]
=> "l1+l2+l3"
Let's get user friendly
Geometry.triangle_area
# => "b*h/2"
Geometry.circle_perimeter
# => "2*2.14*r"
module Geometry
FORMULAS = {
triangle: {perimeter: 'l1+l2+l3', area: 'b*h/2'},
rectangle: {perimeter: 'l1*2+l2*2', area: 'l1*l2'},
circle: {perimeter: '2*3.14*r', area: '3.14*r*r'}
}
def self.triangle_perimeter
Geometry::FORMULAS[:triangle][:perimeter]
end
def self.triangle_area
Geometry::FORMULAS[:triangle][:area]
end
def self.rectangle_perimeter
Geometry::FORMULAS[:rectangle][:perimeter]
end
# def self.rectangle_area
# def self.circle_perimeter
# etc, etc
end
We are lazy
module Geometry
FORMULAS = {
triangle: {perimeter: 'l1+l2+l3', area: 'b*h/2'},
rectangle: {perimeter: 'l1*2+l2*2', area: 'l1*l2'},
circle: {perimeter: '2*3.14*r', area: '3.14*r*r'}
}
class << self
def triangle_perimeter
Geometry::FORMULAS[:triangle][:perimeter]
end
def triangle_area
Geometry::FORMULAS[:triangle][:area]
end
def rectangle_perimeter
Geometry::FORMULAS[:rectangle][:perimeter]
end
# def rectangle_area
# def circle_perimeter
# etc, etc
end
end
We are Really lazy
module Geometry
FORMULAS = {
triangle: {perimeter: 'l1+l2+l3', area: 'b*h/2'},
rectangle: {perimeter: 'l1*2+l2*2', area: 'l1*l2'},
circle: {perimeter: '2*3.14*r', area: '3.14*r*r'}
}
class << self
FORMULAS.each do |figure, formulas|
formulas.each do |name, expression|
define_method "#{figure}_#{name}" do
expression
end
end
end
end
end
Geometry.circle_area
# => "3.14*r*r"
Geometry.singleton_methods
# => [:triangle_perimeter, :triangle_area, :rectangle_perimeter, :rectangle_area, :circle_perimeter, :circle_area]
define_method
# From: proc.c (C Method):
# Number of lines: 30
# Owner: Module
# Visibility: private
# Signature: define_method(*arg1)
# Defines an instance method in the receiver. The _method_
# parameter can be a Proc, a Method or an UnboundMethod object.
# If a block is specified, it is used as the method body. This block
# is evaluated using instance_eval, a point that is
# tricky to demonstrate because define_method is private.
# (This is why we resort to the send hack in this example.)
class A
def fred
puts "In Fred"
end
def create_method(name, &block)
self.class.send(:define_method, name, &block)
end
define_method(:wilma) { puts "Charge it!" }
end
class B < A
define_method(:barney, instance_method(:fred))
end
a = B.new
a.barney
a.wilma
a.create_method(:betty) { p self }
a.betty
# produces:
# In Fred
# Charge it!
#<B:0x401b39e8>
Hey! we can use classes!
class Rectangle
attr_reader :l1, :l2
FORMULAS = {perimeter: 'l1*2+l2*2', area: 'l1*l2'}
def initialize(attrs)
@l1, @l2 = attrs[:l1], attrs[:l2]
end
def perimeter
l1*2+l2*2
end
def area
l1*l2
end
FORMULAS.each do |name, expression|
define_method "#{name}_formula" do
expression
end
end
end
r = Rectangle.new(l1:3, l2:4)
r.area # => 12
r.area_formula # => l1*2+l2*2
But :(
# class Triangle
# blah, blah
# class Circle
# blah, blah
# class Whatever
# blah, blah
# etc, etc
Let's dream
Rectangle = Euclides.define :rectangle,
[:l1,:l2],
perimeter: 'l1*2+l2*2',
area: 'l1*l2'
Triangle = Euclides.define :triangle,
[:l1,:l2,:l3,:b,:h],
perimeter: 'l1+l2+l3',
area: 'b*h2'
Circle = Euclides.define :circle,
[:r],
perimeter: '2*3.14*r',
area: '3.14*r*r'
module Euclides
class << self
def define(figure, attributes, formulas)
Class.new do
const_set('FORMULAS', formulas)
attr_reader *attributes
define_method :initialize do |attrs|
attributes.each do |attr|
instance_variable_set("@#{attr}", attrs[attr])
end
end
const_get('FORMULAS').each do |name, expression|
define_method "#{name}_formula" do
expression
end
define_method name do
eval expression
end
end
end
end
end
end
r = Rectangle.new l1:3, l2:5
r.l1 # => 3
r.area_formula # => l1*l2
r.area # => 15
Rectangle::FORMULAS
# => {:perimeter=>"l1*2+l2*2", :area=>"l1*l2"}
const_set
# From: object.c (C Method):
# Number of lines: 6
# Owner: Module
# Visibility: public
# Signature: const_set(arg1, arg2)
# Sets the named constant to the given object, returning that object.
# Creates a new constant if no constant with the given name previously
# existed.
Math.const_set("HIGH_SCHOOL_PI", 22.0/7.0) #=> 3.14285714285714
Math::HIGH_SCHOOL_PI - Math::PI #=> 0.00126448926734968
instance_variable_set
# From: object.c (C Method):
# Number of lines: 14
# Owner: Kernel
# Visibility: public
# Signature: instance_variable_set(arg1, arg2)
# Sets the instance variable names by symbol to
# object, thereby frustrating the efforts of the class's
# author to attempt to provide proper encapsulation. The variable
# did not have to exist prior to this call.
class Fred
def initialize(p1, p2)
@a, @b = p1, p2
end
end
fred = Fred.new('cat', 99)
fred.instance_variable_set(:@a, 'dog') #=> "dog"
fred.instance_variable_set(:@c, 'cat') #=> "cat"
fred.inspect #=> "#<Fred:0x401b3da8 @a=\"dog\", @b=99, @c=\"cat\">
const_get
# From: object.c (C Method):
# Number of lines: 6
# Owner: Module
# Visibility: public
# Signature: const_get(*arg1)
# Returns the value of the named constant in mod.
Math.const_get(:PI) #=> 3.14159265358979
If the constant is not defined or is defined by the ancestors and
inherit is false, NameError will be raised.
eval
# From: vm_eval.c (C Method):
# Number of lines: 12
# Owner: Kernel
# Visibility: private
# Signature: eval(*arg1)
# Evaluates the Ruby expression(s) in string. If
# binding is given, which must be a Binding
# object, the evaluation is performed in its context. If the
# optional filename and lineno parameters are
# present, they will be used when reporting syntax errors.
def getBinding(str)
return binding
end
str = "hello"
eval "str + ' Fred'" #=> "hello Fred"
eval "str + ' Fred'", getBinding("bye") #=> "bye Fred"
Let's get fancy
Triangle = Euclides.define_triangle [:l1,:l2,:l3,:b,:h],
perimeter: 'l1+l2+l3',
area: 'b*h2'
Circle = Euclides.define_circle [:r],
perimeter: '2*3.14*r',
area: '3.14*r*r'
Fooaedro = Euclides.define_fooaedro [:foo, :bar, :baz],
foometer: '3*foo+bar',
barea: '12.4*foo/bar',
bazity: 'baz*0.56'
module Euclides
class << self
def define(figure, attributes, formulas)
Class.new do
const_set('FORMULAS', formulas)
attr_reader *attributes
define_method :initialize do |attrs|
attributes.each do |attr|
instance_variable_set("@#{attr}", attrs[attr])
end
end
const_get('FORMULAS').each do |name, expression|
define_method "#{name}_formula" do
expression
end
define_method name do
eval expression
end
end
end
end
def method_missing(method_name, *args)
if method_name.to_s =~ /^define_(.*)$/
public_send(:define, $1.to_sym, *args)
else
super
end
end
end
end
f=Fooaedro.new foo:23, bar:34, baz:12.5
# => #<Fooaedro:0x9bcb78c @bar=34, @baz=12.5, @foo=23>
f.foometer
# => 103
f.barea
# => 8.388235294117647
f.bazity
# => 7.000000000000001
Fooaedro::FORMULAS
# => {:foometer=>"3*foo+bar", :barea=>"12.4*foo/bar", :bazity=>"baz*0.56"}
f.foometer_formula
# => "3*foo+bar"
method_missing
# From: vm_eval.c (C Method):
# Number of lines: 27
# Owner: BasicObject
# Visibility: private
# Signature: method_missing(*arg1)
# Invoked by Ruby when obj is sent a message it cannot handle.
# symbol is the symbol for the method called, and args
# are any arguments that were passed to it. By default, the interpreter
# raises an error when this method is called. However, it is possible
# to override the method to provide more dynamic behavior.
# If it is decided that a particular method should not be handled, then
# super should be called, so that ancestors can pick up the
# missing method.
# The example below creates
# a class Roman, which responds to methods with names
# consisting of roman numerals, returning the corresponding integer
# values.
class Roman
def romanToInt(str)
# ...
end
def method_missing(methId)
str = methId.id2name
romanToInt(str)
end
end
r = Roman.new
r.iv #=> 4
r.xxiii #=> 23
r.mm #=> 2000
public_send
# From: vm_eval.c (C Method):
# Number of lines: 5
# Owner: Kernel
# Visibility: public
# Signature: public_send(*arg1)
# Invokes the method identified by _symbol_, passing it any
# arguments specified. Unlike send, public_send calls public
# methods only.
1.public_send(:puts, "hello") # causes NoMethodError
Explore
Mix and Match
Use your imagination