Part 1
Required Concepts
1. yield
2. yield with value
3. block variable
4. Migration file uses both Object Oriented and Functional style of programming.
In a Rails project the migration file looks like this:
class CreateArticles < ActiveRecord::Migration
def change
create_table :articles do |t|
t.string :title
t.text :body
t.timestamps
end
end
end
The final solution for the presentation:
module ActiveRecord
class Migration
def change
puts 'change'
end
def create_table(name)
table = Table.new(name)
yield table
end
end
end
class Table
def initialize(name)
@name = name
end
def string(column_name)
puts "String column #{column_name} for table #{@name}"
end
end
class CreateArticles < ActiveRecord::Migration
def change
create_table :articles do |t|
t.string :title
end
end
end
c = CreateArticles.new.change
Implementing the text and timestamps are left as exercises for the attendees.
Part 2
Required Concepts
1. Changing the value of self.
2. Executing code in a different context
3. Using instance_eval to change self
4. yield vs instance_eval
5. Taking block in the argument of a method
6. Using ampersand in the argument of a method
7. The significance of &block in the method argument
Routes file looks like this:
Rails.application.routes.draw do
get '/home' => 'welcome#home'
end
Step 1
module Rails
def draw
puts 'routes file'
end
end
class Tester
include Rails
end
t = Tester.new.draw
Step 2
module Rails
def draw
yield
end
end
class Tester
include Rails
end
Tester.new.draw do
get '/home' => 'welcome#home'
end
Step 3
module Rails
class Router
def get(hash)
p 'Method to handle HTTP get request'
end
end
def draw
router = Router.new
yield router
end
end
class Tester
include Rails
end
Tester.new.draw do |router|
router.get '/home' => 'welcome#home'
end
The older versions of Rails used the block variable. How to get rid of it and simplify the DSL?
Step 4
Let's move the draw method into the Router class.
module Rails
class Router
def get(hash)
p 'Method to handle HTTP get request'
end
def draw
yield self
end
end
end
Rails::Router.new.draw do |router|
router.get '/home' => 'welcome#home'
end
Step 5
module Rails
class Router
def get(hash)
p 'Method to handle HTTP get request'
end
def draw(&block)
instance_eval(&block)
end
end
end
Rails::Router.new.draw do
get '/home' => 'welcome#home'
end
We have replaced the yield with instance_eval that evaluates the given block in the context of the Router instance. It switches the value of self from main Object to Router object.
Part 3
Symbol to proc trick, for instance : ['ruby', 'powerful'].each(&:upcase) uses the ampersand colon trick.
Concepts Required
1. Coercion
2. Using send for sending messages to an object. Why use send?
3. Significance of ampersand as the prefix to an object in a method argument
4. How does to_proc get invoked?
5. *args in the method argument.
6. Providing default value for block variable.
7. Using send to message an object.
1.
result = ['hi', 'how'].map {|x| x.upcase }
p result
2.
# {|x| x.upcase }
result = ['hi', 'how'].map(&:upcase)
p result
3.
Goal :
map {|x| x.upcase } ----> map(&:upcase)
4.
The & in the argument converts the object to a Proc object by calling to_proc on it. The object is symbol. We need to implement to_proc in Symbol object.
class Symbol
def to_proc
Proc.new {|o| o.upcase }
end
end
result = ['hi', 'how'].map(&:upcase)
p result
5.
def to_proc
Proc.new {|o| o.send(self) }
end
6. Multiple block variables
output = [1,2,3,4].inject(0) {|result, element| result + element }
p output
===
output = [1,2,3,4].inject(&:+)
p output
will break the existing to_proc implementation.
Fix:
def to_proc
Proc.new {|o, args| o.send(self, args) }
end
But this will break:
result = ['hi', 'how'].map(&:upcase)
p result
Fix:
def to_proc
Proc.new {|o, args=nil| o.send(self, *args) }
end
Now both one block variable and two block variables can be used.
Closure
A closure is in it's simplest form a block of code that can be passed around as a value, and that can reference variables in the scope it was created in even after exiting from that scope.
The variables are allocated on the heap. Because normally local variables live in the stack and are gone after a method returns.
References
How to Implement ClosuresClosure in Crystal