概要
MITライセンスに基づいて翻訳・公開いたします。
- 英語README: algolia/algoliasearch-rails: AlgoliaSearch integration to your favorite ORM
- 原文更新日: 2019/03/27
- ライセンス: MIT
- サイト: Fast, reliable and modern search and discovery | Algolia
- お試し: Search | Algolia
Rails: algolia-search-rails gem README(翻訳)
Algolia Searchは、最初のキーストロークを入力した時点でリアルタイムで結果を返せる、ホスト型検索エンジンです。
このgemはalgoliasearch-client-rubyを元に作られたもので、Algolia Search APIを自分好みのORMに簡単に統合できます。
Rails 3.x、4.x、5.xはすべてサポート対象です。
algoliasearch-rails-exampleサイトで、autocomplete.js
ベースのオートコンプリート機能やInstantSearch.js
ベースのインスタント検索結果ページをご覧いただけますので、ご興味がありましたらどうぞ。
API ドキュメント
完全なリファレンスはAlgoliaのWebサイトで参照いただけます。
訳注: 目次は省略しました
セットアップ
インストール方法
gem install algoliasearch-rails
Gemfile
に以下を追加します。
gem "algoliasearch-rails"
続いて以下を実行します。
bundle install
設定
config/initializers/algoliasearch.rb
ファイルを作成し、APPLICATION_ID
とAPI_KEY
をセットアップします。
AlgoliaSearch.configuration = { application_id: 'YourApplicationID', api_key: 'YourAPIKey' }
このgemは、ActiveRecord、Mongoid、Sequelと互換性があります。
タイムアウト
初期化時に以下のオプションを設定することで、さまざまなタイムアウトスレッショルドを設定できます。
AlgoliaSearch.configuration = {
application_id: 'YourApplicationID',
api_key: 'YourAPIKey',
connect_timeout: 2,
receive_timeout: 30,
send_timeout: 30,
batch_timeout: 120,
search_timeout: 5
}
注意
このgemでは、インデックス作成タスクのトリガーにRailsのコールバックを多用しています。after_validation
、before_save
、after_commit
といったコールバックをバイパスするメソッドが使われていると、変更がインデックスに反映されません。たとえば、update_attribute
メソッドはバリデーションチェックを行いません。アップデート時にバリデーションを行うには、update_attributes
をお使いください。
AlgoliaSearch
モジュールによって注入されるメソッド名の冒頭にはすべてalgolia_
が追加され、それらに関連する短いエイリアス名も追加されます(定義されていない場合)。
Contact.algolia_reindex! # <=> Contact.reindex!
Contact.algolia_search("jon doe") # <=> Contact.search("jon doe")
利用法
インデックスのスキーマ
以下のコードは、Contact
インデックスを作成してContact
モデルに検索機能を追加します。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
attribute :first_name, :last_name, :email
end
end
送信する属性を指定する(ここでは:first_name
と:last_name
と:email
に限定します)ことも、指定しない(この場合すべての属性が送信される)こともできます。
class Product < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
# すべての属性が送信される
end
end
add_attribute
メソッドを用いて、モデルのすべての属性に加えて別の属性を送信することもできます。
class Product < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
# すべての属性の他にextra_attrも送信される
add_attribute :extra_attr
end
def extra_attr
"extra_val"
end
end
関連性の高さ
私たちの提供する設定では、インデックス全体の関連性の高さ(relevancy)をチューニングするさまざまな方法が使えます。その中でも最も重要性が高いのは、「検索可能な属性(searchable attributes)」と、「レコードの人気(record popularity)」を反映するいくつかの属性です。
class Product < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
# Algoliaレコードのビルドに使う属性のリスト
attributes :title, :subtitle, :description, :likes_count, :seller_name
# 検索したい属性を`searchableAttributes`設定で定義する
# (旧attributesToIndex)(ここでは`title`、`subtitle`、`description`)。
# 重要性の高い順にリストアップする必要がある。
# `description`に`unordered`とタグ付けすることでその属性のマッチ位置への影響を回避している。
searchableAttributes ['title', 'subtitle', 'unordered(description)']
# `customRanking`設定はランキングの基準(criteria)を定義するもので、
# 2つのレコードのテキスト関連性が等しいかどうかを比較するのに用いられる。
# これはそのレコードの人気(popularity)を反映する。
customRanking ['desc(likes_count)']
end
end
インデックス化
特定のモデルをインデックス化するには、そのクラスで単にreindex
を呼び出します。
Product.reindex
すべてのモデルをインデックス化する場合は以下のようにします。
Rails.application.eager_load! # 全モデルが読み込み済みであること(development環境では必須)
algolia_models = ActiveRecord::Base.descendants.select{ |model| model.respond_to?(:reindex) }
algolia_models.each(&:reindex)
フロントエンド検索(リアルタイムエクスペリエンス)
従来の検索ロジックや機能は、バックエンドで実装される傾向がありました。この方法は、ユーザーが検索クエリを手入力して検索を実行し、結果ページにリダイレクトするという検索エクスペリエンスであれば事足りました。
検索をバックエンドで実装する必然性はもはやありません。現実には、ほとんどの場合ネットワークの遅延や処理の遅延が重なってパフォーマンスが悪化します。そこで、私たちが開発したJavaScript API Clientの利用を強くおすすめします。あらゆる検索リクエストをユーザーのブラウザやスマートフォンやクライアントから直接発行することで、トータルの検索遅延を削減しつつ、サーバーの負荷も同時に軽減します。
私たちのJS APIクライアントはgemに組み込まれているので、JavaScriptマニフェストの手頃な場所(Rails 3.1以降ならapplication.js
など)でalgolia/v3/algoliasearch.min
をrequire
するだけで準備できます。
//= require algolia/v3/algoliasearch.min
あとは以下のようなJavaScriptコードでできます。
var client = algoliasearch(ApplicationID, Search-Only-API-Key);
var index = client.initIndex('YourIndexName');
index.search('something', { hitsPerPage: 10, page: 0 })
.then(function searchDone(content) {
console.log(content)
})
.catch(function searchFailure(err) {
console.error(err);
});
先ごろ(2015年3月)JavaScriptクライアントの新しいバージョン(V3)をリリースしました。V2をお使いの方は移行ガイドをお読みください。
バックエンド検索
注意: クエリをサーバーから送信せずにエンドユーザーのブラウザから直接クエリ送信するのであれば、JavaScript API Clientを使うことをおすすめします。
1件の検索はORMに沿ったオブジェクトを返しますが、そのときにデータベースからの再読み込みが発生します。トータルの遅延とサーバーの負荷を削減するためにも、クエリ実行はJavaScript API Clientで行うことをおすすめします。
hits = Contact.search("jon doe")
p hits
p hits.raw_answer # 元の生JSON answerを取得する
各ORMオブジェクトにはhighlight_result
属性が1つずつ追加されます。
hits[0].highlight_result['first_name']['value']
データベースからのオブジェクト再読み込みを行わずにAPIから生JSON answerを取り出したい場合は、次の方法が使えます。
json_answer = Contact.raw_search("jon doe")
p json_answer
p json_answer['hits']
p json_answer['facets']
検索パラメータは、インデックス設定から静的に指定することも、または検索時にsearch
メソッドの第2引数でsearch parametersを動的に指定することもできます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
attribute :first_name, :last_name, :email
# インデックス設定に保存されているデフォルトの検索パラメータ
minWordSizefor1Typo 4
minWordSizefor2Typos 8
hitsPerPage 42
end
end
# 動的な検索パラメータ
p Contact.raw_search('jon doe', { hitsPerPage: 5, page: 2 })
バックエンドのページネーション
私たちは、あらゆる検索の実行(すなわちページネーションも)をフロントエンドのJavaScriptで行うことを強くおすすめしていますが、ページネーションのバックエンドとしてwill_paginateとkaminariもサポートします。
:will_paginate
を用いる場合は以下のように:pagination_backend
で指定します。
AlgoliaSearch.configuration = { application_id: 'YourApplicationID', api_key: 'YourAPIKey', pagination_backend: :will_paginate }
これで、search
メソッドを呼び出せばたちどころにページネーションされた結果が表示されます。
# コントローラ
@results = MyModel.search('foo', hitsPerPage: 10)
# ビュー(will_paginateを使う場合)
<%= will_paginate @results %>
# ビュー(kaminariを使う場合)
<%= paginate @results %>
タグ付け
tags
メソッドで以下のようにレコードにタグを追加できます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
tags ['trusted']
end
end
以下のように動的な値も使えます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
tags do
[first_name.blank? || last_name.blank? ? 'partial' : 'full', has_valid_email? ? 'valid_email' : 'invalid_email']
end
end
end
結果セットを特定のタグで絞り込むには、クエリ発行時に{ tagFilters: 'tagvalue' }
または{ tagFilters: ['tagvalue1', 'tagvalue2'] }
を検索パラメータとして指定します。
ファセット
検索結果でさらにfacets
メソッドを呼ぶことで、ファセットを取得できます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
# [...]
# ファセットで使える属性のリストを指定する
attributesForFaceting [:company, :zip_code]
end
end
hits = Contact.search('jon doe', { facets: '*' })
p hits # ORM-compliant array of objects
p hits.facets # extra method added to retrieve facets
p hits.facets['company'] # facet values+count of facet 'company'
p hits.facets['zip_code'] # facet values+count of facet 'zip_code'
raw_json = Contact.raw_search('jon doe', { facets: '*' })
p raw_json['facets']
ファセットの検索
以下のようにファセットの値も検索できます。
Product.search_for_facet_values('category', 'Headphones') # {value, highlighted, count}の配列
このメソッドには、クエリで使える任意のパラメータを渡せます。これによって、そのクエリにマッチしそうな結果だけを返すように調整できます。
# 「red Apple products」(およびそれらの個数のみ)を含むカテゴリだけを返す
Product.search_for_facet_values('category', 'phone', {
query: 'red',
filters: 'brand:Apple'
}) # 「red Apple products」にリンクするphoneカテゴリの配列
グループ化(group by)
グループ化をdistinctに行う方法について詳しくはこちらをご覧ください。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
# [...]
# レコードをグループ化する属性を指定する
# (ここではcompanyでレコードをグループ化する)
attributeForDistinct "company"
end
end
地理的な検索(geo-search)
レコードの地理上の位置で絞り込むにはgeoloc
メソッドを使います。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
geoloc :lat_attr, :lng_attr
end
end
結果セットをSan Joseの周囲50km以内に絞り込むには、クエリ発行時に{ aroundLatLng: "37.33, -121.89", aroundRadius: 50000 }
を検索パラメータとして指定します。
オプション
自動インデックスと非同期実行
インデックスは、レコードが1件保存されるたびに「非同期的に」反映され、レコードが1件削除(destroy)されるたびにインデックスから「非同期に」削除されます。具体的には、ADDやDELETEを伴うネットワーク呼び出しは同期的にAlgolia APIに送信されますが、Algoliaのエンジンでの処理は非同期的に行われます。つまり、直後だと結果が反映されない可能性があります。
自動インデックスやインデックスからの自動削除の設定は、以下のオプションで無効にできます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch auto_index: false, auto_remove: false do
attribute :first_name, :last_name, :email
end
end
自動インデックスを一時的に無効にする
自動インデックスは、without_auto_index
スコープで一時的に無効にできます。これはパフォーマンス上の理由でよく使われます。
Contact.delete_all
Contact.without_auto_index do
1.upto(10000) { Contact.create! attributes } # このブロック内では自動インデックスが動かない
end
Contact.reindex! # バッチ操作を用いる
キューとバックグラウンドジョブ
自動インデックスや自動削除の処理を設定することで、キューを用いてこれらの処理をバックグラウンド実行できます。デフォルトではActive Job(Rails 4.2以降)のキューが用いられますが、独自のキューイングメカニズムを定義することもできます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch enqueue: true do # ActiveJobは`algoliasearch`キューでトリガされる
attribute :first_name, :last_name, :email
end
end
考慮すべき点
更新や削除をバックグラウンドで行う場合、ジョブの実際の実行時より前のタイミングでデータベースにレコードの削除がコミットされる可能性があります。万一、レコードを削除するためにレコードをデータベースから読み込むと、ActiveRecord#find
がRecordNotFoundで失敗します。
このような場合は、ActiveRecordからのレコード読み込みをバイパスしてインデックスを直接操作する方法があります。
class MySidekiqWorker
def perform(id, remove)
if remove
# レコードがデータベースから削除された可能性があれば
# ActiveRecord#findで読み込めない
index = Algolia::Index.new("index_name")
index.delete_object(id)
else
# レコードは存在するはず
c = Contact.find(id)
c.index!
end
end
end
Sidekiqの場合
Sidekiqの場合は次のようにします。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch enqueue: :trigger_sidekiq_worker do
attribute :first_name, :last_name, :email
end
def self.trigger_sidekiq_worker(record, remove)
MySidekiqWorker.perform_async(record.id, remove)
end
end
class MySidekiqWorker
def perform(id, remove)
if remove
# レコードがデータベースから削除された可能性があるので
# ActiveRecord#findで読み込めない
index = Algolia::Index.new("index_name")
index.delete_object(id)
else
# レコードは存在するはず
c = Contact.find(id)
c.index!
end
end
end
DelayedJobの場合
delayed_jobの場合は次のようにします。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch enqueue: :trigger_delayed_job do
attribute :first_name, :last_name, :email
end
def self.trigger_delayed_job(record, remove)
if remove
record.delay.remove_from_index!
else
record.delay.index!
end
end
end
同期処理とテストについて
次のオプションを設定することで、インデックス化とインデックスからの削除を同期的に行うよう強制できます(この場合、gemはwait_task
メソッドを呼ぶことで、メソッドから戻ったときにこの操作に対応します)。ただし、この操作は非推奨です(テスト目的を除く)。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch synchronous: true do
attribute :first_name, :last_name, :email
end
end
インデックス名をカスタマイズする
デフォルトではクラス名がインデックス名に使われます(「Contact」など)。index_name
オプションでインデックス名をカスタマイズできます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch index_name: "MyCustomName" do
attribute :first_name, :last_name, :email
end
end
インデックス名に環境を追加する
以下のオプションを用いて、Railsの現在の環境をインデックス名の末尾に追加できます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch per_environment: true do # インデックス名は"Contact_#{Rails.env}"となる
attribute :first_name, :last_name, :email
end
end
属性定義のカスタマイズ
複雑な属性値をブロックで指定できます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
attribute :email
attribute :full_name do
"#{first_name} #{last_name}"
end
add_attribute :full_name2
end
def full_name2
"#{first_name} #{last_name}"
end
end
注意: この種のコードを用いて属性を追加で定義すると、その直後から属性の変更をこのgemで検出不可能になってしまいます(このgemではRailsの#{attribute}_changed?
メソッドで変更を検出しています)。その結果、レコードの属性が変更されていない場合にもレコードがAPIにプッシュされます。次のように_changed?
メソッドを作成することでこの振る舞いを回避できます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch do
attribute :email
attribute :full_name do
"#{first_name} #{last_name}"
end
end
def full_name_changed?
first_name_changed? || last_name_changed?
end
end
ネステッドオブジェクトやネステッドリレーションについて
リレーションシップの定義
追加の属性を定義し、JSONに沿った任意のオブジェクト(配列、ハッシュ、配列とハッシュの組み合わせのいずれか)を返すネステッドオブジェクトを簡単に埋め込むことができます。
class Profile < ActiveRecord::Base
include AlgoliaSearch
belongs_to :user
has_many :specializations
algoliasearch do
attribute :user do
# ネステッド"user"オブジェクトを`name` + `email`に制限
{ name: user.name, email: user.email }
end
attribute :public_specializations do
# public specializationの配列をビルド(`title`と`another_attr`のみを含む)
specializations.select { |s| s.public? }.map do |s|
{ title: s.title, another_attr: s.another_attr }
end
end
end
end
ネステッドな子オブジェクトの変更を反映させる
Active Recordの場合
Active Recordでは、touch
とafter_touch
で行います。
# app/models/app.rb
class App < ApplicationRecord
include AlgoliaSearch
belongs_to :author, class_name: :User
after_touch :index!
algoliasearch do
attribute :title
attribute :author do
author.as_json
end
end
end
# app/models/user.rb
class User < ApplicationRecord
# belongs_to関連付けを使う場合は
# - `touch: true`を使うこと
# - `after_save`フックは定義しないこと
has_many :apps, foreign_key: :author_id
after_save { apps.each(&:touch) }
end
Sequelの場合
Sequelではtouch
プラグインで変更を反映できます。
# app/models/app.rb
class App < Sequel::Model
include AlgoliaSearch
many_to_one :author, class: :User
plugin :timestamps
plugin :touch
algoliasearch do
attribute :title
attribute :author do
author.to_hash
end
end
end
# app/models/user.rb
class User < Sequel::Model
one_to_many :apps, key: :author_id
plugin :timestamps
# この関連付けは利用不可(これはafter_saveをトリガしない)
plugin :touch
# ここでtouchされる必要のある関連付けを定義する
# 効率はよくないが、after_saveをトリガできるようになる
def touch_associations
apps.map(&:touch)
end
def touch
super
touch_associations
end
end
カスタムobjectID
objectID
は、デフォルトではそのレコードのid
に基づきます。:id
オプションを指定すればこの振る舞いを変更できます(ただしuniqフィールドを使うこと)。
class UniqUser < ActiveRecord::Base
include AlgoliaSearch
algoliasearch id: :uniq_name do
end
end
制約でデータのサブセットのみをインデックス化する
:if
オプションや:unless
オプションを用いて、レコードのインデックス化に制約を追加できます。
これによって、条件付きインデックス化や、ドキュメントごとのインデックス化ができるようになります。
class Post < ActiveRecord::Base
include AlgoliaSearch
algoliasearch if: :published?, unless: :deleted? do
end
def published?
# [...]
end
def deleted?
# [...]
end
end
注意: これらの制約を使うと、インデックスをデータベースと同期するために直ちにaddObjects
呼び出しやdeleteObjects
呼び出しが実行されるようになります。その場合、ステートレスなgemからはオブジェクトが制約とマッチするかどうかを認識できなくなるか、一切マッチしなくなるので、私たちはADD操作やDELETE操作を送信するよう強制しています。_changed?
メソッドを作成することでこの振る舞いを変更できます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
algoliasearch if: :published do
end
def published
# trueかfalseを返す
end
def published_changed?
# 「published」ステートが変更された場合にのみtrueを返す
end
end
以下のいずれかの方法で、レコードのサブセットをインデックス化できます。
# will generate batch API calls (recommended)
MyModel.where('updated_at > ?', 10.minutes.ago).reindex!
MyModel.index_objects MyModel.limit(5)
サニタイザ
sanitize
オプションで属性をすべてサニタイズできます。属性に含まれるHTMLタグはすべて取り除かれます。
class User < ActiveRecord::Base
include AlgoliaSearch
algoliasearch per_environment: true, sanitize: true do
attributes :name, :email, :company
end
end
Rails 4.2以降をご利用の場合は、rails-html-sanitizer
への依存も必要です。
gem 'rails-html-sanitizer'
UTF-8エンコーディング
force_utf8_encoding
オプションで属性をすべて強制的にUTF-8エンコーディングにできます。
class User < ActiveRecord::Base
include AlgoliaSearch
algoliasearch force_utf8_encoding: true do
attributes :name, :email, :company
end
end
注意: このオプションはRuby 1.8と互換性がありません。
例外処理
raise_on_failure
オプションで、Algolia APIへのアクセスを試行中にraiseされる可能性のある例外を無効にできます。
class Contact < ActiveRecord::Base
include AlgoliaSearch
# development環境でのみ例外をraiseする
algoliasearch raise_on_failure: Rails.env.development? do
attribute :first_name, :last_name, :email
end
end
設定例
以下は、実際に使われている設定例です(HN Searchより)。
class Item < ActiveRecord::Base
include AlgoliaSearch
algoliasearch per_environment: true do
# the list of attributes sent to Algolia's API
attribute :created_at, :title, :url, :author, :points, :story_text, :comment_text, :author, :num_comments, :story_id, :story_title
# integer version of the created_at datetime field, to use numerical filtering
attribute :created_at_i do
created_at.to_i
end
# `title` is more important than `{story,comment}_text`, `{story,comment}_text` more than `url`, `url` more than `author`
# btw, do not take into account position in most fields to avoid first word match boost
searchableAttributes ['unordered(title)', 'unordered(story_text)', 'unordered(comment_text)', 'unordered(url)', 'author']
# tags used for filtering
tags do
[item_type, "author_#{author}", "story_#{story_id}"]
end
# use associated number of HN points to sort results (last sort criteria)
customRanking ['desc(points)', 'desc(num_comments)']
# google+, $1.5M raises, C#: we love you
separatorsToIndex '+#$'
end
def story_text
item_type_cd != Item.comment ? text : nil
end
def story_title
comment? && story ? story.title : nil
end
def story_url
comment? && story ? story.url : nil
end
def comment_text
comment? ? text : nil
end
def comment?
item_type_cd == Item.comment
end
# [...]
end
インデックス
手動でのインデックス化
index!
インスタンスメソッドでインデクス化をトリガできます。
c = Contact.create!(params[:contact])
c.index!
インデックスからの手動削除
remove_from_index!
インスタンスメソッドでインデックスからの削除をトリガできます。
c.remove_from_index!
c.destroy
再インデックス化
このgemでは、全オブジェクトの再インデックス化方法を2とおり提供しています。
アトミックな再インデックス化
reindex
クラスメソッドは、該当の全オブジェクトを<INDEX_NAME>.tmp
という一時インデックスを作成してから、この一時インデックスを(アトミックにインデックス化完了した)最終インデックスに移動することによって、(削除済みオブジェクトも考慮に入れて)全レコードを再インデックス化します。これは、全コンテンツを再インデックス化する最も安全な方法です。
Contact.reindex
注意: インデックス固有のAPIキーを利用している場合は、<INDEX_NAME>
と<INDEX_NAME>.tmp
の両方を許可してください。
警告: このようなアトミックな再インデックス化は、モデルのスコープやフィルタがかかっている状態で行うべきではありません。理由は、この操作によってインデックス全体が置き換わり、フィルタされたオブジェクトだけが残ってしまうためです。例: MyModel.where(...).reindex
ではなくMyModel.where(...).reindex!
とすること(末尾の!
は必ず付けること!!!)。
正規の再インデックス化
対象の全オブジェクトを(一時インデックスを使わず、除外されたオブジェクトを削除することもなく)インプレースで再インデックス化するには、reindex!
クラスメソッドを使います。
Contact.reindex!
インデックスをクリアする
インデックスをクリアするには、clear_index!
クラスメソッドを使います。
Contact.clear_index!
背後のインデックスにアクセスする
index
クラスメソッドを呼び出すことで、背後のindex
オブジェクトにアクセスできます。
index = Contact.index
# index.get_settings, index.partial_update_object, ...
primary/replica
add_replica
メソッドを使ってreplicaインデックスを定義できます。primary設定をreplicaで継承したい場合はreplicaのブロックでinherit: true
をお使いください。
class Book < ActiveRecord::Base
attr_protected
include AlgoliaSearch
algoliasearch per_environment: true do
searchableAttributes [:name, :author, :editor]
# `author`のみで検索する目的でreplicaインデックスを定義する
add_replica 'Book_by_author', per_environment: true do
searchableAttributes [:author]
end
# 他はメインブロックと同じで並び順だけカスタマイズした
# replicaインデックスを定義する
add_replica 'Book_custom_order', inherit: true, per_environment: true do
customRanking ['asc(rank)']
end
end
end
replicaで検索するには以下のコードを使います。
Book.raw_search 'foo bar', replica: 'Book_by_editor'
# または
Book.search 'foo bar', replica: 'Book_by_editor'
単一のインデックスを共有する
1つのインデックスを複数のモデルで共有するのがよいこともあります。これを実装するには、背後のどのモデルでも決してobjectID
がコンフリクトしないようにする必要が生じます。
class Student < ActiveRecord::Base
attr_protected
include AlgoliaSearch
algoliasearch index_name: 'people', id: :algolia_id do
# [...]
end
private
def algolia_id
"student_#{id}" # teacherとstudentのIDがコンフリクトしないようにすること
end
end
class Teacher < ActiveRecord::Base
attr_protected
include AlgoliaSearch
algoliasearch index_name: 'people', id: :algolia_id do
# [...]
end
private
def algolia_id
"teacher_#{id}" # teacherとstudentのIDがコンフリクトしないようにすること
end
end
注意: 複数のモデルを元にした1つのインデックスを対象とする場合、MyModel.reindex
は絶対に使わないでください。使うのはMyModel.reindex!
だけです。reindex
メソッドは、再インデックス化をアトミックに行う目的で一時インデックスを用います。これが使われると、生成されるインデックスにはモデルの現在のレコードしか含まれなくなってしまいます(他のレコードが再インデックス化されません)。
複数のインデックスを対象に設定する
add_index
メソッドを用いることで、1つのレコードを複数のインデックスでインデックス化できます。
class Book < ActiveRecord::Base
attr_protected
include AlgoliaSearch
PUBLIC_INDEX_NAME = "Book_#{Rails.env}"
SECURED_INDEX_NAME = "SecuredBook_#{Rails.env}"
# すべての本を'SECURED_INDEX_NAME'インデックスに保存する
algoliasearch index_name: SECURED_INDEX_NAME do
searchableAttributes [:name, :author]
# securityをタグに変換する
tags do
[released ? 'public' : 'private', premium ? 'premium' : 'standard']
end
# publicな(つまりreleasedだがpremiumではない)本を
# 'PUBLIC_INDEX_NAME'インデックスに保存する
add_index PUBLIC_INDEX_NAME, if: :public? do
searchableAttributes [:name, :author]
end
end
private
def public?
released && !premium
end
end
追加のインデックスで検索するには、次のコードを使います。
Book.raw_search 'foo bar', index: 'Book_by_editor'
# or
Book.search 'foo bar', index: 'Book_by_editor'
テスト
テストの注意点
specを実行するために、ALGOLIA_APPLICATION_ID
とALGOLIA_API_KEY
の環境変数を設定してください。テストで作成および削除したインデックスは、productionアカウントでは決して使わないでください。
可能なら次のようにdisable_indexing
オプションを設定し、API呼び出しでインデックス化操作(追加/更新/削除)をすべて無効にしておきましょう。
class User < ActiveRecord::Base
include AlgoliaSearch
algoliasearch per_environment: true, disable_indexing: Rails.env.test? do
end
end
class User < ActiveRecord::Base
include AlgoliaSearch
algoliasearch per_environment: true, disable_indexing: Proc.new { Rails.env.test? || more_complex_condition } do
end
end
またはAlgolia API呼び出しをモック(mock)にしてもよいでしょう。私たちは、algolia/webmock
を使えるサンプル設定をWebMockで提供しています。
require 'algolia/webmock'
describe 'With a mocked client' do
before(:each) do
WebMock.enable!
end
it "ここでは一切APIを呼び出してはならない" do
User.create(name: 'My Indexed User') # モック化済み(APIは呼び出されない)
User.search('').should == {} # モック化済み(APIは呼び出されない)
end
after(:each) do
WebMock.disable!
end
end
おたより発掘
おぉ、コレは嬉しい‼︎ Thanks for translating!! #AlgoliaJP / Rails: 高速リアルタイム検索API「algolia-search-rails」gem README(翻訳) https://t.co/QLOazgtZTf
— Eiji Shinohara (@shinodogg) July 6, 2019