####はじめに
先日Rails インクリメンタルサーチという記事を投稿したのですが、作業中ターミナルを見ている中で、一つの勘違いに気づきましたので、記事にまとめてみようと思います。
これまで勘違いしていたのは、Railsには同期的なリクエスト(local:true, HTML)と非同期的なリクエスト(remote:true, JS)の2つのみ存在し、ターミナルのコントローラ#アクション名 as 〇〇 にて判断できるというものでした。
先日の記事にて、dataType:jsonでajax通信を行なった際に、コントローラには下記の出力がありました。
Started GET "/books?key_word=1" for ::1 at 2021-09-05 15:34:00 +0900
Processing by BooksController#index as JSON
Parameters: {"key_word"=>"1"}
Rendering books/index.json.jbuilder
Book Load (0.2ms) SELECT "books".* FROM "books" WHERE (title LIKE('%1%') OR body LIKE('%1%'))
↳ app/views/books/index.json.jbuilder:1
Rendered books/index.json.jbuilder (1.4ms)
Completed 200 OK in 13ms (Views: 6.3ms | ActiveRecord: 0.2ms)
as JSON とは? となり調べてみようと思いました。
(今までは〇〇.jsファイルからajax通信を行なったときにはremote:trueと同じリクエストになる、つまり〇〇.js.erbと〇〇.json.jbuilderのどちらかから、何らかの規則でデータを渡すファイルが選ばれると思っていました...)
####リクエストフォーマットという概念
これまでリクエストフォーマットについてあまり意識せずに実装を行なってきましたが、あまり理解していない初心者でもなんとなく出来てしまうところがRailsの凄いところなのかなと改めて思いました。
例えばlink_toヘルパーを利用すると、デフォルトでHTML形式のリクエストが送られます。
また、form_withヘルパーを利用するとデフォルトでJS形式のリクエストが送られます。
これは「local:true」や「remote:true」のオプションを付加してあげることで制御できます。
Railsには「設定より規約」の原則がありますので、明示的に記述しなくても、
- HTML形式のリクエストの場合、〇〇.html.erb
- JS形式のリクエストの場合、〇〇.js.erb
のファイルを探して処理をしてくれます。
また、これまで2つしかないと思っていたリクエストフォーマットは他にも存在し、先日の記事にて利用したJSON形式やXML形式のリクエストが存在するということを知りました。
ここまできて追加の疑問が生まれました。
- Railsはどのようにしてerbファイルを選択しているのか...
- JSON形式でリクエストした後に呼び出されるファイルはなぜ〇〇.json.erbではないのか...
次の章にまとめていきたいと思います。
####レンダリング
Railsのレンダリングは、ActionView::TemplateHandlersのサブクラスで行われているそうです。
せっかくなのでソースコードを読んで理解しようとしてみましたが、今の私の知識ではほとんど理解することはできませんでした。リンク先には4つのhandlerがありましたので、おそらくこの4つが同次元で扱われているものなのかなというレベルの理解です。
#####handlerの選択
Railsガイドによると、handlerの選択はテンプレートファイルの拡張子によって制御されており、.erbレイアウトが見つからない場合、.builderレイアウトを探索するようです。
.erbはERB(Embedded Ruby)テンプレートシステムを利用するファイルの拡張子で、Rubyスクリプトを埋め込むことができます。
.builderはBuilderテンプレートシステム(ERBの代わりに使用でき、よりプログラミング向きな記法)を利用するファイルです。XMLを生成する際に使われるようです。
#####jbuilderについて
JSONを生成する際に使われます。JbuilderというRailsのGemfileにデフォルトで含まれているgemを導入することで、利用することができるようになります。
.jbuilderをどのように選択しているのかについて、記述を見つけることはできませんでしたが、おそらくgemを導入すると、.erbや.builderと同じように.jbuilderレイアウトを探索するようになるのだと思います。
#####小まとめ
- Railsでは「アクション名.リクエストフォーマット名.テンプレートシステム名」という命名をすれば自動的にテンプレートファイルを探してくれる
- リクエストフォーマット.erbファイルを探し、見つからない場合、リクエストフォーマット.builderファイルを探す
####実験
前述した内容を少し試してみたいと思います。
- show.html.erbを作成し、正しく表示されることを確認した後、show.html.builderに変更
<p>app/views/books/show.html.erb</p>
<p>HTML形式でリクエストを送っています。</p>
show.html.erbを削除し、show.html.builderを作成します。
xml.p('app/views/books/show.html.builder')
xml.p('HTML形式でリクエストを送っています。')
ターミナルでも意図した通りに動いていることを確認できます。
Started GET "/books/1" for ::1 at 2021-09-06 01:28:33 +0900
Processing by BooksController#show as HTML
Parameters: {"id"=>"1"}
Rendering books/show.html.erb within layouts/application
Rendered books/show.html.erb within layouts/application (0.3ms)
Completed 200 OK in 17ms (Views: 16.2ms | ActiveRecord: 0.0ms)
Started GET "/books/1" for ::1 at 2021-09-06 01:33:23 +0900
Processing by BooksController#show as HTML
Parameters: {"id"=>"1"}
Rendering books/show.html.builder within layouts/application
Rendered books/show.html.builder within layouts/application (408.8ms)
Completed 200 OK in 430ms (Views: 429.1ms | ActiveRecord: 0.0ms)
####respond_toメソッドについて
前述のようにRailsでは命名規則が正しければ、リクエストフォーマットに応じて自動的にテンプレートファイルを探してくれます。
さらに、respond_toというメソッドを使うことで、リクエストフォーマットに応じた制御が可能になるようです。
現在作成しているPFのレベルではこの制御を必要とすることはありませんが、学習のために例を一つ挙げたいと思います。
class BooksController < ApplicationController
def show
respond_to do |format|
format.html
format.js { redirect_to books_path}
end
end
end
この例では下記のような動きになります。
- HTMLリクエストの場合、デフォルトの動きでshow.html.erbをレンダリング
- JSリクエストの場合、/booksにリダイレクト
# HTMLリクエストの場合
Started GET "/books/2" for ::1 at 2021-09-06 10:54:31 +0900
Processing by BooksController#show as HTML
Parameters: {"id"=>"2"}
Rendering books/show.html.erb within layouts/application
Rendered books/show.html.erb within layouts/application (0.3ms)
Completed 200 OK in 22ms (Views: 20.8ms | ActiveRecord: 0.0ms)
# JSリクエストの場合
Started GET "/books/2" for ::1 at 2021-09-06 10:55:21 +0900
Processing by BooksController#show as JS
Parameters: {"id"=>"2"}
Redirected to http://localhost:3000/books
Completed 302 Found in 1ms (ActiveRecord: 0.0ms)
Started GET "/books" for ::1 at 2021-09-06 10:55:21 +0900
Processing by BooksController#index as JS
Rendering books/index.js.erb
Book Load (0.4ms) SELECT "books".* FROM "books" WHERE (title LIKE('%%') OR body LIKE('%%'))
↳ app/views/books/_index.html.erb:1
Rendered books/_index.html.erb (4.9ms)
Rendered books/index.js.erb (12.7ms)
Completed 200 OK in 30ms (Views: 19.2ms | ActiveRecord: 0.4ms)
どちらも意図した通りに動いていました。
####もう一つの勘違い
ここまで学習したことで、もう一つ大きな勘違いをしていたことに気づきました。
それは、〇〇.jsファイルにて $.ajax という処理をするには、.doneとjbuilderを使わなければならないと思っていたことです。
例えば先日投稿したインクリメンタルサーチに関する記事では下記の記述をしております。
$.ajax({
type: 'GET',
url: '/books',
data: {key_word: key_word},
dataType: 'json'
})
.done(function(data){
data.forEach(function(data){
const html = `
<tr>
<td class='scroll__td'>${data.title}</td>
<td class='scroll__td'>${data.body}</td>
</tr>
`;
$('.scroll__tbody').prepend(html);
});
});
ajaxというメソッドを使うからremote:trueと同じようなリクエストを送っていると勘違いしていましたが、実際にはdataType:'json'にてJSON形式のリクエストを送っています。
この記事では、先にindex.js.erbを作成し、その後index.json.jbuilderを作成するという流れで記述しています。もちろんそのままでも動くのですが、index.js.erbを作成済みであれば、下記のようにJSリクエストを送ってあげるだけで、index.js.erbがテンプレートフォーマットとして呼び出され、実装が終了していました。
$.ajax({
type: 'GET',
url: '/books',
data: {key_word: key_word},
dataType: 'script' //'js'ではなく'script'とする必要がある
});
// この場合.done以降は不要
####おわりに
最近は新しい概念や機能の学習に目がいっていましたが、振り返ってみると基礎を全く理解できていなかったんだなと感じました。
また、これまでは〇〇.jsと〇〇.js.erbは全く別のものだと思っていましたが、2つを繋げて考えることができるようになった気がしています。