[RUBY] Category search with ransack using categories created with Active hash

** This time I would like to do a category search using active hash **

I'm making an app that allows me to share my impressions of coffee. It's like a coffee version of Instagram.

Expected behavior!

スクリーンショット 2020-12-18 15.49.49.png

In this way, select a category and post like "Region Latin America, Rich"

スクリーンショット 2020-12-18 15.54.57.png

The selected category is displayed firmly.

スクリーンショット 2020-12-18 17.19.30.png

Try searching with the name Guatemala and the region Latin America.

スクリーンショット 2020-12-18 17.26.42.png

The search results are displayed firmly! !!

Specific implementation method

This time, we will introduce a gem called ** active hash ** for category selection and a gem called ** ransack ** that can easily implement the search function.

Active hash means that there is "basically unchanged data" such as a list of prefecture names and categories. There is no need to store it in the database as it is basically unchanged data. On the other hand, if you write those data directly in a view file etc., it will not be readable. Active Hash is useful in such cases. It is a Gem that describes data that does not change such as prefecture name in the model and can handle it as if it were stored in the database. That is, you can use ActiveRecord methods for data such as prefecture names. You don't have to wastefully increase the number of tables.

ransack is a gem that allows you to create simple and advanced search forms.

Let's introduce these gems first.

We have set up various categories, but this time we will focus only on the acidity category, which expresses sourness.

Implementation of categories with active hash (skip if you know)

acidity.rb


class Acidity < ActiveHash::Base
  self.data = [

    { id: 2, name: 'LOW(Few)' },
    { id: 3, name: 'MEDIUM(Moderate)' },
    { id: 4, name: 'HIGH(strong)' }
  ]
end

This is the category created by active hash.

スクリーンショット 2020-12-18 17.43.28.png

Save the coffee post, save the acidity_id in the drinks table

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

By writing, acidity and association will be formed, and you will be able to select a category.


  extend ActiveHash::Associations::ActiveRecordExtensions

By writing and incorporating the module, belongs_to_active_hash method Can be used

drinks/new.html.erb


 <%= f.collection_select(:acidity_id,Acidity.all,:id,:name,{},{class: "You can set the class like this"})%>

You can implement the category like this

The first argument is the column name of the save destination, this time acidity_id

Specify the array data you want to display in the second argument, Acidity.all

DB column name to be referenced when displaying in the third argument

Column name actually displayed in the 4th argument

Strictly speaking, the name in Acidity.rb is not a column, Since it can be treated like a database, specify name

You have now created a pull-down category selection field like the one above.

Implementation of search function!

Routing description

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

** If you do not write it on resources, you may be transferred to an unintended screen, so write it above it! ** **

Controller description

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

The index action gets the information of all posts

The create_searching_object action uses the key (: q) to search the drinks table for product information. I'm creating a search object named @p

Since it is used only in the index and search_drink actions, it is limited by before_action.

In the search_drink action, for @p, by setting it to .result, the search result is acquired and assigned to @result.

That's all for the controller.

Search form implementation

Here, let's implement a post search form. At that time, we use two methods, "search_form_for" and "collection_select".

search_form_for is a helper method that generates a ransack-specific search form.

The collection_select method is a helper method that can display the information in the DB in a pull-down format.

drinks/index.html.erb


<%= search_form_for @p, url: drinks_searchdrink_path do |f| %>
  
  <%= f.search_field :name_cont%>

  <%# _cont is used when it is a string instead of id%>
  <p>Category search</p>
  <%#The base is a drink class, with the second argument%>
  <%= f.label 'acidity'%>
  <%= f.collection_select :acidity_id_eq,Acidity.all,:id,:name,include_blank: 'unspecified' %>

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

A search form is generated by passing "@p (search object)" as an argument of search_form_for.

I want to skip the url to drink # search_drink, so I checked it with rails route and it became like this


  <%= f.collection_select :acidity_id_eq,Acidity.all,:id,:name,include_blank: 'unspecified' %>

First argument: Column name you want to search

Second argument Specify the array data you actually want to display

In this case, it's Acidity.all.

Third argument DB column name to be referenced when displaying

Fourth argument The column name that is actually displayed

Option include_black Contents displayed when nothing is selected, this time "Not specified"

Create a view to display search results

After processing the search_drink action, rails will redirect you to search_drink.html.erb by default, so Create search_drink.html.erb

<h1>
search results
</h1>
<%#Conditional branching based on the number of search results%>
<% if @results.length !=0 %>

<%  @results.each do |drink| %>
  <div class='main'>

  <%#Product list%>
  <div class='item-contents'>
    <h2 class='title'></h2>
    <ul class='item-lists'>

      <%#If there is something in the instance variable of the product, let's make it possible to expand all the contents%>
      <%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 %>Circle<br>(tax included)</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 %>
Origin<%= drink.region.name%>              
            <% end %>
          </div>
          <div>
            <% if drink.body%>
Rich<%= drink.body.name %>
            <% end %>
          </div>
          <div>
            <% if drink.acidity %>
acidity<%= drink.acidity.name%>
            <% end %>
          </div>
          <div>
            <% if drink.processing%>
Processing method<%= drink.processing.name%>
            <% end %>
          </div>
        </div>
        <% if logged_in? && current_user.id == drink.user_id %>
        <div class="item-delete">
          <%= link_to "delete",drink_path(drink),method: :delete %>
        </div>
        <% if drink.trade%>
        <%= link_to "Buy goods", drink_trades_path(drink) %>
        <% end %>
        <% end %>
      </li>
      <%end%>
     
        
      
    </ul>
  </div>
  <%end%>
</div>

<% end %>

<% else %>
There is no applicable product
<% end %>
<br>
<%= link_to 'Go back to the top page', root_path %>

And the search result is in @result, In each statement, I set the local variable to drink and display as many search results as possible.

This completes the implementation of category search with ransack using the category created with Active hash!

Recommended Posts

Category search with ransack using categories created with Active hash
List the contents of categories created with Active Hash
Table design (using Active Hash)
[For those who create portfolios] Search function created with ransack
[Rails] A memo that created an advanced search form with ransack
Create an or search function with Ransack.
[Rails] Implementation of search function using gem's ransack
How to search multiple columns with gem ransack
Let's make a search function with Rails (ransack)