elasticsearch-model 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|