【無職に転生 ~ 就職するまで毎日ブログ出す⑦】【Rails】忘れがちな便利な書き方
はじめに
こんにちは、大ちゃんの駆け出し技術ブログです。
タイトルにあるとおり【無職に転生 ~ 就職するまで毎日ブログ出す】というチャレンジをしています!!!大人気アニメのタイトルをまるパクリした毎日投稿チャレンジです。
RailsやらRubyやらSQLやらその他Webの知識やらが色々と抜け落ちているのを感じており、知識の定着のためにもアウトプットする機会を増やすためです。加えて、退職して文字通り無職に転生しましてプロニートになり、毎日時間に余裕ができたので引き締めるためにも毎日投稿を思い至りました!
【投稿内容】
- SQLの難しい処理 (副問合せ、JOINとか複雑な処理が書けない)
- Rails全般 (純粋に必要な知識が多すぎる、網羅的な理解が足りない)
- Rubyのあまり使わないメソッドや記述方法 (あまり重要ではないけど特に)
- Web知識全般 (クッキーやら、セッションやらなんとなくで理解しているものの自分の言葉で説明できない)
- 書籍 (スタートアップ企業に勤めるので、自分が会社に与える影響やパフォーマンスを高めるためビジネス書を読んでいきます。)
本記事でやること
便利な書き方を3つ紹介したいと思います。
- 自己代入
- ぼっち演算子
- Array.map(&:to_i)
三つも紹介するので他の記事に比べて内容は薄いものとなりますのでご了承を。あくまで個人ブログのアウトプットで。しかし、全く他の記事と同じにしてしまうと意味がないので、ここでは上記3つがRailsでどのタイミングで使われるのかもアウトプットしたいと思います。
自己代入
||=
というよく使われる記号です。gemの中身の処理を見ると至る所にこの記号が見られます。
a ||= 1
自己代入と言われている記事もありますが、正式な名称はググってもわかりませんでした。。
この処理は何をやっているのかというと、以下の処理と同じことをしています。
a = a || 1
変数aにaもしくは1を入れる処理です。aがもし真であれば(つまり、nilやfalseでなければ)aが代入されます。そして、もしaが偽であれば1が代入されます。したがって、元々aに値が入っている場合は1は代入されません。
a = 2 => 2 a ||= 1 => 2
さてさて、この処理の意味は分かったのですが、果たしていつ使えば良いのでしょうか。この記述最近見たと思ったのですが、sorceryの外部認証の処理部分で出てきました。
@access_token ||= @provider.process_callback(params, session) # sends request to oauth agent to get the token @user_hash ||= @provider.get_user_hash(@access_token) # uses the token to send another request to the oauth agent requesting user info
この処理ではなぜ||=
を使う必要があるかを考えたのですが、DRYを避けるためだそうです。
When do we use the "||=" operator in Rails ? What is its significance?
@access_token
、及び@user_hash
は現状nilになることは間違いなく(処理の中でこれら二つのインスタンス変数はどこにも使われていないため)、=
で代入したとしても問題ありません。しかし、今後別の箇所でこれらの値を使うかもしれません。そうなったときに、もし=で評価しておくと、@access_tokenを取得する処理や@user_hashを取得する処理が既に取得済みであるにもかかわらず実行されます。結果2度処理が走ってしまいます。そのため、||=
を使用しておくことで将来的なコードがDRYになることを防いでいます。(記述的には2度同じ処理を書いているのでDRYですが、片方は実行されないため処理的にはDRYです。)
ぼっち演算子
セーフナビゲーションとも言われる処理です。&.
という記述を使用します。nilがレシーバーであるときlength
メソッドは失敗します。よくあるNoMethodError
ですね。
a = nil => nil a.length (irb):2:in `<main>': undefined method `length' for nil:NilClass (NoMethodError)
しかし、もし&.length
と書いておくとレシーバーがnil
の時はエラーを出さずにnil
を返します。
a&.length
=> nil
セーフナビゲーションと名前がついているようにエラーを回避してくれる書き方ですね。
セーフナビゲーションの使い時ですが、Qiitaの記事を引用します。
Rubyにはメソッドの返り値が
「配列またはnil」
、「文字列またはnil」
などを返すものが多いのでそのようなときにnilになったレシーバにnilに対応していないメソッドを使ってしまうとNoMethodError(そんなメソッドないよ!)
というふうに返してしまいエラーが出てしまいます。だからといって場合分けするとコードが複雑になるしコード量が増えてしまう。 そこで導入されたのが&.
です。これはメソッド呼び出しの.
と同じ使い方だけれど,レシーバーが nil のときだけはメソッドが呼び出されないで nil を返す、というものです。
https://qiita.com/yoshi_4/items/e987b698c1978d248cfc
NoMethodErrorのNilClassはほんとに頻繁に起こすと思うので、&.
と書くことでエラーを回避できるのはありがたいですよね。条件分岐を使ってif hoge.nil?
みたいにいちいち場合わけ処理を書かなくても&.
を使ってnil
が変えるようにし、以降の処理でエラーが起きないようにすれば処理はエラーになりません。ただ、エラーが起こらないので気づかないうちに別のバグの温床になる可能性もあるので。セーフナビゲーションをどこに書いてあるのかは意識しておくと良いかもしれませんね。
ちなみにぼっち演算子の名前の由来は下記のとおりです。
おそらく気になった方も多いと思いますが、ぼっち演算子のぼっちの部分は&.がひとりぼっちで座っている姿に見えることから名付けられたそうです。
https://qiita.com/yoshi_4/items/e987b698c1978d248cfc
Array.map(&:to_i)
この書き方も本当によく見ると思います。例えば、下記のような処理があるとします。
["1", "2", "3"].map{|v| v.to_i}
これは下記のような書き方に置き換えることができます。
["1", "2", "3"].map(&:to_i)
競技プログラミングなどで整数の配列を取得するときも以下のような書き方が使われます。
numbers = gets.split.map &:to_i
初見だと何が行われているかわかりませんが、実はProcが大きく関わっています。ここからはかなり余談に聞こえるかもしれませんが後ほど話がつながりますので長い目で見てください。
ProcとはRubyのブロックをオブジェクト化できるクラスです。newメソッドの引数をブロックに指定することでブロックのオブジェクトを作成できます。
Proc.new {a} => #<Proc:0x00007ffaf9bd7a08 (irb):22>
ここで下記の処理は可能なのかどうかを考えます。
prc = Proc.new {|v| v.to_i} ["1", "2", "3"].map prc
答えは不可で下記のエラーが出てしまいます。理由はmapメソッドはブロック引数ですが、Procの場合はそのまま引数の値として渡されているためです。
# `map': wrong number of arguments (given 1, expected 0) (ArgumentError)
ここで&の登場です。
ブロック付きメソッドに対して Proc オブジェクトを `&’ を指定して渡すと 呼び出しブロックのように動作します。
Procオブジェクトを&を使用すると確かに処理はうまく実行されます。
prc = Proc.new {|v| v.to_i} ["1", "2", "3"].map &prc => [1, 2, 3]
ここでなぜ上記の処理を説明したのかというとこの&
がまさに&:to_i
の&の役割と同じであるということです。シンボルである:to_i
に対して&
を付けています。ここでシンボルに&
を修飾することで何が起こるのかというとto_proc
が呼び出し実行されるようです。
to_proc メソッドを持つオブジェクトならば、`&' 修飾した引数として渡すことができます。デフォルトで Proc、Method オブジェ クトは共に to_proc メソッドを持ちます。to_proc はメソッド呼び出し時に実行され、Proc オブジェクトを返すことが期待されます。
class Foo def to_proc Proc.new {|v| p v} end end [1,2,3].each(&Foo.new) => 1 2 3
つまりこの形は以下のように本当は以下のように実行されます。
["1", "2", "3"].map(&:to_i) ↓ ["1", "2", "3"].map(&:to_i.to_proc)
ここらへんの処理、もうちょっとわかりやすく説明したいですね、、、後ほど変更します。。
参考記事
When do we use the "||=" operator in Rails ? What is its significance?