elasticsearch-model 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +30 -5
- data/examples/activerecord_associations.rb +51 -36
- data/lib/elasticsearch/model.rb +30 -0
- data/lib/elasticsearch/model/adapters/active_record.rb +1 -1
- data/lib/elasticsearch/model/adapters/mongoid.rb +1 -1
- data/lib/elasticsearch/model/adapters/multiple.rb +110 -0
- data/lib/elasticsearch/model/indexing.rb +12 -15
- data/lib/elasticsearch/model/multimodel.rb +83 -0
- data/lib/elasticsearch/model/searching.rb +2 -1
- data/lib/elasticsearch/model/version.rb +1 -1
- data/test/integration/mongoid_basic_test.rb +4 -20
- data/test/integration/multiple_models_test.rb +154 -0
- data/test/test_helper.rb +30 -0
- data/test/unit/adapter_multiple_test.rb +106 -0
- data/test/unit/indexing_test.rb +49 -16
- data/test/unit/multimodel_test.rb +38 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62d28c182c4b21b048ad496387e6cc139a79ee1b
|
4
|
+
data.tar.gz: f4bdb161bc448d38ba4fd2da0e7eb8c74fdebd15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce7abb8673ae21d515c48f64e07a057ea82302132cedce60eeade565a44f9743fc0e811903df29aa93fa7cb55b96011d52ec25287064df5c846d5474da5125a4
|
7
|
+
data.tar.gz: 0eb48de6a8a1f7b264582b764de7013727d8909fa024a1777a9bad4304fa57649957dbc54b92fff24aa140364ee38f1ad35ddfc35da552cbab1ee4b0064e07f2
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 0.1.7
|
2
|
+
|
3
|
+
* Improved examples and instructions in README and code annotations
|
4
|
+
* Prevented index methods to swallow all exceptions
|
5
|
+
* Added the `:validate` option to the `save` method for models
|
6
|
+
* Added support for searching across multiple models (elastic/elasticsearch-rails#345),
|
7
|
+
including documentation, examples and tests
|
8
|
+
|
1
9
|
## 0.1.6
|
2
10
|
|
3
11
|
* Improved documentation
|
data/README.md
CHANGED
@@ -216,8 +216,9 @@ response.records.to_a
|
|
216
216
|
```
|
217
217
|
|
218
218
|
The returned object is the genuine collection of model instances returned by your database,
|
219
|
-
i.e. `ActiveRecord::Relation` for ActiveRecord, or `Mongoid::Criteria` in case of MongoDB.
|
220
|
-
|
219
|
+
i.e. `ActiveRecord::Relation` for ActiveRecord, or `Mongoid::Criteria` in case of MongoDB.
|
220
|
+
|
221
|
+
This allows you to chain other methods on top of search results, as you would normally do:
|
221
222
|
|
222
223
|
```ruby
|
223
224
|
response.records.where(title: 'Quick brown fox').to_a
|
@@ -252,11 +253,35 @@ response.records.each_with_hit { |record, hit| puts "* #{record.title}: #{hit._s
|
|
252
253
|
# * Fast black dogs: 0.02250402
|
253
254
|
```
|
254
255
|
|
256
|
+
#### Searching multiple models
|
257
|
+
|
258
|
+
It is possible to search across multiple models with the module method:
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
Elasticsearch::Model.search('fox', [Article, Comment]).results.to_a.map(&:to_hash)
|
262
|
+
# => [
|
263
|
+
# {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_score"=>0.35136628, "_source"=>...},
|
264
|
+
# {"_index"=>"comments", "_type"=>"comment", "_id"=>"1", "_score"=>0.35136628, "_source"=>...}
|
265
|
+
# ]
|
266
|
+
|
267
|
+
Elasticsearch::Model.search('fox', [Article, Comment]).records.to_a
|
268
|
+
# Article Load (0.3ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (1)
|
269
|
+
# Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" IN (1,5)
|
270
|
+
# => [#<Article id: 1, title: "Quick brown fox">, #<Comment id: 1, body: "Fox News">, ...]
|
271
|
+
```
|
272
|
+
|
273
|
+
By default, all models which include the `Elasticsearch::Model` module are searched.
|
274
|
+
|
275
|
+
NOTE: It is _not_ possible to chain other methods on top of the `records` object, since it
|
276
|
+
is a heterogenous collection, with models potentially backed by different databases.
|
277
|
+
|
255
278
|
#### Pagination
|
256
279
|
|
257
280
|
You can implement pagination with the `from` and `size` search parameters. However, search results
|
258
281
|
can be automatically paginated with the [`kaminari`](http://rubygems.org/gems/kaminari) or
|
259
282
|
[`will_paginate`](https://github.com/mislav/will_paginate) gems.
|
283
|
+
(The pagination gems must be added before the Elasticsearch gems in your Gemfile,
|
284
|
+
or loaded first in your application.)
|
260
285
|
|
261
286
|
If Kaminari or WillPaginate is loaded, use the familiar paging methods:
|
262
287
|
|
@@ -430,15 +455,15 @@ class Article < ActiveRecord::Base
|
|
430
455
|
include Elasticsearch::Model
|
431
456
|
|
432
457
|
after_commit on: [:create] do
|
433
|
-
index_document if self.published?
|
458
|
+
__elasticsearch__.index_document if self.published?
|
434
459
|
end
|
435
460
|
|
436
461
|
after_commit on: [:update] do
|
437
|
-
update_document if self.published?
|
462
|
+
__elasticsearch__.update_document if self.published?
|
438
463
|
end
|
439
464
|
|
440
465
|
after_commit on: [:destroy] do
|
441
|
-
delete_document if self.published?
|
466
|
+
__elasticsearch__.delete_document if self.published?
|
442
467
|
end
|
443
468
|
end
|
444
469
|
```
|
@@ -59,9 +59,43 @@ ActiveRecord::Schema.define(version: 1) do
|
|
59
59
|
add_index(:comments, :article_id)
|
60
60
|
end
|
61
61
|
|
62
|
+
# ----- Elasticsearch client setup ----------------------------------------------------------------
|
63
|
+
|
64
|
+
Elasticsearch::Model.client = Elasticsearch::Client.new log: true
|
65
|
+
Elasticsearch::Model.client.transport.logger.formatter = proc { |s, d, p, m| "\e[32m#{m}\n\e[0m" }
|
66
|
+
|
67
|
+
# ----- Search integration ------------------------------------------------------------------------
|
68
|
+
|
69
|
+
module Searchable
|
70
|
+
extend ActiveSupport::Concern
|
71
|
+
|
72
|
+
included do
|
73
|
+
include Elasticsearch::Model
|
74
|
+
include Elasticsearch::Model::Callbacks
|
75
|
+
|
76
|
+
include Indexing
|
77
|
+
after_touch() { __elasticsearch__.index_document }
|
78
|
+
end
|
79
|
+
|
80
|
+
module Indexing
|
81
|
+
|
82
|
+
# Customize the JSON serialization for Elasticsearch
|
83
|
+
def as_indexed_json(options={})
|
84
|
+
self.as_json(
|
85
|
+
include: { categories: { only: :title},
|
86
|
+
authors: { methods: [:full_name], only: [:full_name] },
|
87
|
+
comments: { only: :text }
|
88
|
+
})
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
62
93
|
# ----- Model definitions -------------------------------------------------------------------------
|
63
94
|
|
64
95
|
class Category < ActiveRecord::Base
|
96
|
+
include Elasticsearch::Model
|
97
|
+
include Elasticsearch::Model::Callbacks
|
98
|
+
|
65
99
|
has_and_belongs_to_many :articles
|
66
100
|
end
|
67
101
|
|
@@ -81,6 +115,8 @@ class Authorship < ActiveRecord::Base
|
|
81
115
|
end
|
82
116
|
|
83
117
|
class Article < ActiveRecord::Base
|
118
|
+
include Searchable
|
119
|
+
|
84
120
|
has_and_belongs_to_many :categories, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ],
|
85
121
|
after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ]
|
86
122
|
has_many :authorships
|
@@ -88,43 +124,13 @@ class Article < ActiveRecord::Base
|
|
88
124
|
has_many :comments
|
89
125
|
end
|
90
126
|
|
91
|
-
class Article < ActiveRecord::Base; delegate :size, to: :comments, prefix: true; end
|
92
|
-
|
93
127
|
class Comment < ActiveRecord::Base
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
# ----- Search integration ------------------------------------------------------------------------
|
98
|
-
|
99
|
-
module Searchable
|
100
|
-
extend ActiveSupport::Concern
|
101
|
-
|
102
|
-
included do
|
103
|
-
include Elasticsearch::Model
|
104
|
-
include Elasticsearch::Model::Callbacks
|
105
|
-
|
106
|
-
__elasticsearch__.client = Elasticsearch::Client.new log: true
|
107
|
-
__elasticsearch__.client.transport.logger.formatter = proc { |s, d, p, m| "\e[32m#{m}\n\e[0m" }
|
108
|
-
|
109
|
-
include Indexing
|
110
|
-
after_touch() { __elasticsearch__.index_document }
|
111
|
-
end
|
112
|
-
|
113
|
-
module Indexing
|
128
|
+
include Elasticsearch::Model
|
129
|
+
include Elasticsearch::Model::Callbacks
|
114
130
|
|
115
|
-
|
116
|
-
def as_indexed_json(options={})
|
117
|
-
self.as_json(
|
118
|
-
include: { categories: { only: :title},
|
119
|
-
authors: { methods: [:full_name], only: [:full_name] },
|
120
|
-
comments: { only: :text }
|
121
|
-
})
|
122
|
-
end
|
123
|
-
end
|
131
|
+
belongs_to :article, touch: true
|
124
132
|
end
|
125
133
|
|
126
|
-
Article.__send__ :include, Searchable
|
127
|
-
|
128
134
|
# ----- Insert data -------------------------------------------------------------------------------
|
129
135
|
|
130
136
|
# Create category
|
@@ -149,14 +155,23 @@ article.authors << author
|
|
149
155
|
|
150
156
|
# Add comment
|
151
157
|
#
|
152
|
-
article.comments.create text: 'First comment'
|
158
|
+
article.comments.create text: 'First comment for article One'
|
159
|
+
article.comments.create text: 'Second comment for article One'
|
153
160
|
|
154
|
-
|
161
|
+
Elasticsearch::Model.client.indices.refresh index: Elasticsearch::Model::Registry.all.map(&:index_name)
|
162
|
+
|
163
|
+
puts "\n\e[1mArticles containing 'one':\e[0m", Article.search('one').records.to_a.map(&:inspect), ""
|
164
|
+
|
165
|
+
puts "\n\e[1mModels containing 'one':\e[0m", Elasticsearch::Model.search('one').records.to_a.map(&:inspect), ""
|
166
|
+
|
167
|
+
# Load model
|
155
168
|
#
|
156
169
|
article = Article.all.includes(:categories, :authors, :comments).first
|
157
170
|
|
158
171
|
# ----- Pry ---------------------------------------------------------------------------------------
|
159
172
|
|
173
|
+
puts '', '-'*Pry::Terminal.width!
|
174
|
+
|
160
175
|
Pry.start(binding, prompt: lambda { |obj, nest_level, _| '> ' },
|
161
|
-
input: StringIO.new(
|
176
|
+
input: StringIO.new("article.as_indexed_json\n"),
|
162
177
|
quiet: true)
|
data/lib/elasticsearch/model.rb
CHANGED
@@ -8,10 +8,13 @@ require 'elasticsearch/model/version'
|
|
8
8
|
|
9
9
|
require 'elasticsearch/model/client'
|
10
10
|
|
11
|
+
require 'elasticsearch/model/multimodel'
|
12
|
+
|
11
13
|
require 'elasticsearch/model/adapter'
|
12
14
|
require 'elasticsearch/model/adapters/default'
|
13
15
|
require 'elasticsearch/model/adapters/active_record'
|
14
16
|
require 'elasticsearch/model/adapters/mongoid'
|
17
|
+
require 'elasticsearch/model/adapters/multiple'
|
15
18
|
|
16
19
|
require 'elasticsearch/model/importing'
|
17
20
|
require 'elasticsearch/model/indexing'
|
@@ -119,6 +122,9 @@ module Elasticsearch
|
|
119
122
|
include Elasticsearch::Model::Importing::ClassMethods
|
120
123
|
include Adapter.from_class(base).importing_mixin
|
121
124
|
end
|
125
|
+
|
126
|
+
# Add to the registry if it's a class (and not in intermediate module)
|
127
|
+
Registry.add(base) if base.is_a?(Class)
|
122
128
|
end
|
123
129
|
end
|
124
130
|
|
@@ -149,6 +155,30 @@ module Elasticsearch
|
|
149
155
|
@client = client
|
150
156
|
end
|
151
157
|
|
158
|
+
# Search across multiple models
|
159
|
+
#
|
160
|
+
# By default, all models which include the `Elasticsearch::Model` module are searched
|
161
|
+
#
|
162
|
+
# @param query_or_payload [String,Hash,Object] The search request definition
|
163
|
+
# (string, JSON, Hash, or object responding to `to_hash`)
|
164
|
+
# @param models [Array] The Array of Model objects to search
|
165
|
+
# @param options [Hash] Optional parameters to be passed to the Elasticsearch client
|
166
|
+
#
|
167
|
+
# @return [Elasticsearch::Model::Response::Response]
|
168
|
+
#
|
169
|
+
# @example Search across specific models
|
170
|
+
#
|
171
|
+
# Elasticsearch::Model.search('foo', [Author, Article])
|
172
|
+
#
|
173
|
+
# @example Search across all models which include the `Elasticsearch::Model` module
|
174
|
+
#
|
175
|
+
# Elasticsearch::Model.search('foo')
|
176
|
+
#
|
177
|
+
def search(query_or_payload, models=[], options={})
|
178
|
+
models = Multimodel.new(models)
|
179
|
+
request = Searching::SearchRequest.new(models, query_or_payload, options)
|
180
|
+
Response::Response.new(models, request)
|
181
|
+
end
|
152
182
|
end
|
153
183
|
extend ClassMethods
|
154
184
|
|
@@ -7,7 +7,7 @@ module Elasticsearch
|
|
7
7
|
module ActiveRecord
|
8
8
|
|
9
9
|
Adapter.register self,
|
10
|
-
lambda { |klass| !!defined?(::ActiveRecord::Base) && klass.ancestors.include?(::ActiveRecord::Base) }
|
10
|
+
lambda { |klass| !!defined?(::ActiveRecord::Base) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::ActiveRecord::Base) }
|
11
11
|
|
12
12
|
module Records
|
13
13
|
# Returns an `ActiveRecord::Relation` instance
|
@@ -9,7 +9,7 @@ module Elasticsearch
|
|
9
9
|
module Mongoid
|
10
10
|
|
11
11
|
Adapter.register self,
|
12
|
-
lambda { |klass| !!defined?(::Mongoid::Document) && klass.ancestors.include?(::Mongoid::Document) }
|
12
|
+
lambda { |klass| !!defined?(::Mongoid::Document) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Mongoid::Document) }
|
13
13
|
|
14
14
|
module Records
|
15
15
|
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Model
|
3
|
+
module Adapter
|
4
|
+
|
5
|
+
# An adapter to be used for deserializing results from multiple models,
|
6
|
+
# retrieved through `Elasticsearch::Model.search`
|
7
|
+
#
|
8
|
+
# @see Elasticsearch::Model.search
|
9
|
+
#
|
10
|
+
module Multiple
|
11
|
+
Adapter.register self, lambda { |klass| klass.is_a? Multimodel }
|
12
|
+
|
13
|
+
module Records
|
14
|
+
# Returns a collection of model instances, possibly of different classes (ActiveRecord, Mongoid, ...)
|
15
|
+
#
|
16
|
+
# @note The order of results in the Elasticsearch response is preserved
|
17
|
+
#
|
18
|
+
def records
|
19
|
+
records_by_type = __records_by_type
|
20
|
+
|
21
|
+
response.response["hits"]["hits"].map do |hit|
|
22
|
+
records_by_type[ __type_for_hit(hit) ][ hit[:_id] ]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the collection of records grouped by class based on `_type`
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# {
|
31
|
+
# Foo => {"1"=> #<Foo id: 1, title: "ABC"}, ...},
|
32
|
+
# Bar => {"1"=> #<Bar id: 1, name: "XYZ"}, ...}
|
33
|
+
# }
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
#
|
37
|
+
def __records_by_type
|
38
|
+
result = __ids_by_type.map do |klass, ids|
|
39
|
+
records = __records_for_klass(klass, ids)
|
40
|
+
ids = records.map(&:id).map(&:to_s)
|
41
|
+
[ klass, Hash[ids.zip(records)] ]
|
42
|
+
end
|
43
|
+
|
44
|
+
Hash[result]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the collection of records for a specific type based on passed `klass`
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
#
|
51
|
+
def __records_for_klass(klass, ids)
|
52
|
+
adapter = __adapter_name_for_klass(klass)
|
53
|
+
|
54
|
+
case adapter
|
55
|
+
when Elasticsearch::Model::Adapter::ActiveRecord
|
56
|
+
klass.where(klass.primary_key => ids)
|
57
|
+
when Elasticsearch::Model::Adapter::Mongoid
|
58
|
+
klass.where(:id.in => ids)
|
59
|
+
else
|
60
|
+
klass.find(ids)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the record IDs grouped by class based on type `_type`
|
65
|
+
#
|
66
|
+
# Example:
|
67
|
+
#
|
68
|
+
# { Foo => ["1"], Bar => ["1", "5"] }
|
69
|
+
#
|
70
|
+
# @api private
|
71
|
+
#
|
72
|
+
def __ids_by_type
|
73
|
+
ids_by_type = {}
|
74
|
+
|
75
|
+
response.response["hits"]["hits"].each do |hit|
|
76
|
+
type = __type_for_hit(hit)
|
77
|
+
ids_by_type[type] ||= []
|
78
|
+
ids_by_type[type] << hit[:_id]
|
79
|
+
end
|
80
|
+
ids_by_type
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the class of the model corresponding to a specific `hit` in Elasticsearch results
|
84
|
+
#
|
85
|
+
# @see Elasticsearch::Model::Registry
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
#
|
89
|
+
def __type_for_hit(hit)
|
90
|
+
@@__types ||= {}
|
91
|
+
|
92
|
+
@@__types[ "#{hit[:_index]}::#{hit[:_type]}" ] ||= begin
|
93
|
+
Registry.all.detect do |model|
|
94
|
+
model.index_name == hit[:_index] && model.document_type == hit[:_type]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the adapter registered for a particular `klass` or `nil` if not available
|
100
|
+
#
|
101
|
+
# @api private
|
102
|
+
#
|
103
|
+
def __adapter_name_for_klass(klass)
|
104
|
+
Adapter.adapters.select { |name, checker| checker.call(klass) }.keys.first
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -187,17 +187,10 @@ module Elasticsearch
|
|
187
187
|
delete_index!(options.merge index: target_index) if options[:force]
|
188
188
|
|
189
189
|
unless ( self.client.indices.exists(index: target_index) rescue false )
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
mappings: self.mappings.to_hash }
|
195
|
-
rescue Exception => e
|
196
|
-
unless e.class.to_s =~ /NotFound/ && options[:force]
|
197
|
-
STDERR.puts "[!!!] Error when creating the index: #{e.class}", "#{e.message}"
|
198
|
-
end
|
199
|
-
end
|
200
|
-
else
|
190
|
+
self.client.indices.create index: target_index,
|
191
|
+
body: {
|
192
|
+
settings: self.settings.to_hash,
|
193
|
+
mappings: self.mappings.to_hash }
|
201
194
|
end
|
202
195
|
end
|
203
196
|
|
@@ -217,8 +210,10 @@ module Elasticsearch
|
|
217
210
|
begin
|
218
211
|
self.client.indices.delete index: target_index
|
219
212
|
rescue Exception => e
|
220
|
-
|
221
|
-
STDERR.puts "[!!!]
|
213
|
+
if e.class.to_s =~ /NotFound/ && options[:force]
|
214
|
+
STDERR.puts "[!!!] Index does not exist (#{e.class})"
|
215
|
+
else
|
216
|
+
raise e
|
222
217
|
end
|
223
218
|
end
|
224
219
|
end
|
@@ -241,8 +236,10 @@ module Elasticsearch
|
|
241
236
|
begin
|
242
237
|
self.client.indices.refresh index: target_index
|
243
238
|
rescue Exception => e
|
244
|
-
|
245
|
-
STDERR.puts "[!!!]
|
239
|
+
if e.class.to_s =~ /NotFound/ && options[:force]
|
240
|
+
STDERR.puts "[!!!] Index does not exist (#{e.class})"
|
241
|
+
else
|
242
|
+
raise e
|
246
243
|
end
|
247
244
|
end
|
248
245
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Model
|
3
|
+
|
4
|
+
# Keeps a global registry of classes that include `Elasticsearch::Model`
|
5
|
+
#
|
6
|
+
class Registry
|
7
|
+
def initialize
|
8
|
+
@models = []
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns the unique instance of the registry (Singleton)
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
#
|
15
|
+
def self.__instance
|
16
|
+
@instance ||= new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds a model to the registry
|
20
|
+
#
|
21
|
+
def self.add(klass)
|
22
|
+
__instance.add(klass)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns an Array of registered models
|
26
|
+
#
|
27
|
+
def self.all
|
28
|
+
__instance.models
|
29
|
+
end
|
30
|
+
|
31
|
+
# Adds a model to the registry
|
32
|
+
#
|
33
|
+
def add(klass)
|
34
|
+
@models << klass
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a copy of the registered models
|
38
|
+
#
|
39
|
+
def models
|
40
|
+
@models.dup
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Wraps a collection of models when querying multiple indices
|
45
|
+
#
|
46
|
+
# @see Elasticsearch::Model.search
|
47
|
+
#
|
48
|
+
class Multimodel
|
49
|
+
attr_reader :models
|
50
|
+
|
51
|
+
# @param models [Class] The list of models across which the search will be performed
|
52
|
+
#
|
53
|
+
def initialize(*models)
|
54
|
+
@models = models.flatten
|
55
|
+
@models = Model::Registry.all if @models.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get an Array of index names used for retrieving documents when doing a search across multiple models
|
59
|
+
#
|
60
|
+
# @return [Array] the list of index names used for retrieving documents
|
61
|
+
#
|
62
|
+
def index_name
|
63
|
+
models.map { |m| m.index_name }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get an Array of document types used for retrieving documents when doing a search across multiple models
|
67
|
+
#
|
68
|
+
# @return [Array] the list of document types used for retrieving documents
|
69
|
+
#
|
70
|
+
def document_type
|
71
|
+
models.map { |m| m.document_type }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get the client common for all models
|
75
|
+
#
|
76
|
+
# @return Elasticsearch::Transport::Client
|
77
|
+
#
|
78
|
+
def client
|
79
|
+
Elasticsearch::Model.client
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -1,25 +1,9 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
|
4
|
-
require 'mongoid'
|
5
|
-
session = Moped::Connection.new("localhost", 27017, 0.5)
|
6
|
-
session.connect
|
7
|
-
ENV["MONGODB_AVAILABLE"] = 'yes'
|
8
|
-
rescue LoadError, Moped::Errors::ConnectionFailure => e
|
9
|
-
$stderr.puts "MongoDB not installed or running: #{e}"
|
10
|
-
end
|
11
|
-
|
12
|
-
if ENV["MONGODB_AVAILABLE"]
|
13
|
-
$stderr.puts "Mongoid #{Mongoid::VERSION}", '-'*80
|
14
|
-
|
15
|
-
logger = ::Logger.new($stderr)
|
16
|
-
logger.formatter = lambda { |s, d, p, m| " #{m.ansi(:faint, :cyan)}\n" }
|
17
|
-
logger.level = ::Logger::DEBUG
|
18
|
-
|
19
|
-
Mongoid.logger = logger unless ENV['QUIET']
|
20
|
-
Moped.logger = logger unless ENV['QUIET']
|
3
|
+
Mongo.setup!
|
21
4
|
|
22
|
-
|
5
|
+
if Mongo.available?
|
6
|
+
Mongo.connect_to 'mongoid_articles'
|
23
7
|
|
24
8
|
module Elasticsearch
|
25
9
|
module Model
|
@@ -50,7 +34,7 @@ if ENV["MONGODB_AVAILABLE"]
|
|
50
34
|
setup do
|
51
35
|
Elasticsearch::Model::Adapter.register \
|
52
36
|
Elasticsearch::Model::Adapter::Mongoid,
|
53
|
-
lambda { |klass| !!defined?(::Mongoid::Document) && klass.ancestors.include?(::Mongoid::Document) }
|
37
|
+
lambda { |klass| !!defined?(::Mongoid::Document) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Mongoid::Document) }
|
54
38
|
|
55
39
|
MongoidArticle.__elasticsearch__.create_index! force: true
|
56
40
|
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
Mongo.setup!
|
5
|
+
|
6
|
+
module Elasticsearch
|
7
|
+
module Model
|
8
|
+
class MultipleModelsIntegration < Elasticsearch::Test::IntegrationTestCase
|
9
|
+
context "Multiple models" do
|
10
|
+
setup do
|
11
|
+
ActiveRecord::Schema.define(:version => 1) do
|
12
|
+
create_table :episodes do |t|
|
13
|
+
t.string :name
|
14
|
+
t.datetime :created_at, :default => 'NOW()'
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table :series do |t|
|
18
|
+
t.string :name
|
19
|
+
t.datetime :created_at, :default => 'NOW()'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class ::Episode < ActiveRecord::Base
|
24
|
+
include Elasticsearch::Model
|
25
|
+
include Elasticsearch::Model::Callbacks
|
26
|
+
|
27
|
+
settings index: {number_of_shards: 1, number_of_replicas: 0} do
|
28
|
+
mapping do
|
29
|
+
indexes :name, type: 'string', analyzer: 'snowball'
|
30
|
+
indexes :created_at, type: 'date'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ::Series < ActiveRecord::Base
|
36
|
+
include Elasticsearch::Model
|
37
|
+
include Elasticsearch::Model::Callbacks
|
38
|
+
|
39
|
+
settings index: {number_of_shards: 1, number_of_replicas: 0} do
|
40
|
+
mapping do
|
41
|
+
indexes :name, type: 'string', analyzer: 'snowball'
|
42
|
+
indexes :created_at, type: 'date'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
[::Episode, ::Series].each do |model|
|
48
|
+
model.delete_all
|
49
|
+
model.__elasticsearch__.create_index! force: true
|
50
|
+
model.create name: "The #{model.name}"
|
51
|
+
model.create name: "A great #{model.name}"
|
52
|
+
model.create name: "The greatest #{model.name}"
|
53
|
+
model.__elasticsearch__.refresh_index!
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
should "find matching documents across multiple models" do
|
59
|
+
response = Elasticsearch::Model.search("greatest", [Series, Episode])
|
60
|
+
|
61
|
+
assert response.any?, "Response should not be empty: #{response.to_a.inspect}"
|
62
|
+
|
63
|
+
assert_equal 2, response.results.size
|
64
|
+
assert_equal 2, response.records.size
|
65
|
+
|
66
|
+
assert_instance_of Elasticsearch::Model::Response::Result, response.results.first
|
67
|
+
assert_instance_of Episode, response.records.first
|
68
|
+
assert_instance_of Series, response.records.last
|
69
|
+
|
70
|
+
assert_equal 'The greatest Episode', response.results[0].name
|
71
|
+
assert_equal 'The greatest Episode', response.records[0].name
|
72
|
+
|
73
|
+
assert_equal 'The greatest Series', response.results[1].name
|
74
|
+
assert_equal 'The greatest Series', response.records[1].name
|
75
|
+
end
|
76
|
+
|
77
|
+
should "provide access to results" do
|
78
|
+
q = {query: {query_string: {query: 'A great *'}}, highlight: {fields: {name: {}}}}
|
79
|
+
response = Elasticsearch::Model.search(q, [Series, Episode])
|
80
|
+
|
81
|
+
assert_equal 'A great Episode', response.results[0].name
|
82
|
+
assert_equal true, response.results[0].name?
|
83
|
+
assert_equal false, response.results[0].boo?
|
84
|
+
assert_equal true, response.results[0].highlight?
|
85
|
+
assert_equal true, response.results[0].highlight.name?
|
86
|
+
assert_equal false, response.results[0].highlight.boo?
|
87
|
+
|
88
|
+
assert_equal 'A great Series', response.results[1].name
|
89
|
+
assert_equal true, response.results[1].name?
|
90
|
+
assert_equal false, response.results[1].boo?
|
91
|
+
assert_equal true, response.results[1].highlight?
|
92
|
+
assert_equal true, response.results[1].highlight.name?
|
93
|
+
assert_equal false, response.results[1].highlight.boo?
|
94
|
+
end
|
95
|
+
|
96
|
+
if Mongo.available?
|
97
|
+
Mongo.connect_to 'mongoid_collections'
|
98
|
+
|
99
|
+
context "Across mongoid models" do
|
100
|
+
setup do
|
101
|
+
class ::Image
|
102
|
+
include Mongoid::Document
|
103
|
+
include Elasticsearch::Model
|
104
|
+
include Elasticsearch::Model::Callbacks
|
105
|
+
|
106
|
+
field :name, type: String
|
107
|
+
attr_accessible :name if respond_to? :attr_accessible
|
108
|
+
|
109
|
+
settings index: {number_of_shards: 1, number_of_replicas: 0} do
|
110
|
+
mapping do
|
111
|
+
indexes :name, type: 'string', analyzer: 'snowball'
|
112
|
+
indexes :created_at, type: 'date'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def as_indexed_json(options={})
|
117
|
+
as_json(except: [:_id])
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
Image.delete_all
|
122
|
+
Image.__elasticsearch__.create_index! force: true
|
123
|
+
Image.create! name: "The Image"
|
124
|
+
Image.create! name: "A great Image"
|
125
|
+
Image.create! name: "The greatest Image"
|
126
|
+
Image.__elasticsearch__.refresh_index!
|
127
|
+
Image.__elasticsearch__.client.cluster.health wait_for_status: 'yellow'
|
128
|
+
end
|
129
|
+
|
130
|
+
should "find matching documents across multiple models" do
|
131
|
+
response = Elasticsearch::Model.search("greatest", [Episode, Image])
|
132
|
+
|
133
|
+
assert response.any?, "Response should not be empty: #{response.to_a.inspect}"
|
134
|
+
|
135
|
+
assert_equal 2, response.results.size
|
136
|
+
assert_equal 2, response.records.size
|
137
|
+
|
138
|
+
assert_instance_of Elasticsearch::Model::Response::Result, response.results.first
|
139
|
+
assert_instance_of Image, response.records.first
|
140
|
+
assert_instance_of Episode, response.records.last
|
141
|
+
|
142
|
+
assert_equal 'The greatest Image', response.results[0].name
|
143
|
+
assert_equal 'The greatest Image', response.records[0].name
|
144
|
+
|
145
|
+
assert_equal 'The greatest Episode', response.results[1].name
|
146
|
+
assert_equal 'The greatest Episode', response.records[1].name
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -61,3 +61,33 @@ module Elasticsearch
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|
64
|
+
|
65
|
+
class Mongo
|
66
|
+
def self.setup!
|
67
|
+
begin
|
68
|
+
require 'mongoid'
|
69
|
+
session = Moped::Connection.new("localhost", 27017, 0.5)
|
70
|
+
session.connect
|
71
|
+
ENV['MONGODB_AVAILABLE'] = 'yes'
|
72
|
+
rescue LoadError, Moped::Errors::ConnectionFailure => e
|
73
|
+
$stderr.puts "MongoDB not installed or running: #{e}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.available?
|
78
|
+
!!ENV['MONGODB_AVAILABLE']
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.connect_to(source)
|
82
|
+
$stderr.puts "Mongoid #{Mongoid::VERSION}", '-'*80
|
83
|
+
|
84
|
+
logger = ::Logger.new($stderr)
|
85
|
+
logger.formatter = lambda { |s, d, p, m| " #{m.ansi(:faint, :cyan)}\n" }
|
86
|
+
logger.level = ::Logger::DEBUG
|
87
|
+
|
88
|
+
Mongoid.logger = logger unless ENV['QUIET']
|
89
|
+
Moped.logger = logger unless ENV['QUIET']
|
90
|
+
|
91
|
+
Mongoid.connect_to source
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Elasticsearch::Model::MultipleTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Adapter for multiple models" do
|
6
|
+
|
7
|
+
class ::DummyOne
|
8
|
+
include Elasticsearch::Model
|
9
|
+
|
10
|
+
index_name 'dummy'
|
11
|
+
document_type 'dummy_one'
|
12
|
+
|
13
|
+
def self.find(ids)
|
14
|
+
ids.map { |id| new(id) }
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :id
|
18
|
+
|
19
|
+
def initialize(id)
|
20
|
+
@id = id.to_i
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ::Namespace
|
25
|
+
class DummyTwo
|
26
|
+
include Elasticsearch::Model
|
27
|
+
|
28
|
+
index_name 'dummy'
|
29
|
+
document_type 'dummy_two'
|
30
|
+
|
31
|
+
def self.find(ids)
|
32
|
+
ids.map { |id| new(id) }
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :id
|
36
|
+
|
37
|
+
def initialize(id)
|
38
|
+
@id = id.to_i
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class ::DummyTwo
|
44
|
+
include Elasticsearch::Model
|
45
|
+
|
46
|
+
index_name 'other_index'
|
47
|
+
document_type 'dummy_two'
|
48
|
+
|
49
|
+
def self.find(ids)
|
50
|
+
ids.map { |id| new(id) }
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :id
|
54
|
+
|
55
|
+
def initialize(id)
|
56
|
+
@id = id.to_i
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
HITS = [{_index: 'dummy',
|
61
|
+
_type: 'dummy_two',
|
62
|
+
_id: '2',
|
63
|
+
}, {
|
64
|
+
_index: 'dummy',
|
65
|
+
_type: 'dummy_one',
|
66
|
+
_id: '2',
|
67
|
+
}, {
|
68
|
+
_index: 'other_index',
|
69
|
+
_type: 'dummy_two',
|
70
|
+
_id: '1',
|
71
|
+
}, {
|
72
|
+
_index: 'dummy',
|
73
|
+
_type: 'dummy_two',
|
74
|
+
_id: '1',
|
75
|
+
}, {
|
76
|
+
_index: 'dummy',
|
77
|
+
_type: 'dummy_one',
|
78
|
+
_id: '3'}]
|
79
|
+
|
80
|
+
setup do
|
81
|
+
@multimodel = Elasticsearch::Model::Multimodel.new(DummyOne, DummyTwo, Namespace::DummyTwo)
|
82
|
+
end
|
83
|
+
|
84
|
+
context "when returning records" do
|
85
|
+
setup do
|
86
|
+
@multimodel.class.send :include, Elasticsearch::Model::Adapter::Multiple::Records
|
87
|
+
@multimodel.expects(:response).at_least_once.returns(stub(response: { 'hits' => { 'hits' => HITS } }))
|
88
|
+
end
|
89
|
+
|
90
|
+
should "keep the order from response" do
|
91
|
+
assert_instance_of Module, Elasticsearch::Model::Adapter::Multiple::Records
|
92
|
+
records = @multimodel.records
|
93
|
+
|
94
|
+
assert_equal 5, records.count
|
95
|
+
|
96
|
+
assert_kind_of ::Namespace::DummyTwo, records[0]
|
97
|
+
assert_kind_of ::DummyOne, records[1]
|
98
|
+
assert_kind_of ::DummyTwo, records[2]
|
99
|
+
assert_kind_of ::Namespace::DummyTwo, records[3]
|
100
|
+
assert_kind_of ::DummyOne, records[4]
|
101
|
+
|
102
|
+
assert_equal [2, 2, 1, 1, 3], records.map(&:id)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/test/unit/indexing_test.rb
CHANGED
@@ -12,6 +12,8 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
class NotFound < Exception; end
|
16
|
+
|
15
17
|
context "Settings class" do
|
16
18
|
should "be convertible to hash" do
|
17
19
|
hash = { foo: 'bar' }
|
@@ -336,6 +338,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
|
|
336
338
|
assert_equal 'bar', payload[:type]
|
337
339
|
assert_equal '1', payload[:id]
|
338
340
|
assert_equal({title: 'green'}, payload[:body][:doc])
|
341
|
+
true
|
339
342
|
end
|
340
343
|
|
341
344
|
instance.expects(:client).returns(client)
|
@@ -356,6 +359,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
|
|
356
359
|
assert_equal '1', payload[:id]
|
357
360
|
assert_equal({title: 'green'}, payload[:body][:doc])
|
358
361
|
assert_equal true, payload[:refresh]
|
362
|
+
true
|
359
363
|
end
|
360
364
|
|
361
365
|
instance.expects(:client).returns(client)
|
@@ -380,19 +384,40 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
|
|
380
384
|
end
|
381
385
|
end
|
382
386
|
|
383
|
-
should "delete the index without raising exception" do
|
387
|
+
should "delete the index without raising exception when the index is not found" do
|
384
388
|
client = stub('client')
|
385
389
|
indices = stub('indices')
|
386
390
|
client.stubs(:indices).returns(indices)
|
387
391
|
|
388
|
-
indices.expects(:delete).returns({}).then.raises(
|
392
|
+
indices.expects(:delete).returns({}).then.raises(NotFound).at_least_once
|
389
393
|
|
390
394
|
DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once
|
391
395
|
|
392
|
-
assert_nothing_raised
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
+
assert_nothing_raised { DummyIndexingModelForRecreate.delete_index! force: true }
|
397
|
+
end
|
398
|
+
|
399
|
+
should "raise an exception without the force option" do
|
400
|
+
client = stub('client')
|
401
|
+
indices = stub('indices')
|
402
|
+
client.stubs(:indices).returns(indices)
|
403
|
+
|
404
|
+
indices.expects(:delete).raises(NotFound)
|
405
|
+
|
406
|
+
DummyIndexingModelForRecreate.expects(:client).returns(client)
|
407
|
+
|
408
|
+
assert_raise(NotFound) { DummyIndexingModelForRecreate.delete_index! }
|
409
|
+
end
|
410
|
+
|
411
|
+
should "raise a regular exception when deleting the index" do
|
412
|
+
client = stub('client')
|
413
|
+
|
414
|
+
indices = stub('indices')
|
415
|
+
indices.expects(:delete).raises(Exception)
|
416
|
+
client.stubs(:indices).returns(indices)
|
417
|
+
|
418
|
+
DummyIndexingModelForRecreate.expects(:client).returns(client)
|
419
|
+
|
420
|
+
assert_raise(Exception) { DummyIndexingModelForRecreate.delete_index! force: true }
|
396
421
|
end
|
397
422
|
|
398
423
|
should "create the index with correct settings and mappings when it doesn't exist" do
|
@@ -428,19 +453,18 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
|
|
428
453
|
assert_nothing_raised { DummyIndexingModelForRecreate.create_index! }
|
429
454
|
end
|
430
455
|
|
431
|
-
should "
|
456
|
+
should "raise exception during index creation" do
|
432
457
|
client = stub('client')
|
433
458
|
indices = stub('indices')
|
434
459
|
client.stubs(:indices).returns(indices)
|
435
460
|
|
461
|
+
indices.expects(:delete).returns({})
|
436
462
|
indices.expects(:exists).returns(false)
|
437
|
-
indices.expects(:create).raises(Exception)
|
463
|
+
indices.expects(:create).raises(Exception)
|
438
464
|
|
439
465
|
DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once
|
440
466
|
|
441
|
-
|
442
|
-
DummyIndexingModelForRecreate.create_index!
|
443
|
-
end
|
467
|
+
assert_raise(Exception) { DummyIndexingModelForRecreate.create_index! force: true }
|
444
468
|
end
|
445
469
|
|
446
470
|
should "delete the index first with the force option" do
|
@@ -459,7 +483,19 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
|
|
459
483
|
end
|
460
484
|
end
|
461
485
|
|
462
|
-
should "refresh the index without raising exception" do
|
486
|
+
should "refresh the index without raising exception with the force option" do
|
487
|
+
client = stub('client')
|
488
|
+
indices = stub('indices')
|
489
|
+
client.stubs(:indices).returns(indices)
|
490
|
+
|
491
|
+
indices.expects(:refresh).returns({}).then.raises(NotFound).at_least_once
|
492
|
+
|
493
|
+
DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once
|
494
|
+
|
495
|
+
assert_nothing_raised { DummyIndexingModelForRecreate.refresh_index! force: true }
|
496
|
+
end
|
497
|
+
|
498
|
+
should "raise a regular exception when refreshing the index" do
|
463
499
|
client = stub('client')
|
464
500
|
indices = stub('indices')
|
465
501
|
client.stubs(:indices).returns(indices)
|
@@ -468,10 +504,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
|
|
468
504
|
|
469
505
|
DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once
|
470
506
|
|
471
|
-
assert_nothing_raised
|
472
|
-
DummyIndexingModelForRecreate.refresh_index!
|
473
|
-
DummyIndexingModelForRecreate.refresh_index!
|
474
|
-
end
|
507
|
+
assert_nothing_raised { DummyIndexingModelForRecreate.refresh_index! force: true }
|
475
508
|
end
|
476
509
|
|
477
510
|
context "with a custom index name" do
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Elasticsearch::Model::MultimodelTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Multimodel class" do
|
6
|
+
setup do
|
7
|
+
title = stub('Foo', index_name: 'foo_index', document_type: 'foo')
|
8
|
+
series = stub('Bar', index_name: 'bar_index', document_type: 'bar')
|
9
|
+
@multimodel = Elasticsearch::Model::Multimodel.new(title, series)
|
10
|
+
end
|
11
|
+
|
12
|
+
should "have an index_name" do
|
13
|
+
assert_equal ['foo_index', 'bar_index'], @multimodel.index_name
|
14
|
+
end
|
15
|
+
|
16
|
+
should "have a document_type" do
|
17
|
+
assert_equal ['foo', 'bar'], @multimodel.document_type
|
18
|
+
end
|
19
|
+
|
20
|
+
should "have a client" do
|
21
|
+
assert_equal Elasticsearch::Model.client, @multimodel.client
|
22
|
+
end
|
23
|
+
|
24
|
+
should "include models in the registry" do
|
25
|
+
class ::JustAModel
|
26
|
+
include Elasticsearch::Model
|
27
|
+
end
|
28
|
+
|
29
|
+
class ::JustAnotherModel
|
30
|
+
include Elasticsearch::Model
|
31
|
+
end
|
32
|
+
|
33
|
+
multimodel = Elasticsearch::Model::Multimodel.new
|
34
|
+
assert multimodel.models.include?(::JustAModel)
|
35
|
+
assert multimodel.models.include?(::JustAnotherModel)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elasticsearch-model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Karel Minarik
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-04-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: elasticsearch
|
@@ -348,11 +348,13 @@ files:
|
|
348
348
|
- lib/elasticsearch/model/adapters/active_record.rb
|
349
349
|
- lib/elasticsearch/model/adapters/default.rb
|
350
350
|
- lib/elasticsearch/model/adapters/mongoid.rb
|
351
|
+
- lib/elasticsearch/model/adapters/multiple.rb
|
351
352
|
- lib/elasticsearch/model/callbacks.rb
|
352
353
|
- lib/elasticsearch/model/client.rb
|
353
354
|
- lib/elasticsearch/model/ext/active_record.rb
|
354
355
|
- lib/elasticsearch/model/importing.rb
|
355
356
|
- lib/elasticsearch/model/indexing.rb
|
357
|
+
- lib/elasticsearch/model/multimodel.rb
|
356
358
|
- lib/elasticsearch/model/naming.rb
|
357
359
|
- lib/elasticsearch/model/proxy.rb
|
358
360
|
- lib/elasticsearch/model/response.rb
|
@@ -373,16 +375,19 @@ files:
|
|
373
375
|
- test/integration/active_record_pagination_test.rb
|
374
376
|
- test/integration/dynamic_index_name_test.rb
|
375
377
|
- test/integration/mongoid_basic_test.rb
|
378
|
+
- test/integration/multiple_models_test.rb
|
376
379
|
- test/test_helper.rb
|
377
380
|
- test/unit/adapter_active_record_test.rb
|
378
381
|
- test/unit/adapter_default_test.rb
|
379
382
|
- test/unit/adapter_mongoid_test.rb
|
383
|
+
- test/unit/adapter_multiple_test.rb
|
380
384
|
- test/unit/adapter_test.rb
|
381
385
|
- test/unit/callbacks_test.rb
|
382
386
|
- test/unit/client_test.rb
|
383
387
|
- test/unit/importing_test.rb
|
384
388
|
- test/unit/indexing_test.rb
|
385
389
|
- test/unit/module_test.rb
|
390
|
+
- test/unit/multimodel_test.rb
|
386
391
|
- test/unit/naming_test.rb
|
387
392
|
- test/unit/proxy_test.rb
|
388
393
|
- test/unit/response_base_test.rb
|
@@ -430,16 +435,19 @@ test_files:
|
|
430
435
|
- test/integration/active_record_pagination_test.rb
|
431
436
|
- test/integration/dynamic_index_name_test.rb
|
432
437
|
- test/integration/mongoid_basic_test.rb
|
438
|
+
- test/integration/multiple_models_test.rb
|
433
439
|
- test/test_helper.rb
|
434
440
|
- test/unit/adapter_active_record_test.rb
|
435
441
|
- test/unit/adapter_default_test.rb
|
436
442
|
- test/unit/adapter_mongoid_test.rb
|
443
|
+
- test/unit/adapter_multiple_test.rb
|
437
444
|
- test/unit/adapter_test.rb
|
438
445
|
- test/unit/callbacks_test.rb
|
439
446
|
- test/unit/client_test.rb
|
440
447
|
- test/unit/importing_test.rb
|
441
448
|
- test/unit/indexing_test.rb
|
442
449
|
- test/unit/module_test.rb
|
450
|
+
- test/unit/multimodel_test.rb
|
443
451
|
- test/unit/naming_test.rb
|
444
452
|
- test/unit/proxy_test.rb
|
445
453
|
- test/unit/response_base_test.rb
|