LoginSignup
12
17

More than 5 years have passed since last update.

Ruby on Rails チュートリアル 機能拡張2(マイクロポスト検索)

Last updated at Posted at 2018-01-10

Ruby on Rails チュートリアルについて

Ruby on Railsを勉強したいというと、まず紹介される有名なRailsのチュートリアル。
内容はハードですが、無料でRailsによるWebアプリケーション開発を楽しく学べます。

Ruby on Rails チュートリアル
https://railstutorial.jp/

Sample Appの拡張

チュートリアルの最後には、作成したSampleAppの拡張機能についていくつかのヒントが記載されています。
その中の以下の機能を順に実装していきます(途中で挫折するかも。。。)。
1. ユーザー検索
2. マイクロポスト検索
3. フォロワーの通知
4. 返信機能
5. メッセージ機能

マイクロポスト検索

今回は、2つ目のマイクロポスト検索の実装を行います。
マイクロポストは、ログイン時のホーム画面およびプロフィール画面の二つの画面で表示されるので、検索フォームもホーム画面とプロフィール画面の両方に実装します。

前回(ユーザー検索)はこちら

環境と準備

マイクロポスト検索(および前回のユーザー検索)では、Rails用の検索機能を簡単に実装できるRansack Gemを利用することにします。
Ransackは他のGemと同様に、Gemfileに追加し、bundle installします。

モジュール バージョン
Rails 5.1.2
Ruby 2.3.1
Ransack 1.8.4

実装

まず、マイクロポスト検索用のフォームを作成します。

app/views/shared/_microposts_search_form.html.erb
<%= 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が見つからない!とエラーになります。
routeエラー.png

理由は、search_form_forのデフォルトの送信先が検索モデルのindexアクションだからです。(当たり前ですね。。。)
そのため、今回は明示的にフォームの送信先を指定してやる必要があります。
インスタンス変数で渡しているのは、このフォームをホーム画面/プロフィール画面の両方で使うためです。

検索フォームができたので、次にcontrollerの実装を行います。
まずは、ホーム画面のフィードの検索のため、StaticPagesContorollerのhomeアクションに処理を加えます。

app/controllers/static_pages_controller
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と書かないといけないんですね。
検索フォーム作成の部分でも記載しましたが、ここでは@urlroot_pathを指定しています。

続けて、UsersControllerのshowアクションに処理を追加します。

app/controllers/users_controller
~
省略
~
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に設置しました。

app/controllers/application_controller
private
    def microposts_search_params
      params.require(:q).permit(:content_cont)
    end

忘れかけていましたが、MicropostControllerのcreateアクションに@qransackの処理を追加しないといけません。

ruby
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に挿入します。

app/views/static_pages/_logged_in_home.html.erb
<% 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>
app/views/users/show.html.erb
<% 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ですが、前回のユーザー検索時に設定した値で十分のため、今回は何も加えません。

見た目はこんな感じです。
ホーム画面
ホーム画面.png

プロフィール画面
プロフィール画面.png

テスト

最後にテストについて記載します。
今回は簡単に、検索後に期待したマイクロポストが画面上に表示されているかを、ホーム画面とプロフィール画面の両方に対してテストします。
テストはチュートリアルで作成していた、users_profile_test.rbに追記しました。

test/integration/user_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

12
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
17