Elasticsearch, Tire и вложенные запросы/ассоциации с 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, похоже, это было бы действительно хорошим дополнением к базе знаний здесь.

Спасибо за любые идеи и вклады.


person Masonoise    schedule 27.07.2012    source источник
comment
Если ответ полезен для вас, отметьте его как принятый.   -  person karmi    schedule 01.08.2012
comment
Мне потребовалось немного времени, чтобы проверить и подтвердить (я был вовлечен в другой проект), но да, ваш ответ великолепен, спасибо - я уверен, что он будет полезен многим людям, которые только начинают.   -  person Masonoise    schedule 09.08.2012


Ответы (2)


Поддержка ассоциаций ActiveRecord в Tire работает, но требует пары настроек внутри вашего приложения. Нет никаких сомнений в том, что здесь библиотека должна работать лучше, и в будущем она, безусловно, так и будет.

Тем не менее, вот полноценный пример конфигурации Tire для работы с ассоциациями Rails в elasticsearch: active_record_associations.rb< /а>

Позвольте мне выделить здесь пару вещей.

Прикосновение к родителю

Во-первых, вы должны убедиться, что вы уведомляете родительскую модель ассоциации об изменениях в ассоциации.

Учитывая, что у нас есть модель Chapter, которая «принадлежит» Book, нам нужно сделать:

class Chapter < ActiveRecord::Base
  belongs_to :book, touch: true
end

Таким образом, когда мы делаем что-то вроде:

book.chapters.create text: "Lorem ipsum...."

Экземпляр book получает уведомление о добавленной главе.

Реагирование на прикосновения

Отсортировав эту часть, нам нужно уведомить Tire об изменении и соответствующим образом обновить индекс elasticsearch:

class Book < ActiveRecord::Base
  has_many :chapters
  after_touch() { tire.update_index }
end

(Несомненно, Tire должен перехватывать after_touch уведомления сам по себе, а не заставлять вас это делать. С другой стороны, это свидетельство того, как легко обойти ограничения библиотеки в способ, который не режет глаз.)

Правильная сериализация JSON в Rails ‹ 3.1

Несмотря на то, что в README упоминается, что вы должны отключить автоматическое «добавление корневого ключа в JSON» в Rails ‹ 3.1, многие люди забывают об этом, поэтому вы также должны включить его в определение класса:

self.include_root_in_json = false

Правильное отображение для elasticsearch

Теперь наступает суть нашей работы — определение правильного отображения для наших документов (моделей):

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 как «дату» и текст главы из связанной модели. Все данные фактически «денормализованы» как единый документ в elasticsearch (если такой термин имеет хоть какой-то смысл).

Правильная сериализация документа JSON

В качестве последнего шага мы должны правильно сериализовать документ в индексе elasticsearch. Обратите внимание, как мы можем использовать удобный метод to_json из ActiveRecord:

def to_indexed_json
  to_json( include: { chapters: { only: [:text] } } )
end

Со всеми этими настройками мы можем искать свойства как в Book, так и в Chapter частях нашего документа.

Запустите active_record_associations.rb Ruby-файл, указанный в начале, чтобы увидеть полную картину.

Для получения дополнительной информации, пожалуйста, обратитесь к этим ресурсам:

См. этот ответ StackOverflow: ElasticSearch & Tire: использование сопоставления и to_indexed_json для получения дополнительной информации о взаимодействии mapping / to_indexed_json.

См. этот ответ StackOverflow: Индексировать результаты метода в ElasticSearch (Tire + ActiveRecord), чтобы увидеть, как бороться с n+1 запросами при индексировании моделей с ассоциациями.

person karmi    schedule 29.07.2012
comment
У меня была возможность попробовать это, и это работает; большое спасибо за полноту вашего ответа (и, конечно же, за отличную работу над Tire). В то же время я как бы заставил его работать, создав метод, который делал Chapters.map() для сбора текста, а затем вызывал этот метод в to_indexed_json(), но это, очевидно, что-то вроде хака. Этот подход намного чище. Еще раз спасибо. - person Masonoise; 02.08.2012
comment
Я тоже искал более чистый способ создания ассоциаций. Спасибо за обстоятельность вашего ответа. Жемчужина шины очень крутая. Спасибо за тяжелую работу. - person GreenEggs; 23.08.2012
comment
Это интересно — не хотите ли подробнее рассказать о более чистом способе? Здесь или на Github Проблемы, почта и т. д.? - person karmi; 24.08.2012
comment
На это влияет отношение «многие ко многим» с использованием :through =› ? У меня есть Resource, Tag и ResourceTag. Так запутанно :С. Я не уверен, что я должен добавить: коснитесь - person Tallboy; 26.09.2012
comment
Честно говоря, мне даже не нужно индексировать этот материал, я просто пытаюсь заставить его работать, поэтому мне не нужно использовать load: true. - person Tallboy; 26.09.2012
comment
@karmi Если бы у книги было много категорий, как бы я искал книги определенной категории (с заданным идентификатором)? Book.search{ запрос {термин 'categories.id', 1234}} ?? Как мне найти, нужна помощь в этом stackoverflow.com/questions/14579392/ - person letronje; 29.01.2013

Я создал это как решение в одном из моих приложений, которое индексирует глубоко вложенный набор моделей.

https://gist.github.com/paulnsorensen/4744475

ОБНОВЛЕНИЕ: я выпустил гем, который делает это: https://github.com/paulnsorensen/lifesaver

person paulnsorensen    schedule 09.02.2013
comment
Ваш драгоценный камень все еще работает, я вижу, что он давно не обновлялся - person svelandiag; 25.01.2017
comment
Я не прикасался к нему долгое время с тех пор, как elasticsearch выпустил свой собственный драгоценный камень рельсов. Я бы предложил создавать определенные задания для каждого документа, который вы индексируете, и явно ставить задание в очередь в ваших обратных вызовах модели/службы. - person paulnsorensen; 02.02.2017