def call_this_block_twice
yield
yield
end
call_this_block_twice {puts "Hello World!"}
What if we want to store this block for execute it later?
Two ways:
- Proc
- Lambda
my_proc = Proc.new { puts "test" }
my_proc.call # => test
my_proc = lambda { puts "test" }
my_proc.call # => test
Block
tweet = Tweet.new('Ruby Bits!')
tweet.post { puts "Sent!" }
class Tweet
def post
if authenticate?(@user, @password)
# submit the tweet
yield
else
raise 'Auth Error'
end end
end
Lambda
tweet = Tweet.new('Ruby Bits!')
success = -> { puts "Sent!" }
tweet.post(success)
class Tweet
def post(success)
if authenticate?(@user, @password)
# submit the tweet
success.call
else
raise 'Auth Error'
end end
end
tweets = ["First tweet", "Second tweet"]
tweets.each do |tweet|
puts tweet
end
tweets = ["First tweet", "Second tweet"]
printer = lambda { |tweet| puts tweet }
tweets.each(&printer)
'&' turns proc into block
Proc
library = Library.new(GAMES)
print_details = Proc.new do |game|
puts "#{game.name} (#{game.system}) - #{game.year}"
end
library.exec_game('Contra', print_details)
Lambda
library = Library.new(GAMES)
print_details = lambda { |game|
puts "#{game.name} (#{game.system}) - #{game.year}"
}
library.exec_game('Contra', print_details)
class Library
attr_accessor :games
def initialize(games)
@games = games
end
def exec_game(name, action,error_handler)
game = games.detect { |game| game.name == name }
begin
action.call(game)
rescue
error_handler.call
end
end
end
Before
library = Library.new(GAMES)
library.each { |game| puts "#{game.name} (#{game.system}) - #{game.year}" }
After
my_code = Proc.new {
|game| puts "#{game.name} (#{game.system}) - #{game.year}"
}
library = Library.new(GAMES)
library.each(&my_code)
Before
class Library
attr_accessor :games
def initialize(games)
@games = games
end
def each
games.each do |game|
yield game
end
end
end
After
class Library
attr_accessor :games
def initialize(games)
@games = games
end
def each(&block)
games.each do |game|
block.call(game)
end
end
end
Before
class Library
attr_accessor :games
def initialize(games)
@games = games
end
def each(&block)
games.each do |game|
block.call(game)
end
end
end
After
class Library
attr_accessor :games
def initialize(games)
@games = games
end
def each(&block)
games.each(&block)
end
end
Before
class Library
attr_accessor :games
def initialize(games)
@games = games
end
def names
games.map { |game| game.name }
end
end
After
class Library
attr_accessor :games
def initialize(games)
@games = games
end
def names
games.map(&:name)
end
end
def list
games.each do |game|
if block_given?
puts yield game
else
puts game.name
end
end
end
class Tweet
attr_accessor :user, :status
def initialize(user, status)
@user, @status = user, status
end
end
Using Struct to do the same thing
Tweet = Struct.new(:user, :status)
We'll get the same result:
tweet = Tweet.new('Gregg', 'compiling!')
tweet.user # => Gregg
tweet.status # => compiling!
Tweet = Struct.new(:user, :status) do
def to_s
"#{user}: #{status}"
end
end
class Timeline
def initialize(tweets = [])
@tweets = tweets
end
def tweets
@tweets
end
def contents
@tweets
end
end
We've two methods with the same implementation. So, we can use the alias_method.
class Timeline
def initialize(tweets = [])
@tweets = tweets
end
def tweets
@tweets
end
alias_method :contents, :tweets
end
We can also define method dynamically.
Before
class Game
SYSTEMS = ['SNES', 'PS1', 'Genesis']
attr_accessor :name, :year, :system
def runs_on_snes?
self.system == 'SNES'
end
def runs_on_ps1?
self.system == 'PS1'
end
def runs_on_genesis?
self.system == 'Genesis'
end
end
After
class Game
SYSTEMS = ['SNES', 'PS1', 'Genesis']
attr_accessor :name, :year, :system
SYSTEMS.each do |var|
define_method "runs_on_#{var.downcase}?" do
system == var
end
end
end
Before
class Library
attr_accessor :games
def each(&block)
games.each(&block)
end
def map(&block)
games.map(&block)
end
def select(&block)
games.select(&block)
end
end
After
class Library
attr_accessor :games
[:each, :map, :select].each do |iterator|
define_method iterator do |&block|
games.send iterator, &block
end
end
end
We've the following ruby class:
class Timeline
def initialize(tweets)
@tweets = tweets
end
def contents
@tweets
end
private
def direct_messages
end
end
We can use the send method to run methods:
tweets = ["First Tweet","Second Tweet"]
timeline = Timeline.new(tweets)
#Normal way
timeline.contents
#Using send method
timeline.send(:contents)
timeline.send("contents")
Send can also runs private or protected methods. To avoid it, we can use public_send. It prevents running private or protected methods.
Methods are also objects.So, to get this object, we can use the method method.
tweets = ["First Tweet","Second Tweet"]
timeline = Timeline.new(tweets)
#Getting the method
content_method = timeline.method(:contents)
#Invoking
content_method.call
Sets self to the given class and executes a block
Game.class_eval do
def self.find_by_owner
end
end
Using class_eval and defining a method in execution time
class LibraryManager
def self.make_available(klass, user)
klass.class_eval do
define_method "lend_to_#{user}" do
end
end
end
end
Sets self to the given instance and executes a block
tweet = Tweet.new
tweet.instance_eval do
self.status = "Changing the tweet's status"
end
Inside the block self is the Tweet instance
contra_game = Game.new('Contra')
contra_game.instance_eval do
self.owner = 'Alice'
end
class Game
def initialize(&block)
instance_eval(&block) if block_given?
end
def owner(name=nil)
if name
@owner = name
else
@owner
end
end
end
class Library
def method_missing(method_name,*args)
puts method_name
end
end
Sending to other class
class Library
def initialize(console)
@manager = console
end
def method_missing(name, *args)
@manager.send(name,*args)
end
end
class Library
def initialize(console)
@manager = console
end
def method_missing(name, *args)
match = name.to_s.include?("atari")
if match
@manager.send(name, *args)
else
super
end
end
end
class Library
SYSTEMS = ['arcade', 'atari', 'pc']
attr_accessor :games
def method_missing(name, *args)
system = name.to_s
if SYSTEMS.include?(system)
self.class.class_eval do
define_method(system) do
find_by_system(system)
end
end
else
super
end
end
private
def find_by_system(system)
games.select { |game| game.system == system }
end
end
A DSL is a language that has terminology and constructs designed for a specific domain. If you're familiar with rails, ActiveRecord is a good example of a DSL.