Railsで呼び出すjsをページごとに分けたい

Railsにかぎらず,ある程度のフレームワークに乗っかって開発していると「できることはできるんだけど,ちょっと好みに合わせたい」みたいな要望が発生してくる.

今回のも,そんな「気を使えばできるけど頑張りたくない」ときに便利なことです.


Railsjavascriptをassetsというフォルダの配下においている.
この辺の活用方法はSprocketsやAssetPipelineを理解してもらうとして,とにかくそういう構成になっている.

Railsは,何もせずそのまま"application"のレイアウトを使用していると,このassetsフォルダ配下にあるjsをすべて読み込んでいる.
なんでこうなっているかというと,デフォルトで用意されているapplication.jsが原因だ.

application.js内には

//= require .

という記述がある.

そしてこのapplication.jsはapp/views/layouts/application.html.erb内で呼び出されている.

ということは,レイアウトとして"application"を使うページでは,app/assets/javascripts内のすべてのファイルが読み込まれていることになる.



jsが複数読み込まれると困る問題


時と場合によるのだが,それなりに大きなアプリケーションになってくると,jsをすべて読み込むというのが非常に困ることがよくある.

例えば,

app/assets/javascripts/statics.js.coffee

$ ->
  $(".sample").click ()->
    $(@).hide()

と,

app/assets/javascripts/homes.js.coffee

$ ->
  $(".sample").click ()->
    alert("test")

こんなファイルがあったとしよう.

実はこの2つ,まったく別のリソースで,StaticsControllerHomesControllerでそれぞれ使う記述だとする.
しかし,同じクラスにバインドしているのだから,当然どちらのページでもsampleクラスが付いたhtml要素に対するクリックイベントで発火してしまう.

これだけの規模であれば全く問題ないが,そもそも「どこでどのクラスをつけていたか」なんてことをすべて頭に入れながらなんて,常人のやるべき手法ではない.

というか,そもそも関係ないページのjsが実行されている事自体,まったくもってスマートとは言い難い.


というわけで分けよう

そんなわけで,呼び出すjsをコントローラ単位で分けていこう,というのが一番まっとうなやり方だと思います.
あくまで読み込みは全ファイル対象に行います.
呼び出すものをわけます.


とりあえず,js側からcontroller名,action名が取得できるようにしましょう.

app/views/layouts/application.html.erb

  <body data-controller="<%= params[:controller] %>" data-action="<%= params[:action] %>">
    <%= yield %>
  </body>

としておきます.

で,
app/assets/javascripts/application.js

//= require .

ここはそのまま.

新たに,_initial.js.coffeeというファイルを作ります.

app/assets/javascripts/_initial.js.coffee

#= require underscore.string

$ ->
  $body = $("body")
  controller = _.str.classify($body.data("controller") + "-controller")
  action = $body.data("action")
  if window[controller]
    activeController = new window[controller]

    if activeController && $.isFunction(activeController[action])
      activeController[action]()

underscore.stringを使って名前を加工します.
導入は,以下を参照.

epeli/underscore.string · GitHub


rails-assetsを使うのもアリです.


_initial.js.coffeeは実行されるたびに,controller名,action名が見合った形のクラスを呼び出します.

対象となるクラスは以下のような形で作っておきます.

app/assets/javascripts/statics.js.coffee

class @StaticsController
  index: ->
    $(".sample").click ()->
      $(@).hide()


見事ここだけ呼び出されます.

turbolinksどうする問題

ここまででうまく呼び出すクラスを分けられた気がしますが,Rails4.0以降にはturbolinksというものが標準搭載されています.


Rails - Turbolinksをオフしないためにやった事 - Qiita



これが有効になっているため,_initial.js.coffeeの$ ->が,初回のページロード時しか発動しません.
つまり,ページ遷移した場合には,$ ->が発火しないので,新たに関数呼び出しも行ってくれません.

jquery-turbolinksを使おう

素直にjquery-turbolinksを使いましょう.

kossnocorp/jquery.turbolinks · GitHub


これでturbolinksを使った状態で,該当するコントローラの該当するアクションに相当するjsだけ呼び出すことができます.




何が嬉しいって,application.jsではすべてのjsファイルを読み込んでいる上で,該当するクラスを実行しているだけなので,turbolinksと合わせると本当にページ遷移時のロードがなくなります.
それでいて,これだけきっぱりjsを分離できるので,jsが多めになってしまっても非常にすっきりしていて開発しやすい.