Route 36

ギジュツ的な事を書くブログ

RailsでController毎に違うサイドメニューをレンダリングしたい場合

tl; dr

僕はこんな感じにした。
後述するがnamespace等でviewのディレクトリを分けた場合に対応するためにcontroller.controller_nameではなくcontroller_pathを使っている。

# app/views/layouts/application.html.slim

- if lookup_context.exists?('sidemenu', controller_path, true)
  = render "#{controller_path}/sidemenu"
$ find ./app/views -name '_sidemenu.html.slim' -print
./app/views/posts/_sidemenu.html.slim
./app/views/posts/status/_sidemenu.html.slim
./app/views/items/_sidemenu.html.slim

動機

例えば、全ページに共通して用意されているサイドメニューの内容を、Controllerごとに変えたいとしよう。

PostsControllerの時はapp/views/posts/_sidemenu.html.slimを、ItemsControllerの時は、app/views/items/_sidemenu.html.slimを呼び出したいというような感じ。

application.html.slimにif文の羅列を書いてもいいけど、それだと微妙なので、さてどうしようか。

解説

lookup_context.exits?

ActionViewで提供されている lookup_context. exists? というものを使えば、テンプレートが存在するかどうかを調べることができる。

メソッドのシグネチャはこんな感じである。

exists?(name, prefixes = [], partial = false, keys = [], **options)

詳しくはこちらを参照。 http://api.rubyonrails.org/classes/ActionView/LookupContext/ViewPaths.html#method-i-exists-3F

この場合、nameには'sidemenu'、prefixesにはコントローラ名を、今回欲しいのはpartialだけなのでpartialにはtrueを渡してやればよさそうだ。

controller.controller_name

viewからcontroller名を取得するには、

controller.controller_name

とすれば良いらしい。

よって、application.html.slimに

- if lookup_context.exists?('sidemenu', controller.controller_name, true)
  = render "#{controller.controller_name}/sidemenu"

と書けば良さそうである。

ただこれだと、app/controllers/posts/status_controller.rbで定義されているPosts::StatusControllerが描画したapp/views/posts/status/index.html.slimにおいてcontroller.controller_namestatusとなってしまうため、app/views/posts/status/_sidemenu.html.slimは呼び出されない。

controller_path

そこで、controller_pathを使えば良いという事になった。
参照: Get full rails controller name, including the namespace - Stack Overflow

controller_pathを使えば、Posts::StatusControllerのようなコントローラでもposts/statusというような値を返してくれるため、lookup_contextsはapp/views/posts/status/_sidebar.html.slimというpartialを発見してくれるというわけだ。

以上を踏まえると、application.html.slimはこうなる。

- if lookup_context.exists?('sidemenu', controller_path, true)
  = render "#{controller_path}/sidemenu"