elastic_record 1.1.8 → 1.2.1

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -1
  3. data/Gemfile +3 -1
  4. data/elastic_record.gemspec +1 -1
  5. data/lib/elastic_record/callbacks.rb +2 -0
  6. data/lib/elastic_record/connection.rb +5 -9
  7. data/lib/elastic_record/index/mapping.rb +2 -2
  8. data/lib/elastic_record/model.rb +1 -1
  9. data/lib/elastic_record/relation/search_methods.rb +0 -9
  10. data/lib/elastic_record/relation/value_methods.rb +1 -1
  11. data/lib/elastic_record/relation.rb +7 -26
  12. data/lib/elastic_record.rb +0 -1
  13. data/test/elastic_record/config_test.rb +1 -1
  14. data/test/elastic_record/connection_test.rb +2 -2
  15. data/test/elastic_record/index/mapping_test.rb +11 -1
  16. data/test/elastic_record/integration/active_record_test.rb +62 -25
  17. data/test/elastic_record/relation_test.rb +0 -47
  18. data/test/helper.rb +1 -7
  19. data/test/support/models/test_model.rb +1 -19
  20. data/test/support/models/warehouse.rb +0 -1
  21. data/test/support/models/widget.rb +0 -2
  22. metadata +11 -24
  23. data/lib/elastic_record/searches_many/association.rb +0 -149
  24. data/lib/elastic_record/searches_many/autosave.rb +0 -72
  25. data/lib/elastic_record/searches_many/builder.rb +0 -42
  26. data/lib/elastic_record/searches_many/collection_proxy.rb +0 -26
  27. data/lib/elastic_record/searches_many/reflection.rb +0 -45
  28. data/lib/elastic_record/searches_many.rb +0 -100
  29. data/test/elastic_record/searches_many/association_test.rb +0 -47
  30. data/test/elastic_record/searches_many/autosave_test.rb +0 -32
  31. data/test/elastic_record/searches_many/collection_proxy_test.rb +0 -23
  32. data/test/elastic_record/searches_many/reflection_test.rb +0 -33
  33. data/test/elastic_record/searches_many_test.rb +0 -76
  34. data/test/support/models/option.rb +0 -24
  35. data/test/support/query_counter.rb +0 -56
@@ -1,149 +0,0 @@
1
- require 'active_support/core_ext/module/delegation'
2
-
3
- module ElasticRecord
4
- module SearchesMany
5
- class Association
6
- attr_reader :owner, :reflection, :collection
7
-
8
- delegate :klass, :options, to: :reflection
9
-
10
- def initialize(owner, reflection)
11
- @owner = owner
12
- @reflection = reflection
13
- @collection = []
14
- @loaded = false
15
- end
16
-
17
- def writer(other_records)
18
- other_records = other_records.map do |other_record|
19
- other_record.is_a?(Hash) ? klass.new(other_record) : other_record
20
- end
21
-
22
- delete(load_collection - other_records)
23
- merge_collections(load_collection, other_records)
24
- concat(other_records - load_collection)
25
-
26
- if reflection.counter_cache_column
27
- owner.send("#{reflection.counter_cache_column}=", other_records.size)
28
- end
29
-
30
- if reflection.touch_column
31
- owner.send("#{reflection.touch_column}=", Time.current)
32
- end
33
- end
34
-
35
- def reader
36
- CollectionProxy.new(self)
37
- end
38
-
39
- def loaded?
40
- @loaded
41
- end
42
-
43
- def concat(*records)
44
- load_collection if owner.new_record?
45
-
46
- result = true
47
-
48
- records.flatten.each do |record|
49
- add_to_collection(record) do |r|
50
- result &&= record.save unless owner.new_record?
51
- end
52
- end
53
-
54
- result && records
55
- end
56
-
57
- def delete(records)
58
- records.each do |record|
59
- callback(:before_remove, record)
60
-
61
- if options[:autosave] || owner.new_record?
62
- record.mark_for_destruction
63
- else
64
- record.destroy
65
- end
66
-
67
- callback(:after_remove, record)
68
- end
69
- end
70
-
71
- def scope
72
- search = klass.elastic_search.filter("#{reflection.belongs_to}_id" => owner.id).limit(1000000)
73
- if options[:as]
74
- search.filter! "#{reflection.belongs_to}_type" => owner.class.name
75
- end
76
- search
77
- end
78
-
79
- def load_collection
80
- unless @loaded
81
- @collection = merge_collections(persisted_collection, collection)
82
- @loaded = true
83
- end
84
-
85
- collection
86
- end
87
-
88
- def eager_loaded_collection(records)
89
- unless @loaded
90
- @collection = records
91
- @loaded = true
92
- end
93
-
94
- collection
95
- end
96
-
97
- private
98
-
99
- def load_persisted_collection?
100
- !loaded? || owner.new_record?
101
- end
102
-
103
- def persisted_collection
104
- @persisted_collection ||= scope.to_a
105
- end
106
-
107
- def merge_collections(existing_records, new_records)
108
- return existing_records if new_records.empty?
109
- return new_records if existing_records.empty?
110
-
111
- existing_records.map! do |existing_record|
112
- if new_record = new_records.delete(existing_record)
113
- (existing_record.attributes.keys - new_record.changes.keys).each do |name|
114
- new_record.send("#{name}=", existing_record.send(name))
115
- end
116
-
117
- new_record
118
- else
119
- existing_record
120
- end
121
- end
122
-
123
- existing_records + new_records
124
- end
125
-
126
- def add_to_collection(record)
127
- callback(:before_add, record)
128
-
129
- record.send("#{reflection.belongs_to}=", owner)
130
- yield(record) if block_given?
131
- @collection << record
132
-
133
- callback(:after_add, record)
134
-
135
- record
136
- end
137
-
138
- def callback(method, record)
139
- reflection.callbacks[method].each do |callback|
140
- if callback.respond_to?(:call)
141
- callback.call(owner, record)
142
- else
143
- owner.send(callback, record)
144
- end
145
- end
146
- end
147
- end
148
- end
149
- end
@@ -1,72 +0,0 @@
1
- module ElasticRecord
2
- module SearchesMany
3
- module Autosave
4
- extend ActiveSupport::Concern
5
-
6
- module ClassMethods
7
- def add_autosave_callbacks(reflection)
8
- add_autosave_after_save_callbacks(reflection)
9
- add_autosave_validation_callbacks(reflection)
10
- end
11
-
12
- def add_autosave_after_save_callbacks(reflection)
13
- before_save { save_autosave_records(reflection) }
14
- end
15
-
16
- def add_autosave_validation_callbacks(reflection)
17
- validate { validate_autosave_records(reflection) }
18
- end
19
- end
20
- end
21
-
22
- def save_autosave_records(reflection)
23
- if association = searches_many_instance_get(reflection.name)
24
- associated_records_to_autosave(association).each do |record|
25
- if record.marked_for_destruction?
26
- record.destroy
27
- elsif record.changed?
28
- record.save
29
- end
30
- end
31
- end
32
- end
33
-
34
- def validate_autosave_records(reflection)
35
- if association = searches_many_instance_get(reflection.name)
36
- associated_records_to_autosave(association).each do |record|
37
- unless record.valid?
38
- record.errors.each do |attribute, message|
39
- attribute = "#{reflection.name}.#{attribute}"
40
- errors[attribute] << message
41
- errors[attribute].uniq!
42
- end
43
- end
44
- end
45
- end
46
- end
47
-
48
- def associated_records_to_autosave(association)
49
- if association.loaded?
50
- association.load_collection
51
- else
52
- []
53
- end
54
- end
55
-
56
- # Marks this record to be destroyed as part of the parents save transaction.
57
- # This does _not_ actually destroy the record instantly, rather child record will be destroyed
58
- # when <tt>parent.save</tt> is called.
59
- #
60
- # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
61
- def mark_for_destruction
62
- @marked_for_destruction = true
63
- end
64
-
65
- # Returns whether or not this record will be destroyed as part of the parents save transaction.
66
- #
67
- # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
68
- def marked_for_destruction?
69
- @marked_for_destruction
70
- end
71
- end
72
- end
@@ -1,42 +0,0 @@
1
- module ElasticRecord
2
- module SearchesMany
3
- class Builder
4
- def self.build(model, name, options)
5
- new(model, name, options).build
6
- end
7
-
8
- attr_reader :model, :name, :options
9
- def initialize(model, name, options)
10
- @model, @name, @options = model, name, options
11
- end
12
-
13
- def build
14
- define_writer
15
- define_reader
16
-
17
- reflection = ElasticRecord::SearchesMany::Reflection.new(model, name, options)
18
- model.searches_many_reflections = model.searches_many_reflections.merge(name => reflection)
19
-
20
- model.add_autosave_callbacks(reflection) if options[:autosave]
21
- end
22
-
23
- def mixin
24
- model.generated_searches_many_methods
25
- end
26
-
27
- def define_writer
28
- name = self.name
29
- mixin.redefine_method("#{name}=") do |records|
30
- searches_many_association(name).writer(records)
31
- end
32
- end
33
-
34
- def define_reader
35
- name = self.name
36
- mixin.redefine_method(name) do
37
- searches_many_association(name).reader
38
- end
39
- end
40
- end
41
- end
42
- end
@@ -1,26 +0,0 @@
1
- module ElasticRecord
2
- module SearchesMany
3
- class CollectionProxy < ElasticRecord::Relation
4
- def initialize(association)
5
- @association = association
6
- super association.klass
7
- merge! association.scope
8
- end
9
-
10
- def eager_loaded(records)
11
- @association.eager_loaded_collection(records)
12
- end
13
-
14
- def to_a
15
- records = @association.load_collection.reject(&:destroyed?)
16
- records = eager_load_associations(records) if eager_loading?
17
- records
18
- end
19
-
20
- def <<(*records)
21
- @association.concat(records) && self
22
- end
23
- alias_method :push, :<<
24
- end
25
- end
26
- end
@@ -1,45 +0,0 @@
1
- module ElasticRecord
2
- module SearchesMany
3
- class Reflection
4
- attr_reader :model, :name, :options
5
- attr_reader :callbacks
6
- def initialize(model, name, options)
7
- @model, @name, @options = model, name, options
8
- @callbacks = define_callbacks(options)
9
- end
10
-
11
- def klass
12
- klass_name.constantize
13
- end
14
-
15
- def klass_name
16
- options[:class_name] || name.to_s.classify
17
- end
18
-
19
- def belongs_to
20
- options[:as] ? options[:as].to_s : model.name.to_s.demodulize.underscore
21
- end
22
-
23
- def foreign_key
24
- options[:foreign_key] ? options[:foreign_key].to_s : "#{model.name.to_s.demodulize.underscore}_id"
25
- end
26
-
27
- CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
28
- def define_callbacks(options)
29
- Hash[CALLBACKS.map { |callback_name| [callback_name, Array(options[callback_name.to_sym])] }]
30
- end
31
-
32
- def touch_column
33
- if options[:touch]
34
- options[:touch] == true ? :updated_at : options[:touch].to_sym
35
- end
36
- end
37
-
38
- def counter_cache_column
39
- if options[:counter_cache]
40
- (options[:counter_cache] == true ? "#{name}_count" : options[:counter_cache]).to_sym
41
- end
42
- end
43
- end
44
- end
45
- end
@@ -1,100 +0,0 @@
1
- require 'elastic_record/searches_many/association'
2
- require 'elastic_record/searches_many/autosave'
3
- require 'elastic_record/searches_many/builder'
4
- require 'elastic_record/searches_many/collection_proxy'
5
- require 'elastic_record/searches_many/reflection'
6
-
7
- module ElasticRecord
8
- module SearchesMany
9
- def self.included(base)
10
- base.class_eval do
11
- extend ClassMethods
12
-
13
- class_attribute :searches_many_reflections
14
- self.searches_many_reflections = {}
15
-
16
- include ElasticRecord::SearchesMany::Autosave
17
- end
18
- end
19
-
20
- module ClassMethods
21
- # Specifies a one-to-many association. The following methods for retrieval and query of
22
- # collections of associated objects will be added:
23
- #
24
- # [collection]
25
- # Returns an array of all the associated objects.
26
- # [collection=objects]
27
- # Replaces the collections content by deleting and adding objects as appropriate.
28
- # [collection_params=objects]
29
- # Support for nested assignment from a form
30
- # === Options
31
- # [:as]
32
- # Specifies a polymorphic interface (See <tt>belongs_to</tt>).
33
- # [:touch]
34
- # Specify to update the owner when changed. Specify <tt>true</tt>
35
- # to update the updated_at field. If you specify a symbol, that attribute
36
- # will be updated with the current time in addition to the updated_at/on attribute.
37
- # [:autosave]
38
- # If true, always save the associated objects or destroy them if marked for destruction, when
39
- # saving the parent object.
40
- # [:counter_cache]
41
- # Caches the number of belonging objects on the associate class. This requires that a column
42
- # named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
43
- # is used on the associate class (such as a Post class). You can also specify a custom counter
44
- # cache column by providing a column name instead of a +true+/+false+ value to this
45
- # option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
46
- # [:class_name]
47
- # Specify the class name of the association. Use it only if that name can't be inferred
48
- # from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
49
- # if the real class name is Person, you'll have to specify it with this option.
50
- #
51
- # === Example
52
- #
53
- # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
54
- # * <tt>Firm#clients</tt>
55
- # * <tt>Firm#clients=(objects)</tt>
56
- # * <tt>Firm#client_params=(params)</tt>
57
- def searches_many(name, options = {})
58
- ElasticRecord::SearchesMany::Builder.build(self, name, options)
59
- end
60
-
61
- def generated_searches_many_methods
62
- @generated_searches_many_methods ||= begin
63
- mod = const_set(:GeneratedSearchesManyMethods, Module.new)
64
- include mod
65
- mod
66
- end
67
- end
68
- end
69
-
70
- # Returns the searches_many instance for the given name, instantiating it if it doesn't already exist
71
- def searches_many_association(name)
72
- association = searches_many_instance_get(name)
73
-
74
- if association.nil?
75
- association = ElasticRecord::SearchesMany::Association.new(self, searches_many_reflections[name])
76
- searches_many_instance_set(name, association)
77
- end
78
-
79
- association
80
- end
81
-
82
- def reload
83
- super
84
- searches_many_cache.clear
85
- end
86
-
87
- private
88
- def searches_many_cache
89
- @searches_many_cache ||= {}
90
- end
91
-
92
- def searches_many_instance_get(name)
93
- searches_many_cache[name.to_sym]
94
- end
95
-
96
- def searches_many_instance_set(name, association)
97
- searches_many_cache[name.to_sym] = association
98
- end
99
- end
100
- end
@@ -1,47 +0,0 @@
1
- require 'helper'
2
-
3
- class ElasticRecord::SearchesMany::AssociationTest < MiniTest::Spec
4
-
5
- def test_writer_assignment_from_hash
6
- warehouse = Warehouse.new
7
- warehouse.widgets = [{color: 'blue', name: 'thing'}]
8
-
9
- assert_equal 1, warehouse.widgets.all.count
10
- expected = {
11
- 'color' => 'blue',
12
- 'name' => 'thing',
13
- 'warehouse_id' => warehouse.id,
14
- }
15
- assert_equal expected, warehouse.widgets[0].attributes
16
- end
17
-
18
- def test_writer
19
- warehouse = Warehouse.new
20
-
21
- warehouse.widgets = [{color: 'blue', name: 'thing'}]
22
-
23
- assert_equal 1, warehouse.widgets.all.count
24
-
25
- warehouse.widgets = [ {color: 'red', name: 'device'}, {color: 'blue', name: 'thing'} ]
26
-
27
- assert_equal 3, warehouse.widgets.all.count
28
- assert warehouse.widgets[0].marked_for_destruction?
29
- refute warehouse.widgets[1].marked_for_destruction?
30
- refute warehouse.widgets[2].marked_for_destruction?
31
-
32
- expected = {
33
- 'color' => 'red',
34
- 'name' => 'device',
35
- 'warehouse_id' => warehouse.id,
36
- }
37
- assert_equal expected, warehouse.widgets[1].attributes
38
-
39
- expected = {
40
- 'color' => 'blue',
41
- 'name' => 'thing',
42
- 'warehouse_id' => warehouse.id,
43
- }
44
- assert_equal expected, warehouse.widgets[2].attributes
45
- end
46
-
47
- end
@@ -1,32 +0,0 @@
1
- require 'helper'
2
-
3
- class ElasticRecord::SearchesMany::AutosaveTest < MiniTest::Unit::TestCase
4
- def test_save_associations_autosave_callback
5
- warehouse = Warehouse.new
6
- widget = Widget.new
7
- warehouse.widgets = [widget]
8
- assert warehouse.new_record?
9
- assert widget.new_record?
10
-
11
- warehouse.save
12
-
13
- assert widget.persisted?
14
- end
15
-
16
- def test_validate_associations_autosave_callback
17
- warehouse = Warehouse.new
18
- widget = Widget.new color: 123
19
- warehouse.widgets = [widget]
20
-
21
- assert warehouse.invalid?
22
- assert_equal ["is invalid"], warehouse.errors['widgets.color']
23
- end
24
-
25
- def test_mark_for_destruction
26
- widget = Widget.new
27
-
28
- widget.mark_for_destruction
29
-
30
- assert widget.marked_for_destruction?
31
- end
32
- end
@@ -1,23 +0,0 @@
1
- require 'helper'
2
-
3
- class ElasticRecord::SearchesMany::CollectionProxyTest < MiniTest::Unit::TestCase
4
- def test_add_to_new_record
5
- warehouse = Warehouse.new
6
- widget = Widget.new
7
-
8
- warehouse.widgets << widget
9
-
10
- assert widget.new_record?
11
- assert_equal [widget], warehouse.widgets
12
- end
13
-
14
- def test_add_to_persisted_record
15
- warehouse = Warehouse.create
16
- widget = Widget.new
17
-
18
- warehouse.widgets << widget
19
-
20
- assert !widget.new_record?
21
- assert_equal [widget], warehouse.widgets
22
- end
23
- end
@@ -1,33 +0,0 @@
1
- require 'helper'
2
-
3
- class ElasticRecord::SearchesMany::ReflectionTest < MiniTest::Unit::TestCase
4
-
5
- def test_foreiegn_key
6
- assert_equal 'warehouse_id', reflection_class.new(Warehouse, :widgets, {}).foreign_key
7
- assert_equal 'foo_id', reflection_class.new(Warehouse, :widgets, {:foreign_key => 'foo_id' }).foreign_key
8
- end
9
-
10
- def test_klass_name
11
- assert_equal 'Product', reflection_class.new(Warehouse, :widgets, {class_name: 'Product'}).klass_name
12
- assert_equal 'Widget', reflection_class.new(Warehouse, :widgets, {}).klass_name
13
- end
14
-
15
- def test_touch_column
16
- assert_nil reflection_class.new(Warehouse, :widgets, {}).touch_column
17
- assert_equal :updated_at, reflection_class.new(Warehouse, :widgets, touch: true).touch_column
18
- assert_equal :my_column, reflection_class.new(Warehouse, :widgets, touch: :my_column).touch_column
19
- end
20
-
21
- def test_counter_cache_column
22
- assert_nil reflection_class.new(Warehouse, :widgets, {}).counter_cache_column
23
- assert_equal :widgets_count, reflection_class.new(Warehouse, :widgets, counter_cache: true).counter_cache_column
24
- assert_equal :my_column, reflection_class.new(Warehouse, :widgets, counter_cache: :my_column).counter_cache_column
25
- end
26
-
27
- private
28
-
29
- def reflection_class
30
- ElasticRecord::SearchesMany::Reflection
31
- end
32
-
33
- end
@@ -1,76 +0,0 @@
1
- require 'helper'
2
-
3
- class ElasticRecord::SearchesManyTest < MiniTest::Unit::TestCase
4
- def test_reader
5
- warehouse = Warehouse.create
6
- related_widget = Widget.create warehouse: warehouse
7
- unrelated_widget = Widget.create
8
-
9
- assert_equal [related_widget], warehouse.widgets
10
- end
11
-
12
- def test_write_with_objects
13
- warehouse = Warehouse.new
14
- widget = Widget.new
15
-
16
- warehouse.widgets = [widget]
17
-
18
- assert widget.new_record?
19
- assert_equal warehouse.id, widget.warehouse_id
20
- # assert_equal 1, warehouse.widgets_count
21
- # assert_in_delta Time.current, warehouse.widgets_updated_at, 5
22
- end
23
-
24
- def test_write_with_attributes
25
- warehouse = Warehouse.new
26
-
27
- warehouse.widgets = [
28
- {
29
- color: 'blue',
30
- name: 'Toy'
31
- }
32
- ]
33
-
34
- widgets = warehouse.widgets
35
- assert_equal 1, widgets.size
36
- end
37
-
38
- def test_write_marks_destroyed
39
- warehouse = Warehouse.new
40
- widget = Widget.create warehouse: warehouse
41
-
42
- warehouse.widgets = []
43
-
44
- association = warehouse.searches_many_association(:widgets)
45
- assert_equal 1, association.reader.size
46
- assert association.reader.first.marked_for_destruction?
47
- end
48
-
49
- def test_write_existing_record
50
- widget = Widget.create name: 'Toy', color: 'green'
51
- warehouse = Warehouse.new widgets: [widget]
52
-
53
- warehouse.widgets = [
54
- {
55
- id: widget.id,
56
- color: "blue"
57
- }
58
- ]
59
-
60
- widgets = warehouse.widgets
61
- assert_equal 1, widgets.size
62
- assert_equal "blue", widgets.first.color
63
- assert_equal "Toy", widgets.first.name
64
- end
65
-
66
- def test_reload
67
- warehouse = Warehouse.create
68
- widget = Widget.create name: 'Toy', color: 'green', warehouse: warehouse
69
- assert_equal [widget], warehouse.widgets
70
- widget.destroy
71
-
72
- warehouse.reload
73
-
74
- assert_equal [], warehouse.widgets
75
- end
76
- end
@@ -1,24 +0,0 @@
1
- class Option
2
- include TestModel
3
-
4
- define_attributes [:name, :widget_id]
5
-
6
- searches_many :options
7
-
8
- self.elastic_index.mapping[:properties].update(
9
- name: {
10
- type: 'multi_field',
11
- fields: {
12
- name: {type: 'string', index: 'not_analyzed'},
13
- analyzed: {type: 'string', index: 'analyzed'}
14
- }
15
- },
16
- widget_id: {
17
- type: 'string', index: 'not_analyzed'
18
- }
19
- )
20
-
21
- def widget=(other)
22
- self.widget_id = other.id
23
- end
24
- end