INODEVLOG

WEBアプリ開発・事業開発・ビジネスモデル・読書の情報などをお届けしています。

Rails学習者にrails-ujsの動作説明したら感動された話

TechCommit Advent Calendar 2019 - Qiita 3日目担当の井上です。

TechCommitのアドベントカレンダー 12/3が空いてたので軽くネタ投稿をしておきます!

前回はrattcvさんが「ターミナルで使えるファイラrangerを使ってみる」という記事を投稿されています。

ranger使ったことがないので入れてみようかと思いますw

さて、今回自分はTechCommitメンバーのRails初学者さんに質問されて回答したrails-ujsのウケが良かったのでそれの話でもします。

rails-ujsイメージ

rails-ujsとはなんぞや

Railsの一部のRESTfulな動作や非同期な処理などを実現するために、JavaScriptの送信に関する処理などが書かれたライブラリです。

github.com

例えば、Viewヘルパーの form_withform_for など)の remote: true オプションだったり、 link_tomethod: :delete などのオプションを「それっぽく動くように」動作させられるようになります。

rails-ujsの使い方はどんなんや

Rails 5.1.0 からはこれらの処理はRails本体のコードに取り込まれている為、Railsで内部的に使われる分には気にしなくても使えます。

昔は rails-ujs もjQueryで書かれていましたが、Rails自体も脱jQueryが出来るように、CoffeeScriptで書き直されたのでjQueryを入れる必要はなくなりました。

なお、Webpacker(Webpack)を使ってフロントを切り離す場合や、半SPAっぽい動作でRailsのRESTfulなルーティングが動作するようにしたりさせる場合には、npm(yarn)で入れてあげないと使えない場合があります。

www.npmjs.com

具体的にrails-ujsはの動作はどういう感じなんや

例えばViewに、 <%= link_to 'logout', logout_path, method: :delete, data: { confirm: 'マジでログアウトすんのか?' } %> という処理があった場合、

  • JSの処理で確認ダイアログとして「マジでログアウトすんのか?」という表示をする
  • JSの処理でログアウトのために「deleteメソッドとして」HTTPのPOSTリクエストをする

などの動作をします。

どのように実現してるんや

そんなに難しいことをしていないので、コードを見ればそれぞれ何をしているのか大体分かると思われますが、

ここでは link_tomethod 指定が特に面白い動作をしているので見てみましょう。

まず、Railsでは link_to ヘルパーに method のオプションを付けると data-method="" という属性を持ったHTMLのaタグが出力されます。

さきほど例にあげたerbの処理、 <%= link_to 'logout', logout_path, method: :delete, data: { confirm: 'マジでログアウトすんのか?' } %> は、サーバーサイドレンダリングで作られるHTMLでは、 <a href="(ログアウトのパス)" data-method="delete" data-confirm="マジでログアウトすんのか?"> のようになります。

rails-ujs ではこの link_to ヘルパーによって付与された data-methoddata-confirm という属性に対して、JavaScriptで処理をするように書かれています。

rails-ujsでdata-method等に対する処理をしている部分

下記が rails-ujsdata-method 属性に対する処理です。

何をしているのかコメントをいくつか追加して見たので見てみましょう。

https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts/rails-ujs/features/method.coffee#L7-L34

Rails.handleMethod = (e) -> # aタグがクリックされた時に走る
  link = this
  method = link.getAttribute('data-method') # クリックしたaタグに `data-method` 属性がついていたら取得
  return unless method # ついていなければこの処理は不要

  href = Rails.href(link)
  csrfToken = Rails.csrfToken()
  csrfParam = Rails.csrfParam()
  form = document.createElement('form') # formを生成
  formContent = "<input name='_method' value='#{method}' type='hidden' />" # formタグ内に子要素として `_method` という名前で `data-method` の値のhiddenフィールドを準備

  if csrfParam? and csrfToken? and not Rails.isCrossDomain(href)
    formContent += "<input name='#{csrfParam}' value='#{csrfToken}' type='hidden' />" # CSRF Tokenもform内容として準備

  # Must trigger submit by click on a button, else "submit" event handler won't work!
  # https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit
  formContent += '<input type="submit" />' # 送信ボタンもform内容として準備

  form.method = 'post' # formを送信するHTTPリクエスト自体はPOSTのmethod
  form.action = href # formの送信先(action)はaタグで設定していたhref
  form.target = link.target
  form.innerHTML = formContent # form内容として準備したものをformの子要素として追加
  form.style.display = 'none'

  document.body.appendChild(form) # bodyタグに作ったformを動的にこっそり挿入
  form.querySelector('[type="submit"]').click() # 自分で発火

  stopEverything(e)

つまり、 link_to によって delete メソッドなどをaタグで実装できているように感じますが、実際にはrails-ujsが動的にformを組み立てて送信しているという動作になります。

一見とてもキモいですが、実装上はやっぱり便利ですね。

rails-ujsは何でこんな事をしているのか

HTTPの仕様としてはもちろんmethodとしてPUTだのDELETEだののmethodは設定できます。

developer.mozilla.org

しかし、(ちょっと理由は探せませんでしたが…)HTMLとしての送信はGETとPOSTしか対応していないからのようです。

よって、送信パラメーターに _method という名前で "delete" などの文字列を含めることで、

RailsらしいRESTfulなルーティングでの動作ができるようになっているということですね。

rails-ujswebpacker を使っていてimportし忘れたりすると、Railsでサーバーサイドレンダリングした link_tomethod: :delete などが動作しなくなります

まとめ

  • rails-ujs は今ではRails本体に含められているが、RailsでSSRしてJSを別管理してるならnpm等で読む必要がある
  • rails-ujs はViewヘルパー等で作られる属性と組み合わせて、RailsのRESTfulな処理を支える役目をしている
  • rails-ujs の動作で、実は link_tomethod: :delete オプションなどがついたaタグは普通のリンクではなくformの送信として動作するように魔改造されている

前回これらの処理を一緒に見ていた方も物凄くテンションが上っていましたw

Railsは特におまじないが多いですが、このような動作を1つ1つ見ていくのは物凄く勉強になるので良いですね。

何か仕様についてゆるく聞きたい方はTechCommitにもぜひ遊びに来てくださいね!

次回TechCommit Advent Calendar 2019 - Qiita 4日目はikemoさんですね!よろしくお願い致します!