es-elasticity 0.3.9 → 0.3.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0b43991880f2556f0dfaa3e6a0a641fe21f39816
4
- data.tar.gz: 25afec66923b407330c648fb767d9405cc1ec3cd
3
+ metadata.gz: b5d3a0524062086414be4b1e007baead644752fb
4
+ data.tar.gz: f94156960b946ae47439f0d0f66254686f6ff98f
5
5
  SHA512:
6
- metadata.gz: ba981a87ef6b156d589c9f8c60505998a77b5cd6827c70af2f34719aa15c3306904288f9715713536d18570258c34b4332271bb7b11a3cd2eaae193fec58ba82
7
- data.tar.gz: 0a6dabe8a4d45f85928e1bd5531a6d29dbc625115652a6029abe395f7506341458b40f14327cd166322f5d657b538549edb1859ab1eab42da8ce516074782b42
6
+ metadata.gz: 608ddfc31bcb487007447cb666b9a3476516c81c23baba6f239856b939e8ba6517939f7afef1a01040c6a27d5e66b0562da09b074ae5a9afe3a3533c1f8eba75
7
+ data.tar.gz: 035dd04cac5fa2bb1614037ca6126b859054d215fe97653c5f52bac0d1e9aa3f007821c7b6bebb21bbdcd41b887ef2b7a07ca5c0d6b65ca69a30a7b3a8e855e7
@@ -6,6 +6,8 @@ rvm:
6
6
  - 2.1.2
7
7
  - 2.1.3
8
8
  - 2.1.4
9
+ - 2.2.2
10
+ - 2.2.3
9
11
  services:
10
12
  - elasticsearch
11
13
  env:
data/README.md CHANGED
@@ -79,7 +79,7 @@ class Search::User < Elasticity::Document
79
79
  from: 0,
80
80
  size: 10,
81
81
  filter: {
82
- { range: { birthdate: { gte: date.iso8601 }}},
82
+ range: { birthdate: { lte: date.iso8601 }},
83
83
  },
84
84
  }
85
85
 
@@ -173,6 +173,90 @@ adults = adults.active_record(User)
173
173
 
174
174
  For more information about the `active_record` method, read [ActiveRecord integration](#activerecord-integration).
175
175
 
176
+ ### Segmented Documents
177
+
178
+ The idea of segmented documents is that documents of the same type/class can be distributed over separate segments, which are backed by separated indexes. This is good for manually sharding documents into their own indexes. For example, an application that supports multiple clients/organizations might want to separate the documents for each organization under separate indexes, making it easier to delete the data and isolate the documents.
179
+
180
+ Using this feature is very easy and very similar to traditional documents. The only difference is that your document class should inherit from `Elasticity::SegmentedDocument`. Adjusting the definition that we had before:
181
+
182
+ ```ruby
183
+ class Search::User < Elasticity::SegmentedDocument
184
+ configure do |c|
185
+ # Defines how the index will be named, the final name
186
+ # will depend on the stragy being used.
187
+ c.index_base_name = "users"
188
+
189
+ # Defines the document type that this class represents.
190
+ c.document_type = "user"
191
+
192
+ # Select which strategy should be used. AliasIndex uses two aliases
193
+ # in order to support hot remapping of indexes. This is the recommended
194
+ # strategy.
195
+ c.strategy = Elasticity::Strategies::AliasIndex
196
+
197
+ # Defines the mapping for this index/document_type.
198
+ c.mapping = {
199
+ properties: {
200
+ name: { type: "string" },
201
+ birthdate: { type: "date" },
202
+ }
203
+ }
204
+ end
205
+
206
+ # Defines a search method.
207
+ def self.adults
208
+ date = Date.today - 21.years
209
+
210
+ # This is the query that will be submited to ES, same format ES would
211
+ # expect, translated to a Ruby hash, note the pagination params.
212
+ body = {
213
+ from: 0,
214
+ size: 10,
215
+ filter: {
216
+ range: { birthdate: { lte: date.iso8601 }},
217
+ },
218
+ }
219
+
220
+ # Creates a search object from the body and return it.
221
+ # The returned object # is a lazy evaluated search that behaves like a collection, being
222
+ # automatically triggered when data is iterated over.
223
+ self.search(body)
224
+ end
225
+
226
+ # All models automatically have the id attribute but you need to define the
227
+ # other accessors so that they can be set and get properly.
228
+ attr_accessor :name, :birthdate
229
+
230
+ # to_document is the only required method that needs to be implemented so an
231
+ # instance of this model can be indexed.
232
+ def to_document
233
+ {
234
+ name: self.name,
235
+ birthdate: self.birthdate.iso8601
236
+ }
237
+ end
238
+ end
239
+ ```
240
+
241
+ This class on itself can't be queried or manipulated directly. Trying to call the `adults` search method, one would get an error: `NoMethodError: undefined method 'search' for #<Class:0x007fd582933460>`. To be able to call any method you first need to define the segment that you want to use, which can easily be done by calling the method `segment`, which will return a class derived from the base class that contains all the available methods:
242
+
243
+ ```ruby
244
+ users = Search::User.segment("doximity.com")
245
+ users.create_index
246
+
247
+ # users is a dynamically defined class that inherits from the Search::User class,
248
+ # therefore having all the necessary methods defined just properly.
249
+ users # => Search::User{"doximity.com"}
250
+ users.class # => Class
251
+ users.ancestors # => [Search::User{"doximity.com"}, User, Elasticity::SegmentedDocument, ...]
252
+
253
+ john = users.new(name: "John", birthdate: Date.civil(1985, 10, 31))
254
+ john # => #<Search::User{"doximity.com"}:0x81a3ab6a0dea @name="John" @birthdate=Thu, 31 Oct 1985>
255
+ john.update
256
+
257
+ users.adults.to_a # => [#<Search::User{"doximity.com"}:0x819cc5a50cd5 @_id="AVCHLz5JyttLSz7M-tRI" @name="John" @birthdate="1985-10-31" @highlighted=nil>]
258
+ ```
259
+
176
260
  ### Strategies and Hot-remapping
177
261
 
178
262
  Strategies define how index creation and index operation happens on the lower level. Basically it define the structure that backs the document model. Currently, there are two strategies available: single-index and alias-index.
@@ -10,7 +10,11 @@ require "elasticsearch"
10
10
  module Elasticity
11
11
  autoload :Bulk, "elasticity/bulk"
12
12
  autoload :Config, "elasticity/config"
13
+ autoload :IndexConfig, "elasticity/index_config"
14
+ autoload :IndexMapper, "elasticity/index_mapper"
15
+ autoload :BaseDocument, "elasticity/base_document"
13
16
  autoload :Document, "elasticity/document"
17
+ autoload :SegmentedDocument, "elasticity/segmented_document"
14
18
  autoload :InstrumentedClient, "elasticity/instrumented_client"
15
19
  autoload :LogSubscriber, "elasticity/log_subscriber"
16
20
  autoload :MultiSearch, "elasticity/multi_search"
@@ -0,0 +1,53 @@
1
+ module Elasticity
2
+ class BaseDocument
3
+ include ::ActiveModel::Model
4
+
5
+ # Stores configuration for this class and all subclasses.
6
+ class_attribute :config
7
+
8
+ # Configure the given klass, changing default parameters and resetting
9
+ # some of the internal state.
10
+ def self.configure(&block)
11
+ self.config = IndexConfig.new(Elasticity.config, &block)
12
+ end
13
+
14
+ # Define common attributes for all documents
15
+ attr_accessor :_id, :highlighted
16
+
17
+ def attributes=(attributes)
18
+ attributes.each do |attr, value|
19
+ self.public_send("#{attr}=", value)
20
+ end
21
+ end
22
+
23
+ # Defines equality by comparing the ID and values of each instance variable.
24
+ def ==(other)
25
+ return false if other.nil?
26
+ return false if _id != other._id
27
+
28
+ instance_variables.all? do |ivar|
29
+ instance_variable_get(ivar) == other.instance_variable_get(ivar)
30
+ end
31
+ end
32
+
33
+ # IMPLEMENT
34
+ # Returns a hash with the attributes as they should be stored in the index.
35
+ # This will be stored as _source attributes on Elasticsearch.
36
+ def to_document
37
+ raise NotImplementedError, "to_document needs to be implemented for #{self.class}"
38
+ end
39
+
40
+ # Update this object on the index, creating or updating the document.
41
+ def update
42
+ self._id, @created = self.class.index_document(_id, to_document)
43
+ end
44
+
45
+ def delete
46
+ self.class.delete(self._id)
47
+ end
48
+
49
+ def created?
50
+ @created || false
51
+ end
52
+ end
53
+ end
@@ -1,196 +1,12 @@
1
1
  module Elasticity
2
- class Document
3
- include ::ActiveModel::Model
2
+ class Document < BaseDocument
3
+ IndexMapper.set_delegates(singleton_class, :mapper)
4
4
 
5
- class NotConfigured < StandardError; end
5
+ private
6
6
 
7
- Config = Struct.new(:index_base_name, :document_type, :mapping, :strategy)
8
-
9
- # Configure the given klass, changing default parameters and resetting
10
- # some of the internal state.
11
- def self.configure
12
- @config = Config.new
13
- @config.strategy = Strategies::SingleIndex
14
- yield(@config)
15
- end
16
-
17
- # Returns the stategy class being used.
18
- # Check Elasticity::Strategies for more information.
19
- def self.strategy
20
- if @config.nil? || @config.strategy.nil?
21
- raise NotConfigured, "#{self} has not been configured, make sure you call the configure method"
22
- end
23
-
24
- return @strategy if defined?(@strategy)
25
-
26
- if namespace = Elasticity.config.namespace
27
- index_base_name = "#{namespace}_#{@config.index_base_name}"
28
- end
29
-
30
- @strategy = @config.strategy.new(Elasticity.config.client, index_base_name, document_type)
31
- end
32
-
33
- # Document type
34
- def self.document_type
35
- if @config.nil? || @config.document_type.blank?
36
- raise NotConfigured, "#{self} has not been configured, make sure you call the configure method"
37
- end
38
-
39
- @config.document_type
40
- end
41
-
42
- # Document type
43
- def self.mapping
44
- if @config.nil? || @config.mapping.blank?
45
- raise NotConfigured, "#{self} has not been configured, make sure you call the configure method"
46
- end
47
-
48
- @config.mapping
49
- end
50
-
51
- # Creates the index for this document
52
- def self.create_index
53
- self.strategy.create_if_undefined(settings: Elasticity.config.settings, mappings: { document_type => mapping })
54
- end
55
-
56
- # Re-creates the index for this document
57
- def self.recreate_index
58
- self.strategy.recreate(settings: Elasticity.config.settings, mappings: { document_type => mapping })
59
- end
60
-
61
- # Deletes the index
62
- def self.delete_index
63
- self.strategy.delete
64
- end
65
-
66
- # Does the index exist?
67
- def self.index_exists?
68
- !self.strategy.missing?
69
- end
70
-
71
- # Gets the index name to be used when you need to reference the index somewhere.
72
- # This depends on the strategy being used, but it always refers to the search index.
73
- def self.ref_index_name
74
- self.strategy.ref_index_name
75
- end
76
-
77
- # Remap
78
- def self.remap!
79
- self.strategy.remap(settings: Elasticity.config.settings, mappings: { document_type => mapping })
80
- end
81
-
82
- # Flushes the index, forcing any writes
83
- def self.flush_index
84
- self.strategy.flush
85
- end
86
-
87
- # Creates a instance of a document from a ElasticSearch hit data.
88
- def self.from_hit(hit_data)
89
- attrs = { _id: hit_data["_id"] }
90
- attrs.merge!(hit_data["_source"]) if hit_data["_source"]
91
-
92
- if hit_data["highlight"]
93
- highlighted_attrs = attrs.dup
94
- attrs_set = Set.new
95
-
96
- hit_data["highlight"].each do |name, v|
97
- name = name.gsub(/\..*\z/, '')
98
- next if attrs_set.include?(name)
99
- highlighted_attrs[name] = v
100
- attrs_set << name
101
- end
102
-
103
- highlighted = new(highlighted_attrs)
104
- end
105
-
106
- new(attrs.merge(highlighted: highlighted))
107
- end
108
-
109
- # Searches the index using the parameters provided in the body hash, following the same
110
- # structure Elasticsearch expects.
111
- # Returns a DocumentSearch object.
112
- def self.search(body)
113
- search = self.strategy.search(self.document_type, body)
114
- Search::DocumentProxy.new(search, self)
115
- end
116
-
117
- # Fetches one specific document from the index by ID.
118
- def self.get(id)
119
- if doc = self.strategy.get_document(document_type, id)
120
- new(doc["_source"].merge(_id: doc['_id']))
121
- end
122
- end
123
-
124
- # Removes one specific document from the index.
125
- def self.delete(id)
126
- self.strategy.delete_document(document_type, id)
127
- end
128
-
129
- # Removes entries based on a search
130
- def self.delete_by_search(search)
131
- self.strategy.delete_by_query(document_type, search.body)
132
- end
133
-
134
- # Bulk index the provided documents
135
- def self.bulk_index(documents)
136
- self.strategy.bulk do |b|
137
- documents.each do |doc|
138
- b.index(self.document_type, doc._id, doc.to_document)
139
- end
140
- end
141
- end
142
-
143
- # Bulk delete documents matching provided ids
144
- def self.bulk_delete(ids)
145
- self.strategy.bulk do |b|
146
- ids.each do |id|
147
- b.delete(self.document_type, id)
148
- end
149
- end
150
- end
151
-
152
- # Define common attributes for all documents
153
- attr_accessor :_id, :highlighted
154
-
155
- # Creates a new Document instance with the provided attributes.
156
- def initialize(attributes = {})
157
- super(attributes)
158
- end
159
-
160
- def attributes=(attributes)
161
- attributes.each do |attr, value|
162
- self.public_send("#{attr}=", value)
163
- end
164
- end
165
-
166
- # Defines equality by comparing the ID and values of each instance variable.
167
- def ==(other)
168
- return false if other.nil?
169
- return false if _id != other._id
170
-
171
- instance_variables.all? do |ivar|
172
- instance_variable_get(ivar) == other.instance_variable_get(ivar)
173
- end
174
- end
175
-
176
- # IMPLEMENT
177
- # Returns a hash with the attributes as they should be stored in the index.
178
- # This will be stored as _source attributes on Elasticsearch.
179
- def to_document
180
- raise NotImplementedError, "to_document needs to be implemented for #{self.class}"
181
- end
182
-
183
- # Update this object on the index, creating or updating the document.
184
- def update
185
- self._id, @created = self.class.strategy.index_document(self.class.document_type, _id, to_document)
186
- end
187
-
188
- def delete
189
- self.class.delete(self._id)
190
- end
191
-
192
- def created?
193
- @created || false
7
+ def self.mapper
8
+ raise "document class not configured" unless config.present?
9
+ @mapper ||= IndexMapper.new(self, config)
194
10
  end
195
11
  end
196
12
  end
@@ -0,0 +1,50 @@
1
+ module Elasticity
2
+ class IndexConfig
3
+ ATTRS = [:index_base_name, :document_type, :mapping, :strategy].freeze
4
+ attr_accessor *ATTRS
5
+
6
+ def initialize(elasticity_config)
7
+ @elasticity_config = elasticity_config
8
+ yield(self)
9
+ validate!
10
+ end
11
+
12
+ def segment(name)
13
+ new_config = self.dup
14
+ new_config.index_base_name = "#{index_base_name}_#{name.underscore}"
15
+ new_config
16
+ end
17
+
18
+ def client
19
+ @elasticity_config.client
20
+ end
21
+
22
+ def definition
23
+ { settings: @elasticity_config.settings, mappings: { @document_type => @mapping } }
24
+ end
25
+
26
+ def fq_index_base_name
27
+ return @fq_index_base_name if defined?(@fq_index_base_name)
28
+
29
+ if namespace = @elasticity_config.namespace
30
+ @fq_index_base_name = "#{namespace}_#{@index_base_name}"
31
+ else
32
+ @fq_index_base_name = @index_base_name
33
+ end
34
+
35
+ @fq_index_base_name
36
+ end
37
+
38
+ def strategy
39
+ @strategy ||= Strategies::SingleIndex
40
+ end
41
+
42
+ private
43
+
44
+ def validate!
45
+ ATTRS.each do |attr|
46
+ raise "#{attr} is not set" if public_send(attr).nil?
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,145 @@
1
+ module Elasticity
2
+ class IndexMapper
3
+ def self.set_delegates(obj, to)
4
+ obj.delegate(
5
+ :document_type,
6
+ :mapping,
7
+ :ref_index_name,
8
+ :create_index,
9
+ :recreate_index,
10
+ :delete_index,
11
+ :index_exists?,
12
+ :remap!,
13
+ :flush_index,
14
+ :index_document,
15
+ :search,
16
+ :get,
17
+ :delete,
18
+ :delete_by_search,
19
+ :bulk_index,
20
+ :bulk_delete,
21
+ to: to
22
+ )
23
+ end
24
+
25
+ def initialize(document_klass, index_config)
26
+ @document_klass = document_klass
27
+ @index_config = index_config
28
+ @strategy = @index_config.strategy.new(@index_config.client, @index_config.fq_index_base_name, @index_config.document_type)
29
+ end
30
+
31
+ delegate(
32
+ :document_type,
33
+ :mapping,
34
+ :ref_index_name,
35
+ to: :@index_config
36
+ )
37
+
38
+ # Creates the index for this document
39
+ def create_index
40
+ @strategy.create_if_undefined(@index_config.definition)
41
+ end
42
+
43
+ # Re-creates the index for this document
44
+ def recreate_index
45
+ @strategy.recreate(@index_config.definition)
46
+ end
47
+
48
+ # Deletes the index
49
+ def delete_index
50
+ @strategy.delete
51
+ end
52
+
53
+ # Does the index exist?
54
+ def index_exists?
55
+ !@strategy.missing?
56
+ end
57
+
58
+ # Gets the index name to be used when you need to reference the index somewhere.
59
+ # This depends on the @strategy being used, but it always refers to the search index.
60
+ def ref_index_name
61
+ @strategy.ref_index_name
62
+ end
63
+
64
+ # Remap
65
+ def remap!
66
+ @strategy.remap(@index_config.definition)
67
+ end
68
+
69
+ # Flushes the index, forcing any writes
70
+ def flush_index
71
+ @strategy.flush
72
+ end
73
+
74
+ # Index the given document
75
+ def index_document(id, document_hash)
76
+ @strategy.index_document(document_type, id, document_hash)
77
+ end
78
+
79
+ # Searches the index using the parameters provided in the body hash, following the same
80
+ # structure Elasticsearch expects.
81
+ # Returns a DocumentSearch object.
82
+ def search(body)
83
+ search_obj = Search.build(@index_config.client, @strategy.search_index, document_type, body)
84
+ Search::DocumentProxy.new(search_obj, self.method(:map_hit))
85
+ end
86
+
87
+ # Fetches one specific document from the index by ID.
88
+ def get(id)
89
+ doc = @strategy.get_document(document_type, id)
90
+ @document_klass.new(doc["_source"].merge(_id: doc['_id'])) if doc.present?
91
+ end
92
+
93
+ # Removes one specific document from the index.
94
+ def delete(id)
95
+ @strategy.delete_document(document_type, id)
96
+ end
97
+
98
+ # Removes entries based on a search
99
+ def delete_by_search(search)
100
+ @strategy.delete_by_query(document_type, search.body)
101
+ end
102
+
103
+ # Bulk index the provided documents
104
+ def bulk_index(documents)
105
+ @strategy.bulk do |b|
106
+ documents.each do |doc|
107
+ b.index(document_type, doc._id, doc.to_document)
108
+ end
109
+ end
110
+ end
111
+
112
+ # Bulk delete documents matching provided ids
113
+ def bulk_delete(ids)
114
+ @strategy.bulk do |b|
115
+ ids.each do |id|
116
+ b.delete(document_type, id)
117
+ end
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ # Creates a instance of a document from a ElasticSearch hit data.
124
+ def map_hit(hit)
125
+ attrs = { _id: hit["_id"] }
126
+ attrs.merge!(hit["_source"]) if hit["_source"]
127
+
128
+ if hit["highlight"]
129
+ highlighted_attrs = attrs.dup
130
+ attrs_set = Set.new
131
+
132
+ hit["highlight"].each do |name, v|
133
+ name = name.gsub(/\..*\z/, '')
134
+ next if attrs_set.include?(name)
135
+ highlighted_attrs[name] = v
136
+ attrs_set << name
137
+ end
138
+
139
+ highlighted = @document_klass.new(highlighted_attrs)
140
+ end
141
+
142
+ @document_klass.new(attrs.merge(highlighted: highlighted))
143
+ end
144
+ end
145
+ end
@@ -1,5 +1,10 @@
1
1
  module Elasticity
2
2
  module Search
3
+ def self.build(client, index_name, document_type, body)
4
+ search_def = Search::Definition.new(index_name, document_type, body)
5
+ Search::Facade.new(client, search_def)
6
+ end
7
+
3
8
  # Elasticity::Search::Definition is a struct that encapsulates all the data specific to one
4
9
  # ElasticSearch search.
5
10
  class Definition
@@ -15,6 +20,10 @@ module Elasticity
15
20
  self.class.new(@index_name, @document_type, @body.deep_merge(body_changes))
16
21
  end
17
22
 
23
+ def to_count_args
24
+ { index: @index_name, type: @document_type, body: { query: @body.fetch(:query) }}
25
+ end
26
+
18
27
  def to_search_args
19
28
  { index: @index_name, type: @document_type, body: @body }
20
29
  end
@@ -45,11 +54,11 @@ module Elasticity
45
54
  end
46
55
 
47
56
  # Performs the search using the default search type and returning an iterator that will yield
48
- # each document, converted to the provided document_klass.
49
- def documents(document_klass)
57
+ # each document, converted using the provided mapper
58
+ def documents(mapper)
50
59
  return @documents if defined?(@documents)
51
60
  @documents = LazySearch.new(@client, @search_definition) do |hit|
52
- document_klass.from_hit(hit)
61
+ mapper.(hit)
53
62
  end
54
63
  end
55
64
 
@@ -57,9 +66,9 @@ module Elasticity
57
66
  # as fast as possible. The sort option will be discarded.
58
67
  #
59
68
  # More info: http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/scan-scroll.html
60
- def scan_documents(document_klass, **options)
69
+ def scan_documents(mapper, **options)
61
70
  return @scan_documents if defined?(@scan_documents)
62
- @scan_documents = ScanCursor.new(@client, @search_definition, document_klass, **options)
71
+ @scan_documents = ScanCursor.new(@client, @search_definition, mapper, **options)
63
72
  end
64
73
 
65
74
  # Performs the search only fetching document ids using it to load ActiveRecord objects from the provided
@@ -105,7 +114,7 @@ module Elasticity
105
114
  end
106
115
 
107
116
  def count(args = {})
108
- @client.count(@search_definition.to_search_args.reverse_merge(args))["count"]
117
+ @client.count(@search_definition.to_count_args.reverse_merge(args))["count"]
109
118
  end
110
119
 
111
120
  def search_results
@@ -147,10 +156,10 @@ module Elasticity
147
156
  class ScanCursor
148
157
  include Enumerable
149
158
 
150
- def initialize(client, search_definition, document_klass, size: 100, scroll: "1m")
159
+ def initialize(client, search_definition, mapper, size: 100, scroll: "1m")
151
160
  @client = client
152
161
  @search_definition = search_definition
153
- @document_klass = document_klass
162
+ @mapper = mapper
154
163
  @size = size
155
164
  @scroll = scroll
156
165
  end
@@ -190,7 +199,7 @@ module Elasticity
190
199
  hits = response["hits"]["hits"]
191
200
  break if hits.empty?
192
201
 
193
- y << hits.map { |hit| @document_klass.from_hit(hit) }
202
+ y << hits.map { |hit| @mapper.(hit) }
194
203
  end
195
204
  end
196
205
  end
@@ -0,0 +1,33 @@
1
+ module Elasticity
2
+ class SegmentedDocument < BaseDocument
3
+ # Creates a new segment which behaves almost the same as a Document class dynamically
4
+ # configured to access a segmented index.
5
+ #
6
+ # It creates a new class in runtime that inherits from your defined class, allowing
7
+ # methods defined in your class to be callable from the dynamic class.
8
+ def self.segment(segment_name)
9
+ qn = segment_name.camelize
10
+
11
+ klass = Class.new(self) do
12
+ class_attribute :mapper, :segment_name
13
+ IndexMapper.set_delegates(singleton_class, :mapper)
14
+
15
+ def self.inspect
16
+ "#{superclass.name}{\"#{segment_name}\"}"
17
+ end
18
+
19
+ def inspect
20
+ ivars = instance_variables.map do |name|
21
+ "#{name}=#{instance_variable_get(name).inspect}"
22
+ end
23
+
24
+ "#<#{self.class.inspect}:0x#{object_id.to_s(15)} #{ivars.join(" ")}>"
25
+ end
26
+ end
27
+
28
+ klass.segment_name = segment_name
29
+ klass.mapper = IndexMapper.new(klass, config.segment(segment_name))
30
+ klass
31
+ end
32
+ end
33
+ end
@@ -213,8 +213,8 @@ module Elasticity
213
213
  @client.get(index: @main_alias, type: type, id: id)
214
214
  end
215
215
 
216
- def search(type, body)
217
- Search::Facade.new(@client, Search::Definition.new(@main_alias, type, body))
216
+ def search_index
217
+ @main_alias
218
218
  end
219
219
 
220
220
  def delete_by_query(type, body)
@@ -64,8 +64,8 @@ module Elasticity
64
64
  @client.get(index: @index_name, type: type, id: id)
65
65
  end
66
66
 
67
- def search(type, body)
68
- Search::Facade.new(@client, Search::Definition.new(@index_name, type, body))
67
+ def search_index
68
+ @index_name
69
69
  end
70
70
 
71
71
  def delete_by_query(type, body)
@@ -1,3 +1,3 @@
1
1
  module Elasticity
2
- VERSION = "0.3.9"
2
+ VERSION = "0.3.10"
3
3
  end
@@ -0,0 +1,78 @@
1
+ RSpec.describe "Segmented indexes", elasticsearch: true do
2
+ subject do
3
+ Class.new(Elasticity::SegmentedDocument) do
4
+ configure do |c|
5
+ c.index_base_name = "people"
6
+ c.document_type = "person"
7
+ c.mapping = {
8
+ properties: {
9
+ name: { type: "string" },
10
+ },
11
+ }
12
+ end
13
+
14
+ attr_accessor :name
15
+
16
+ def self.by_name(name)
17
+ search(query: { match: { name: name } })
18
+ end
19
+
20
+ def to_document
21
+ { name: name }
22
+ end
23
+ end
24
+ end
25
+
26
+ def ensure_index(*segments)
27
+ @indexed ||= []
28
+ segments.each(&:recreate_index)
29
+ @indexed += segments
30
+ end
31
+
32
+ after do
33
+ Array(@indexed).each { |i| i.delete_index }
34
+ end
35
+
36
+ it "allows all operations on a segment" do
37
+ seg = subject.segment("A")
38
+ ensure_index(seg)
39
+
40
+ rodrigo = seg.new(name: "rodrigo")
41
+ id, success = rodrigo.update
42
+ expect(id).to be_kind_of(String)
43
+ expect(success).to be true
44
+
45
+ seg.flush_index
46
+ results = seg.by_name("rodrigo").to_a
47
+ expect(results).to eq [rodrigo]
48
+
49
+ rodrigo.delete
50
+ seg.flush_index
51
+
52
+ results = seg.by_name("rodrigo").to_a
53
+ expect(results).to be_empty
54
+ end
55
+
56
+ it "isolates segments from one another" do
57
+ seg_a = subject.segment("A")
58
+ seg_b = subject.segment("B")
59
+ ensure_index(seg_a, seg_b)
60
+
61
+ doc_a = seg_a.new(name: "doc a")
62
+ _, success = doc_a.update
63
+ expect(success).to be true
64
+
65
+ doc_b = seg_b.new(name: "doc b")
66
+ _, success = doc_b.update
67
+ expect(success).to be true
68
+
69
+ seg_a.flush_index
70
+ seg_b.flush_index
71
+
72
+ res_a = seg_a.by_name("doc").to_a
73
+ expect(res_a).to eq [doc_a]
74
+
75
+ res_b = seg_b.by_name("doc").to_a
76
+ expect(res_b).to eq [doc_b]
77
+ end
78
+ end
@@ -42,49 +42,6 @@ RSpec.describe Elasticity::Document do
42
42
  expect { Class.new(described_class).new.to_document }.to raise_error(NotImplementedError)
43
43
  end
44
44
 
45
- context "class" do
46
- subject { klass }
47
-
48
- it "properly instantiate from search hit" do
49
- hit = { "_id" => 1, "_source" => { "name" => "foo", "items" => [{ name: "bar" }] }, "highlight" => { "name" => "<em>foo</em>" } }
50
- doc = subject.from_hit(hit)
51
- expect(doc.name).to eq "foo"
52
- expect(doc.items).to eq [{ name: "bar" }]
53
- expect(doc.highlighted.name).to eq "<em>foo</em>"
54
- expect(doc.highlighted.items).to eq [{ name: "bar" }]
55
- end
56
-
57
- it "searches using DocumentSearch" do
58
- body = double(:body)
59
- search = double(:search)
60
-
61
- expect(strategy).to receive(:search).with("class_name", body).and_return(search)
62
-
63
- doc_search = double(:doc_search)
64
- expect(Elasticity::Search::DocumentProxy).to receive(:new).with(search, subject).and_return(doc_search)
65
-
66
- expect(subject.search(body)).to be doc_search
67
- end
68
-
69
- it "gets specific document from the strategy" do
70
- doc = { "_id" => 1, "_source" => { "name" => "Foo", "items" => [{ "name" => "Item1" }]}}
71
- expect(strategy).to receive(:get_document).with("class_name", 1).and_return(doc)
72
- expect(subject.get(1)).to eq klass.new(_id: 1, name: "Foo", items: [{ "name" => "Item1" }])
73
- end
74
-
75
- it "deletes specific document from strategy" do
76
- strategy_ret = double(:strategy_return)
77
- expect(strategy).to receive(:delete_document).with("class_name", 1).and_return(strategy_ret)
78
- expect(subject.delete(1)).to eq strategy_ret
79
- end
80
-
81
- it "properly instantiates from search hit when no source is given" do
82
- hit = { "_id" => 1, "_type" => "foo" }
83
- doc = subject.from_hit(hit)
84
- expect(doc._id).to eq(1)
85
- end
86
- end
87
-
88
45
  context "instance" do
89
46
  subject { klass.new _id: 1, name: "Foo", items: [{ name: "Item1" }] }
90
47
 
@@ -0,0 +1,4 @@
1
+ require "elasticity/index_mapper"
2
+
3
+ RSpec.describe Elasticity::IndexMapper do
4
+ end
@@ -59,15 +59,17 @@ RSpec.describe "Search" do
59
59
  ]}}
60
60
  end
61
61
 
62
+ let :mapper do
63
+ -> (hit) {
64
+ klass.new(_id: hit["_id"], name: hit["_source"]["name"], age: hit["_source"]["age"])
65
+ }
66
+ end
67
+
62
68
  let :klass do
63
69
  Class.new do
64
70
  include ActiveModel::Model
65
71
  attr_accessor :_id, :name, :age
66
72
 
67
- def self.from_hit(hit)
68
- new(_id: hit["_id"], name: hit["_source"]["name"], age: hit["_source"]["age"])
69
- end
70
-
71
73
  def ==(other)
72
74
  self._id == other._id && self.name == other.name
73
75
  end
@@ -82,7 +84,7 @@ RSpec.describe "Search" do
82
84
  it "searches the index and return document models" do
83
85
  expect(client).to receive(:search).with(index: index_name, type: document_type, body: body).and_return(full_response)
84
86
 
85
- docs = subject.documents(klass)
87
+ docs = subject.documents(mapper)
86
88
  expected = [klass.new(_id: 1, name: "foo"), klass.new(_id: 2, name: "bar")]
87
89
 
88
90
  expect(docs.total).to eq 2
@@ -101,7 +103,7 @@ RSpec.describe "Search" do
101
103
  it "searches and the index returns aggregations" do
102
104
  expect(client).to receive(:search).with(index: index_name, type: document_type, body: body).and_return(full_response_with_aggregations)
103
105
 
104
- docs = subject.documents(klass)
106
+ docs = subject.documents(mapper)
105
107
  expect(docs.aggregations).to eq aggregations
106
108
  end
107
109
 
@@ -110,7 +112,7 @@ RSpec.describe "Search" do
110
112
  expect(client).to receive(:scroll).with(scroll_id: "abc123", scroll: "1m").and_return(scroll_response)
111
113
  expect(client).to receive(:scroll).with(scroll_id: "abc456", scroll: "1m").and_return(empty_response)
112
114
 
113
- docs = subject.scan_documents(klass)
115
+ docs = subject.scan_documents(mapper)
114
116
  expected = [klass.new(_id: 1, name: "foo"), klass.new(_id: 2, name: "bar")]
115
117
 
116
118
  expect(docs.total).to eq 2
@@ -144,7 +146,7 @@ RSpec.describe "Search" do
144
146
  it "provides defaul properties for pagination" do
145
147
  subject = Elasticity::Search::Facade.new(client, Elasticity::Search::Definition.new(index_name, document_type, body))
146
148
  expect(client).to receive(:search).with(index: index_name, type: document_type, body: body).and_return(full_response)
147
- docs = subject.documents(klass)
149
+ docs = subject.documents(mapper)
148
150
 
149
151
  expect(docs.per_page).to eq(10)
150
152
  expect(docs.total_pages).to eq(1)
@@ -166,7 +168,7 @@ RSpec.describe "Search" do
166
168
  type: document_type,
167
169
  body: { size: 14, from: 25, filter: {} }
168
170
  ).and_return({ "hits" => { "total" => 112 } })
169
- docs = subject.documents(klass)
171
+ docs = subject.documents(mapper)
170
172
 
171
173
  expect(docs.per_page).to eq(14)
172
174
  expect(docs.total_pages).to eq(8)
@@ -180,7 +182,7 @@ RSpec.describe "Search" do
180
182
  end
181
183
 
182
184
  subject do
183
- described_class.new(search, klass)
185
+ described_class.new(search, mapper)
184
186
  end
185
187
 
186
188
  it "automatically maps the documents into the provided Document class" do
@@ -69,16 +69,6 @@ RSpec.describe Elasticity::Strategies::SingleIndex, elasticsearch: true do
69
69
  expect(subject.get_document("document", 2)).to eq({"_index"=>"test_index_name", "_type"=>"document", "_id"=>"2", "_version"=>1, "found"=>true, "_source"=>{"name"=>"bar"}})
70
70
  end
71
71
 
72
- it "allows searching documents" do
73
- subject.index_document("document", 1, name: "test")
74
- subject.flush
75
-
76
- search = subject.search("document", filter: { term: { name: "test" }})
77
- hashes = search.document_hashes
78
- expect(hashes.size).to be 1
79
- expect(hashes[0]).to eq("_id" => "1", "_index" => "test_index_name", "_type" => "document", "_score" => 1.0, "_source" => { "name" => "test" })
80
- end
81
-
82
72
  it "allows deleting by queryu" do
83
73
  subject.index_document("document", 1, name: "foo")
84
74
  subject.index_document("document", 2, name: "bar")
metadata CHANGED
@@ -1,167 +1,167 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: es-elasticity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.9
4
+ version: 0.3.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Kochenburger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-08 00:00:00.000000000 Z
11
+ date: 2015-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.7'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.7'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '10.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: 3.1.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 3.1.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: simplecov
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: 0.7.1
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ~>
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.7.1
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: oj
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: pry
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '>='
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '>='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: codeclimate-test-reporter
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - '>='
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - '>='
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: redis
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - '>='
115
+ - - ">="
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - '>='
122
+ - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: activesupport
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - ~>
129
+ - - "~>"
130
130
  - !ruby/object:Gem::Version
131
131
  version: '4.0'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - ~>
136
+ - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: '4.0'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: activemodel
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - ~>
143
+ - - "~>"
144
144
  - !ruby/object:Gem::Version
145
145
  version: '4.0'
146
146
  type: :runtime
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - ~>
150
+ - - "~>"
151
151
  - !ruby/object:Gem::Version
152
152
  version: '4.0'
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: elasticsearch
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
- - - ~>
157
+ - - "~>"
158
158
  - !ruby/object:Gem::Version
159
159
  version: 1.0.12
160
160
  type: :runtime
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
- - - ~>
164
+ - - "~>"
165
165
  - !ruby/object:Gem::Version
166
166
  version: 1.0.12
167
167
  description: Elasticity provides a higher level abstraction on top of [elasticsearch-ruby](https://github.com/elasticsearch/elasticsearch-ruby)
@@ -172,10 +172,10 @@ executables: []
172
172
  extensions: []
173
173
  extra_rdoc_files: []
174
174
  files:
175
- - .gitignore
176
- - .rspec
177
- - .simplecov
178
- - .travis.yml
175
+ - ".gitignore"
176
+ - ".rspec"
177
+ - ".simplecov"
178
+ - ".travis.yml"
179
179
  - Gemfile
180
180
  - LICENSE.txt
181
181
  - README.md
@@ -184,21 +184,27 @@ files:
184
184
  - bin/rspec
185
185
  - elasticity.gemspec
186
186
  - lib/elasticity.rb
187
+ - lib/elasticity/base_document.rb
187
188
  - lib/elasticity/bulk.rb
188
189
  - lib/elasticity/config.rb
189
190
  - lib/elasticity/document.rb
191
+ - lib/elasticity/index_config.rb
192
+ - lib/elasticity/index_mapper.rb
190
193
  - lib/elasticity/instrumented_client.rb
191
194
  - lib/elasticity/log_subscriber.rb
192
195
  - lib/elasticity/multi_search.rb
193
196
  - lib/elasticity/railtie.rb
194
197
  - lib/elasticity/search.rb
198
+ - lib/elasticity/segmented_document.rb
195
199
  - lib/elasticity/strategies.rb
196
200
  - lib/elasticity/strategies/alias_index.rb
197
201
  - lib/elasticity/strategies/single_index.rb
198
202
  - lib/elasticity/version.rb
199
203
  - spec/functional/persistence_spec.rb
204
+ - spec/functional/segmented_spec.rb
200
205
  - spec/rspec_config.rb
201
206
  - spec/units/document_spec.rb
207
+ - spec/units/index_mapper_spec.rb
202
208
  - spec/units/multi_search_spec.rb
203
209
  - spec/units/search_spec.rb
204
210
  - spec/units/strategies/single_index_spec.rb
@@ -212,24 +218,27 @@ require_paths:
212
218
  - lib
213
219
  required_ruby_version: !ruby/object:Gem::Requirement
214
220
  requirements:
215
- - - '>='
221
+ - - ">="
216
222
  - !ruby/object:Gem::Version
217
223
  version: '0'
218
224
  required_rubygems_version: !ruby/object:Gem::Requirement
219
225
  requirements:
220
- - - '>='
226
+ - - ">="
221
227
  - !ruby/object:Gem::Version
222
228
  version: '0'
223
229
  requirements: []
224
230
  rubyforge_project:
225
- rubygems_version: 2.0.0
231
+ rubygems_version: 2.4.5
226
232
  signing_key:
227
233
  specification_version: 4
228
234
  summary: ActiveModel-based library for working with Elasticsearch
229
235
  test_files:
230
236
  - spec/functional/persistence_spec.rb
237
+ - spec/functional/segmented_spec.rb
231
238
  - spec/rspec_config.rb
232
239
  - spec/units/document_spec.rb
240
+ - spec/units/index_mapper_spec.rb
233
241
  - spec/units/multi_search_spec.rb
234
242
  - spec/units/search_spec.rb
235
243
  - spec/units/strategies/single_index_spec.rb
244
+ has_rdoc: