7
4

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 3 years have passed since last update.

rubyの「||=」の仕様を理解と、Railsのpresenceメソッドとは

Last updated at Posted at 2021-01-09

なにこれ

筆者が||=の扱いでずっこけたので、備忘録として書いてます。
presenceメソッドの解説(?)もしてるので、良かったら見てください!
ご指摘があればコメント欄までお願いいたします。

そもそも「||=」とは

簡単に言うと自己代入
rubyでよく見る下記のコードと同じ意味

result = 1
result += 2
p result #=> 3

これを「もしnil or falseだったら、右辺を代入する」に変えたバージョン。

def check(arg)
  arg ||= 'nil or false'
  p arg
end
check('hoge') #=> 'hoge'
check(nil) #=> 'nil or false'
check(false) #=> 'nil or false'

||= の詳しい動き

Rubyのドキュメントに素晴らしいサンプルコードがあったので、抜粋します

p obj.foo ||= true
p obj.foo || (obj.foo = true)

まさに上記と同じ動きをしています。

||で比較をする。||は最初に真になったものを返す。
obj.fooがnil or falseじゃなければ真つまり左辺を返す。
左辺が偽なら、左辺の変数に対して右辺のコードを代入する。ということです。

筆者がハマった罠(余談)

上記のnil or falseだったら左辺に代入するという仕様を理解できてなかったせいで、
若干沼ったので備忘録として残します。
この記事を書こうとした理由でもあります。

Railsで開発してて、次の要件を満たすメソッドを作りたかった。

・DBから指定したidかつboolean型がtrueのレコードを取得する
・取得したレコードのboolean型をfalseにしてupdateする

・必ずレコードが存在するとは限らない。
・条件に一致したレコードが取得できなければreturnする

下記のようなコードを書きました(変数名は仮)

def find_and_change_default(post_id)
  post = Post.find_by(id: post_id, boolean: true)
  if post.present?
    post.update(boolean: false)
  end
end

1つ問題として思ったのが
「postをfind_byしたのにpresent?で存在確認するって冗長じゃね?」
と、思いました。

そこで色々調べたところ、Railsのpresenceメソッドを知って、下記のように修正しました

def find_and_change_default(post_id)
  post = Post.find_by(id: post_id, boolean: true).presence || return
  post.update(boolean: false)
end

presneceメソッドはRailsで次のように定義されてます。

activesupport/lib/active_support/core_ext/object/blank.rb
  def presence
    self if present?
  end

レシーバ(今回はpost)がpresent?でtrueが返されれば、selfを返します。
Railsガイドに下記のような用法が紹介されてました。

host = config[:host].presence || 'localhost'

configにhostがあれば、そちらを代入。なければlocalhostを代入する。というコードです。

「||=」とpresenceメソッドの関係性

ここを一番説明したかった。今までは前座です

||=の左辺がnil or falseなら右辺を代入する。という仕様を理解してなくて起こった悲劇です。

nilだったらreturnするから、||=が使えそう!と何故か思ってしまい、
こんなコードを書いてしまいました

  post = Post.find_by(id: post_id, boolean: true).presence ||= return
  #=> undefined method `presence=' for nil:NilClass (NoMethodError)

上記は一致するレコードがなかった時の結果です。
エラーを吐いた時は頭を抱えましたが、rubyのドキュメントを100回確認したら理解できました。
https://docs.ruby-lang.org/ja/latest/doc/spec=2foperator.html

用法を調べたら、Railsに下記のようなコードがありました(メソッドはRailsのコードから適当に抜粋しました)

def open_connection(server = nil)
  server ||= TestServer.new
  # 後続の処理
end

引数にとった値を確認する時に使うのが一般的だと思いました。
さっきの場合は、DBからデータを取得して変数に格納したいのに、左辺に代入するという意味が分かってなくて、
上記のようなコードを書いてしまいました。
nil.presenceメソッドを実行してる左辺に対してreturnを代入するって、もはやメチャクチャですね

7
4
2

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
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?