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
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
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
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