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
@@ -10,35 +10,27 @@ module ElasticRecord
10
10
  end
11
11
 
12
12
  module ClassMethods
13
+ DEFAULT_PERCOLATOR_MAPPING = {
14
+ properties: {
15
+ query: { type: 'percolator' }
16
+ }
17
+ }
13
18
  def elastic_index
14
19
  @elastic_index ||=
15
20
  begin
16
- index = ElasticRecord::Index.new([self, percolates_model])
21
+ index = ElasticRecord::Index.new(self)
22
+ index.mapping = DEFAULT_PERCOLATOR_MAPPING
23
+ index.mapping = percolates_model.elastic_index.mapping
24
+ index.analysis = percolates_model.elastic_index.analysis
17
25
  index.partial_updates = false
18
26
  index
19
27
  end
20
28
  end
21
29
 
22
- def doctype
23
- @doctype ||= Doctype.percolator_doctype
24
- end
25
-
26
30
  def percolate(document)
27
- query = {
28
- "query" => {
29
- "percolate" => {
30
- "field" => "query",
31
- "document_type" => percolates_model.doctype.name,
32
- "document" => document
33
- }
34
- },
35
- "size" => 1000
36
- }
37
-
38
- hits = elastic_index.search(query)['hits']['hits']
39
- ids = hits.map { |hits| hits['_id'] }
31
+ query = Arelastic::Queries::Percolate.new("query", document)
40
32
 
41
- where(id: ids)
33
+ elastic_search.filter(query).limit(5000)
42
34
  end
43
35
  end
44
36
  end
@@ -2,19 +2,20 @@ require 'elastic_record/relation/value_methods'
2
2
  require 'elastic_record/relation/batches'
3
3
  require 'elastic_record/relation/delegation'
4
4
  require 'elastic_record/relation/finder_methods'
5
+ require 'elastic_record/relation/hits'
5
6
  require 'elastic_record/relation/merging'
6
7
  require 'elastic_record/relation/none'
7
8
  require 'elastic_record/relation/search_methods'
8
9
 
9
10
  module ElasticRecord
10
11
  class Relation
11
- include Batches, Delegation, FinderMethods, Merging, SearchMethods
12
+ include Batches, Delegation, FinderMethods, Hits, Merging, SearchMethods
12
13
 
13
14
  attr_reader :klass, :values
14
15
 
15
- def initialize(klass)
16
+ def initialize(klass, values = {})
16
17
  @klass = klass
17
- @values = {}
18
+ @values = values
18
19
  end
19
20
 
20
21
  def count
@@ -22,7 +23,10 @@ module ElasticRecord
22
23
  end
23
24
 
24
25
  def aggregations
25
- search_results['aggregations']
26
+ @aggregations ||= begin
27
+ results = search_results['aggregations']
28
+ ElasticRecord::AggregationResponse::Builder.extract(results)
29
+ end
26
30
  end
27
31
 
28
32
  def explain(id)
@@ -35,11 +39,7 @@ module ElasticRecord
35
39
  end
36
40
 
37
41
  def to_a
38
- @records ||= load_hits
39
- end
40
-
41
- def to_ids
42
- search_hits.map { |hit| hit['_id'] }
42
+ @records ||= load_hits(search_hits)
43
43
  end
44
44
 
45
45
  def delete_all
@@ -64,30 +64,9 @@ module ElasticRecord
64
64
 
65
65
  private
66
66
 
67
- def search_hits
68
- search_results['hits']['hits']
69
- end
70
-
71
67
  def reset
72
68
  @search_results = @records = nil
73
69
  end
74
70
 
75
- def search_results
76
- @search_results ||= begin
77
- options = search_type_value ? {search_type: search_type_value} : {}
78
- search = as_elastic.update('_source' => klass.elastic_index.load_from_source)
79
-
80
- klass.elastic_index.search(search, options)
81
- end
82
- end
83
-
84
- def load_hits
85
- if klass.elastic_index.load_from_source
86
- search_hits.map { |hit| klass.new(hit['_source'].update('id' => hit['_id'])) }
87
- else
88
- scope = select_values.any? ? klass.select(select_values) : klass
89
- scope.find(to_ids)
90
- end
91
- end
92
71
  end
93
72
  end
@@ -8,13 +8,15 @@ module ElasticRecord
8
8
  end
9
9
 
10
10
  def find_in_batches(options = {})
11
- find_ids_in_batches(options) do |ids|
12
- yield klass.where(id: ids)
11
+ build_scroll_enumerator(options).each_slice do |hits|
12
+ yield load_hits(hits)
13
13
  end
14
14
  end
15
15
 
16
16
  def find_ids_in_batches(options = {}, &block)
17
- build_scroll_enumerator(options).each_slice(&block)
17
+ build_scroll_enumerator(options).each_slice do |hits|
18
+ yield map_hits_to_ids(hits)
19
+ end
18
20
  end
19
21
 
20
22
  def build_scroll_enumerator(options)
@@ -10,15 +10,19 @@ module ElasticRecord
10
10
  end
11
11
 
12
12
  private
13
+ def respond_to_missing?(method, include_private = false)
14
+ super || klass.respond_to?(method, include_private) || Array.method_defined?(method)
15
+ end
16
+
13
17
  def method_missing(method, *args, &block)
14
- if klass.respond_to?(method)
15
- scoping { klass.send(method, *args, &block) }
16
- elsif Array.method_defined?(method)
18
+ if Array.method_defined?(method)
17
19
  to_a.send(method, *args, &block)
20
+ elsif klass.respond_to?(method)
21
+ scoping { klass.send(method, *args, &block) }
18
22
  else
19
23
  super
20
24
  end
21
25
  end
22
26
  end
23
27
  end
24
- end
28
+ end
@@ -27,6 +27,14 @@ module ElasticRecord
27
27
  to_a
28
28
  end
29
29
 
30
+ def find_by(*args)
31
+ filter(*args).first
32
+ end
33
+
34
+ def find_by!(*args)
35
+ filter(*args).first!
36
+ end
37
+
30
38
  private
31
39
 
32
40
  def find_one(relation)
@@ -0,0 +1,34 @@
1
+ module ElasticRecord
2
+ class Relation
3
+ module Hits
4
+ def to_ids
5
+ map_hits_to_ids search_hits
6
+ end
7
+
8
+ def load_hits(search_hits)
9
+ if klass.elastic_index.load_from_source
10
+ search_hits.map { |hit| klass.new(hit['_source'].update('id' => hit['_id'])) }
11
+ else
12
+ klass.find map_hits_to_ids(search_hits)
13
+ end
14
+ end
15
+
16
+ def map_hits_to_ids(hits)
17
+ hits.map { |hit| hit['_id'] }
18
+ end
19
+
20
+ def search_hits
21
+ search_results['hits']['hits']
22
+ end
23
+
24
+ def search_results
25
+ @search_results ||= begin
26
+ options = {typed_keys: true}
27
+ options[:search_type] = search_type_value if search_type_value
28
+
29
+ klass.elastic_index.search(as_elastic, options)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -48,6 +48,22 @@ module ElasticRecord
48
48
  end
49
49
  end
50
50
 
51
+ %i(
52
+ includes
53
+ joins
54
+ select
55
+ where
56
+ ).each do |ar_method|
57
+ define_method ar_method do |*args, &block|
58
+ result = klass.send(ar_method, *args, &block)
59
+ if result.is_a?(ActiveRecord::Relation)
60
+ self.class.new(result, values)
61
+ else
62
+ result
63
+ end
64
+ end
65
+ end
66
+
51
67
  def query!(value)
52
68
  self.query_value = value
53
69
  self
@@ -70,14 +86,6 @@ module ElasticRecord
70
86
  end
71
87
  end
72
88
 
73
- def find_by(*args)
74
- filter(*args).first
75
- end
76
-
77
- def find_by!(*args)
78
- filter(*args).first!
79
- end
80
-
81
89
  def limit!(value)
82
90
  self.limit_value = value
83
91
  self
@@ -96,19 +104,6 @@ module ElasticRecord
96
104
  clone.offset! value
97
105
  end
98
106
 
99
- def select!(*args)
100
- self.select_values += args.flatten
101
- self
102
- end
103
-
104
- def select(*args, &block)
105
- if block_given?
106
- to_a.select(&block)
107
- else
108
- clone.select! *args
109
- end
110
- end
111
-
112
107
  def search_options!(options)
113
108
  self.search_options_value ||= {}
114
109
  self.search_options_value.merge! options
@@ -233,7 +228,7 @@ module ElasticRecord
233
228
  if filter.is_a?(Arelastic::Nodes::Node)
234
229
  nodes << filter
235
230
  elsif filter.is_a?(ElasticRecord::Relation)
236
- nodes << Arelastic::Queries::HasChild.new(filter.elastic_index.type, filter.as_elastic['query'])
231
+ nodes << Arelastic::Queries::HasChild.new(filter.elastic_index.mapping_type, filter.as_elastic['query'])
237
232
  else
238
233
  filter.each do |field, terms|
239
234
  case terms
@@ -1,6 +1,6 @@
1
1
  module ElasticRecord
2
2
  class Relation
3
- MULTI_VALUE_METHODS = [:extending, :filter, :order, :select, :aggregation]
3
+ MULTI_VALUE_METHODS = [:extending, :filter, :order, :aggregation]
4
4
  SINGLE_VALUE_METHODS = [:query, :limit, :offset, :search_options, :search_type, :reverse_order]
5
5
  end
6
6
  end
@@ -1,7 +1,19 @@
1
- class Project < ActiveRecord::Base
1
+ class Project
2
+ class << self
3
+ def base_class
4
+ self
5
+ end
6
+ end
7
+
8
+ include ActiveModel::Model
2
9
  include ElasticRecord::Model
3
10
 
4
- self.doctype.mapping[:properties].update(
5
- 'name' => { type: 'string', index: 'not_analyzed' }
6
- )
11
+ attr_accessor :id, :name
12
+ alias_method :as_json, :as_search_document
13
+
14
+ elastic_index.load_from_source = true
15
+
16
+ def as_search_document
17
+ { name: name }
18
+ end
7
19
  end
@@ -1,19 +1,8 @@
1
- class Warehouse
2
- class << self
3
- def base_class
4
- self
5
- end
6
- end
7
-
8
- include ActiveModel::Model
1
+ class Warehouse < ActiveRecord::Base
9
2
  include ElasticRecord::Model
10
3
 
11
- attr_accessor :id, :name
12
- alias_method :as_json, :as_search_document
13
-
14
- elastic_index.load_from_source = true
15
-
16
- def as_search_document
17
- { name: name }
18
- end
4
+ elastic_index.mapping_type = 'warehouse'
5
+ elastic_index.mapping[:properties].update(
6
+ 'name' => { type: 'keyword' }
7
+ )
19
8
  end
@@ -1,22 +1,37 @@
1
- class Widget
2
- include TestModel
1
+ class Widget < ActiveRecord::Base
2
+ include ElasticRecord::Model
3
+ self.elastic_index.partial_updates = true
3
4
 
5
+ belongs_to :warehouse
4
6
  validates :color, format: {with: /[a-z]/}
5
7
 
6
- define_attributes [:name, :color, :warehouse_id]
8
+ class WidgetPart
9
+ include ElasticRecord::Model
10
+ attr_accessor :name
11
+ end
7
12
 
8
- self.doctype.mapping[:properties].update(
13
+ self.elastic_index.mapping_type = 'widget'
14
+ self.elastic_index.mapping[:properties].update(
9
15
  'name' => {
10
- type: 'string', index: 'not_analyzed',
16
+ type: 'text',
11
17
  fields: {
12
- analyzed: {type: 'string', index: 'analyzed'}
18
+ raw: { type: 'keyword' }
13
19
  }
14
20
  },
15
21
  'color' => {
16
- type: 'string', index: 'not_analyzed'
22
+ type: 'keyword'
17
23
  },
18
24
  'warehouse_id' => {
19
- type: 'string', index: 'not_analyzed'
25
+ type: 'keyword'
26
+ },
27
+ 'price' => {
28
+ type: 'long'
29
+ },
30
+ 'widget_part' => {
31
+ type: 'object',
32
+ properties: {
33
+ 'name' => { type: 'keyword' }
34
+ }
20
35
  }
21
36
  )
22
37
 
@@ -31,8 +46,4 @@ class Widget
31
46
  end
32
47
  end
33
48
  end
34
-
35
- def warehouse=(other)
36
- self.warehouse_id = other.id
37
- end
38
49
  end
@@ -4,6 +4,7 @@ class WidgetQuery
4
4
  define_attributes [:name, :color]
5
5
 
6
6
  self.percolates_model = Widget
7
+ self.elastic_index.mapping_type = 'widget'
7
8
 
8
9
  def as_search_document
9
10
  filters = {}
@@ -0,0 +1,7 @@
1
+ class CreateWarehouses < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :warehouses do |t|
4
+ t.string :name, null: false
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ class CreateWidgets < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :widgets do |t|
4
+ t.string :warehouse_id
5
+ t.string :name
6
+ t.string :color
7
+ t.integer :price
8
+ t.jsonb :widget_part
9
+ end
10
+ end
11
+ end
@@ -1,4 +1,3 @@
1
- # encoding: UTF-8
2
1
  # This file is auto-generated from the current state of the database. Instead
3
2
  # of editing this file, please use the migrations feature of Active Record to
4
3
  # incrementally modify your database, and then regenerate this schema definition.
@@ -11,13 +10,21 @@
11
10
  #
12
11
  # It's strongly recommended that you check this file into your version control system.
13
12
 
14
- ActiveRecord::Schema.define(version: 20151211225259) do
13
+ ActiveRecord::Schema.define(version: 20180215140125) do
15
14
 
16
15
  # These are extensions that must be enabled in order to support this database
17
16
  enable_extension "plpgsql"
18
17
 
19
- create_table "projects", force: :cascade do |t|
18
+ create_table "warehouses", force: :cascade do |t|
20
19
  t.string "name", null: false
21
20
  end
22
21
 
22
+ create_table "widgets", force: :cascade do |t|
23
+ t.string "warehouse_id"
24
+ t.string "name"
25
+ t.string "color"
26
+ t.integer "price"
27
+ t.jsonb "widget_part"
28
+ end
29
+
23
30
  end