Advanced Rails Studio: Meta Programming
Chad is giving a very nice presentation walking us through meta programming step by step. You can see the code examples we are creating during his talk, but just looking at the code will note give the whole picture.
# ruby it self uses meta programming
class Person
attr_accessor :name
end
chad = Person.new
chad.name = 'chad'
# classes are open
# create new class
class Blah
def greeting
puts "hello"
end
end
# reopen class and return id
class Blah
def do_something!
greeting
end
end
b = Blah.new
b.greeting
b.do_something!
# reopen existing class
class String
def encrypt
tr "a-z", "b-za"
end
end
puts "cat".encrypt
# Conceptually ruby (the virtual machine) creates a structure to represent the class
# And this structure can dynamically be defined and changed at runtime.
{
:String => {:name => "String",
:methods => {
:ecryypt => '<method body>',
:tr => '<method body>',
:update => '<method body>',
},
:instance_variables => {
"@name" => "Chad"
}
}
}
# replace existing method
class String
def encrypt
upcase.reverse
end
end
puts "cat".encrypt
# Rails extends base classes in activesupport. I.e Fixnum 20.minutes.ago
class Fixnum
def minutes
self*60
end
end
puts 20.minutes
class Fixnum
def from_now
Time.now + self
end
def ago
Time.now - self
end
end
puts 20.minutes.from_now
puts 20.minutes.ago
# Class definition are executed line by line
class Chad
#exit #uncomment this and the program will halt here!
puts "Hello, defining #{self}"
end
puts Chad.new.inspect
# Can conditionaly create class
class Chad
#exit #uncomment this and the program will halt here!
puts "Hello, defining #{self}"
puts "Type OK when prompted"
response = gets.chomp
if response == "OK"
def greeting
puts "OK"
end
else
def greeting
puts "O NO!!!"
end
end
end
puts Chad.new.greeting
# Could use this to have different code for RAILS_ENV is "Prodution" or "Development"# Sending messages to Object. Object receive message, all method calls have received
"Chad".upcase #,I.e. String "Chad" gets message upcase
puts "Hello" # event 'puts' is a message
puts self.class.name # Even when running script, running in context of an Object
class Person
def initialize(name)
@name = name
greeting
puts "self.inspect: #{self.inspect}"
end
def greeting
puts "0, hello #{@name}."
end
end
puts Person.new('daniel').greeting
# Calling class methods
class Person
puts "Puts is send to self. Self is person:#{self} when defining class."
end
# Can point to a class
person_class = Person
puts person_class.class
class Man
end
class AstroMan
end
# Factory method, classes are just object that can be passed around
def man_or_astroman
klass = (rand(2) > 0 ? Man : AstroMan)
klass.new
end
puts man_or_astroman
puts man_or_astroman
puts man_or_astroman
puts man_or_astroman
# Constance in Ruby are not Constants
if false #Don't run this
String = "HAHAHA" #You can even change the class constants implementation
# You'll get a warning (Warning: already initialized constant String), but you can change it
Integer = "bla"
Array = 123
end
# Methods can be defined on Objects and not just Classes
animal = "Cat"
def animal.speak
"woof"
end
puts "animal.speak: #{animal.speak}"
# "dog".speak doesn't exists, only the specific animal instance has speak
# It's not often done in Ruby, but in another context you'll do it all the time...
# .. Adding class methods (singleton methods (not related to pattern)).
class Human
def self.announce_self
puts "I AM #{self}, and I AM BEING DEFINED"
end
announce_self # Can invoked defined class method while defining class
end
# Same as doing def Human.annouce_sef end
Human.announce_self
# We are getting closer to has_many
class Superman < Human
announce_self
end
# Let's try doing something similar to ActiveRecord
module ActiveRecord
class Base
def self.has_many(*things)
puts "#{self} has_many #{things}"
end
end
end
class BuzzLightYear < ActiveRecord::Base
has_many :space_ships # does nothing for now but it's valid syntax
end# This is more the way Rails works
# Can do included hook and extend
module ActiveRecord
module Associations
module HashManyAssocation
def self.included(klass)
klass.extend(ClassMethods)
end
module ClassMethods
def has_many(things, options = {})
# TODO: define methods
puts "#{self} has many #{things}"
end
end
end
end
end
module ActiveRecord
class Base
# INCLUDE
include ActiveRecord::Associations::HashManyAssocation
end
end
class AstorMan < ActiveRecord::Base
has_many :space_ships
end# But could simply extend
module ActiveRecord
module Associations
module HashManyAssocation
def has_many(things, options = {})
# TODO: define methods
puts "#{self} has many #{things}"
end
end
end
end
module ActiveRecord
class Base
# EXTENDS
extend ActiveRecord::Associations::HashManyAssocation
end
end
class AstroMan < ActiveRecord::Base
has_many :space_ships
end
# acts_as_ ... to add functionality without extend ActiveRecord::Base
# We could use include with the included hook
module SuperHero
def self.included(klass)
klass.extend(ClassMethods)
end
module ClassMethods
def acts_as_superhero
puts "I'm a bird, I'm a plane, no I'm #{self}"
end
end
def fight_crime
puts "OK, fighting crime"
end
end
# use include
class AstroMan < ActiveRecord::Base
include SuperHero
end
AstroMan.new.fight_crime
# And include in your base class
class ActiveRecord::Base
include SuperHero
end
# then acts_as is available
class SuperMan < ActiveRecord::Base
acts_as_superhero
end
SuperMan.new.fight_crime# Calling non-existant methods
class Chad
def method_missing(method_name, *args)
puts "You called #{method_name} with #{args.inspect}"
end
end
Chad.new.just_do_it('again', 'and again')
# Calling non-existant classes
def Object.const_missing(name)
puts "Trying to get to non existing clas #{name}"
# trick: could require the file if class is missing
end
AnythingClass# Now that we went through the concepts let's do some meta programming
# eval
def evaluator(str, a_binding)
a_value = 123
eval(str, a_binding)
end
str = "puts a_value"
a_value = 321
evaluator(str, binding) # -> 321. binding is the current scope of the program
evaluator(str, nil) # -> 123. don't pass binding
# instance_eval
class Thing
def a_value
123
end
end
Thing.new.instance_eval("puts self.a_value") # -> 123. run in context of an instance
# Two more 'eval': class_eval and module_val
#class_eval
class Person
end
Person.class_eval do # Be in context of class
p self # -> Person
def greeting
puts "Hello"
end
end
Person.new.greeting # defining instance method
def add_greeting_to(klass)
klass.class_eval do
def greeting
puts "Greeting"
end
end
end
add_greeting_to(String)
"asdf".greeting # -> Greeting
# module_eval is basically same thing as class_eval
# define_method
class Chad
define_method(:foo) do |arg1|
puts "hello, #{arg1}"
end
end
Chad.new.foo(:bar)
Great work! Thanks for share
Do you use metaprogramming in every day apps or is just a specjal feature?