Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6dd4972

Browse files
miguelffkarmi
authored andcommitted
[MODEL] Implement multi-model search
Related: #10, #30, #50, #129, #346
1 parent d6cce61 commit 6dd4972

File tree

6 files changed

+544
-0
lines changed

6 files changed

+544
-0
lines changed

‎elasticsearch-model/lib/elasticsearch/model.rb‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88

99
require 'elasticsearch/model/client'
1010

11+
require 'elasticsearch/model/multimodel'
12+
1113
require 'elasticsearch/model/adapter'
1214
require 'elasticsearch/model/adapters/default'
1315
require 'elasticsearch/model/adapters/active_record'
1416
require 'elasticsearch/model/adapters/mongoid'
17+
require 'elasticsearch/model/adapters/multiple'
1518

1619
require 'elasticsearch/model/importing'
1720
require 'elasticsearch/model/indexing'
@@ -64,6 +67,50 @@ module Elasticsearch
6467
module Model
6568
METHODS = [:search, :mapping, :mappings, :settings, :index_name, :document_type, :import]
6669

70+
71+
# Keeps a registry of the classes that include `Elasticsearch::Model`
72+
#
73+
class Registry < Array
74+
75+
# Add the class of a model to the registry
76+
#
77+
def self.add(klass)
78+
__instance.add(klass)
79+
end
80+
81+
# List all the registered models
82+
#
83+
# @return [Class]
84+
#
85+
def self.all
86+
__instance.models
87+
end
88+
89+
# Returns the unique instance of the registry
90+
#
91+
# @api private
92+
#
93+
def self.__instance
94+
@instance ||= new
95+
end
96+
97+
def initialize
98+
@models = []
99+
end
100+
101+
# Adds a model to the registry
102+
#
103+
def add(klass)
104+
@models << klass
105+
end
106+
107+
# Gets a copy of the registered models
108+
#
109+
def models
110+
@models.dup
111+
end
112+
end
113+
67114
# Adds the `Elasticsearch::Model` functionality to the including class.
68115
#
69116
# * Creates the `__elasticsearch__` class and instance methods, pointing to the proxy object
@@ -119,6 +166,9 @@ class << self
119166
include Elasticsearch::Model::Importing::ClassMethods
120167
include Adapter.from_class(base).importing_mixin
121168
end
169+
170+
# Add to the registry if it's a class (and not in intermediate module)
171+
Registry.add(base) if base.is_a?(Class)
122172
end
123173
end
124174

@@ -149,6 +199,28 @@ def client=(client)
149199
@client = client
150200
end
151201

202+
# Search across models which include Elasticsearch::Model
203+
#
204+
# @param query_or_payload [String,Hash,Object] The search request definition
205+
# (string, JSON, Hash, or object responding to `to_hash`)
206+
# @param models [Array] The list of Model objects to search
207+
# @param options [Hash] Optional parameters to be passed to the Elasticsearch client
208+
#
209+
# @return [Elasticsearch::Model::Response::Response]
210+
#
211+
# @example Search across specified models
212+
#
213+
# Elasticsearch::Model.search('foo', [Author, Article])
214+
#
215+
# @example Search across all models
216+
#
217+
# Elasticsearch::Model.search('foo')
218+
#
219+
def search(query_or_payload, models=[], options={})
220+
models = Multimodel.new(models)
221+
request = Searching::SearchRequest.new(models, query_or_payload, options)
222+
Response::Response.new(models, request)
223+
end
152224
end
153225
extend ClassMethods
154226

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
module Elasticsearch
2+
module Model
3+
module Adapter
4+
5+
# An adapter to be used for deserializing results from multiple models, retrieved through
6+
# Elasticsearch::Model.search
7+
#
8+
# @see Elasticsearch::Model.search
9+
#
10+
module Multiple
11+
12+
Adapter.register self, lambda { |klass| klass.is_a? Multimodel }
13+
14+
module Records
15+
16+
# Returns an Array, which elements are the model instances represented
17+
# by the search results.
18+
#
19+
# This means that if the models queried are a Mixture of ActiveRecord, Mongoid, or
20+
# POROs, the elements contained in this array will also be instances of those models
21+
#
22+
# Ranking of results across multiple indexes is preserved, and queries made to the different
23+
# model's datasources are minimal.
24+
#
25+
# Internally, it gets the results, as ranked by elasticsearch.
26+
# Then results are grouped by _type
27+
# Then the model corresponding to each _type is queried to retrieve the records
28+
# Finally records are rearranged in the same way results were ranked.
29+
#
30+
# @return [ElasticSearch::Model]
31+
#
32+
def records
33+
@_records ||= begin
34+
result = []
35+
by_type = __records_by_type
36+
__hits.each do |hit|
37+
result << by_type[__type(hit)][hit[:_id]]
38+
end
39+
result.compact
40+
end
41+
end
42+
43+
# Returns the record representation of the results retrieved from Elasticsearch, grouped
44+
# by model type
45+
#
46+
# @example
47+
# {Series =>
48+
# {"1"=> #<Series id: 1, series_name: "The Who S01", created_at: "2015年02月23日 17:18:28">},
49+
#
50+
# "Title =>
51+
# {"1"=> #<Title id: 1, name: "Who Strikes Back", created_at: "2015年02月23日 17:18:28">}}
52+
#
53+
# @api private
54+
#
55+
def __records_by_type
56+
array = __ids_by_type.map do |klass, ids|
57+
records = __type_records(klass, ids)
58+
ids = records.map(&:id).map(&:to_s)
59+
[klass, Hash[ids.zip(records)]]
60+
end
61+
Hash[array]
62+
end
63+
64+
# Returns the records for a specific type
65+
#
66+
# @api private
67+
#
68+
def __type_records(klass, ids)
69+
if (adapter = Adapter.adapters[ActiveRecord]) && adapter.call(klass)
70+
klass.where(klass.primary_key => ids)
71+
elsif (adapter = Adapter.adapters[Mongoid]) && adapter.call(klass)
72+
klass.where(:id.in => ids)
73+
else
74+
klass.find(ids)
75+
end
76+
end
77+
78+
79+
# @return A Hash containing for each type, the ids to retrieve
80+
#
81+
# @example {Series =>["1"], Title =>["1", "5"]}
82+
#
83+
# @api private
84+
#
85+
def __ids_by_type
86+
ids_by_type = {}
87+
__hits.each do |hit|
88+
type = __type(hit)
89+
ids_by_type[type] ||= []
90+
ids_by_type[type] << hit[:_id]
91+
end
92+
ids_by_type
93+
end
94+
95+
# Returns the class of the model associated to a certain hit
96+
#
97+
# A simple class-level memoization over the `_index` and `_type` properties of the hit is applied.
98+
# Hence querying the Model Registry is done the minimal amount of times.
99+
#
100+
# @see Elasticsearch::Model::Registry
101+
#
102+
# @return Class
103+
#
104+
# @api private
105+
#
106+
def __type(hit)
107+
@@__types ||= {}
108+
@@__types[[hit[:_index], hit[:_type]].join("::")] ||= begin
109+
Registry.all.detect { |model| model.index_name == hit[:_index] && model.document_type == hit[:_type] }
110+
end
111+
end
112+
113+
114+
# Memoizes and returns the hits from the response
115+
#
116+
# @api private
117+
#
118+
def __hits
119+
@__hits ||= response.response["hits"]["hits"]
120+
end
121+
end
122+
end
123+
end
124+
end
125+
end
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
module Elasticsearch
2+
module Model
3+
4+
# Wraps a series of models to be used when querying multiple indexes via Elasticsearch::Model.search
5+
#
6+
# @see Elasticsearch::Model.search
7+
#
8+
class Multimodel
9+
10+
attr_reader :models
11+
12+
# @param models [Class] The list of models across which the search will be performed.
13+
def initialize(*models)
14+
@models = models.flatten
15+
@models = Model::Registry.all if @models.empty?
16+
end
17+
18+
# Get the list of index names used for retrieving documents when doing a search across multiple models
19+
#
20+
# @return [String] the list of index names used for retrieving documents
21+
#
22+
def index_name
23+
models.map { |m| m.index_name }
24+
end
25+
26+
# Get the list of document types used for retrieving documents when doing a search across multiple models
27+
#
28+
# @return [String] the list of document types used for retrieving documents
29+
#
30+
def document_type
31+
models.map { |m| m.document_type }
32+
end
33+
34+
# Get the client common for all models
35+
#
36+
# @return Elasticsearch::Transport::Client
37+
#
38+
def client
39+
Elasticsearch::Model.client
40+
end
41+
end
42+
end
43+
end

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /