Railsでは基本的に、
①リクエストが送られてくると、routes.rbによってcontrollerに処理が振り分けられる。
②contorollerでインスタンス変数などを定義する。(もちろんこれだけではありません。)
③viewの中でcontrollerで定義したインスタンス変数を使って、サイトの動的な部分を埋め込んでいく。
というのが大体の流れになっています。
この流れで、webサイトでしたい基本的なことはほぼほぼできてしまうので、あたかもこの流れ自体を完璧に理解したかのような気分になってしまいがちですが、おそらく、『なんでcontroller内のインスタンス変数がviewで使えるの』という質問に対して、きちんと答えられる人は少ないのではないでしょうか。
今回はなぜcontroller内のインスタンス変数が対応するviewの中で使えるのかについて探って行きたいと思います。
作戦
インスタンス変数がどのようにcontrollerからviewに渡されているかについて知るためには、controllerとviewの関係を知る必要があります。よって、viewとcontrollerのクラスの継承関係から、怪しそうな物を見つけて探っていくのが良さそうです。
①rails newで新しいrailsアプリを作る。
②viewの中でbinding.pryをする。
③viewでselfをしてancestorsを見ていく
④super classを見て行きながらも、viewに渡していたインスタンス変数もきちんと確認する
⑤インスタンス変数に動きがある部分や、ちょっと怪しげなクラス名を見つけたら、そこを探る。
⑥view側から上に上がっていったので、逆に同じようにcontroller側から下がっていく。
##view側から探る
###①rails newで新しいrailsアプリを作る
①は自分の作っているアプリだったり、なんでもいいです。
###②viewの中でbinding.pryをする
インスタンス変数をviewの中で表示して、その直後でbinding.pryをすることで、インスタンス変数の出どころを探っていく。
def index
@user = "aaa" #ここはなんでもいい。
end
<%= @user %>
<% bindning.pry %>
これで、このindex.html.erbを表示しようとすると、pryが起動する。
###③pryでviewの継承しているクラスやmoduleを見てみる。
まずはbinding.pryでself.classを実行することで、view自体がどのクラスのオブジェクトなのかを探る。
self.class
これをすると、どうやら特異クラスであるとわかる。
self.class.superclass
をしてみると、ActionView::Baseとでる。さらにsuperclassを重ねてもobjectに行ってしまうだけなので、superclassからは探ることができない。
Action Viewとselfの間にある、特異クラスはおそらくhelperのメソッドを追加しているのではないだろうか。
これは、railsはclassというよりも多数のmoduleをincludeすることで出来上がっているからである。
superclassではclassの継承関係しか見れないので、当然、super classでは探っていくことができない。
ここで、使うのが、moduleの継承関係もみることができる。ancestorsというメソッド。
self.class.ancestors
を実行してみると、いっぱい出てくる。
最初に出てくるのが、hrlperたち、こいつらはメソッドをとってくるためなので、確かに真上にあるべき。
せっかく、ancestorsが出てくれたのに、中身の情報がないと、せっかくの一覧も意味がないです。ここで使うのがinstance_variablesとinstance_mathods(false)というメソッド。(falseをつけるのは継承してきたメソッドを表示しないため。)
試しに、application_helperに適当なメソッドを作って、
ApplicationHelper.instance_methods(false)
をしてみると、配列になったメソッド名がか行ってくるはずです。
module一覧を見ていると怪しそうなのがActionView::Helpers::ControllerHelperとかって
###ここで重大なミスに気づく
継承関係わかって、メソッドの中身とか見たいなら、railsのgit行けばええやん。
railsをgitでとってきて改めて、ソースコードを側から探ってみることに。
@_assignにインスタンス変数が保持されているので、とりあえず、@_assignの出どころを探ることに
git grep @_assignをすると、
actionview/lib/action_view/base.rb: @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
これはなんか怪しい。
この部分のソースコードを見にいくと、
def assign(new_assigns)
@_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } |
end
こんな感じで、すぐに下にあるinitializeメソッドでassignが呼ばれているので、ActionView.newをした時にassignが呼び出されていることがわかる。
ここから先はブログの丸パクリ
actionview/lib/action_view/rendering.rb の _render_templateで
actionpack/lib/abstract_controller/rendering.rb
Controllerのancestorを探ってみると、AbstractController::Renderingが呼ばれているので、結果として値が渡されていることがわかる。
view_assignに@kkkを追加して見たらちゃんとinstance_cvariablesで見に行った時に追加されていた
##参考にしたurl
実行はほぼほぼこれと同じ