今回はアクティブハッシュを用いたカテゴリ検索をしたいと思います
自分は今コーヒーの感想を共有できるアプリを作ってます。
インスタのコーヒー版的な感じです。
期待する動作!
このように,「地域 ラテンアメリカ、コク ほどよい」みたいにカテゴリーを選択して投稿
選択した、カテゴリがしっかり表示されてます。
名前は、グアテマラ、地域はラテンアメリカにして、検索してみて
しっかり検索結果が表示されました!!
具体的な実装方法
今回はカテゴリ選択のactive hashと言うgemと検索機能を簡単に実装できるransackと言うgemを導入します。
active hashとは、都道府県名一覧やカテゴリーなど「基本的に変更されないデータ」があったとします。基本的に変更されないデータであるため、データベースに保存する必要性はありません。一方、ビューファイルなどにそれらのデータを直接書いてしまうと、可読性に欠けます。
そのようなケースでは、ActiveHashが有用です。
都道府県名などの変更がないデータをモデルに記述し、あたかもデータベースに保存されていたデータとして取り扱うことができるようにするGemです。すなわち、都道府県名などのデータに対して、ActiveRecordのメソッドを用いることができます。
テーブルの数を無駄に増やす必要もなくなります。
ransackとはシンプルな検索フォームと高度な検索フォームの作成を可能にするgemです。
まずはこれらのgemを導入しましょう。
色々なカテゴリーを設けてますが、今回は酸味を表す、acidityカテゴリーだけに着目して解説していきます。
active hashでのカテゴリーの実装(わかってる方は飛ばしてください)
acidity.rb
class Acidity < ActiveHash::Base
self.data = [
{ id: 2, name: 'LOW(少ない)' },
{ id: 3, name: 'MEDIUM(ほどよい)' },
{ id: 4, name: 'HIGH(強い)' }
]
end
これが、active hashで作った カテゴリーです。
コーヒーの投稿を保存する、drinksテーブルにacidity_idを保存してます
drinks.rb
class Drink < ApplicationRecord
extend ActiveHash::Associations::ActiveRecordExtensions
belongs_to :user
has_one :trade
has_many :drink_tag_relations
has_many :tags,through: :drink_tag_relations
has_one_attached :image
belongs_to_active_hash :region
belongs_to_active_hash :body
belongs_to_active_hash :acidity
belongs_to_active_hash :processing
with_options presence: true do
validates :name
validates :explain
end
end
belongs_to_active_hash :acidity
と記述することで、acidityとアソシエーションが組まれて、カテゴリ選択ができるようになります
extend ActiveHash::Associations::ActiveRecordExtensions
と記述して、moduleを取り込むことによって、 belongs_to_active_hashメソッド
が使えます
drinks/new.html.erb
<%= f.collection_select(:acidity_id,Acidity.all,:id,:name,{},{class: "こんな感じでクラスを設定できます"})%>
こんな感じでカテゴリが実装できます
第一引数に、保存先のカラム名,今回はacidity_id
第二引数に、表示したい配列データを指定する、Acidity.all
第三引数に、表示する際に参照するDBのカラム名
第四引数に、実際に表示されるカラム名
Acidity.rbのnameが厳密に言えばカラムではないが、
データベースのように扱えるので、nameを指定する
これで先ほどのようなプルダウン形式のカテゴリーの選択欄が作成できました。
検索機能の実装!
ルーティングの記述
routes.rb
Rails.application.routes.draw do
root to: 'drinks#index'
get '/drinks/searchdrink', to: 'drinks#search_drink'
resources :drinks, only: [:index,:new,:show,:create,:destroy] do
collection do
get 'search'
end
end
resourcesの上に、書かないと意図しない画面に遷移させられたりするので、それより上に書きましょう!
コントローラーの記述
drinks_controller.rb
class DrinksController < ApplicationController
include SessionsHelper
before_action :create_searching_object,only: [:index,:search_drink]
def index
@user = current_user
@drinks = Drink.all.order("created_at DESC")
end
def new
@drink = DrinkTag.new
end
def create
@drink = DrinkTag.new(drink_params)
if @drink.valid?
@drink.save
redirect_to drinks_path
else
render 'new'
end
end
def search_drink
@results = @p.result
end
private
def drink_params
params.require(:drink_tag).permit(:name,:price,:explain,:image,:tag_name,:region_id,:body_id,:acidity_id,:processing_id).merge(user_id: current_user.id)
end
def create_searching_object
@p = Drink.ransack(params[:q])
end
indexアクションでは、全投稿の情報を取得しています
create_searching_objectアクションでは、キー(:q)を使って、drinksテーブルから商品情報を探しています
@p と言う名前の検索オブジェクトを生成しています
index,search_drinkアクションのみで使用するので、before_actionで限定しています
search_drinkアクションでは@pに対して、.resultとすることで検索結果を取得して、@resultに代入しています
コントローラーの処理は以上です。
検索フォームの実装
ここでは、投稿の検索フォームを実装しましょう。その際、「search_form_for」と「collection_select」という2つのメソッドを使用します。
search_form_forはransack特有の検索フォームを生成するヘルパーメソッドです。
collection_selectメソッドはDBにある情報をプルダウン形式で表示できるヘルパーメソッドです。
drinks/index.html.erb
<%= search_form_for @p, url: drinks_searchdrink_path do |f| %>
<%= f.search_field :name_cont%>
<%# _contはidじゃなくて文字列のときに使う%>
<p>カテゴリー検索</p>
<%# ベースはドリンククラスで、第二引数で%>
<%= f.label '酸味'%>
<%= f.collection_select :acidity_id_eq,Acidity.all,:id,:name,include_blank: '指定なし' %>
<%= f.submit '検索' %>
<% end %>
search_form_forの引数に「@p(検索オブジェクト)」を渡すことで検索フォームを生成しています。
urlはdrink#search_drinkに飛ばしたいので、rails routeで確認してこう言う感じになりました
<%= f.collection_select :acidity_id_eq,Acidity.all,:id,:name,include_blank: '指定なし' %>
第一引数 検索したいカラム名
第二引数 実際に表示したい配列データを指定する
今回で言えば、Acidity.allです。
第三引数 表示する際に参照するDBのカラム名
第四引数 実際に表示されるカラム名
オプション include_black 何も選択してないときに表示される内容、今回は「指定無し」
検索結果を表示するビューを作成
search_drinkアクションの処理が終わったら、railsのデフォルトでsearch_drink.html.erbにリダイレクトされるので、
search_drink.html.erbを作成して
<h1>
検索結果
</h1>
<%# 検索結果の個数で条件分岐 %>
<% if @results.length !=0 %>
<% @results.each do |drink| %>
<div class='main'>
<%# 商品一覧 %>
<div class='item-contents'>
<h2 class='title'></h2>
<ul class='item-lists'>
<%# 商品のインスタンス変数になにか入っている場合、中身のすべてを展開できるようにしましょう %>
<%if drink%>
<li class='list'>
<%= link_to drink_path(drink.id) do %>
<div class='item-img-content'>
<%= image_tag drink.image , class: "item-img" if drink.image.attached? %>
<%# if drink.trade%>
<%# end %>
</div>
<div class='item-info'>
<h3 class='item-name'>
<%= drink.name %>
</h3>
<div class='item-price'>
<span><%= drink.price %>円<br>(税込み)</span>
<div class='star-btn'>
<%# image_tag "star.png", class:"star-icon" %>
<span class='star-count'>0</span>
</div>
</div>
<div class='item-explain'>
<%= drink.explain%>
</div>
<div>
<% if drink.region %>
産地 <%= drink.region.name%>
<% end %>
</div>
<div>
<% if drink.body%>
コク <%= drink.body.name %>
<% end %>
</div>
<div>
<% if drink.acidity %>
酸味 <%= drink.acidity.name%>
<% end %>
</div>
<div>
<% if drink.processing%>
加工法 <%= drink.processing.name%>
<% end %>
</div>
</div>
<% if logged_in? && current_user.id == drink.user_id %>
<div class="item-delete">
<%= link_to "削除する",drink_path(drink),method: :delete %>
</div>
<% if drink.trade%>
<%= link_to "商品を購入する", drink_trades_path(drink) %>
<% end %>
<% end %>
</li>
<%end%>
</ul>
</div>
<%end%>
</div>
<% end %>
<% else %>
該当する商品はありません
<% end %>
<br>
<%= link_to 'トップページへ戻る', root_path %>
と、検索結果が、@resultに入っていて、
それをeach文で、ローカル変数をdrinkにして、検索結果があるだけ表示させています
以上で、Active hashで作ったカテゴリーを用いてransackでカテゴリ検索をする実装が終わりました!