RubyOnRails

redirect_to @userが何を省略しているかわかりますか?〜挫折しないRailsチュートリアル7章〜

この記事は、ProgateでRuby on Railsのレッスンを一通り学んだ方に向けて、Railsチュートリアル(第4版)の7章を理解するための知識を紹介するものです。


ProgaterがRailsチュートリアルに挫折する理由

ProgateでRailsを勉強したプログラミング初心者(以下「Progater」)が、Railsチュートリアルに挑んで挫折する例は案外多いそうです。

この点については、自分も進行形でチュートリアルに挑んでいるProgaterなのでよくわかります。

チュートリアルは7章あたりから難易度が格段に上がります。

自分も6章までは1日1章ペースでとんとんと進んでいきましたが、7章あたりからペースが落ち始め、9章で完全に進捗が止まりました。

かれこれ1週間ほど停滞したあと、やっと9章をクリアできましたが、その時には総学習時間が130時間を超えてしまっていました:fearful:

おそらく、もし自分にRailsを学ぶ明確な目標、作りたいプロダクトの明確なイメージなどがなかったら、自分も途中で挫折していたのではないかと思います。

ただ、試行錯誤で壁を超えてきたおかげで、私自身RailsチュートリアルのどのあたりでProgaterがつまづくのかも理解できた気がします。

なのでこの記事では、


  • ProgaterがRailsチュートリアル(主に7章)でつまづくポイント

  • 壁を乗り越えるための知識

について紹介していきます。

悶々とする過去の自分に教えてあげたかった記事です。


Railsチュートリアルが難しい3つの理由

ProgaterにとってRailsチュートリアルが難しい理由は、下の3つにあると思います。


  • 簡略すぎるコード

  • 自動化によって隠された処理

  • 頭のいい人にありがちな説明不足

まず一つ目に、Railsチュートリアルでのコードは、書き方がかなり簡略化・省略されます。

この点は、書き方を省略せずに丁寧に教えてくれるProgateとは異なるので、Progaterが困惑する理由のひとつになっていると思います。

二つ目に、Railsチュートリアルでは、なるべく処理を自動化しようとします。

たとえば、Progateでは全てのルートを、自分の手で作りましたよね。

しかし、チュートリアルでは、RESTfulといってルートを簡単なコードで自動で設定してしまいます。

自動化は、便利な反面、初心者にとっては「処理が見えなくなるため、何を行なっているか理解しずらくなる」というデメリットもあるので、この点もProgaterがつまづく理由になっていると思われます。

三つ目に、Railsチュートリアルでは、コードと同様に、説明もまた、シンプルです。

つまりまぁ、「わかんなかったらググってね」というのがチュートリアルの著者Michael Hartl先生の基本スタンスなので、ひつじ仙人のような優しい教師に慣れたProgaterにとってはツライところでしょう。

実は、この三つの挫折ポイントをすべてあわせもったコードがあります。

それがタイトルにもある redirect_to @userです。


redirect_to @userが省略している記述

このコードはシンプルで一見簡単そうに見えますが、印象だけで判断するとわりと痛い目を見ます。

チュートリアル内でこのコードの説明が行われているのは、7.4.1「フォームの完成」の章です。



app/controllers/users_controller.rb

class UsersController < ApplicationController

.
.
.
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render 'new'
end
end

private

def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end


ここで、

redirect_to @user

といった行がありますが、これは次のコードと等価になります。

redirect_to user_url(@user)

これはredirect_to @userというコードから (Railsエンジニアが) user_url(@user)といったコードを実行したいということを、Railsが推察してくれた結果になります。


なるほど。簡潔な説明で、わかったような気にはなります。

けど、Rails初心者でこの説明だけで本当にこのコードの処理が理解できたなら、私はその人は天才だと思います。

実際のところ、Michael Hartl先生には、あともう二行だけ説明を付け加えて欲しかった。

たとえば


そしてリンク先のパスとしてモデルオブジェクトが渡された場合、

Railsはオブジェクトを一意に表す値、つまり、idを取得しようします。だから最終的には、redirect_to @userは、redirect_to user_url(@user.id)と等価となります。


みたいに。

とはいえ、redirect_to("/users/#{@user.id}")といった書き方しか知らないProgaterにとっては、上の説明でも難解に思えるでしょう。

なのでProgaterにもわかりやすく説明するために、redirect_to("/users/#{@user.id}")からredirect_to @userまでどのような省略の過程をたどっているのかを下に示しました。


やってることはほぼ全部同じ!!.rb

#/users/:idへのリダイレクトを、相対パスで指定する(1)

redirect_to("/users/#{@user.id}")

#/users/:idへのリダイレクトを、絶対パスで指定する(2)
redirect_to("https://228e5b796b37495aa0c17e02856dccfa.vfs.cloud9.us-east-2.amazonaws.com/users/#{@user.id}")

#/users/:idへのリダイレクトを、user_url(id)ヘルパーを使って、絶対パスで指定する(3)
redirect_to(user_url(@user.id))

#リンクのパスとしてモデルオブジェクトが渡されると自動でidにリンクされるので、.idを省略する(4)
redirect_to(user_url(@user))

#_urlヘルパーは、省略できる(5)
redirect_to(@user)

#Rubyでは、()は省略できる(6)
redirect_to @user


う〜ん、省略の嵐(笑)

ちなみに、redirect_to @userの説明が行われているRailsチュートリアル7.4.1 で作っているプログラムは、ProgateのRuby on Rails5 Ⅵ 8/11で作っているものと(出力上は)同じものなので、比べてみると勉強になると思います。

さて、(1)は、Progate流のリダイレクトの指定方法です。

しかし、 チュートリアル5.3.2 によると、Railsの規約では、基本的にはリンクには相対パスを使うべきですが、リダイレクトのリンクでは絶対パスを利用すべきとのことです。

というわけでまず、相対パス指定である(1)を、絶対パス指定である(2)に変換します

とはいえ、こんな長ったるいURLをいちいち指定するのは面倒です。

そこでRailsは気を利かせて、絶対パスを指定するためのメソッドを用意してくれています。

それが_urlヘルパーです。

ただし、この_urlヘルパーを利用するには、前提としてRESTfulなルーティングを知っておく必要があります。


RESTfulなルーティング

RESTfulなルーティングというのは、簡単に言えば、Rails側が自動で設定してくれるルーティングのことです。

逆に、非RESTfulなルーティングというのは、おなじみProgateで学んだ手動のルート設定のことです。

get "url" => "controller#action"のアレですね。

実は、Railsでは、この非RESTfulなルーティングは推奨されていません。

Railsが用意しているform_forなどの主要なメソッドは、RESTfulなルーティングを前提に設計されているからです。

というわけで、ProgaterもおとなしくRESTfulなルーティングを覚えましょう(笑)

RESTfulなルーティングは、設定するのだけは簡単です。

おなじみroutes.rbに、resouces :リソース名の一行を加えるだけです。下が例になります。


routes.rb

Rails.application.routes.draw do

resources :users

end


resouce's'なので、リソース名も複数形にしましょう。

この一行を加えるだけで、下のルートが設定したのと同じことになります。


routes.rb

Rails.application.routes.draw do

get "/users" => "users#index" #ユーザー一覧画面を生成
get "/users/:id" => "users#show" #個別ユーザー詳細画面を生成
get "/users/new" => "users#new" #新規ユーザー登録画面を生成
post "/users" => "users#create" #新規ユーザー登録画面からの入力を受けて登録処理
get "/users/:id/edit" => "users#edit" #既存ユーザー編集画面を生成
patch "/users/:id" => "users#update" #編集画面からの入力を受けて更新処理
put "/users/:id" => "users#update" #編集画面からの入力を受けて更新処理
delete "/users/:id" => "users#destroy" #一覧画面で選択されたデータを削除処理

end


すごい😳

たった一行でこれだけのルートを設定できるのは便利!!・・・ではあるのですが、

今までのように「ルートを忘れたらroutes.rbを参照すればわかる」という具合にはいかないので、頑張ってRESTfulなルートのパターンを覚えなくてはいけません。

覚えきれないうちは、Progaterは上のルーティング表を印刷して手元に置いておくといいと思います。


「URLヘルパー」あるいは「名前つきルート」

「まぁつまり、RESTfulっていっても、上の表のルーティングにしたがえばいいってだけでしょ?簡単簡単」

いや残念ながら、resoucesメソッドで自動生成されるルーティングを覚えてしたがうだけでは、少なくともRailsチュートリアルをクリアできません。

なぜならRails(そしてチュートリアル)では、リンクの指定などは、上の表のルートのURLをそのまま指定するのではなく、「名前付きルート」あるいは「URLヘルパー」と呼ばれるメソッドを使って指定することが慣例となっているからです。


やってることはほぼ全部同じ!!.rb


#/users/:idへのリダイレクトをURLで指定する(1) ⇦こっちじゃなくて
redirect_to("/users/#{@user.id}")

#/users/:idへのリダイレクトを、user_url(id)ヘルパーを使って指定する(3) ⇦こっちの方が好ましい
redirect_to(user_url(@user.id))


上の行(1)は、Progateでおなじみの書き方ですね。

覚えてますか?redirect_toメソッドは、引数にルートのURLを渡すんでしたね。

(1)では、"/users/#user.id}"が引数として渡されています。

しかしRailsでは(少なくてもRailsチュートリアルでは)、この書き方は好まれません。

(2)のチュートリアル流の書き方では、redirect_toメソッドの引数にuser_url(@user.id)というメソッドが渡されています。

このメソッドは「URLヘルパー(Railsチュートリアルでは「名前つきルート」)」と呼ばれるもので、戻り値としてルートURLを返してくれます。

そしてこのURLヘルパーは、RESTfulなルートを設定した時、つまりroutes.rbresouces :nameの一行をつけ加えたあの時に、自動で生成されています

この自動生成されたURLヘルパーは、RESTfulなルートのパターンと同じように覚える必要があります。

さぁ、下の表を覚えましょう!(それか印刷して手元に置いときましょう)

ヘルパー名(_path)
ヘルパー名(_url)  
戻り値(パス)        

users_path
users_url
/users         

user_path(id)
user_url(id)
/users/:id   

new_user_path
new_user_url
/users/new

edit_user_path
edit_user_url(id)
/users/:id/edit

URLヘルパーには、_pathヘルパー_urlヘルパーの二種類があります。

この二つのヘルパーの違いは、たとえばuser_path(id)が戻り値として/users/:idという相対パスを返すのに対して、user_url(id)は戻り値としてhttps://~~/users/:idといった絶対パスを返す点です。

前にも述べたとおり、Railsでは、基本は相対パスすなわち_pathヘルパーを利用しつつ、redirect_toメソッドでは絶対パスすなわち_urlヘルパーを利用するのが慣例となっています。


redirect_to @userは何を行なっているのか?

RESTfulなルーティングとURLヘルパーについて理解できたなら、あのシンプルすぎて不審なredirect_to @userが何をしているのかも理解できるはずです。


やってることはほぼ全部同じ!!.rb

#/users/:idへのリダイレクトを、相対パスで指定する(1)

redirect_to("/users/#{@user.id}")

#/users/:idへのリダイレクトを、絶対パスで指定する(2)
redirect_to("https://228e5b796b37495aa0c17e02856dccfa.vfs.cloud9.us-east-2.amazonaws.com/users/#{@user.id}")

#/users/:idへのリダイレクトを、user_url(id)ヘルパーを使って、絶対パスで指定する(3)
redirect_to(user_url(@user.id))

#リンクのパスとしてモデルオブジェクトが渡されると自動でidにリンクされるので、.idを省略する(4)
redirect_to(user_url(@user))

#_urlヘルパーは、省略できる(5)
redirect_to(@user)

#Rubyでは、()は省略できる(6)
redirect_to @user


(3)に注目してください。

(2)のredirect_toメソッドでは、引数に絶対パスのURLが渡されていましたが、それが(3)では、@user.idを引数にもつuser_url(id)メソッドが渡されています。

先ほどの表を確認すると、user_url(id)メソッドは、戻り値としてhttps://~~/users/:idを返すことがわかります(気をつけてください。user_url(id)でも、戻り値は、/user"s"/:idです。)。

つまり、この場合は・・・・はい、下のルーティングを確認してみましょう。


routes.rb

Rails.application.routes.draw do

get "/users" => "users#index" #ユーザー一覧画面を生成
get "/users/:id" => "users#show" #個別ユーザー詳細画面を生成 ⇦こいつです!!!
get "/users/new" => "users#new" #新規ユーザー登録画面を生成
post "/users" => "users#create" #新規ユーザー登録画面からの入力を受けて登録処理
get "/users/:id/edit" => "users#edit" #既存ユーザー編集画面を生成
patch "/users/:id" => "users#update" #編集画面からの入力を受けて更新処理
put "/users/:id" => "users#update" #編集画面からの入力を受けて更新処理
delete "/users/:id" => "users#destroy" #一覧画面で選択されたデータを削除処理

end


行き着く先は、usersコントローラーのshowアクションだとわかりますね。

つまり、redirect_to(user_url(@user.id))は、ユーザーの詳細画面にリダイレクトされます。

ややこしいですね(笑)

しかし、これでredirect_to @userの動作は理解できました。

残る問題は、redirect_to(user_url(@user.id))がどのようにしてredirect_to @userにまで省略されるかだけです。


モデルオブジェクトから自動でidを取得してくれるRails

(3)と(4)を見比べて見ましょう。


やってることはほぼ全部同じ!!.rb


#/users/:idへのリダイレクトを、user_url(id)ヘルパーを使って、絶対パスで指定する(3)
redirect_to(user_url(@user.id))

#リンクのパスとしてモデルオブジェクトが渡されると自動でidにリンクされるので、.idを省略する(4)
redirect_to(user_url(@user))


redirect_toの引数であるuser_url(@user.id)が、なんとuser_url(@user)に省略されているではありませんか!

そんな些細なこと、と思われるかもしれませんが、これはかなり重要なポイントです。

実は、メソッドの引数URLとしてモデルオブジェクトが渡された場合、Railsはそのモデルオブジェクトのidを自動で取得して、最終的なURLを生成するのです。

モデルオブジェクトとは、モデルのインスタンスを戻り値として返すオブジェクトのことです。


モデルオブジェクト.rb

module UsersHelper

#戻り値にモデルのインスタンスを返すなら、そのメソッドはモデルオブジェクト
def model_object

#モデルのインスタンスが代入されているなら、その変数はモデルオブジェクト
@model_object = User.find_by(id: 1)
model_object2 = User.new
model_object3 = User.create

return @model_object

end


自動でモデルオブジェクトのidを取得して、それを自動でURLに組み込むRailsの仕組みは、redirect_toメソッドやuser_url(id)メソッドだけでなく、おなじみのlink_toメソッドなどでURLを指定するために引数としてモデルオブジェクトを渡した場合にも働きます。

たとえば、こんなふうに↓

<%= link_to "show", model_object %>

正直、初見ではシンプルすぎて誤植を疑いたくなるレベルですが、残念ながらRailsチュートリアルではよく出会うURLの指定方式です。

それなのにチュートリアルの中でろくにこの仕組みが説明されてるように見えないのはどうなんすかねぇ!!おかげで挫折しかけましたよ!!

・・・失礼。

上のリンク指定方法の場合、もしリンクが貼られていたページのURLが/model_objects/だったなら、賢くて気の利くRailsくんはmodel_objectメソッドの戻り値からidを取得して/model_objects/1へのリンクを自動で生成してくれるというわけです。簡単ですね〜(白目)。


地獄への道は善意で舗装されている。

さて、ここまで理解できたなら、あとの省略の仕組みを理解するのは簡単です。


やってることはほぼ全部同じ!!.rb


#リンクのパスとしてモデルオブジェクトが渡されると自動でidにリンクされるので、.idを省略する(4)
redirect_to(user_url(@user))

#_urlヘルパーは、省略できる(5)
redirect_to(@user)

#Rubyでは、()は省略できる(6)
redirect_to @user


(4)から(5)への省略は、チュートリアルの中でMichael Hartl先生が説明してくれています。

urlメソッドは省略しても良いというのですね。

(5)から(6)への省略を説明するにも、それほど言葉はいらないでしょう。

Rubyでは、( )を省略できるのです。

試しに、ProgateのRubyコースやRailsコースで()を省略してみるのも面白いですよ。きちんとクリアできるはずです。

さぁこれで、Progate流のredirect_to("/users/#{@user.id}")からチュートリアル流のredirect_to @userにまで省略できました。

予想していたより、説明が長くなってしまいましたね。

とはいえ、このURLヘルパーとモデルオブジェクトでURLを指定する方式は、(その説明の少なさに関わらず)Railsチュートリアルの中ではガンガン出てきます。

きちんと理解しておかないと7章から先でつまづくことになるので(実際つまづいたので)、この記事で紹介しました。

ただ、つまづいたり挫折しかけたりすることは、学習にとって一概に悪いことであるとはいえません。

むしろ自分が専門としている(学習)心理学の分野では、「望ましい困難」や「生成効果」という名前で、障害による知的努力の投入が教材をより深く理解させ、記憶にも長く定着させることが証明されています。

一般的に「つまづき」や「失敗」や「挫折」というものは「停滞」と考えられていますが、最新の科学的知見によると、むしろ学びを深めるために不可欠のものだと考えられているのです。

だからまぁ、Railsチュートリアルは難しいですし、脱落者も結構多いらしいですけど、多少壁にぶつかってもめげずに、焦らずに、ともに壁を乗り越えていこうじゃないですか(130時間も使っておいて、自分もあと4章も残ってますからね)。

私自身、つまづき、壁を乗り越える中で、ひとつ偉大な真理を学びました。

それは「プログラミング初心者がつまづく部分の多くは、プログラマーやプログラム側の気遣い(善意)である」ということです。

Progater(初心者)がRailsチュートリアルでつまづく理由に、私は「簡略すぎるコード」と「自動化によって隠された処理」の二つを挙げましたが、これらは悪意ではなく、プログラマーとプログラムの善意から生まれています。

簡略なコードも自動化も、省略されている部分や隠された処理を知らない初心者にとっては障害ですが、それらをきちんと理解しているプログラマーにとってはプログラムの可読性や開発効率を高めるメリットでしょう(トレードオフというやつです)。

ということは、今はまだそうしたメリットのデメリットを被っている立場(初心者)であっても、つまづくことで理解を深めていけば、いずれはメリットを享受できる立場に立てるということです。

こう考えると、つまづいても未来に希望がもてるし、なんかちょっと、ワクワクしませんか?


まとめ



  • resouces: nameroutes.rbに記述するだけで、自動で適切なルートURLと対応するアクションが生成される。

  • RailsでのURLは、URLヘルパーを使って指定すべし。基本は_pathヘルパーで、redirectするときは_urlヘルパーを使って指定する

  • メソッドのURL引数としてモデルオブジェクトを渡すと、Railsが勝手にモデルオブジェクトのidを取得してよしなにやってくれる。

  • Rubyの()は省略できる。

  • 心理学的には、つまづいたほうが学べることは多いし、記憶にも定着する。

  • (プログラミング初心者にとって)地獄への道は、(プログラマーの)善意で舗装されている。


参考文献


  • 山田祥寛「Ruby on Rails 5 アプリケーションプログラミング」技術評論社 2017年 p76,402, p404