読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

RailsでAngularJSを使ってTodoアプリを作成 - 6. AngularJS + Ransackで検索機能を実装

Rails中級 AngularJS 連載

f:id:nipe880324:20150112194806p:plain

「RailsでAngularJSを使ってTodoアプリを作成」の連載6回目です。
前回は、ng-routeをいれることで、複数のビューファイルをシングルページとして遷移できるようにします。
前回までで、Todoアプリの基本的な最低限の機能は作成できたので、今回からは使いやすさを改善するための機能を追加していきます。

今回は、検索機能を追加するRansackというgemを使って、AngularJSを使ったTodoの検索機能を追加します。

動作確認

  • Rails 4.2.0
  • AngularJS 1.3.8
  • Bootstrap 3.3.1
  • UI Bootstrap 0.12.0
  • ransack 1.6.2

目次

  1. ransackのインストール
  2. 検索フォームとイベントの追加
  3. Rails側で検索APIを実装


1. ransackのインストール

まず、ransackをインストールします。

Gemfileransackを追加します。

# Gemfile
gem 'ransack'


bundle installを実行します。

bundle install


2. 検索フォームとイベントの追加

次に、検索フォームを作成します。TodoのDescriptionと完了/未完了で検索できる入力フィールドを追加します。
AnguarJSのControllerから各入力フィールドの値を取得するため、ng-modelを追加しておきます。

<!-- app/views/templates/todo_list.html.erb -->

<div class="container" ng-init="init()" ng-cloak>
  <h1>Todo リスト</h1>

  <!-- 追加箇所 開始 -->
  <div class="panel panel-default">
    <div class="panel-body">
      <form role="form" class="form-horizontal" ng-submit="search()">

        <div class="form-group">
          <label for="description" class="col-sm-2 control-label">Description:</label>
          <div class="col-sm-8">
            <input type="text" ng-model="descriptionCont" id="description" class="form-control">
          </div>
        </div>

        <div class="form-group">
          <label for="completed" class="col-sm-2 control-label">Completed:</label>
          <div class="col-sm-8">
            <input type="checkbox" ng-model="completedTrue" id="completed">
          </div>
        </div>

        <div class="form-group">
          <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-default">Search</button>
          </div>
        </div>

      </form>
    </div>
  </div>
  <!-- 追加箇所 終了 -->


  <div class="panel panel-success">

    <div class="panel-heading">
      <span>{{list.name}}</span>
    </div>
    ...

検索フォームの検索ボタンを押したときに呼ばれる、search()メソッドをAngularJSのControllerに定義します。

# app/assets/javascripts/controllers/TodoListCtrl.coffee

$scope.search = ->
  # Ransackに対応したparamsを作成する
  # description_cont => descriptionカラムが特定の値を含む(like句に変換される)
  # completed_true   => completedカラムがtrueか
  params = {
    'q[description_cont]' : $scope.descriptionCont,
    'q[completed_true]'   : $scope.completedTrue
  }

  # 検索結果を $scope.list.todos にセットする
  $scope.list.todos = @todoService.all(params)


次に、TodoServiceクラスにallメソッドを追加します。

# app/assets/javascripts/services/TodoService.coffee

all: (params)->
  @service.query(params, (-> null), @errorHandler)



3. Rails側で検索APIを実装

AngularJSのTodoServiceのallメソッドに対応したRailsの検索APIを追加します。

まずは、ルートを追加します。

# config/routes.rb
...
namespace :api, defaults: { format: :json } do
  resources :todo_lists, only: [:index, :show, :create, :destroy] do
    # except: [:index] を削除する
    resources :todos, except: [:new, :edit, :show]
  end
end


ransackのgemを入れると、各モデルにransackメソッド(別名でsearchメソッドもある)が追加されます。
そのため、TodosControllerにindexメソッドを追加し、その中でransackメソッドを使うようにします。

# app/controllers/api/todos_controller.rb

def index
  render json: @todo_list.todos.ransack(params[:q]).result
end

ransackメソッドにハッシュでカラム名と値を渡すことでransackの検索モデルが生成されます。
そして、resultメソッドで検索が実行され、その結果がActiveRecord::Relationで返されます。


挙動がわかりづらい場合は次のようにいろいろとコンソールから実行してみることをお勧めします。

# ransack - ransackの検索モデルを作成する
> Todo.ransack(description_cont: "todo 1")
# => Ransack::Search<class: Todo, base: Grouping <conditions: [Condition <attributes: ["description"], predicate: cont, values: ["todo 1"]>], combinator: and>>


# result - ransackで検索を実施する
Todo.ransack(description_cont: "todo 1").result
#  Todo Load (0.5ms)  SELECT "todos".* FROM "todos" WHERE ("todos"."description" LIKE '%todo 1%')
# => #<ActiveRecord::Relation [#<Todo id: 9, todo_list_id: 10, description: "todo 1", completed: true, created_at: "2015-01-14 10:10:01", updated_at: "2015-01-14 10:10:09">, #<Todo id: 18, todo_list_id: 10, description: "todo 10",  ...]>


# to_sql - 検索で発行されるSQLを確認することができます。
Todo.ransack(description_cont: "todo 1").result.to_sql
# => "SELECT \"todos\".* FROM \"todos\" WHERE (\"todos\".\"description\" LIKE '%todo 1%')"


より詳しくransackについて知りたい場合は、「ransackを使って検索機能を作成」を参考にしてみてください。


では、動作を確認します。
サーバーを起動し、TodoList画面を開きます。検索フォームが追加されています。
f:id:nipe880324:20150120235606j:plain:w480


そして、検索フォームに適当に値を入力し、「検索」ボタンを押すと、検索結果が表示されます。
f:id:nipe880324:20150120235607j:plain:w480


以上です。