CSVファイルのデータをseedで取り込む、検索機能の概要の実装、ransackによるor検索

問題一覧をDBに取り組む

手始めに1000問くらいあるExcelファイルを用意した。

ExcelファイルからDBにとりくむ方法を調べた。

 

https://qiita.com/mmmasuke/items/545afaf5876d3dc52670

 

このサイトの通りにCSVに保存し直してみる

 

rails aborted!

NameError: uninitialized constant CSV

/Users/kubomasato/projects/search_app/db/seeds.rb:1:in `<main>'

/Users/kubomasato/projects/search_app/bin/rails:9:in `<top (required)>'

/Users/kubomasato/projects/search_app/bin/spring:15:in `<top (required)>'

bin/rails:3:in `load'

bin/rails:3:in `<main>'

Tasks: TOP => db:seed

 

 

require "csv"

を入れ損ねていた。これを入力して再度実行

 

f:id:mst_kb:20210511152810p:plain

問題一覧の一挙登録ができた。

 

次は〜ページ以上、〜ページ未満の範囲を選択できるようにする

 

f:id:mst_kb:20210511195719j:plain

以下、以上だけであれば実装したことがあるが、今回のような範囲検索はしたことがないので、検索。

https://qiita.com/nojinoji/items/e1b174220da8c81a1756

 

このサイトが参考になりそう。

(このサイトに記載されているソート機能も覚えておきたい。前回苦戦したランダム並び替えに使えそう)

 

f:id:mst_kb:20210511214922p:plain

今のところ検索ふぉーむはこうなっている

ただこのままでは、定期テスト時に

地理は121〜150

歴史は170〜200

などというテスト範囲時にいちいち地理と歴史の問題一覧を出力する必要がある。

これでは(まだ未実装だが)印刷する際など余計な紙が出てきてしまう。

もちろん分けて出力したい人もいるだろうから、どちらもできるようにしたい。

 

f:id:mst_kb:20210511215631j:plain

 

そもそも、地理と歴史で複数の条件になる。

だから

地理で121〜150  or 歴史 170 〜200 で検索できるか。

を調べる必要がある。

だから「地理 && 121~150」||「歴史 && 170〜200」みたいな処理をしたい

 

ransackではand検索になっているはずなので

or検索をする必要がある。

「ransuck  or検索」で検索。

http://j-ogawa.hatenadiary.jp/entry/2014/03/25/000334

こちらのサイトがやりたいことと同じことをやろうとしているんだけど...

 

User.search(
  m: 'or',
  g: {
    '0' => { m: 'or', power_gteq: 100, magic_gteq: 30 },
    '1' => { m: 'and', level_gteq: 20, hp_lteq: 30 }
  }
).result

 

...見たことない記述だ?

保留にしつつ、別のサイトへ

http://nekorails.hatenablog.com/entry/2017/05/31/173925

# シンプルモード
q = {name_eq: "ほげ"}
Product.ransack(q).result.to_sql
=> "SELECT `products`.* FROM `products` WHERE `products`.`name` = 'ほげ'"

# アドバンストモード
q = {
  # conditions(条件)
  "c" => {
    "0" => {
      # attributes(属性)
      "a" => { "0" => { "name" => "name" } },
      # predicate(述語)
      "p" => "eq",
      # values(値)
      "v" => { "0" => { "value" => "ほげ" } }
    }
  }
}
Product.ransack(q).result.to_sql
=> "SELECT `products`.* FROM `products` WHERE `products`.`name` = 'ほげ'"

これだ。

アドバンストモードとやらを使っているらしい。

ビューにするとこうなるらしい。

# シンプルモード
<%= f.search_field :name_eq %>

# アドバンストモード
# conditions(条件)
<%= f.condition_fields do |c| %>

  # attributes(属性)
  <%= c.attribute_fields do |a| %>
    <%= a.attribute_select %>
  <% end %>

  # predicate(述語)
  <%= c.predicate_select %>

  # values(値)
  <%= c.value_fields do |v| %>
    <%= v.search_field :value %>
  <% end %>

<% end %>

...何がどうなっているのかさっぱりだ。

その後の解説を読んでいく。

 

今、自分の記述は

def search_product
@p = Content.ransack(params[:q]) # 検索オブジェクトを生成
end

こうなっている。

この@pに何が入っているかの解説があった。

f:id:mst_kb:20210512150838j:plain

conditionが条件で

 その中身にattribute=探すカラム

      predicate=取り出す条件

      value=入力された値

となっていることが確認できた。

 

と言うことは先ほどのやつはこうなるのか、

f:id:mst_kb:20210512152124j:plain

なるほど、今見ると、コメントアウトでしっかり書いてあったけど、このcとかaとかpとかvの意味が一応わかったぞ。

ビューファイルの方は...

f:id:mst_kb:20210512152247j:plain

うーん、この_selectって何しているんだ?

これだけでnameカラムから探すとか、eqを使うっていう意味になっていないはずなんだけど...

とりあえず、先に読み進める

 

f:id:mst_kb:20210512153809j:plain

シンプルとアドバンスで共存できるみたい。

記述がものすごく長くなりそうだからそれはいい。

or検索もできそう。

gでグループ分けするみたいだから

f:id:mst_kb:20210512154251j:plain

こんな感じにグループ分けするのかな?

科目数とかもユーザーが気軽増やせるようにしたいから後で考え直さないと

 

さて、処理的にできそうなのはいいけど、

ビューファイルの件はまだ全くわかっていないので、

続き

# シンプルモード
Product.ransack(name_eq: "ほげ", id_eq: 1).base.conditions
=> [Condition <attributes: ["name"], predicate: eq, values: ["ほげ"]>, Condition <attributes: ["id"], predicate: eq, values: [1]>]

# アドバンストモード
q = {
  "c" => {
    "0" => {
      "a" => { "0" => { "name" => "name" } },
      "p" => "eq",
      "v" => { "0" => { "value" => "ほげ" } },
    },
    "1" => {
      "a" => { "0" => { "name" => "id" } },
      "p" => "eq",
      "v" => { "0" => { "value" => 1 } },
    }
  }
}
Product.ransack(q).base.conditions
=> [Condition <attributes: ["name"], predicate: eq, values: ["ほげ"]>, Condition <attributes: ["id"], predicate: eq, values: [1]>]

c(条件)が2つ以上ある時は上のように

0,1で区切るみたい。

なるほど先ほどの

 

User.search(
  m: 'or',
  g: {
    '0' => { m: 'or', power_gteq: 100, magic_gteq: 30 },
    '1' => { m: 'and', level_gteq: 20, hp_lteq: 30 }
  }
).result

 こちら様も

グループ:0

 パワーが100以上、もしくは、マジックが30以上

グループ:1

 レベルが20以上、かつ、HPが30以下

で、グループ0もしくはグループ1になっているわけかな

 (p100↑ or m30↑) or (lv20↑ and hp30↓)

あれ、最初のなんで「or」なんだろう。andの間違いかな、

 

何しているかわかったはいいけど

やっぱりビューのほうはわかっていない

# index.html.erb

<%= search_form_for @search do |f| %>

  # conditions
  # conditionsやattributesのようにコレクションになるものに対しては、`f.condition_fields`などの`f.*_fields`を利用してね。
  # params[:q][:c]["0"]に対応するよ("0"はコレクションの連番の1つ目を表すよ。)
  <%= f.condition_fields do |c| %>

    # attributes
    # params[:q][:c]["0"][:a]に対応するよ。
    <%= c.attribute_fields do |a| %>
      # 属性のセレクトボックスだよ。
      # `id`などの全ての属性が選択可能だよ。
      # params[:q][:c]["0"][:a]["0"][:name]に対応するよ。
      <%= a.attribute_select %>
    <% end %>

    # predicate
    # 述語のセレクトボックスだよ。
    # `eq`などの全ての述語が選択可能だよ。
    # params[:q][:c]["0"][:p]に対応するよ。
    <%= c.predicate_select %>

    # values
    # params[:q][:c]["0"][:v]に対応するよ。
    <%= c.value_fields do |v| %>
      # 値のサーチフィールドだよ。
      # params[:q][:c]["0"][:v]["0"]["value"]に対応するよ。
      <%= v.search_field :value %>
    <% end %>

  <% end %>

  <%= f.submit %>
<% end %>

・・・セレクトボックスやサーチフィールドになるのはいいんだけど、そのほかの記述方法が書いていないのかな。

とりあえずいじってみるか

<%= search_form_for @p, url: products_search_path do |f| %>
<%= f.label :question_cont, '問題文検索' %>
<%= f.search_field :question_cont, class:"items-search-text", placeholder:"問題文に含まれている文字を検索" %>
<br>

今、最初の問題文検索はこうなっているから、

同じようにして表示されるかどうかやってみよう。

<%= search_form_for @p, url: products_search_path do |f| %>
<%# conditions %>
<%= f.condition_fields do |c| %>
<%# attributes %>
<%# <%= c.attribute_fieldsd do |a| %>
<%= a.attribute :question %>
<%# <% end %>
<%# predicate %>
<%= c.predicate :_cont %>
<%# values %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
 

・・・まあでないよね。

でもエラーすら吐かないのはなんでだろう。

なーんも表示されないのもなんでだろう。

# products_controller.rb

def index
  @search = Product.ransack(params[:q])
  # 初期状態の@searchはconditionsが空配列なので、1つだけ初期状態のconditionを作っておくよ。
  # これをやっておかないと、`f.condition_fields`で処理すべきconditionが1つもなくて、画面に何も表示されないよ。
  # 検索した場合はparams[:q]からconditionを作成するから、`@search.conditions`が`empty?`の場合だけ作るようにしてね。
  @search.build_condition if @search.conditions.empty?
  @products = @search.result
end

これをやっていなかったのでやってみる。

 

そうすると、ちゃんとエラーをはいてくれた。

 

とりあえずお手本通りに

<%= f.label :question_cont, '問題文検索' %>
<%= f.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.attribute_select %>
<% end %>

<%= c.predicate_select %>

<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>

<% end %>

こうする。

 

 

gyazo.com

こうなる。

うん、お手本通り、

そうしたら、

conditionと

attributeは固定でいい。

 

固定の仕方を調べてみたけど

<%= f.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :question %>
<% end %>

<%= c.hidden_field :p, value: "cont" %>

<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>

こうしたら

f.search_field :question_cont

これと同じことができた。

classとかplaceholderはどこにつけるんだ?

よくわからんから、今思いつく作業である

<%= f.label :question_cont, '問題文検索' %>
<%= f.search_field :question_cont, class:"items-search-text", placeholder:"問題文に含まれている文字を検索" %>
<%= f.label :answer_cont, '解答検索' %>
<%= f.search_field :answer_cont, class:"items-search-text", placeholder:"解答に含まれている文字を検索" %>
<%= f.label :subject_eq, '科目:' %>
<%= f.collection_select :subject_eq, @product_subject, :subject, :subject, include_blank: '指定なし'%>
<%= f.label :page, 'ページ:' %>
<%= f.number_field :page_gteq %>
<%= f.number_field :page_lt %>

これをアドバンストに対応したものに変えてみる

<%= search_form_for @p, url: products_search_path do |f| %>
<%= f.label :question_cont, '問題文検索' %>
<%= f.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :question %>
<% end %>
<%= c.hidden_field :p, value: "cont" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
<br>
<%= f.label :question_cont, '解答検索' %>
<%= f.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :answer %>
<% end %>
<%= c.hidden_field :p, value: "cont" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
<br>
<%= f.label :subject_eq, '科目:' %>
<%= f.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :subject %>
<% end %>
<%= c.hidden_field :p, value: "eq" %>
<%= c.value_fields do |v| %>
<%= v.collection_select :value, @product_subject, :subject, :subject %>
<% end %>
<% end %>
<%= f.label :page, 'ページ:' %>
<%= f.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :page %>
<% end %>
<%= c.hidden_field :p, value: "qteq" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
から
<%= f.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :page %>
<% end %>
<%= c.hidden_field :p, value: "lt" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
まで

こうしてみた。

みてくれはこうなる。

gyazo.com

相変わらずplaceholderがわからんけど、

本題を先に片付けていこう。

 

ここで、

現段階での検査構造を整理してみる

f:id:mst_kb:20210513150247j:plain

こうかな。

ここから、アドバンストに対応した記述に

グルーピングしていけばいいはず。

<%= search_form_for @p, url: products_search_path do |f| %>
<%# グループ1 問題文、解答検索 %>
<%= f.grouping_fields do |g| %>
<%= g.label :question_cont, '問題文検索' %>
<%= g.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :question %>
<% end %>
<%= c.hidden_field :p, value: "cont" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
<br>
<%# 解答 %>
<%= g.label :question_cont, '解答検索' %>
<%= g.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :answer %>
<% end %>
<%= c.hidden_field :p, value: "cont" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
<% end %>
<br>
<%# グループ2 科目ごと %>
<%= f.grouping_fields do |g| %>
<%# グループ2-1 地理 %>
<%= g.grouping_fields do |g1| %>
<%= g1.label :subject_eq, '科目:' %>
<%= g1.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :subject %>
<% end %>
<%= c.hidden_field :p, value: "eq" %>
<%= c.value_fields do |v| %>
<%= v.collection_select :value, @product_subject, :subject, :subject %>
<% end %>
<% end %>
<%= g1.label :page, 'ページ:' %>
<%= g1.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :page %>
<% end %>
<%= c.hidden_field :p, value: "qteq" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
から
<%= g1.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :page %>
<% end %>
<%= c.hidden_field :p, value: "lt" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
まで
<% end %>
<br>
<%# グループ2-2 歴史 %>
<%= g.grouping_fields do |g1| %>
<%= g1.label :subject_eq, '科目:' %>
<%= g1.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :subject %>
<% end %>
<%= c.hidden_field :p, value: "eq" %>
<%= c.value_fields do |v| %>
<%= v.collection_select :value, @product_subject, :subject, :subject %>
<% end %>
<% end %>
<%= g1.label :page, 'ページ:' %>
<%= g1.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :page %>
<% end %>
<%= c.hidden_field :p, value: "qteq" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
から
<%= g1.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :page %>
<% end %>
<%= c.hidden_field :p, value: "lt" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
まで
<% end %>
<br>
<%# グループ2-3 公民 %>
<%= g.grouping_fields do |g1| %>
<%= g1.label :subject_eq, '科目:' %>
<%= g1.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :subject %>
<% end %>
<%= c.hidden_field :p, value: "eq" %>
<%= c.value_fields do |v| %>
<%= v.collection_select :value, @product_subject, :subject, :subject %>
<% end %>
<% end %>
<%= g1.label :page, 'ページ:' %>
<%= g1.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :page %>
<% end %>
<%= c.hidden_field :p, value: "qteq" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
から
<%= g1.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.hidden_field :name, value: :page %>
<% end %>
<%= c.hidden_field :p, value: "lt" %>
<%= c.value_fields do |v| %>
<%= v.search_field :value %>
<% end %>
<% end %>
まで
<% end %>
<%# グループ2のOR検索の設定 %>
<%# <%= g.hidden_select :m, value: "or"%>
<%= g.hidden_field :m, value: "or" %>
<% end %>

なんだこの長さは・・・でもビューは

gyazo.com

良さそうだ

検索してみる

gyazo.com

できているといいなあ

ああ、エラーだあ

gyazo.com

なんだあこれは・・・

negativeが定義されていない?

どういうことだあ・・・