elastic_record 4.1.8 → 5.1.0

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.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +15 -8
  3. data/Gemfile +1 -1
  4. data/README.md +29 -21
  5. data/elastic_record.gemspec +1 -1
  6. data/lib/elastic_record.rb +25 -13
  7. data/lib/elastic_record/aggregation_response/aggregation.rb +19 -0
  8. data/lib/elastic_record/aggregation_response/bucket.rb +24 -0
  9. data/lib/elastic_record/aggregation_response/builder.rb +55 -0
  10. data/lib/elastic_record/aggregation_response/has_aggregations.rb +9 -0
  11. data/lib/elastic_record/aggregation_response/multi_bucket_aggregation.rb +9 -0
  12. data/lib/elastic_record/aggregation_response/multi_value_aggregation.rb +6 -0
  13. data/lib/elastic_record/aggregation_response/single_bucket_aggregation.rb +8 -0
  14. data/lib/elastic_record/aggregation_response/single_value_aggregation.rb +7 -0
  15. data/lib/elastic_record/as_document.rb +33 -16
  16. data/lib/elastic_record/callbacks.rb +1 -1
  17. data/lib/elastic_record/config.rb +3 -0
  18. data/lib/elastic_record/connection.rb +4 -4
  19. data/lib/elastic_record/errors.rb +7 -1
  20. data/lib/elastic_record/index.rb +6 -8
  21. data/lib/elastic_record/index/analyze.rb +10 -0
  22. data/lib/elastic_record/index/deferred.rb +2 -2
  23. data/lib/elastic_record/index/documents.rb +42 -29
  24. data/lib/elastic_record/index/manage.rb +6 -8
  25. data/lib/elastic_record/index/mapping.rb +16 -8
  26. data/lib/elastic_record/index/mapping_type.rb +16 -0
  27. data/lib/elastic_record/index/mapping_type_test.rb +18 -0
  28. data/lib/elastic_record/index/settings.rb +6 -17
  29. data/lib/elastic_record/model.rb +2 -14
  30. data/lib/elastic_record/percolator_model.rb +11 -19
  31. data/lib/elastic_record/relation.rb +9 -30
  32. data/lib/elastic_record/relation/batches.rb +5 -3
  33. data/lib/elastic_record/relation/delegation.rb +8 -4
  34. data/lib/elastic_record/relation/finder_methods.rb +8 -0
  35. data/lib/elastic_record/relation/hits.rb +34 -0
  36. data/lib/elastic_record/relation/search_methods.rb +17 -22
  37. data/lib/elastic_record/relation/value_methods.rb +1 -1
  38. data/test/dummy/app/models/project.rb +16 -4
  39. data/test/dummy/app/models/warehouse.rb +5 -16
  40. data/test/dummy/app/models/widget.rb +23 -12
  41. data/test/dummy/app/models/widget_query.rb +1 -0
  42. data/test/dummy/db/migrate/20151211225259_create_warehouses.rb +7 -0
  43. data/test/dummy/db/migrate/20180215140125_create_widgets.rb +11 -0
  44. data/test/dummy/db/schema.rb +10 -3
  45. data/test/elastic_record/aggregation_response/bucket_test.rb +8 -0
  46. data/test/elastic_record/aggregation_response/multi_bucket_aggregation_test.rb +33 -0
  47. data/test/elastic_record/aggregation_response/single_bucket_aggregation_test.rb +15 -0
  48. data/test/elastic_record/as_document_test.rb +55 -29
  49. data/test/elastic_record/callbacks_test.rb +7 -11
  50. data/test/elastic_record/connection_test.rb +3 -16
  51. data/test/elastic_record/index/documents_test.rb +56 -11
  52. data/test/elastic_record/index/manage_test.rb +0 -7
  53. data/test/elastic_record/index/mapping_test.rb +18 -5
  54. data/test/elastic_record/index/settings_test.rb +1 -7
  55. data/test/elastic_record/index_test.rb +0 -4
  56. data/test/elastic_record/integration/active_record_test.rb +6 -9
  57. data/test/elastic_record/model_test.rb +5 -2
  58. data/test/elastic_record/percolator_model_test.rb +25 -11
  59. data/test/elastic_record/relation/batches_test.rb +29 -47
  60. data/test/elastic_record/relation/delegation_test.rb +1 -1
  61. data/test/elastic_record/relation/finder_methods_test.rb +17 -31
  62. data/test/elastic_record/relation/hits_test.rb +49 -0
  63. data/test/elastic_record/relation/search_methods_test.rb +20 -16
  64. data/test/elastic_record/relation_test.rb +19 -50
  65. data/test/helper.rb +7 -3
  66. metadata +20 -9
  67. data/lib/elastic_record/doctype.rb +0 -43
  68. data/lib/elastic_record/json.rb +0 -29
  69. data/lib/elastic_record/name_cache.rb +0 -23
  70. data/test/dummy/db/migrate/20151211225259_create_projects.rb +0 -7
  71. data/test/elastic_record/doctype_test.rb +0 -45
  72. data/test/elastic_record/name_cache_test.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: def9a67ba563b5ae2dfc9928f1bf6f9167763c7d
4
- data.tar.gz: 345857b95dea56d079fd7e431d07659af21cda94
2
+ SHA256:
3
+ metadata.gz: bb2dad78eb2851499ca2b7099f7774d79c375b5a180001bd612113353dfba33c
4
+ data.tar.gz: ff1262c6527d45f1e9b0e8eb46e24dc881cdb10b5cb51b93e40f72f5a2f366dc
5
5
  SHA512:
6
- metadata.gz: 40c79f1e780cc01977532cec6c5feb3950f464b9afa38615661f6aa978f8ffb1d5e4763cdf4ca9028b0433048820b2acee24f9ed0eb6cb472e35c018b2d843bf
7
- data.tar.gz: 634ce4d865cef8c57fa3ce1b9556cba7d4a0b53618eb96f3d3489389387b5d9b0d48a8250a499d21e772520b7055e88330244d321e06cc7efb1b85e60342e5ff
6
+ metadata.gz: b1c4fab2b261935713156c78ebf2fd675a7a7b107ac5a415a25dd20c8364690492f566d82b4685e9c223818abafbaf21ef2083d89ad3ad1e2a60bb93ed150685
7
+ data.tar.gz: 8f9d7ad4d081fe9096c65872c8b0e4fd0c2d4a3fa2df79bd7a8a8a3975fd7a7055c48b664a5f47beb2b7fcea07020e542f64b4f64e188c125e66c27ee11ad70f
data/.travis.yml CHANGED
@@ -2,17 +2,24 @@ rvm: 2.4.1
2
2
  cache: bundler
3
3
  sudo: false
4
4
  dist: trusty
5
- addons:
6
- apt:
7
- sources:
8
- - elasticsearch-5.x
9
- packages:
10
- - elasticsearch
5
+
6
+ before_install:
7
+ - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ES_VERSION}.tar.gz
8
+ - tar -xzf elasticsearch-${ES_VERSION}.tar.gz
9
+ - ./elasticsearch-${ES_VERSION}/bin/elasticsearch -d
10
+
11
11
  before_script:
12
12
  - cp test/dummy/.env.example test/dummy/.env
13
- - curl localhost:9200
13
+ - wget --quiet --waitretry=1 --retry-connrefused --timeout=20 -O - http://127.0.0.1:9200
14
14
  - bundle exec rake app:db:setup
15
15
  - bundle exec rake app:index:reset
16
+
17
+ env:
18
+ global:
19
+ - ES_VERSION=6.2.3
20
+
16
21
  services:
17
- - elasticsearch
18
22
  - postgresql
23
+
24
+ addons:
25
+ postgresql: 9.6
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ gemspec
4
4
 
5
5
  gem 'dotenv-rails'
6
6
  gem 'oj'
7
- gem 'pg'
7
+ gem 'pg', '~> 0.21'
8
8
  gem 'rails'
9
9
  gem 'rake', '~> 10.5.0'
10
10
  gem 'webmock', require: false
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  [![Build Status](https://secure.travis-ci.org/data-axle/elastic_record.png?rvm=2.0.0)](http://travis-ci.org/data-axle/elastic_record)
3
3
  [![Code Climate](https://codeclimate.com/github/data-axle/elastic_record.png)](https://codeclimate.com/github/data-axle/elastic_record)
4
4
 
5
- ElasticRecord is an Elasticsearch 2.x ORM.
5
+ ElasticRecord is an Elasticsearch 6.x ORM.
6
6
 
7
7
  ## Setup ##
8
8
 
@@ -68,9 +68,6 @@ search.filter(Product.arelastic[:name].prefix("Sca").negate)
68
68
 
69
69
  # Size is greater than 5
70
70
  search.filter(Product.arelastic[:size].gt(5))
71
-
72
- # Name is 'hola' or name is missing
73
- search.filter(Product.arelastic[:name].eq("hola").or(Product.arelastic[:name].missing))
74
71
  ```
75
72
 
76
73
  Helpful Arel builders can be found at https://github.com/matthuhiggins/arelastic/blob/master/lib/arelastic/builders/filter.rb.
@@ -113,12 +110,11 @@ Aggregations are added with the aggregate method:
113
110
  search.aggregate('popular_colors' => {'terms' => {'field' => 'color'}})
114
111
  ```
115
112
 
116
- It is important to note that adding aggregations to a query is different than retrieving the results of the query:
113
+ Results are retrieved at query time within `aggregations`:
117
114
 
118
115
  ```ruby
119
116
  search = search.aggregate('popular_colors' => {'terms' => {'field' => 'color'}})
120
- search.aggregations
121
- #=> {"popular_colors" => {"buckets" => ...}}
117
+ search.aggregations['popular_colors'].buckets
122
118
  ```
123
119
 
124
120
  ### Getting Results ###
@@ -180,12 +176,14 @@ Use the `percolate` method to find records with queries that match.
180
176
 
181
177
  ## Index Configuration
182
178
 
183
- To avoid elasticsearch dynamically mapping fields, you can directly configure Product.doctype.mapping
184
- and Product.elastic_index.settings:
179
+ To avoid elasticsearch dynamically mapping fields, you can directly configure `elastic_index.mapping`
180
+ and `elastic_index.settings`:
185
181
 
186
182
  ```ruby
187
183
  class Product
188
- doctype.mapping = {
184
+ include ElasticRecord::Model
185
+
186
+ elastic_index.mapping = {
189
187
  properties: {
190
188
  name: {type: "text"},
191
189
  status: {type: "keyword"}
@@ -194,6 +192,27 @@ class Product
194
192
  end
195
193
  ```
196
194
 
195
+ Mapping types will be removed in ElasticSearch 7.x. To rename the default mapping type (`_doc`), use `elastic_index.mapping_type`:
196
+
197
+ ```ruby
198
+ class Product
199
+ include ElasticRecord::Model
200
+
201
+ elastic_index.mapping_type = 'product'
202
+ end
203
+ ```
204
+
205
+ ### Inheritance
206
+
207
+ When one model inherits from another, ElasticRecord makes some assumptions about how the child index should be configured. By default:
208
+
209
+ * `alias_name` - Same as parent
210
+ * `mapping` - Same as parent
211
+ * `mapping_type` - Same as parent
212
+ * `settings` (including analysis configuration) - Same as parent
213
+
214
+ These can all be overridden. For instance, it might be desirable for the child documents to be in a separate index.
215
+
197
216
  ### Load Documents from Source
198
217
 
199
218
  To fetch documents without an additional request to a backing ActiveRecord database you can load the documents from `_source`.
@@ -242,14 +261,3 @@ Product.elastic_index.reset # Delete related indexes and deploy a n
242
261
  Product.elastic_index.refresh # Call the refresh API
243
262
  Product.elastic_index.get_mapping # Get the index mapping defined by elastic search
244
263
  ```
245
-
246
- ## JSON Adapter ##
247
-
248
- By default, ElasticRecord uses ActiveSupport::JSON to serialize Elasticsearch payloads. There
249
- is optional support for using the Oj gem. To use Oj, ensure that oj is required and set:
250
-
251
- ```ruby
252
- ElasticRecord::JSON.parser = :oj
253
- ```
254
-
255
- To return to the default parser, set the variable to :active_support.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'elastic_record'
5
- s.version = '4.1.8'
5
+ s.version = '5.1.0'
6
6
  s.summary = 'An Elasticsearch querying ORM'
7
7
  s.description = 'Find your records with Elasticsearch'
8
8
 
@@ -3,19 +3,31 @@ require 'active_support/concern'
3
3
  require 'active_model'
4
4
 
5
5
  module ElasticRecord
6
- autoload :AsDocument, 'elastic_record/as_document'
7
- autoload :Callbacks, 'elastic_record/callbacks'
8
- autoload :Config, 'elastic_record/config'
9
- autoload :Connection, 'elastic_record/connection'
10
- autoload :Doctype, 'elastic_record/doctype'
11
- autoload :Index, 'elastic_record/index'
12
- autoload :JSON, 'elastic_record/json'
13
- autoload :Lucene, 'elastic_record/lucene'
14
- autoload :Model, 'elastic_record/model'
15
- autoload :NameCache, 'elastic_record/name_cache'
16
- autoload :PercolatorModel, 'elastic_record/percolator_model'
17
- autoload :Relation, 'elastic_record/relation'
18
- autoload :Searching, 'elastic_record/searching'
6
+ extend ActiveSupport::Autoload
7
+ autoload :AsDocument
8
+ autoload :Callbacks
9
+ autoload :Config
10
+ autoload :Connection
11
+ autoload :Doctype
12
+ autoload :Index
13
+ autoload :Lucene
14
+ autoload :Model
15
+ autoload :PercolatorModel
16
+ autoload :Relation
17
+ autoload :Searching
18
+
19
+ module AggregationResponse
20
+ extend ActiveSupport::Autoload
21
+
22
+ autoload :Aggregation
23
+ autoload :Bucket
24
+ autoload :Builder
25
+ autoload :HasAggregations
26
+ autoload :MultiBucketAggregation
27
+ autoload :MultiValueAggregation
28
+ autoload :SingleBucketAggregation
29
+ autoload :SingleValueAggregation
30
+ end
19
31
 
20
32
  class << self
21
33
  def configure
@@ -0,0 +1,19 @@
1
+ module ElasticRecord
2
+ module AggregationResponse
3
+ class Aggregation
4
+ attr_accessor :name, :results, :meta
5
+ def initialize(name, results)
6
+ @name = name
7
+ @results = results
8
+
9
+ @results.each do |key, value|
10
+ send("#{key}=", value) if respond_to?("#{key}=")
11
+ end
12
+ end
13
+
14
+ def inspect
15
+ "#<#{self.class} #{results}>"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ module ElasticRecord
2
+ module AggregationResponse
3
+ class Bucket
4
+ include HasAggregations
5
+
6
+ attr_accessor :results
7
+ def initialize(results)
8
+ @results = results
9
+ end
10
+
11
+ def key
12
+ results['key']
13
+ end
14
+
15
+ def doc_count
16
+ results['doc_count']
17
+ end
18
+
19
+ def inspect
20
+ "#<#{self.class} #{results}>"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,55 @@
1
+ module ElasticRecord
2
+ module AggregationResponse
3
+ class Builder
4
+ AGGREGATION_KLASSES = {
5
+ SingleBucketAggregation => %w(
6
+ children
7
+ sampler
8
+ filter
9
+ missing
10
+ nested
11
+ reverse_nested
12
+ global
13
+ ),
14
+ MultiBucketAggregation => %w(
15
+ composite
16
+ date_histogram
17
+ filters
18
+ geohash_grid
19
+ histogram
20
+ range
21
+ dterms
22
+ lterms
23
+ sterms
24
+ ),
25
+ SingleValueAggregation => %w(
26
+ avg
27
+ cardinality
28
+ max
29
+ min
30
+ value_count
31
+ ),
32
+ MultiValueAggregation => %w(
33
+ stats
34
+ dpercentiles
35
+ lpercentiles
36
+ spercentiles
37
+ )
38
+ }
39
+
40
+ AGGREGATIONS_BY_TYPE = AGGREGATION_KLASSES.each_with_object({}) do |(klass, types), hash|
41
+ types.each { |type| hash[type] = klass }
42
+ end
43
+
44
+ def self.extract(hash)
45
+ hash.each_with_object({}) do |(key, results), aggs|
46
+ next unless key.include?('#')
47
+
48
+ type, name = key.split('#')
49
+ klass = AGGREGATIONS_BY_TYPE.fetch(type)
50
+ aggs[name] = klass.new(name, results)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,9 @@
1
+ module ElasticRecord
2
+ module AggregationResponse
3
+ module HasAggregations
4
+ def aggregations
5
+ @aggregations ||= Builder.extract(results)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ElasticRecord
2
+ module AggregationResponse
3
+ class MultiBucketAggregation < Aggregation
4
+ def buckets
5
+ @buckets ||= results['buckets'].map { |bucket| ElasticRecord::AggregationResponse::Bucket.new(bucket) }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module ElasticRecord
2
+ module AggregationResponse
3
+ class MultiValueAggregation < Aggregation
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ module ElasticRecord
2
+ module AggregationResponse
3
+ class SingleBucketAggregation < Aggregation
4
+ include HasAggregations
5
+ attr_accessor :doc_count
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module ElasticRecord
2
+ module AggregationResponse
3
+ class SingleValueAggregation < Aggregation
4
+ attr_accessor :value, :value_as_string
5
+ end
6
+ end
7
+ end
@@ -1,8 +1,8 @@
1
1
  module ElasticRecord
2
2
  module AsDocument
3
- def as_search_document
4
- doctype.mapping[:properties].each_with_object({}) do |(field, mapping), result|
5
- value = elastic_search_value field, mapping
3
+ def as_search_document(mapping_properties = elastic_index.mapping[:properties])
4
+ mapping_properties.each_with_object({}) do |(field, mapping), result|
5
+ value = value_for_elastic_search field, mapping, mapping_properties
6
6
 
7
7
  unless value.nil?
8
8
  result[field] = value
@@ -11,31 +11,48 @@ module ElasticRecord
11
11
  end
12
12
 
13
13
  def as_partial_update_document
14
- mappings = doctype.mapping[:properties]
14
+ mapping_properties = elastic_index.mapping[:properties]
15
+ changed_fields = respond_to?(:saved_changes) ? saved_changes.keys : changed
15
16
 
16
- changed.each_with_object({}) do |field, result|
17
- if field_mapping = mappings[field]
18
- result[field] = elastic_search_value field, field_mapping
17
+ changed_fields.each_with_object({}) do |field, result|
18
+ if field_mapping = mapping_properties[field]
19
+ result[field] = value_for_elastic_search field, field_mapping, mapping_properties
19
20
  end
20
21
  end
21
22
  end
22
23
 
23
- def elastic_search_value(field, mapping)
24
+ def value_for_elastic_search(field, mapping, mapping_properties)
24
25
  value = try field
25
26
  return if value.nil?
26
27
 
27
- value = case mapping[:type]
28
- when :object
29
- value.as_search_document
30
- when :nested
31
- value.map(&:as_search_document)
32
- else
33
- value
34
- end
28
+ value =
29
+ case mapping[:type]&.to_sym
30
+ when :object
31
+ object_mapping_properties = mapping_properties.dig(field, :properties)
32
+ value_for_elastic_search_object(value, object_mapping_properties)
33
+ when :nested
34
+ object_mapping_properties = mapping_properties.dig(field, :properties)
35
+ value.map { |entry| value_for_elastic_search_object(entry, object_mapping_properties) }
36
+ when :integer_range, :float_range, :long_range, :double_range, :date_range
37
+ value_for_elastic_search_range(value)
38
+ else
39
+ value
40
+ end
35
41
 
36
42
  if value.present? || value == false
37
43
  value
38
44
  end
39
45
  end
46
+
47
+ def value_for_elastic_search_object(object, nested_mapping)
48
+ object.respond_to?(:as_search_document) ? object.as_search_document(nested_mapping) : object
49
+ end
50
+
51
+ def value_for_elastic_search_range(range)
52
+ gte = range.begin unless range.begin == -Float::INFINITY
53
+ lte = range.end unless range.end == Float::INFINITY
54
+
55
+ {'gte' => gte, 'lte' => lte}
56
+ end
40
57
  end
41
58
  end
@@ -5,7 +5,7 @@ module ElasticRecord
5
5
 
6
6
  base.class_eval do
7
7
  after_create :create_index_document
8
- after_update :update_index_document, if: :changed?
8
+ after_update :update_index_document, if: -> { respond_to?(:saved_changes?) ? saved_changes? : changed? }
9
9
  after_destroy :delete_index_document
10
10
  end
11
11
  end
@@ -5,6 +5,9 @@ module ElasticRecord
5
5
  class_attribute :connection_options
6
6
  self.connection_options = {}
7
7
 
8
+ class_attribute :default_index_settings
9
+ self.default_index_settings = {}
10
+
8
11
  class_attribute :model_names
9
12
  self.model_names = []
10
13