Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Ruby's block syntax work?

I'm new in Ruby and trying to understand this syntax:

create_table :posts do |t|
  t.string :title
  t.string :content
  t.string :likes
  t.string :comments

  t.timestamps null: false
end

I fully understand what this code is doing, but I don't understand how it works. More specifically, I understand that create_table is a method and :posts is a parameter, but I don't understand the rest of the code.

like image 707
prappo prince Avatar asked May 11 '16 21:05

prappo prince


2 Answers

Brace for it :)

  1. create_table is a method. :posts is a symbol that is passed as parameter. Parenthesis are optional, so it looks odd but it is a simple method call.

  2. Everything between do and end is a code block. It is one of the many ways how to pass code as an argument to a method. Code blocks are great for common case. Other similar (but different) ways to do it is to use Proc or lambda or ->.

  3. |t| is an argument passed into the code block by create_table. create_table will execute your code block, and will pass a table object as single argument to it. You chose to name that object t.

  4. Now inside your code block you are calling method string on object t and passing it symbol as an argument. You are doing it four times. Again parenthesis are optional.

  5. You are calling timestamps method on same object t. Here you are passing it one parameter, which is a Hash with the value { :null => false }.

    • Not only parenthesis are optional, but also curly braces are optional when passing hash as last or only parameter to a method.
    • null: false, is a shortcut syntax for { :null => false }.

So all of the above is equivalent to:

create_table(:posts) do |t|
  t.string(:title)
  t.string(:content)
  t.string(:likes)
  t.string(:comments)
  t.timestamps({:null => false})
end
like image 190
quarterdome Avatar answered Nov 15 '22 11:11

quarterdome


Let's first forget about Active Record and focus on the code structure itself. Here is a super simple version of that structure.

class MyBuilder
  def initialize
    # keys are property names, values are options
    @properties = {}
  end

  def property(name, options={})
    @properties[name] = options
  end

  def build
    # For simplicity, just return all properties
    @properties
  end
end

def create_thing(name)
  puts "Begin creating #{name}"

  builder = MyBuilder.new

  puts "Let user use the builder to define properties"
  yield builder

  puts "Consume the builder"
  properties = builder.build

  puts "Persist changes to #{name}..."
  # For simplicity just print them out
  p properties

  puts 'done'
end

create_thing :bar do |builder|
  builder.property :counter, color: 'brown'
  builder.property :wine_storage, texture: 'wood'
end

Please type the code above by hand to grab some feel.

Although the code above has nothing to do with Active Record, it has the same structure as the migration.

When ever create_table is called, it instantiates a builder (of type TableDefinition), and "pushes" that builder to the block (by yielding it) in order to let user define the tables columns. The builder is consumed later by create_table when the user is done defining the columns.

like image 21
Aetherus Avatar answered Nov 15 '22 12:11

Aetherus