ステップバイステップ
で解説!Rails検索処理
Ruby / Ruby on Rails ビギナーズ勉強会 第9回 #coedorb
photo by poluz!
http://www.flickr.com/photos/poluz
自己紹介
• 派遣で6年程エンジニア&PM
• WindowsのActiveDirecotryとか
• 2005年∼2014年までは人材系の仕事
• エンジニア専門のキャリアコンサルタント
• 昨年からなぜかフリーランス
• しかもWeb系のエンジニア
• Rails4+AngularJS。最近は仕事でRubyでク
ローラー開発とかも行う
• http://qiita.com/h5y1m141@github/
items
• 実はJavaScriptが好きでこっちのほうが経験が長
い
•Titanium Mobile、Node.js、最近だとNW.js
•(最近メンテ辞めましたが)クラフトビールが飲め
る買えるお店が探せるスマフォアプリ、Webサイト
を全部JavaScriptベースの技術で作ってました
©jeffrey james pacres
http://www.flickr.com/photos/jjpacres/3293117576/
本題に入る前置き:

1年半の仕事を通じた学び
• Railsの検索処理が色々あるの
を実感
• 検索処理について深掘りすると
仕事の幅が広がる
©dliban

https://www.flickr.com/photos/dliban/1747257576/
ということで検索系の処理に
ついて3つのステップで解説
これからお話するRailsの構成は以下のような感じ
ruby 2.2.2

source 'https://
rubygems.org'
gem 'rails', '4.2.0'
# 省略
gem ‘ransack'


#以下は本来不要(おまけのネタの
ため設定)
gem 'elasticsearch-rails'
gem 'elasticsearch-model'
class InitSchema < ActiveRecord::Migration
def up
create_table "items", force: :cascade do |t|
t.string "name", limit: 255
t.string "url", limit: 255
t.integer "price", limit: 7
t.integer "sale_price", limit: 7
t.text "description", limit: 65535
t.datetime "created_at"
t.datetime "updated_at"
end
end
end
class Item < ActiveRecord::Base
end
Gemfile db/migarte/001_init_schema.rb
app/model/item.rb
STEP1: find系
Photo By peter burge
https://www.flickr.com/photos/peterburge/14441995464/
find系
class ItemsController < ApplicationController
before_action :set_task, only: [:show, :edit, :update, :destroy]
# 中略
private
def set_item
@item = Item.find(params[:id])
end
end
• 特定の条件にマッチする値を抽出する時に利用
• ./bin/rails generate scaffold itemとかすると自動的に生成さ
れる
@item = Item.find_by_id(params[:id]) # 特定のIDにマッチするアイテムを抽出

@item = Item.find_by_name(params[:name]) # 特定の名前にマッチするアイテムを抽出
• find_by_カラム名という書き方もできる
• find_by・・・はたくさんあるので、以下Qiitaの記事がわかりや
すい
• http://qiita.com/jnchito/items/2b7d64851665071ed6e6
STEP2: where
Photo By peter burge
https://www.flickr.com/photos/peterburge/14441995464/
where基本
•特定の条件にマッチする値が1つ以上抽出される
•配列で値が返ります
Item.where(price: 1998)
=> [#<Item:0x007fa44dd9b9f0
id: 1438,
price: 1998,
#<Item:0x007fa44dd9b888
id: 1445,
price: 1998
]
•配列で値が得られるのでその値に対する処理もや
りやすい
exclude_list = ['トップス']
Item.where(price: 1998).select{|item| item unless exclude_list.include?
(item[:name])}}
where複数条件指定
•whereの引数をハッシュで渡すことで複数条件指
定ができます
Item.where(price: params[:price], sale_price: params[:sale_price])
=> [#<Item:0x007fa44dd9b9f0
id: 1438,
price: 1998,
#<Item:0x007fa44dd9b888
id: 1445,
price: 1998
]
•以下の書き方はセキュリティ上の問題あるので避
けましょう!
※詳しくは英語のサイトですがWhere Methodをご覧ください

http://rails-sqli.org
Item.where(“price = ‘#{params[:sale]} AND sale_price= ‘#{sale_price}”)
whereついでにModel#scope
•よく利用する検索処理は Rails のModelの機能の
1つであるscope活用しましょう!
•意図が伝わりやすいコードになりやすい。
class Item < ActiveRecord::Base
# セール中のアイテムを抽出するためのメソッド

scope :on_sale, -> do
where.not(sale_price: nil)
end
end
Item.on_sale
=> [#<Item:0x007fa44dd9b9f0
id: 3,
sale_price: 998
#<Item:0x007fa44dd9b888
id: 4,
sale_price: 1998
]
app/model/item.rb
STEP3: Ransack
Photo By peter burge
https://www.flickr.com/photos/peterburge/14441995464/
Ransack
•gem
•https://github.com/activerecord-hackery/ransack
•入力フォームとの連携の仕組みが最初から整ってて便利
= search_form_for @search, {url: admin_items_path, html: {class: 'form-
horizontal'}} do |f|
%div.row
%div.col-md-offset-2
%div.form-group
= f.label 'アイテムID'
= f.number_field :id_eq, class: "form-control"
%div.col-md-offset-2
%div.form-group.col-md-1
= f.label '価格'
%div.form-group
%div.input-group.col-md-2
= f.text_field :price_gteq, size: 3, class: "form-control"
%div.input-group-addon 円以上
@search = Item.search(
price_gteq: params[:q][:price_gteq],
id_in: item_ids
)
View
Controller
Photo By Eugene Zemlyanskiy
http://www.flickr.com/photos/pictureperfectpose/76138988/
Ransack利用時の注意
• whereと条件指定がかなり似てるので混在して利用す
る時に注意した方が良い
• whereの場合にはカラム名だけでOK
• Ransack使う場合カラム名+SQLのwhere句の
条件となる文字を指定
• 具体例を次のスライドで!
Photo By Eugene Zemlyanskiy
http://www.flickr.com/photos/pictureperfectpose/76138988/
Ransack利用時の注意
item_ids = %w(1 2 3 4 5)
@items = Item.where(id: item_ids)
=> [#<Item:0x007fc199b42410
id: 1,
name: "#1 Item",
#<Item:0x007fc199b422a8
id: 2,
name: "#2 Item"
whereの場合 Ransackの場合
item_ids = %w(1 2 3 4 5)
@search = Item.search(id_in: item_ids)
@search.result
=> [#<Item:0x007fc199b42410
id: 1,
name: "#1 Item",
#<Item:0x007fc199b422a8
id: 2,
name: "#2 Item"
おまけ: ElasticSearch
Photo By peter burge
https://www.flickr.com/photos/peterburge/14441995464/
ElasticSearch
• 何?
• オープンソースの全文検索エンジンでJavaで書かれてるソフト
ウェアです
• https://github.com/elastic/elasticsearch
• 採用してる会社・サービス多数
• FacebookやLinkedIn、GitHub・・
• 何が出来るのか?
• 多機能で色々なことが出来ます。(詳しくは次のスライド)
• 簡単?
• 検索エンジンの仕組みの概念を理解しないと使うのは難しい
• 検索のアルゴリズムも色々あるので学習コストがとても高い
• ElasticSearch自体+Railsから利用するgemの使い方も知ら
ないといけないので・・・
ElasticSearch使った例
•あいまい検索っぽいもの
•Nikeという名前でデーターベースに登録されてる
•ユーザーからの検索はNikeはもちろんナイキでもヒットするようにしたい
Item.__elasticsearch__.client.indices.delete index: Item.index_name rescue nil
Item.create_index! force: true
Item.import
ナイキ, Nike
items = Item.__elasticsearch__.search(query:{term:{ 'name':'ナイキ' }})
trendwords.response
=> {"took"=>1,
"timed_out"=>false,
"_shards"=>{"total"=>5, "successful"=>5, "failed"=>0},
"hits"=>
{"total"=>2,
"max_score"=>5.9938283,
"hits"=>
[{"_index"=>"sample",
"_type"=>"item",
"_id"=>"514",
"_score"=>5.9938283,
"_source"=>
{"id"=>514,
"name"=>"Nike",
独自の辞書
ElasticSearchの設定(本当はもっとあるけど割愛)
ご清聴ありがとうございました

2015 12-19-ruby rails