27
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ransackの導入時に躓いたり転んだりした。

Last updated at Posted at 2018-05-22

Ransackの導入って単純だと思っていましたが、躓きました。
タスク一覧画面で、タイトルのキーワードと進捗のステータスでタスクを絞り込み検索する機能を実装しようと考えています。

手順

公式ドキュメントの手順に沿って実装しました。
https://github.com/activerecord-hackery/ransack

gem 'ransack' のインストール


gem 'ransack', '~> 1.8', '>= 1.8.8'
$ bundle install

app/controllers/tasks_controller.rbの設定


def index
  @q = Task.ransack(params[:q])
  @tasks = @q.result(distinct: true)
end

ビューファイルの編集

app/views/tasks/index.slim

.search__body
  = search_form_for(@q, url:root_path) do |f|
    = f.label :title_cont
    = f.search_field :title_cont, placeholder: "Input Title"
    = f.label :status_id_eq
    = f.select :status_id_eq, Task.status_ids.to_a.map{|s|[t("activerecord.enum.status_id.#{s[0]}"), "#{s[0]}"]}
    = f.submit

その他

db/schema.rb はこんな感じです。

ActiveRecord::Schema.define(version: 20180516232642) do

  create_table "tasks", force: :cascade do |t|
    t.string "title", null: false
    t.text "detail", default: "detail", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deadline"
    t.integer "status_id"
  end

end

app/models/task.rbはこんな感じです。

class Task < ApplicationRecord
  validates :title, presence: true
  validates :detail, presence: true
  enum status_id: %i[undefined untouched in_progress done]
end

status_idの翻訳は以下の通りになります。
config/locale/ja.yml

  activerecord:
    enum:
      status_id:
        undefined: "未設定"
        untouched: "未着手"
        in_progress: "作業中"
        done: "完了"

参考程度にapp/helpers/tasks_helper.rb

module TasksHelper
  def deadline_format(datetime)
    datetime.present? ? datetime.strftime('%Y.%m.%d') : t(:undefined)
  end

  def status_format(status)
    t(status.presence) || t(:undefined)
  end
end

何に躓いているのか。

上記の状態で絞り込み検索を行なっても正しい結果を返してくれません。
検索結果はいつもゼロ。

試行錯誤

デバックで流れを確認してみます。

いつもお世話になっているbinding.pry

  def index
    @q = Task.ransack(params[:q])
    @tasks = @q.result(distinct: true)
    binding.pry
  end

最初のデバックはexitします。(意味ないんで。)
そして2回目。今回は明日というキーワードを含み、且つ進捗が未着手のタスクを探します。
注)未着手=>untouched=>1
未着手はi18nでは"untouched", そして "untouched"enum1と設定されています。

止まったところで色々と調べます。


    4: def index
    5:   @q = Task.ransack(params[:q])
    6:   @tasks = @q.result(distinct: true)
    7:   binding.pry
 => 8: end

[1] pry(#<TasksController>)> @tasks
  Task Load (0.7ms)  SELECT DISTINCT "tasks".* FROM "tasks" WHERE ("tasks"."title" ILIKE '%明日%' AND "tasks"."status_id" = 0)
=> []
[2] pry(#<TasksController>)> 

@tasksの値

@tasksは空の配列を返しています。何故?
検索が期待通りの結果を返してくれるならば該当するタスクを表示してくれるはずなのですが。


[2] pry(#<TasksController>)> @tasks.class
=> Task::ActiveRecord_Relation
[3] pry(#<TasksController>)> @q
=> Ransack::Search<class: Task, base: Grouping <conditions: [Condition <attributes: ["title"], predicate: cont, values: ["明日"]>, Condition <attributes: ["status_id"], predicate: eq, values: ["untouched"]>], combinator: and>>
[4] pry(#<TasksController>)> params[:q]
=> <ActionController::Parameters {"title_cont"=>"明日", "status_id_eq"=>"untouched"} permitted: false>

@tasksのクラスはTask::ActiveRecord_Relationというのは大丈夫です。
params[:q]で検索条件をパラメーターとして取得し、それをSQL文に変換してDBから該当するインスタンスを取得したいのですが、よく見るとAND "tasks"."status_id" = 0となっていますね。
未着手はenum1と設定しているのに@tasksにはAND "tasks"."status_id" = 0と書かれていま。
ここ、怪しいです。
案の定ほかの進捗ステータスで検索してもAND "tasks"."status_id" = 0でした。
そりゃ、検索結果は0件だ。

何故?

今回タスクの進捗を管理するためにstatus_idというカラムを用意しました。
status_idカラムの型はintegerにして、enumを使い数値に対応する文字列(半角の英語)を設定しています。そしてその文字列をi18nで日本語にしてビューで表示するようにしています。

先程の例だと、未着手=>untouched=>1という感じ
未着手i18nでは"untouched", そして "untouched"enum1と設定されています。)

これで、何が期待した通りに出来ていないのか判明しましたが、
次はどこが原因でそうなったのか調べます。

今度はキーワード明日、ステータス作業中で実行し、binding.pryをしてみます。
(作業中=>in_progress=>2)


    4: def index
    5:   @q = Task.ransack(params[:q])
    6:   @tasks = @q.result(distinct: true).order('created_at DESC')
    7:   binding.pry
 => 8: end

[1] pry(#<TasksController>)> params[:q]
=> <ActionController::Parameters {"title_cont"=>"明日", "status_id_eq"=>"in_progress"} permitted: false>
[2] pry(#<TasksController>)> Task.ransack(params[:q])
=> Ransack::Search<class: Task, base: Grouping <conditions: [Condition <attributes: ["title"], predicate: cont, values: ["明日"]>, Condition <attributes: ["status_id"], predicate: eq, values: ["in_progress"]>], combinator: and>>
[3] pry(#<TasksController>)> @q
=> Ransack::Search<class: Task, base: Grouping <conditions: [Condition <attributes: ["title"], predicate: cont, values: ["明日"]>, Condition <attributes: ["status_id"], predicate: eq, values: ["in_progress"]>], combinator: and>>
[4] pry(#<TasksController>)> @q.result(distinct: true)
  Task Load (0.6ms)  SELECT DISTINCT "tasks".* FROM "tasks" WHERE ("tasks"."title" ILIKE '%明日%' AND "tasks"."status_id" = 0)
=> []

@qまでは期待した検索結果が反映されています。
しかし@qに対してresultメソッドを実行すると
返してくれるActiveRecord::RelationクラスのオブジェクトのSQL文の部分に書かれた
進捗ステータスのidは0になってしまいました。
期待した進捗ステータスのidは2だったのですが…

注)記事内で行なっている検索では期待する検索結果が必ず存在する条件で検索を行なっています。

原因判明

このことをメンターの方に質問をしたところ
以下の記事を教えて頂きました。
http://www.tom08.net/entry/2016/12/05/121746
上記リンクの記事によると、
ransackenumに対応していないので、f.selectで選択したstatus_idActiveRecord::Relationクラスのオブジェクトに変換される際、常に0になってしまうようです。
なんでもっと早く気づけなかったのか…
enumi18n導入時は頑なにenum_helpを使わなかったのですが、もうダメなようです。

解決

といことでenum_helpを導入。

gem 'enum_help' のインストール


gem 'enum_help', '~> 0.0.15'
$ bundle install

config/locale/ja.ymlを編集

enums:
  task:
    status_id:
      undefined: "未設定"
      untouched: "未着手"
      in_progress: "作業中"
      done: "完了"

ビューファイルの編集

http://319ring.net/blog/archives/3041/
上記リンクの記事を参考にして修正しました。

app/views/tasks/index.slim

.search__body
  = search_form_for(@q, url:root_path) do |f|
    = f.label :title_cont
    = f.search_field :title_cont, placeholder: "#{t(:keyword)}"
    = f.label :status_id_eq
    = f.select :status_id_eq, Task.status_ids.map{|k, v| [Task.status_ids_i18n[k], v]}
    = f.submit

bin/rails cコマンドでコンソールを立ち上げて値を確認しました。


Running via Spring preloader in process 10882
Loading development environment (Rails 5.1.6)
[1] pry(main)> statuses = Task.status_ids
=> {"undefined"=>0, "untouched"=>1, "in_progress"=>2, "done"=>3}
[2] pry(main)> statuses = Task.status_ids.map{|k,v| [Task.status_ids_i18n[k], v]}
=> [["未設定", 0], ["未着手", 1], ["作業中", 2], ["完了", 3]]
[3] pry(main)> 

これで無事ransackの検索機能が機能するようになりました。

課題

今回の問題を解決するにあたって以下のことが大事だと感じました。

①エラー解決のためのヒントの検索力

今回の問題の解決にかなりの時間を割き原因を探りましたがましたが、結局メンターの方に教えていただいた記事がきっかけで解決することができました。その時、もっと可能性のある原因を推測しそれぞれに対応した適切なキーワードで検索をすれば良かったと感じました。

②ソースコードを読む能力

ソースコードはやはり問題解決の最大のヒントになると思います。今回はたまたま記事を見つけたことで解決できましたが、そうでない場合は実際に何が、どのように動いているのか調べる必要があります。正直まだソースコードを理解できるほどの能力はないですが、ソースコードを理解できようになることをRubyを学習する一つの目標にしようと思いました。

27
20
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
27
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?