はじめに
この記事は書籍「プロを目指す人のためのRuby入門」に掲載できなかったトピックを著者自らが紹介するアドベントカレンダーの24日目です。
本文に出てくる章番号や項番号は書籍の中で使われている番号です。
今回はRailsチュートリアルのサンプルコードを文法解析します。
必要な前提知識
「プロを目指す人のためのRuby入門」の最後(付録を含む)まで読み終わっていること。
Railsチュートリアルのサンプルコードを文法解析してみる
「プロを目指す人のためのRuby入門」の書籍ページには次のような紹介文を載せています。
本書の内容を理解すれば,開発の現場で必要とされるRuby関連の知識を一通り習得できます。そして,「今まで呪文のようにしか見えなかった不思議な構文」や「実はあまりよくわからないまま,見よう見まねで書いているコード」も自信をもって読み書きできるようになるはずです。
そうなんです。「プロを目指す人のためのRuby入門」を読めば、自信をもってRubyのコードを読み書きできるようになります!
あ、そこのあなた!今「本当に~?」って思いましたね??
いいでしょう、それをこれから証明してみせます!
というわけで、この記事ではRuby on Railsチュートリアル(以下Railsチュートリアル)のサンプルコードを題材にして、「プロを目指す人のためのRuby入門」で学んだ知識で本当にRubyのコードが読めるようになるのかどうか確認していきます。
対象となるコード
Railsチュートリアル Rails 5.1版・第14章のサンプルコード
ただし、Viewやテストコードは今回対象外とします。
文法解析のフルバージョン
どのプログラムのどの要素が、「プロを目指す人のためのRuby入門」のどこで説明されているのかは、以下のpull requestのdiffを確認するとわかります。
diffでは以下のように簡単な文法説明と、その内容を詳しく説明している「プロを目指す人のためのRuby入門」内の項番号や掲載ページをコメントとして記入しています。
解説動画
diffを見るだけではピンと来ないと思うので、口頭で説明を入れた動画も作成しました。
こちらもあわせてご覧ください。
ただし、ちょっと長いので(37分)、1.5倍速ぐらいで見ることをオススメします。
やや上級者向けのポイント10個
上のdiffや動画を全部チェックするのは大変だと思うので、その中からやや上級者向けのポイントを10個ピックアップして以下で説明していきます。
1. ||
でnilでない方の値を返す
# request.referrerが偽(nilまたはfalse)ならroot_urlをredirect_toの引数とする(2.10.1)
redirect_to request.referrer || root_url
Rubyの||
や&&
は毎回true
やfalse
を返すとは限りません。そうではなく、真と偽が決定した時点でそのときの値を返すようになっています。なので、request.referrer || root_url
はrequest.referrer
がnil
(またはfalse
)でなければその値を、nil
ならroot_url
を返します(2.10.1項参照)。
ただ、上のような書き方だと一瞬redirect_to request.referrer
とroot_url
を||
で比較しているようにも見えてしまうので、次のように()
で囲んだ方が読み手に優しいと思います。
# ()を付けなくても結果は同じだが、こう書いた方が読み手に優しい
redirect_to(request.referrer || root_url)
2. 条件分岐で変数に値を代入する
# 条件分岐で変数に代入(コラム173ページ)
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
2つの値が同じかどうかを確認するときは通常==
を使いますが、上のコードのif文では==
ではなく=
が使われています。ここでやっているのは値の比較ではなく、変数への代入です。user_id
に真の値(false
でもnil
でもない値)が代入されたら条件分岐自体も真になります(コラム173ページ参照)。
なお、()
は付けなくても同じように実行できます。
# ()は付けなくても動きは同じ
if user_id = session[:user_id]
@current_user ||= User.find_by(id: user_id)
3. nilなら代入、nilでなければ何もしない
先ほどのコードの2行目にも注目してください。
@current_user ||= User.find_by(id: user_id)
||=
を使うと、左辺の値がnil
(またはfalse
)である場合に右辺の値を代入します。左辺の値が真であれば何も代入しません(コラム174ページ参照)。
なので、上のコードは「@current_user
がnil
なら(=まだ何も代入されていないなら)、指定されたuser_id
のUserを代入しろ、そうでなければそのままでOK」の意味になります。
4. 二重コロンでメソッドを呼び出す
# 二重コロンを使ってメソッドを呼び出す(コラム324ページ)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
hexdigest
の左にある二重コロン(::
)に注目してください。オブジェクトのメソッドを呼び出すときは通常ドット(.
)を使いますが、実は二重コロンを使って呼び出すこともできます(コラム324ページ参照)。
s = '123'
# ドットでメソッドを呼び出す
s.to_i #=> 123
# 二重コロンでメソッドを呼び出す
s::to_i #=> 123
なので、Digest::MD5::hexdigest
はDigest::MD5.hexdigest
と書いたことと同じになります。
5. クラス名.メソッド名の形でクラスメソッドを定義する
class User < ApplicationRecord
# 省略
# クラスメソッドの定義(def self.xxxを使わないパターン)(7.3.4, 7.10.8)
def User.digest(string)
# ...
「プロを目指す人のためのRuby入門」の中ではdef self.digest(string)
のように、self.xxx
の形式でクラスメソッドを定義していましたが、上のコードであれば「self
= Userクラス」になるのでself.
をUser.
に置き換えても同じことになります(7.3.4項、7.10.8項参照)。
6. sendメソッドでメソッドを呼び出すメソッドを動的に切り替える
def authenticated?(attribute, token)
# sendメソッドの利用(12.6)
digest = self.send("#{attribute}_digest")
send
メソッドを使うと、文字列やシンボルを使って呼び出すメソッドを動的に指定できます(12.6節参照)。上のコードが何をやりたいのかピンと来ない人は、次のように書いた方がイメージが付きやすいかもしれません。
def authenticated?(attribute, token)
digest =
case attribute
when :activation
activation_digest
when :reset
reset_digest
when :remember
remember_digest
else
raise ArgumentError, attribute
end
つまり引数のattribute
の値と、xxx_digest
メソッドのxxx
の部分が同じになるので、self.send("#{attribute}_digest")
のようにsend
メソッドを使って引数をそのままメソッド名の一部に置き換えているわけです。
ただし、send
メソッドを多用しすぎるとコードの見通しが悪くなりますし、ユーザーの入力値をそのままsend
メソッドに引き渡すような実装になっていると悪意のあるユーザーから任意のメソッドを実行されかねません。くれぐれも用法と用量をお守りください
7. selfをメソッドの引数として渡す
class User < ApplicationRecord
# 省略
def send_activation_email
# 自分自身(Userクラスのインスタンス)を引数として渡す(7.5)
UserMailer.account_activation(self).deliver_now
end
self
はRubyの文法上「擬似変数」(最初から定義されている特殊な変数で、再代入できないもの)になります(2.12.4項参照)。つまり、「一種の変数」と見なすことができるので、上のコードのようにself
をメソッドの引数として渡すことも可能です。self
については7.5節で詳しく説明しています。
8. クラス構文の直下でif文のような処理を書く
class PictureUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
process resize_to_limit: [400, 400]
# クラス構文の直下に処理を書く(7.5.2)
if Rails.env.production?
storage :fog
else
storage :file
end
Rubyではクラス構文の直下にも自由にコードを書くことができます。クラス構文の直下に書いたコードはそのクラス定義が読み込まれる瞬間に上から順に実行されます(7.5.2項参照)。
上のコードはRailsの実行環境が本番環境であればstorage :fog
を、そうでなければstorage :file
を実行(設定を切り替え)します。
9. 左辺に何もない二重コロンでトップレベルの定数を参照する
# 左辺に何もない二重コロンでトップレベルの定数を参照する(コラム308ページ)
config.log_formatter = ::Logger::Formatter.new
上のコードでは::Logger
の左側に何もありません。二重コロンの左辺に何もない場合は、必ずトップレベルの定数(定数にはクラスやモジュールも含まれる)が参照されます(コラム308ページ参照)。
つまり、上のコードであればRuby標準のLoggerクラスを参照していることになります。
10. 例外的に大文字で始まるメソッドを呼び出す
# 例外的に大文字で始まるメソッドを呼び出す(2.6節の備考欄)
# https://docs.ruby-lang.org/ja/latest/method/Kernel/m/Integer.html
workers Integer(ENV['WEB_CONCURRENCY'] || 2)
Rubyでは整数を表すクラスとしてIntegerクラスがあります。しかし、上のコードではInteger(...)
のようにクラス名のうしろに丸括弧が付いています。これはいったい何をやっているのでしょうか?
実はこのIntegerはクラスではなく、KernelモジュールのIntegerメソッドです。Rubyのメソッドは通常小文字から始まりますが、まれに大文字で始まるものが存在します(2.6節の備考欄参照)。
Integerメソッドは引数で渡された値を整数に変換しようと試みます。変換できなかった場合はエラーが発生します。
# 出典: https://docs.ruby-lang.org/ja/latest/method/Kernel/m/Integer.html (一部抜粋)
p Integer(4) #=> 4
p Integer(9.88) #=> 9
p Integer(nil) # can't convert nil into Integer (TypeError)
p Integer("hoge") # `Integer': invalid value for Integer: "hoge" (ArgumentError)
p Integer("") # `Integer': invalid value for Integer: "" (ArgumentError)
Integerメソッドの仕様についてはRubyの公式ドキュメントを参考にしてください。
他にもあります!
いかがだったでしょうか?初心者の方はこうやってみると「まだまだ知らないポイントがたくさんあった」と感じるのではないでしょうか?
冒頭で紹介したpull requestや動画の中では、他にもこうしたポイントを紹介しています。全部チェックしたい方は以下のリンクを参照してください。
このようなRubyの文法や言語機能に関する知識を深めるためにも、ぜひ「プロを目指す人のためのRuby入門」を活用してください
次回予告
いよいよ次回で最終回です。次回はRuby 2.5で発生する「プロを目指す人のためのRuby入門」との差異を説明します。