Elasticsearch, Tire и Nested запросы / ассоциации с ActiveRecord
Я использую ElasticSearch с Tire для индексирования и поиска некоторых моделей ActiveRecord, и я искал "правильный" способ индексирования и поиска ассоциаций. Я не нашел того, что кажется наилучшей практикой для этого, поэтому я хотел спросить, есть ли у кого-то подход, который, по его мнению, работает действительно хорошо.
В качестве примера настройки (это сделано, но иллюстрирует проблему), скажем, у нас есть книга с главами. У каждой книги есть название и автор, а также несколько глав. У каждой главы есть текст. Мы хотим проиндексировать поля книги и текст глав, чтобы вы могли искать книгу по автору или любую книгу с определенными словами в ней.
class Book < ActiveRecord::Base
include Tire::Model::Search
include Tire::Model::Callbacks
has_many :chapters
mapping do
indexes :title, :analyzer => 'snowball', :boost => 100
indexes :author, :analyzer => 'snowball'
indexes :chapters, type: 'object', properties: {
chapter_text: { type: 'string', analyzer: 'snowball' }
}
end
end
class Chapter < ActiveRecord::Base
belongs_to :book
end
Итак, я делаю поиск с:
s = Book.search do
query { string query_string }
end
Это не работает, хотя кажется, что индексирование должно это делать. Если вместо этого я индексирую:
indexes :chapters, :as => 'chapters.map{|c| c.chapter_text}.join('|'), :analyzer => 'snowball'
Это делает текст доступным для поиска, но, очевидно, это не очень хороший взлом, и он теряет фактический связанный объект. Я пробовал варианты поиска, такие как:
s = Book.search do
query do
boolean do
should { string query_string }
should { string "chapters.chapter_text:#{query_string}" }
end
end
end
Без удачи там тоже. Если у кого-то есть хороший, ясный пример индексации и поиска связанных объектов ActiveRecord с использованием Tire, то это, похоже, было бы действительно хорошим дополнением к базе знаний.
Спасибо за любые идеи и вклады.
2 ответа
Поддержка ассоциаций ActiveRecord в Tire работает, но требует нескольких настроек внутри вашего приложения. Нет сомнений, что библиотека должна работать лучше, и в будущем это, безусловно, будет.
Тем не менее, вот полный пример конфигурации Tire для работы с ассоциациями Rails в asticsearch: active_record_associations.rb
Позвольте мне выделить несколько вещей здесь.
Касаясь родителю
Во-первых, вы должны убедиться, что уведомили родительскую модель ассоциации об изменениях в ассоциации.
Учитывая, что у нас есть Chapter
модель, которая "принадлежит" Book
нам нужно сделать:
class Chapter < ActiveRecord::Base
belongs_to :book, touch: true
end
Таким образом, когда мы делаем что-то вроде:
book.chapters.create text: "Lorem ipsum...."
book
Экземпляр уведомляется о добавленной главе.
Отвечая на прикосновения
Сортировав эту часть, мы должны уведомить Tire об изменении и соответствующим образом обновить индекс эластичного поиска:
class Book < ActiveRecord::Base
has_many :chapters
after_touch() { tire.update_index }
end
(Там нет вопросов, шина должна перехватить after_touch
уведомления сами по себе, и не заставляют вас делать это. С другой стороны, это свидетельство того, как легко обойти ограничения библиотеки, не повредив глазам.)
Правильная JSON-сериализация в Rails < 3.1
Несмотря на то, что README упоминает, что вы должны отключить автоматическое "добавление корневого ключа в JSON" в Rails < 3.1, многие забывают об этом, поэтому вы должны включить его и в определение класса:
self.include_root_in_json = false
Правильное отображение эластичного поиска
Теперь самое главное в нашей работе - определение правильного отображения для наших документов (моделей):
mapping do
indexes :title, type: 'string', boost: 10, analyzer: 'snowball'
indexes :created_at, type: 'date'
indexes :chapters do
indexes :text, analyzer: 'snowball'
end
end
Обратите внимание, мы индексируем title
с повышением, created_at
в качестве "даты" и текста главы из связанной модели. Все данные фактически "денормализованы" как единый документ в эластичном поиске (если такой термин имеет небольшой смысл).
Правильный документ JSON сериализации
В качестве последнего шага мы должны правильно сериализовать документ в индексе эластичного поиска. Обратите внимание, как мы можем использовать удобное to_json
метод из ActiveRecord:
def to_indexed_json
to_json( include: { chapters: { only: [:text] } } )
end
Со всей этой настройкой мы можем искать в свойствах как Book
и Chapter
части нашего документа.
Пожалуйста, запустите файл active_record_associations.rb Ruby, связанный в начале, чтобы увидеть полную картину.
Для получения дополнительной информации, пожалуйста, обратитесь к этим ресурсам:
- https://github.com/karmi/railscasts-episodes/commit/ee1f6f3
- https://github.com/karmi/railscasts-episodes/commit/03c45c3
- https://github.com/karmi/tire/blob/master/test/models/active_record_models.rb#L10-20
Посмотрите этот ответ Stackru: ElasticSearch & Tire: Использование Mapping и to_indexed_json для получения дополнительной информации о mapping
/ to_indexed_json
взаимодействие.
См. Этот ответ Stackru: индексируйте результаты метода в ElasticSearch (Tire + ActiveRecord), чтобы увидеть, как бороться с n+1 запросами при индексации моделей с ассоциациями.
Я создал это как решение в одном из моих приложений, которое индексирует глубоко вложенный набор моделей
https://gist.github.com/paulnsorensen/4744475
ОБНОВЛЕНИЕ: я теперь выпустил драгоценный камень, который делает это: https://github.com/paulnsorensen/lifesaver