#Ruby on Rails チュートリアルについて
Ruby on Railsを勉強したいというと、まず紹介される有名なRailsのチュートリアル。
内容はハードですが、無料でRailsによるWebアプリケーション開発を楽しく学べます。
Ruby on Rails チュートリアル
https://railstutorial.jp/
#Sample Appの拡張
チュートリアルの最後には、作成したSampleAppの拡張機能についていくつかのヒントが記載されています。
その中の以下の機能を順に実装していきます(途中で挫折するかも。。。)。
- ユーザー検索
- マイクロポスト検索
- フォロワーの通知
- 返信機能
- メッセージ機能
#マイクロポスト検索
今回は、2つ目のマイクロポスト検索の実装を行います。
マイクロポストは、ログイン時のホーム画面およびプロフィール画面の二つの画面で表示されるので、検索フォームもホーム画面とプロフィール画面の両方に実装します。
前回(ユーザー検索)はこちら
##環境と準備
マイクロポスト検索(および前回のユーザー検索)では、Rails用の検索機能を簡単に実装できるRansack Gemを利用することにします。
Ransackは他のGemと同様に、Gemfileに追加し、bundle installします。
モジュール | バージョン |
---|---|
Rails | 5.1.2 |
Ruby | 2.3.1 |
Ransack | 1.8.4 |
##実装
まず、マイクロポスト検索用のフォームを作成します。
<%= search_form_for @q, url: @url do |f| %>
<%= f.label :content_cont, 'Micoropost Search' %>
<div class="input-group">
<%= f.text_field :content_cont, placeholder: "Enter keyword...",
class: 'form-control' %>
<span class="input-group-btn">
<%= f.submit 'Go', class: "btn btn-primary" %>
</span>
</div>
<% end %>
今回の実装ではここがいきなり肝になりました。というか、ハマりました。。。
検索条件のcontent_cont
はMicropostのcontentからの検索ということで、難しくはないと思います。
前回のユーザー検索フォームとの大きな違いは、url: @url
の部分になります。
ユーザー検索時は、urlをわざわざ指定せずとも上手く動きましたが、
マイクロポスト検索ではここでurlを以下のように指定(上の実装では各controllerから与えています)しなければ、検索実行時に「get /microposts(= index)」のrouteが見つからない!とエラーになります。
理由は、search_form_for
のデフォルトの送信先が検索モデルのindexアクションだからです。(当たり前ですね。。。)
そのため、今回は明示的にフォームの送信先を指定してやる必要があります。
インスタンス変数で渡しているのは、このフォームをホーム画面/プロフィール画面の両方で使うためです。
検索フォームができたので、次にcontrollerの実装を行います。
まずは、ホーム画面のフィードの検索のため、StaticPagesContorollerのhomeアクションに処理を加えます。
def home
if logged_in?
@micropost = current_user.microposts.build
if params[:q] && params[:q].reject { |key, value| value.blank? }.present?
@q = current_user.feed.ransack(microposts_search_params)
@feed_items = @q.result.paginate(page: params[:page])
else
@q = Micropost.none.ransack
@feed_items = current_user.feed.paginate(page: params[:page])
end
@url = root_path
end
end
~
省略
~
ここの処理も前回のユーザー検索とほぼ同じですね。
個人的に驚いたのは、current_user.feed.ransack
と書けるところです。
Micropost.ransack(〜)とfeedメソッドでやっていることを、ransackの条件としてだらだら実装しないといけないと思っていましたが、そんな必要ありませんでした。(でもDBアクセスとかぜんぜん考えてない。。。)
あとは、@q
が必要なのは当然として、検索しない場合はnilとかじゃなくてModel名.none.ransack
と書かないといけないんですね。
検索フォーム作成の部分でも記載しましたが、ここでは@url
にroot_path
を指定しています。
続けて、UsersControllerのshowアクションに処理を追加します。
~
省略
~
def show
redirect_to root_url and return unless @user.activated?
if params[:q] && params[:q].reject { |key, value| value.blank? }.present?
@q = @user.microposts.ransack(microposts_search_params)
@microposts = @q.result.paginate(page: params[:page])
else
@q = Micropost.none.ransack
@microposts = @user.microposts.paginate(page: params[:page])
end
@url = user_path(@user)
end
~
省略
~
特に気になるところはないと思います。
@url
にはuser_path(@user)
を代入しています。
そういえば、microposts_search_params
は、UsersControllerとStaticPagesControllerの両方から利用されるため、ApplicationControllerに設置しました。
private
def microposts_search_params
params.require(:q).permit(:content_cont)
end
忘れかけていましたが、MicropostControllerのcreateアクションに@q
とransack
の処理を追加しないといけません。
def create
@micropost = current_user.microposts.build(micropost_params)
if @micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
@q = Micropost.none.ransack
@feed_items = current_user.feed.paginate(page: params[:page])
render 'static_pages/home'
end
end
追加するのは、マイクロポストの投稿が失敗した際の処理のみです。
失敗した場合のみ、MicropostContorollerからホームのviewをrender
(redirect_to
ではなく)しているため、@q
が必要になります。
renderとredirectの違いについては、こちらにとてもわかりやすくまとめられています。
最後にviewに戻って、最初に作った_microposts_search_form.html.erbをそれぞれのviewに挿入します。
<% provide(:title, @user.name) %>
~
省略
~
<div class="col-md-8">
<div class="row">
<div class="col-md-4">
<h3>Micropost Feed</h3>
</div>
<div class="search_form">
<%= render 'shared/microposts_search_form' %>
</div>
</div>
<%= render 'shared/feed' %>
</div>
</div>
<% provide(:title, @user.name) %>
~
省略
~
<div class="col-md-8">
<%= render 'follow_form' if logged_in? %>
<% if @user.microposts.any? %>
<div class="row">
<div class="col-md-4">
<h3>Microposts (<%= @user.microposts.count %>)</h3>
</div>
<div class="search_form">
<%= render 'shared/microposts_search_form' %>
</div>
</div>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
scssですが、前回のユーザー検索時に設定した値で十分のため、今回は何も加えません。
##テスト
最後にテストについて記載します。
今回は簡単に、検索後に期待したマイクロポストが画面上に表示されているかを、ホーム画面とプロフィール画面の両方に対してテストします。
テストはチュートリアルで作成していた、users_profile_test.rbに追記しました。
~
省略
~
test "profile display" do
~
省略
~
# Micropost Search
get user_path(@user), params: {q: {content_cont: "a"}}
q = @user.microposts.ransack(content_cont: "a")
q.result.paginate(page:1).each do |micropost|
assert_match micropost.content, response.body
end
end
test "home profile display" do
~
省略
~
# Micropost Search
get root_path, params: {q: {content_cont: "a"}}
q = @user.feed.ransack(content_cont: "a")
q.result.paginate(page:1).each do |micropost|
assert_match micropost.content, response.body
end
end
テストが通ったことを確認して終了です。
#最後に
Rails歴1週間もなく、以上の実装/テストも私の環境で動いたということに過ぎません。
修正点や指摘等ございましたら、ぜひコメントお願いいたします。
#参考記事
マイクロポスト検索では、下記のページを参考にさせていただきました。
GitHub
https://github.com/activerecord-hackery/ransack/issues/21
https://github.com/activerecord-hackery/ransack/issues/170