なにこれ
筆者が||=
の扱いでずっこけたので、備忘録として書いてます。
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で次のように定義されてます。
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を代入するって、もはやメチャクチャですね