Object Model and
Metaprogramming in
Ruby
Monday, November 1, 2010
Santiago Pastorino
@spastorino
Monday, November 1, 2010
Monday, November 1, 2010
Monday, November 1, 2010
Monday, November 1, 2010
Monday, November 1, 2010
Monday, November 1, 2010
Monday, November 1, 2010
Monday, November 1, 2010
Ruby
Rails
Monday, November 1, 2010
Java get/set version 1
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Monday, November 1, 2010
.NET get/set version 1
class User
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
Monday, November 1, 2010
Ruby get/set version 1
class User
def name
@name
end
def name=(value)
@name = value
end
end
Monday, November 1, 2010
Java get/set version 2
?
public property String name;
Monday, November 1, 2010
.NET get/set version 2
class User
{
public string Name { get; set; }
}
Monday, November 1, 2010
Ruby get/set version 2
The right way
class User
attr_accessor :name
end
Monday, November 1, 2010
Metaprogramming
Monday, November 1, 2010
Metaprogramming
“Metaprogramming is writing code that
writes code.”
Monday, November 1, 2010
Metaprogramming
“Metaprogramming is writing code that
writes code.”
Monday, November 1, 2010
Metaprogramming
“Metaprogramming is writing code that
writes code.”
“Metaprogramming is writing code that
manipulates language constructs at runtime.”
Monday, November 1, 2010
Metaprogramming
Monday, November 1, 2010
Metaprogramming
• Classes are always open
• Everything is an Object even classes and
modules
• All methods calls have a receiver
Monday, November 1, 2010
Classes are Open
class Hash
def except!(*keys)
keys.each { |key| delete(key) }
self
end
end
hsh = {:a => 1, :b => 2}
hsh.except!(:a) # => {:b => 2}
Monday, November 1, 2010
Everything is an Object
class User
puts self # => User
puts self.class # => Class
end
User.methods.grep /get/
=> [:const_get,
:class_variable_get,
:instance_variable_get]
Monday, November 1, 2010
All method calls have a
receiver
class User
attr_accessor :name
end
Monday, November 1, 2010
attr_reader (class_eval)
class User
def initialize(name)
@name = name
end
attr_reader :name
end
u = User.new(‘Santiago’)
u.name # => ‘Santiago’
Monday, November 1, 2010
attr_reader (class_eval)
class Module
def attr_reader(sym)
class_eval “def #{sym}; @#{sym}; end”
end
end
class User
attr_reader :name # def name; @name; end
end
Monday, November 1, 2010
attr_reader (class_eval)
class Module
private
def attr_reader(sym)
class_eval <<-READER, __FILE__, __LINE__ + 1
def #{sym}
@#{sym}
end
READER
end
end
Monday, November 1, 2010
attr_writer (class_eval)
class Module
private
def attr_writer(sym)
class_eval <<-WRITER, __FILE__, __LINE__ + 1
def #{sym}=(value)
@#{sym} = value
end
WRITER
end
end
Monday, November 1, 2010
attr_accessor
class Module
private
def attr_accessor(*syms)
attr_reader(syms)
attr_writer(syms)
end
end
Monday, November 1, 2010
define_method
class User
attr_accessor_with_default :age, 22
end
user = User.new
user.age # => 22
user.age = 26
user.age # => 26
Monday, November 1, 2010
define_method
class Module
def attr_accessor_with_default(sym, default)
class_eval <<-READER, __FILE__, __LINE__ + 1
def #{sym}
#{default}
end
# + something else
READER
end
end
Monday, November 1, 2010
define_method
class User
attr_accessor_with_default :age, 22
end
user = User.new
user.age # => 22
Monday, November 1, 2010
define_method
class USer
attr_accessor_with_default :complex_obj,
Struct.new(:x, :y)
end
user = User.new
user.complex_obj # => nil
Monday, November 1, 2010
define_method
class Module
def attr_accessor_with_default(sym, default)
class_eval <<-READER, __FILE__, __LINE__ + 1
def #{sym}
default
end
# + something else
READER
end
end
Monday, November 1, 2010
define_method
class User
attr_accessor_with_default :complex_obj,
Struct.new(:x, :y)
end
user = User.new
user.complex_obj
NameError: undefined local variable or method
`default' for #<User:0x00000100978050>
Monday, November 1, 2010
define_method
class Module
def attr_accessor_with_default(sym, default)
define_method(sym, Proc.new { default })
end
end
Monday, November 1, 2010
define_method
class Module
def attr_accessor_with_default(sym, default)
define_method(sym, Proc.new { default })
class_eval(<<-EVAL, __FILE__, __LINE__ + 1)
def #{sym}=(value)
class << self; attr_accessor :#{sym} end
@#{sym} = value
end
EVAL
end
end
Monday, November 1, 2010
define_method
class Person
attr_accessor_with_default :complex_obj,
Struct.new(:x, :y)
end
person = Person.new
person.complex_obj # => #<Class:0x00000101045c70>
person.complex_obj = Struct.new(:a, :b)
person.complex_obj # => #<Class:0x000001009be9d8>
Monday, November 1, 2010
Method Lookup
BasicObject
Kernel Kernel
Object
x XML
Monday, November 1, 2010
method_missing
(An internal DSL)
XML.generate(STDOUT) do
html do
head do
title { ‘pagetitle’ }
comment ‘This is a test’
end
body do
h1 style: ‘font-family: sans-serif‘ do
‘pagetitle’
end
ul id: ‘info’ do
li { Time.now }
li { RUBY_VERSION }
end
end
end
Monday, November 1, 2010
method_missing
(An internal DSL)
<html>
<head>
<title>pagetitle</title>
<!-- This is a test -->
</head>
<body>
<h1 style=”font-family: sans-serif”>
pagetitle
</h1>
<ul id=”info”>
<li>2010-10-30 17:39:45 -0200</li>
<li>1.9.2</li>
</ul>
</body>
</html>
Monday, November 1, 2010
method_missing
(An internal DSL)
class XML
def initialize(out)
@out = out
end
def content(text)
@out << text.to_s
end
def comment(text)
@out << “<!-- #{text} -->”
end
end
Monday, November 1, 2010
method_missing
(An internal DSL)
def method_missing(tagname, attributes={})
@out << “<#{tagname}”
attributes.each { |attr, value|
@out << “#{attr}=’#{value}’”
}
if block_given?
@out << ‘>’
content = yield
if content
@out << content.to_s
end
@out << “</#{tagname}>”
else
@out << ‘/>’
end
end
Monday, November 1, 2010
method_missing
(An internal DSL)
class XML
def self.generate(out, &block)
XML.new(out).instance_eval(&block)
end
end
XML.generate(STDOUT) do
# code
end
Monday, November 1, 2010
method_missing
(An internal DSL)
XML.generate(STDOUT) do
html do
head do
title { ‘pagetitle’ }
comment ‘This is a test’
end
body do
h1 style: ‘font-family: sans-serif‘ do
‘pagetitle’
end
ul id: ‘info’ do
li { Time.now }
li { RUBY_VERSION }
end
end
end
Monday, November 1, 2010
It’s all about the self
Monday, November 1, 2010
Singleton Method
d = “9/5/1982”
def d.to_date
# code here
end
# or
class << d
def to_date
# code here
end
end
Monday, November 1, 2010
Object Model
BasicObject
Kernel Kernel
Object
String
d (d)
Monday, November 1, 2010
Class Methods?
class String
def self.to_date
# code here
end
# or
class << self
def to_date
# code here
end
end
end
Monday, November 1, 2010
The same
d = “9/5/1982”
def d.to_date
# code here
end
# or
class << d
def to_date
# code here
end
end
Monday, November 1, 2010
Object Model
BasicObject (BasicObject)
Object (Object)
Module
String (String)
Class
Monday, November 1, 2010
Everything about self
class User
p self # => User
class << self
p self # => <Class:User>
end
end
Monday, November 1, 2010
Real life use cases for
fibers?
require 'fiber'
module Eigenclass
def eigenclass
class << self; self end
end
module_function :eigenclass
public :eigenclass
end
class Object
include Eigenclass
end
Monday, November 1, 2010
Real life use cases for
fibers?
class EigenclassesGenerator
def initialize(obj)
@eigenclasses = [obj.eigenclass]
@fiber = Fiber.new do
loop do
Fiber.yield @eigenclasses.last
@eigenclasses[@eigenclasses.length] = @eigenclasses.last.eigenclass
end
end
end
def [](index)
if index >= @eigenclasses.length
(index - @eigenclasses.length + 2).times { @fiber.resume }
end
@eigenclasses[index]
end
end
Monday, November 1, 2010
Rails Magic?
class User < ActiveRecord::Base
belongs_to :bill_address
has_one :role
has_many :orders
validates_presence_of :name, :country
validates_acceptance_of :terms
validates_uniqueness_of :name
end
Monday, November 1, 2010