elastic_record 4.1.8 → 5.1.0

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