ElasticSearch i gem Tire/Elasticsearch - wyszukiwanie danych na podstawie relacji [SOLVED]

Witam.

Po raz pierwszy mam (nie)przyjemność wykorzystywać elasticsearch’a w swojej aplikacji. Interesuje mnie teraz sposoby bardzo zaawansowanego wyszukiwania. Mianowicie mam modele mniej więcej w takiej relacji

class MyFirstModel < ActiveRecord::Base
  include Tire::Model::Search
  include Tire::Model::Callbacks
  
  has_many :my_second_models
  
  validates_presence_of: :some_data_field
end

class MySecondModel < ActiveRecord::Base
  include Tire::Model::Search
  include Tire::Model::Callbacks
  
  belongs_to :my_first_model, inverse_of: my_second_models
  
  has_many :my_third_models
  
  validates_presence_of: :some_data_field
end

class MyThirdModel < ActiveRecord::Base
  include Tire::Model::Search
  include Tire::Model::Callbacks
  
  belongs_to :my_second_model, inverse_of: my_third_models
  
  has_many :my_fourth_models
  
  validates_presence_of: :some_data_field
end

class MyFourthModel < ActiveRecord::Base
  include Tire::Model::Search
  include Tire::Model::Callbacks
  
  belongs_to :my_third_model, inverse_of: my_fourth_models
  
  validates_presence_of: :some_data_field
end

Na chwile obecną wybieram obiekty klasy MySecondModel (zależne od interesującego mnie obiektu MyFirstModel) i następnie MyThirdModel i MyFourthModel. Pewnie już sobie wyobraziliście ten (paskudny) kod. Dlatego chciałbym pobierać bezpośrednio elementy MyFourthModel, który jest w relacji ze wskazanym MyFirstModel. Czy powinienem użyć tutaj “has_one :my_first_model, through: :my_third_model” i zbudować takie drzewo zależności czy może powinienem wykorzystać opcje Tire i Elasticsearch. Ogólnie do przeszukiwania wynalazłem sobie taki szablon metody search. Chciałbym przeszukiwać MyFourthModel nie tylko po jego parametrach, ale po parametrach pozostałych klas z nią związanych.

class << self
  def search(params)
    page = params[:page] || 1
    my_first_model_id = params[:my_first_model_id]
    param_list = []
    param_list << "my_first_model_id: #{my_first_model_id}" if my_first_model_id
    
    ... # Adding some another parameters to param_list
    
    tire.search(load: true, page: page || 1, per_page: 30) do
      query do
        string param_list.empty? ? '*' : param_list.join(' '), default_operator: 'AND'
      end
    end
  end
end

Mile widziane wszelkie porady/kursy.

Pozdrawiam.

tire nie jest już wspierany.

NOTICE: This library has been renamed and retired in September 2013 (read the explanation). It is not considered compatible with Elasticsearch 1.x.

Have a look at the http://github.com/elasticsearch/elasticsearch-rails suite of gems, which contain similar set of features for ActiveModel/Record and Rails integration as Tire.

O. Nawet nie zauważyłem. Zapoznam się z następcą ale czuję, że problemu to nie rozwiąże.

Ok. Zastąpiłem cześć kodu odpowiedzialną za przeszukiwanie, ale nie mogę tego przetestować. Wszędzie controller.synchronization_reports zwraca mi pustą wartość. bardzo bezsensowna metoda ze sleep 1 zamiast refresh również nic nie daje.

require 'rails_helper'

RSpec.describe SynchronizationReportsController, type: :controller do
  let(:user) { build(:user) }
  before { sign_in user }
  after { sign_out user }

  it "#index" do
    get :index
    expect(response.status).to be == 200
  end

  describe "to check search form", commit: true do
    let(:push_report) { create(:synchronization_report, mode: 'PUSH', synch_completed_time: Time.now - 1.days, server: 'Foo', server_agent_number: 1, source_server: 'Hip', source_server_agent_number: 999) }
    let(:pull_report) { create(:synchronization_report, mode: 'PULL', synch_completed_time: Time.now, server: 'Bar', server_agent_number: 2, source_server: 'Bas', source_server_agent_number: 666) }
    before do
      push_report
      pull_report
      SynchronizationReport.__elasticsearch__.refresh_index!
    end
    after do
      push_report.destroy
      pull_report.destroy
    end

    it "#index with empty search_form" do
      get :index, synchronization_report: {}
      expect(controller.synchronization_reports).to match_array([pull_report, push_report])
    end

    %w( PUSH PULL ).each do |mode|
      eval(%Q{
        it "#index searching #{mode} synchronization_reports" do
          get :index, synchronization_report: { mode: "#{mode}" }
          expect(controller.synchronization_reports).to match_array([#{mode.downcase}_report])
        end
      })

      %w( server server_agent_number source_server source_server_agent_number synch_completed_time ).each do |attr|
        eval(%Q{
          it "#index search using attr #{attr}" do
            get :index, synchronization_report: { #{attr}: #{mode.downcase}_report.send('#{attr}') }
            expect(controller.synchronization_reports).to match_array([#{mode.downcase}_report])
          end
        })
      end
    end
  end
end

Wiem że to nie jest odpowiedź na Twoje pytanie ale jeżeli chodzi o biblioteki do elasticsearcha to ja wykorzystywałem gem Chewy który jest wrapperem na elastichsearch-ruby i był całkiem ok.

Znalazłem przyczynę problemu. Mianowicie gdy mamy taki kod

module Searchable
  extend ActiveSupport::Concern

  included do
    include Elasticsearch::Model
    include Elasticsearch::Model::Callbacks
    index_name [Rails.env, model_name.collection].join('_')

    def delete_document
      logger.debug ["Delating document... ", __elasticsearch__.delete_document].join
    end

    def index_document
      logger.debug ["Indexing document... ", __elasticsearch__.index_document].join
    end
  end

  class_methods do
    def recreate_index!
      __elasticsearch__.create_index! force: true
      __elasticsearch__.refresh_index!
    end
  end
end

i wykonam

bundle exec rake environment elasticsearch:import:all

to curl zwraca mi moją wartość

 curl -XGET localhost:9200/development_synchronization_reports/synchronization_report/5
{"_index":"development_synchronization_reports","_type":"synchronization_report","_id":"5","_version":1,"found":true, "_source" : {"id":5,"mode":"PUSH","server":"LinuxServer","server_agent_number":2,"synch_agent_id":1,"source_sync_id":"ce84d97b-6495-42a8-aa5a-bd8fdfb90796","source_server":null,"source_server_agent_number":null,"synch_completed_time":"2015-07-09T13:28:07.000Z","created_at":"2015-08-14T12:34:43.180Z","updated_at":"2015-08-14T12:34:43.180Z"}}

ale gdy wykonam

RAILS_ENV=test environment=test bundle exec rake environment elasticsearch:import:all

to

curl -XGET localhost:9200/test_synchronization_reports/synchronization_report/12

nie zwraca mi nic. Jakieś pomysły jak zmusić elasticsearch aby to zadziałało?

Nie wiem jak dokładnie rozwiązać Twój problem, natomiast bardzo szybko nauczyłem się w ES tego, że nie warto polegać na gemach, ES jest zbyt skomplikowany, a biblioteki nie zawsze nadążają za rozwojem.

Fakt. ES jest skomplikowany ale bardzo pożyteczny. Bardzo możliwe, że sam rozwiąże problem a to tutaj może posłużyć w przyszłości mnie i mi podobnym gdyby ktoś miał bardzo podobny problem :slight_smile:

Udało się zmusić ES do współpracy

W pliku rails_helper znalazły się takie wpisy

config.before :each, elasticsearch: true do
 # tworzę sobie indeksy
  SynchronizationReport.recreate_index!
 # ...
end

config.before(:suite) do
  # czyszczenie bazy
  DatabaseCleaner.strategy = :truncation, { :except => %w[system_settings] }
  DatabaseCleaner.clean
end

config.after :suite do
  # usuwam indeksy
  SynchronizationReport.__elasticsearch__.client.indices.delete index: SynchronizationReport.index_name
  # ...
end

plik models/concerns/searchable.rb

module Searchable
  extend ActiveSupport::Concern

  included do
    include Elasticsearch::Model
    include Elasticsearch::Model::Callbacks
    index_name [Rails.env, model_name.collection].join('_')

    def delete_document
      logger.debug ["Delating document... ", __elasticsearch__.delete_document].join
    end

    def index_document
      logger.debug ["Indexing document... ", __elasticsearch__.index_document].join
    end
  end

  class_methods do
    def recreate_index!
      __elasticsearch__.create_index! force: true
      __elasticsearch__.refresh_index!
    end
  end
end

I taka mała modyfikacja w testach

before do
  push_report
  pull_report
  #Po utworzeniu elementów importujemy indeksy i je odświerzamy
  SynchronizationReport.import
  SynchronizationReport.__elasticsearch__.refresh_index!
end
after do
  push_report.destroy
  pull_report.destroy
end

# każdy it ma wykorzystywać nasz wpis z rails_helper podany powyżej więc dajemy mu elasticsearch: true
it "#index with empty search_form", elasticsearch: true do
  get :index, synchronization_report: {}
  expect(controller.synchronization_reports).to match_array([pull_report, push_report])
end

Mam nadzieję, ze to komuś w przyszłości pomoże.

1 Like

Gdyby ktoś potrzebowa wyszukiwania/przeszukiwania danych z wykorzystaniem relacji proszę spojrzeć tu http://ericlondon.com/2014/09/02/rails-4-elasticsearch-integration-with-dynamic-facets-and-filters-via-model-concern.html

W moim przypadku ten wpis rozwiązał problem.

Pozdrawiam.

1 Like