【Ruby】procとblock
procとblockについて
block
ブロックとは
do...end
や{ }
のカタマリのこと。
do...endと{ }
- 改行を含む長いブロックを書く場合はdo...end
- 1行でコンパクトに書きたい場合は{ } プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design plusシリーズ) | 伊藤 淳一 |本 | 通販 | Amazon
ブロックの中の|i|
のi
はブロック引数と呼ばれるもの。
ブロックの中で| |
を書くことで、eachメソッドから渡された配列の要素が順番に入ってくる。
[1, 2, 3].each do |i| puts i*2 end 2 4 6 => [1, 2, 3]
[1, 2, 3].each { |a| p a } 1 2 3 => [1, 2, 3]
値を受け取らない場合は、| |
を書かなければいい。| |
を使わなければ、単に配列の要素の数だけ処理が実行される。
3.times do p 'おはよ!' end "おはよ!" "おはよ!" "おはよ!" => 3
[1, 2, 3].each { p 'おはよ' } "おはよ" "おはよ" "おはよ" => [1, 2, 3]
do...endの応用
# @meal_record = MealRecord.new(food_id: params[:food_id]) # @meal_record.meal_record_pictures = ActiveStorage::Blob.find(session[:meal_picture_id]) if session[:meal_picture_id] @meal_record = MealRecord.new(food_id: params[:food_id]) do |meal_record| meal_record.meal_record_pictures = ActiveStorage::Blob.find(session[:meal_picture_id]) if session[:meal_picture_id] end
ブロック引数
ブロック引数は引数と同じように、関数の()に入れてあげればいい。
そしてブロック引数を受け取る関数を定義するには&block
という仮引数を指定してあげるといい。この&block
でブロック引数を受け取る宣言をしているといった感じである。
注意点は、
- ブロック引数は1つしか受け取れない。
- 複数の引数が存在する場合、ブロック引数は最後の引数として指定してあげる。
以下の例では、&blockという仮引数に、puts "Hello block"
というブロックが渡されている。
def receive_block(&block) puts "受け取ったブロックを実行する" # block.callで、受け取ったブロック(puts "Hello block"のこと)を実行 block.call puts "実行したよ" end receive_block do puts "Hello block" end #出力結果 受け取ったブロックを実行する Hello block 実行したよ
proc
procとは、ブロックという処理のカタマリをオブジェクト化するためのクラスのこと。
callメソッドで呼び出せる。
hello_proc = Proc.new do 'Hello' end # 上記と同じ hello_proc = Proc.new { 'Hello' } hello_proc => #<Proc:0x00007fcbae10af50 (irb):66> hello_proc.call => "Hello"
引数を受け取る処理もできる。
proc = Proc.new do |message| puts message end proc.call("Hello proc") =>Hello proc
以下の処理は、上記の処理と同じ挙動となる。
つまり、以下の&block
とblock
は、上記のブロックとProcインスタンスの関係にあるということ。以下では、内部的に受け取ったブロックをProcインスタンス化しているのである。
def receive_block(&block) block.call end receive_block do puts "Hello block" end =>Hello block
&はブロックを表す記号であり&blockのblockはブロック引数につけた仮引数名にあたるので実は何でも良い。 つまり引数として受け取ったblockという名のブロックを、blockという名のProcインスタンスに変換しているからblock.callで実行することができる。
Rubyのブロック、ブロック引数、Proc、yieldをまとめてみた – Ruby on Rails 始めました
&:method
配列の要素1つ1つに、(&:メソッド名)
で与えられたメソッドを呼び出してくれる。
['a' ,'b'].map(&:upcase) => ["A", "B"]
以下の2つは同じ意味になる。
→do...end に限らず { |object| object.published! } も同じく短く出来る!
find_each do |object| object.published! end find_each(&:published)
以下であれば、array.map(&:メソッド名)
の(&:メソッド名)部分は、:メソッド名.to_proc.call
を省略した形。
# array.map(&:メソッド名)の記法 ['a','b'].map(&:upcase) #=> ["A", "B"] # 省略しない記法 ['a','b'].map do |string| :upcase.to_proc.call(string) end #=> ["A", "B"]
さらに例文で理解する(余計ややこしいかも)
(app/models/article.rb) class Article < ApplicationRecord enum state: { draft: 0, published: 1, publish_wait: 2 } # 公開日時が過去の日付になっている記事を取得 scope :past_publish, -> { where('published_at <= ?', Time.current) } end
(lib/tasks/status_update.rake) namespace :status_update do desc '記事が公開待ち状態で、公開日時が過去の場合、ステータスを「公開」にする' task article_status: :environment do # 公開待ち(publish_wait)の記事の中で、公開日時が過去(past_publish)になっているものがあれば、ステータスを「公開(published!で)」に変更する # `Article.publish_wait`で、enum値がpublish_waitに該当するデータを取得 @articles = Article.publish_wait.past_publish.find_each(&:published!) # 以下と同じ # @articles.each do |article| # if article.published_at.past? # article.state = :published # article.save! # end # end end end