elastic_record 1.1.8 → 1.2.1

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