2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ruby でメモ化する時に return をしてはいけない

2
Last updated at Posted at 2020-06-09

結論から言うと Ruby でメモ化するときはアーリーリターンしてはいけないという話です(恐らく Ruby に限らず)。

  • NG
def user
  @user ||= begin
    user = User.find_or_initialize_by(id: 1)
    return user if user.persisted? # インスタンス変数定義中に return してはいけない
    
    user.save
    user
  end
end
  • OK
def user
  @user ||= begin
    user = User.find_or_initialize_by(id: 1)
    if user.persisted?
      user
    else
      user.save
      user
    end
  end
end

# もしくは

def user
  @user ||= begin
    user = User.find_or_initialize_by(id: 1)
    user.save if user.new_record?
    user
  end
end

Ruby(Rails)でメモ化を使う際、以下のように書くことがあると思います。

def user
  @user ||= User.first
end

複数行であれば beginend で囲みます。

def user
  @user ||= begin
    user = user.find_or_initialize_by(id: 1)
    if user.persisted?
      user
    else
      user.save
      user
    end
  end
end

ここでよくあるリファクタリング手法であるアーリーリターンを使ってみます。

def user
  @user ||= begin
    user = User.find_or_initialize_by(id: 1)
    return user if user.persisted?
      
    user.save
    user
  end
end

一見するといい感じに見えますが、このメソッドを rails console で実行してみると以下のようになります。


[1] pry(main)> def user
[1] pry(main)*   @user ||= begin
[1] pry(main)*     user = User.find_or_initialize_by(id: 1)
[1] pry(main)*     return user if user.persisted?
[1] pry(main)*
[1] pry(main)*     user.save
[1] pry(main)*     user
[1] pry(main)*   end
[1] pry(main)* end
=> :user
[2] pry(main)> user
  User Load (3.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User:0x00007ff0b4d2e338
 id: 1,
 created_at: Tue, 09 Jun 2020 15:04:04 JST +09:00,
 updated_at: Tue, 09 Jun 2020 15:04:04 JST +09:00>
[3] pry(main)> user
  User Load (1.8ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User:0x00007ff0ad554740
 id: 1,
 created_at: Tue, 09 Jun 2020 15:04:04 JST +09:00,
 updated_at: Tue, 09 Jun 2020 15:04:04 JST +09:00>

メモ化が行われておらず毎回クエリが発行されています。リファレンスを改めて確認してみると、

Ruby 2.7.0 リファレンスマニュアル
https://docs.ruby-lang.org/ja/latest/doc/spec=2fcontrol.html#return
return
式の値を戻り値としてメソッドの実行を終了します。

とありますので return した段階で user メソッドが終了し、@user のインスタンス変数が nil のままなので毎回処理を実行してしまっているということでした。
考えてみれば当然ですが、クセで早期リターンをよく使っているとつい書いてしまってハマってしまうこともあるので気をつけましょうということで。

2
1
0

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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?