LoginSignup
0
3

More than 5 years have passed since last update.

railsで独自の検索機能をつける

Posted at

railsで検索機能をつけるには

便利なgemがあります。おそらく検索して上位に出てくるのはransakではないでしょうか?しかし、railsで検索機能のついたapiを提供するにあたって、どうしても独自の実装が必要になることもあります。ここではrails wayにのった検索の実装の仕方について説明します。

こちらのサイトを大変参考にさせていただきました。

完成形

urlで次のようにたたくと条件を満たすeventが返ってくる.

http://example.com/event?capacity=10&location=0&genre[]=1人でもOK&genre[]=おしゃれなカフェ&order=popular&offset=3&limit=3

この場合、最大人数10人以上で。場所が梅田、ジャンルが「1人でもOK」もしくは「おしゃれなカフェ」、人気順に表示、最初から3番目のデータで、全部で3件取得という意味になる。

説明

  • capacity
    イベントの最大人数を指定。例えばこの値が10なら最大人数が10人以上のものが返ってくる。

  • location
    開催場所。0:梅田 1:なんば 2:神戸

  • genre
    イベントの種類。例えば「女性多め」「年上男性多め」「おしゃれなカフェ」など。

  • order
    返ってくる順番。popular: 見られた数が多い順 new:更新日が新しい順

  • offset
    データベースで初めから何番目のデータかを指定。pagenationとかに使う。

  • limit
    最大何件取得するかを指定。

実装

今回はeventgenreが多対多なので、eventsテーブルとgenresテーブルとevent_geresテーブルの中間テーブルを作る。

schema

rails g modelをした結果のschema.rb配下の通り。アソシエーションの作り方はqiitaの他の記事を参照してください。referencesとかそこらへんです。

 create_table "events", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "name" #イベント名
    t.string "days" #イベントが実施される曜日
    t.text "description" #イベント詳細
    t.integer "capacity" #イベントに来れる最大人数
    t.integer "location" #イベント開催場所 0:梅田 1:なんば 2:神戸
    t.integer "views" #みられた数
    t.datetime "created_at", null: false #作成日
    t.datetime "updated_at", null: false #更新日
end
create_table "genres", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "name", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
end


add_foreign_key "event_genres", "events"
add_foreign_key "event_genres", "genres"
create_table "event_genres", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
  t.bigint "event_id"
  t.bigint "genre_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["event_id"], name: "index_event_genres_on_event_id"
  t.index ["genre_id"], name: "index_event_genres_on_genre_id"
end

model

1番のポイント。
検索機能自体はモデルに直接書く。なおvalidation機能のみをもったmodelSearchFormも作成。

class Event < ApplicationRecord
    has_many :event_genres, dependent: :destroy
    has_many :genres, through: :event_genres
    scope :regexp_days, -> (pattern){ where("events.days REGEXP ?", pattern)}

    def self.search(params)

        # order is determined when order is new or popular
        order = nil
        puts params
        if params[:order] == "new"
            order = "events.updated_at ASC"
        elsif params[:order] == "popular"
            order = "events.views DESC"
        end

        events = Event.where(nil)
        events = events.where('capacity > ?', params[:capacity]) if params[:capacity].present?
        events = events.where(location: params[:location]) if params[:location].present?
        if params[:days].present? 
            days = params[:days].split(//).join("|") # 1文字ずつばらばらにする "013" => "0|1|3"
            events = events.regexp_days(days)
        end
        events = events.includes(:genres).where(genres: {name: params[:genre]}) if params[:genre].present?
        events = events.offset(params[:min]) if params[:min].present?
        events = events.limit(params[:max]) if params[:max].present?
        events = events.order(order) if order.present?

        events

    end

end
class Genre < ApplicationRecord
    has_many :event_genres, dependent: :destroy
    has_many :events, through: :event_genres
    validates :name, uniqueness: true

end
class EventGenre < ApplicationRecord
  belongs_to :event
  belongs_to :genre
end
class SearchForm
    include ActiveModel::Model, ActiveModel::Serialization, ActiveModel::Validations

    attr_accessor :days, :min, :max, :order, :genre, :location, :capacity
    # 以下にvalidationを書いて行く。
    validates :order, inclusion: { in: %w(new popular)}, allow_nil: true


    # デフォルトの値
    def attributes
        {days: nil, frequency: nil, min: nil, max: nil, order: "new", genre: nil, fee: nil, location: nil }
    end

end

controller

こちらは
rails g scaffold_controller enventで生成したものを少し加工しました。

events_controller.rb
 def index
    search_word = SearchForm.new(event_params)
    if search_word.valid?
      @events = Event.search(search_word.serializable_hash)
      puts "event is #{@events}"
      @events
    else
      render json: search_word.errors, status: :unprocessable_entity 
    end

  end

  #以下略

  private

    # Never trust parameters from the scary internet, only allow the white list through.
    def event_params
      params.permit({genre: []}, :order, :min, :max, :capacity, :days, :location)
    end

view

index.json.jbuilder
json.array! @events 
0
3
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
0
3