ryota21silvaの技術ブログ

Funna(ふんな)の技術ブログ

これまで学んだ技術の備忘録。未来の自分が救われることを信じて

【Ruby】procとblock

procとblockについて

block

ブロックとは

do...end{ }のカタマリのこと。

do...endと{ }

ブロックの中の|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. ブロック引数は1つしか受け取れない。
  2. 複数の引数が存在する場合、ブロック引数は最後の引数として指定してあげる。

以下の例では、&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
実行したよ

rails-study.net

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

以下の処理は、上記の処理と同じ挙動となる。 つまり、以下の&blockblockは、上記のブロックと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"]

docs.ruby-lang.org

以下の2つは同じ意味になる。
→do...end に限らず { |object| object.published! } も同じく短く出来る!

find_each do |object|
  object.published!
end

find_each(&:published)

qiita.com

以下であれば、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