LoginSignup
0

More than 5 years have passed since last update.

【翻訳】Rubyのブロックはスコープに注意!

Last updated at Posted at 2017-10-10

Ruby学習の一環として、週に1度Ruby関連のブログ記事(英語)を翻訳しています。
今回はRubyのブロックのスコープに関する話。

原文:Watch out for Ruby blocks scope gotcha

↓以下本文↓


Ruby使っているとブロックって便利ですよね。Rubyの柔軟性とパワーはブロックに依るところも大きいです。

ブロックには独自のスコープがあって、クロージャを作るのにも使えます。

これに関連して最近、バグに遭遇しました。その時のコードはだいたいこんな感じ。

def update
  availability.update(availability_params)
  metrics_tracker = MetricsTracker.build_for(activity)
  metrics_tracker.save

  if metrics_tracker.trigger_notification?
    # send email to admin
  end
  # rendering code
end

# code omitted...

def metrics_trakcer
  @metrics_tracker ||= MetricsTracker.new(current_user)
end

まず、DBトランザクションが抜けているということでバグ発生。なので以下のように追記しました。

def update
  ApplicationRecord.transaction do # <= ここにトランザクションを追加
    availability.update!(availability_params)
    metrics_tracker = MetricsTracker.build_for(activity)
    metrics_tracker.save!
  end

  if metrics_tracker.trigger_notification?
    # send email to admin
  end
  # rendering code
end

しかし、するとadminへのメールが送信されなくなりました……なんで?
ひとまず、トランザクションの箇所を見てみましょう。

ApplicationRecord.transaction do
  availability.update!(availability_params)
  metrics_tracker = MetricsTracker.build_for(activity)
  metrics_tracker.save!
end

metrics_trackerはブロックの中で定義しています。ここでのmetrics_trackerはローカル変数で、スコープがブロックの中だけになっています。つまり、ブロック外では、metrics_trackerは定義されていないことになってしまいました。結果として、コントローラで定義されたmetrics_trackerメソッドが呼び出されて、availabilityについては何も知らない新しいオブジェクトが返り値となり、trigger_notification?falseを返してしまっています。

 ・・・

もちろん、ローカル変数とメソッド名に同じ名前を使ったのがそもそもの間違いなんですが…
でもまあ、レガシーコードを引き継いで、それでなんとかしなきゃならなくなったとしましょう。

結論、応急処置的にはこういう書き方になりますね。

def update
  ApplicationRecord.transaction do
    availability.update!(availabiity_params)
    @metrics_tracker = MetricsTracker.builde_for(activity) # <= ここを修正!
    metrics_tracker.save!
  end

  if metrics_tracker.trigger_notification?
    # send email to admin
  end
  # rendering code
end

# code omitted...

def metrics_tracker
  @metrics_tracker ||= MetricsTracker.new(current_user)
end

ブロックでは扱うオブジェクトを切り替えることができるので、ここではインスタンス変数で区別してみました。こう書けば、metrics_trackerメソッドはメモ化効果で期待通りの値を返してくれるというわけです。

要するに、ブロックを使うことはオブジェクトを分離するに等しいので、オブジェクトを利用することはできるけどもローカル変数はローカルなままになってしまう、ということです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0