elastic_record 0.8.2 → 0.9.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 (34) hide show
  1. data/elastic_record.gemspec +1 -1
  2. data/lib/elastic_record/index/deferred.rb +81 -0
  3. data/lib/elastic_record/index.rb +6 -4
  4. data/lib/elastic_record/model.rb +3 -1
  5. data/lib/elastic_record/relation/merging.rb +6 -5
  6. data/lib/elastic_record/relation/search_methods.rb +8 -8
  7. data/lib/elastic_record/searches_many/association.rb +134 -0
  8. data/lib/elastic_record/searches_many/autosave.rb +72 -0
  9. data/lib/elastic_record/searches_many/builder.rb +42 -0
  10. data/lib/elastic_record/searches_many/collection_proxy.rb +20 -0
  11. data/lib/elastic_record/searches_many/reflection.rb +41 -0
  12. data/lib/elastic_record/searches_many.rb +91 -0
  13. data/lib/elastic_record.rb +3 -1
  14. data/test/elastic_record/callbacks_test.rb +2 -7
  15. data/test/elastic_record/config_test.rb +1 -1
  16. data/test/elastic_record/index/documents_test.rb +1 -1
  17. data/test/elastic_record/index/manage_test.rb +5 -1
  18. data/test/elastic_record/index/percolator_test.rb +5 -0
  19. data/test/elastic_record/relation/batches_test.rb +1 -3
  20. data/test/elastic_record/relation/delegation_test.rb +0 -5
  21. data/test/elastic_record/relation/finder_methods_test.rb +2 -4
  22. data/test/elastic_record/relation/none_test.rb +0 -5
  23. data/test/elastic_record/relation/search_methods_test.rb +19 -0
  24. data/test/elastic_record/relation_test.rb +0 -7
  25. data/test/elastic_record/searches_many/autosave_test.rb +31 -0
  26. data/test/elastic_record/searches_many/collection_proxy_test.rb +23 -0
  27. data/test/elastic_record/searches_many/reflection_test.rb +20 -0
  28. data/test/elastic_record/searches_many_test.rb +65 -0
  29. data/test/helper.rb +16 -1
  30. data/test/support/connect.rb +1 -1
  31. data/test/support/models/test_model.rb +98 -0
  32. data/test/support/models/warehouse.rb +6 -0
  33. data/test/support/{widget.rb → models/widget.rb} +10 -19
  34. metadata +16 -3
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'elastic_record'
5
- s.version = '0.8.2'
5
+ s.version = '0.9.0'
6
6
  s.summary = 'Use Elastic Search with your objects'
7
7
  s.description = 'Find your records with elastic search'
8
8
 
@@ -0,0 +1,81 @@
1
+ module ElasticRecord
2
+ class Index
3
+ module Deferred
4
+ class DeferredConnection
5
+ class DeferredAction < Struct.new(:method, :args, :block)
6
+ def run(index)
7
+ index.send(method, *args, &block)
8
+ end
9
+ end
10
+
11
+ attr_accessor :index
12
+ attr_accessor :deferred_actions
13
+ attr_accessor :writes_made
14
+
15
+ def initialize(index)
16
+ self.index = index
17
+ reset!
18
+ end
19
+
20
+ def reset!
21
+ if writes_made
22
+ begin
23
+ index.disable_deferring!
24
+ index.reset
25
+ ensure
26
+ index.enable_deferring!
27
+ end
28
+ end
29
+ self.deferred_actions = []
30
+ self.writes_made = false
31
+ end
32
+
33
+ def flush!
34
+ deferred_actions.each do |queued_action|
35
+ self.writes_made = true
36
+ queued_action.run(index.real_connection)
37
+ end
38
+ deferred_actions.clear
39
+ end
40
+
41
+ private
42
+ READ_METHODS = [:json_get, :head]
43
+ def method_missing(method, *args, &block)
44
+ super unless index.real_connection.respond_to?(method)
45
+
46
+ if READ_METHODS.include?(method)
47
+ flush!
48
+ index.real_connection.json_post "/#{index.alias_name}/_refresh"
49
+ index.real_connection.send(method, *args, &block)
50
+ else
51
+ deferred_actions << DeferredAction.new(method, args, block)
52
+ end
53
+ end
54
+ end
55
+
56
+ def enable_deferring!
57
+ @deferring_enabled = true
58
+ end
59
+
60
+ def disable_deferring!
61
+ @deferring_enabled = false
62
+ end
63
+
64
+ def connection
65
+ if @deferring_enabled
66
+ deferred_connection
67
+ else
68
+ real_connection
69
+ end
70
+ end
71
+
72
+ def reset_deferring!
73
+ deferred_connection.reset!
74
+ end
75
+
76
+ def deferred_connection
77
+ @deferred_connection ||= DeferredConnection.new(self)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -1,3 +1,4 @@
1
+ require 'elastic_record/index/deferred'
1
2
  require 'elastic_record/index/documents'
2
3
  require 'elastic_record/index/manage'
3
4
  require 'elastic_record/index/mapping'
@@ -10,6 +11,7 @@ module ElasticRecord
10
11
  include Manage
11
12
  include Mapping
12
13
  include Percolator
14
+ include Deferred
13
15
 
14
16
  attr_accessor :model
15
17
  attr_accessor :disabled
@@ -35,13 +37,13 @@ module ElasticRecord
35
37
  @disabled = false
36
38
  end
37
39
 
40
+ def real_connection
41
+ model.elastic_connection
42
+ end
43
+
38
44
  private
39
45
  def new_index_name
40
46
  "#{alias_name}_#{Time.now.to_i}"
41
47
  end
42
-
43
- def connection
44
- model.elastic_connection
45
- end
46
48
  end
47
49
  end
@@ -2,7 +2,9 @@ module ElasticRecord
2
2
  module Model
3
3
  def self.included(base)
4
4
  base.class_eval do
5
- extend Searching, ClassMethods
5
+ extend Searching
6
+ extend ClassMethods
7
+ include SearchesMany
6
8
  end
7
9
  end
8
10
 
@@ -18,16 +18,17 @@ module ElasticRecord
18
18
  @values = other.values
19
19
  end
20
20
 
21
- def normal_values
22
- Relation::MULTI_VALUE_METHODS + Relation::SINGLE_VALUE_METHODS
23
- end
24
-
25
21
  def merge
26
- normal_values.each do |name|
22
+ Relation::SINGLE_VALUE_METHODS.each do |name|
27
23
  value = values[name]
28
24
  relation.send("#{name}!", value) unless value.blank?
29
25
  end
30
26
 
27
+ Relation::MULTI_VALUE_METHODS.each do |name|
28
+ value = values[name]
29
+ relation.send("#{name}!", *value) unless value.blank?
30
+ end
31
+
31
32
  relation
32
33
  end
33
34
  end
@@ -35,7 +35,7 @@ module ElasticRecord
35
35
  end
36
36
 
37
37
  def filter!(*args)
38
- self.filter_values += args.flatten
38
+ self.filter_values += args
39
39
  self
40
40
  end
41
41
 
@@ -137,9 +137,9 @@ module ElasticRecord
137
137
  if query && filter
138
138
  arelastic.query.filtered(query, filter)
139
139
  elsif query
140
- query
140
+ Arelastic::Searches::Query.new(query)
141
141
  elsif filter
142
- arelastic.query.constant_score(filter)
142
+ arelastic.query.constant_score(Arelastic::Searches::Filter.new(filter))
143
143
  else
144
144
  arelastic.query.match_all
145
145
  end
@@ -150,9 +150,7 @@ module ElasticRecord
150
150
  query = Arelastic::Queries::QueryString.new query
151
151
  end
152
152
 
153
- if query
154
- Arelastic::Searches::Query.new query
155
- end
153
+ query
156
154
  end
157
155
 
158
156
  def build_filter(filters)
@@ -161,6 +159,8 @@ module ElasticRecord
161
159
  filters.map do |filter|
162
160
  if filter.is_a?(Arelastic::Filters::Filter)
163
161
  nodes << filter
162
+ elsif filter.is_a?(ElasticRecord::Relation)
163
+ nodes << Arelastic::Filters::HasChild.new(filter.elastic_index.type, filter.as_elastic['query'])
164
164
  else
165
165
  filter.each do |field, terms|
166
166
  case terms
@@ -176,9 +176,9 @@ module ElasticRecord
176
176
  end
177
177
 
178
178
  if nodes.size == 1
179
- Arelastic::Searches::Filter.new nodes.first
179
+ nodes.first
180
180
  elsif nodes.size > 1
181
- Arelastic::Searches::Filter.new Arelastic::Filters::And.new(nodes)
181
+ Arelastic::Filters::And.new(nodes)
182
182
  end
183
183
  end
184
184
 
@@ -0,0 +1,134 @@
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
+ if reflection.counter_cache_column
23
+ owner.send("#{reflection.counter_cache_column}=", other_records.size)
24
+ end
25
+
26
+ if reflection.touch_column
27
+ owner.send("#{reflection.touch_column}=", Time.current)
28
+ end
29
+
30
+ delete(load_collection - other_records)
31
+ merge_collections(load_collection, other_records)
32
+ concat(other_records - load_collection)
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
+ if options[:autosave] || owner.new_record?
59
+ records.each(&:mark_for_destruction)
60
+ else
61
+ record.destroy
62
+ end
63
+ end
64
+
65
+ def scope
66
+ search = klass.elastic_search.filter "#{reflection.belongs_to}_id" => owner.id
67
+ if options[:as]
68
+ search.filter! "#{reflection.belongs_to}_type" => owner.class.name
69
+ end
70
+ search
71
+ end
72
+
73
+ def load_collection
74
+ unless @loaded
75
+ @collection = merge_collections(persisted_collection, collection)
76
+ @loaded = true
77
+ end
78
+
79
+ loaded = true
80
+ collection
81
+ end
82
+
83
+ private
84
+ def load_persisted_collection?
85
+ !loaded? || owner.new_record?
86
+ end
87
+
88
+ def persisted_collection
89
+ scope.to_a
90
+ end
91
+
92
+ def merge_collections(existing_records, new_records)
93
+ return existing_records if new_records.empty?
94
+ return new_records if existing_records.empty?
95
+
96
+ existing_records.map! do |existing_record|
97
+ if new_record = new_records.delete(existing_record)
98
+ (existing_record.attributes.keys - new_record.changes.keys).each do |name|
99
+ new_record.send("#{name}=", existing_record.send(name))
100
+ end
101
+
102
+ new_record
103
+ else
104
+ existing_record
105
+ end
106
+ end
107
+
108
+ existing_records + new_records
109
+ end
110
+
111
+ def add_to_collection(record)
112
+ callback(:before_add, record)
113
+
114
+ record.send("#{reflection.belongs_to}=", owner)
115
+ yield(record) if block_given?
116
+ @collection << record
117
+
118
+ callback(:after_add, record)
119
+
120
+ record
121
+ end
122
+
123
+ def callback(method, record)
124
+ reflection.callbacks[method].each do |callback|
125
+ if callback.is_a?(Symbol)
126
+ owner.send(callback, record)
127
+ else
128
+ callback.call(owner, record)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,72 @@
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
@@ -0,0 +1,42 @@
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
@@ -0,0 +1,20 @@
1
+ module ElasticRecord
2
+ module SearchesMany
3
+ class CollectionProxy < ElasticRecord::Relation
4
+ def initialize(association)
5
+ @association = association
6
+ super association.klass, association.klass.arelastic
7
+ merge! association.scope
8
+ end
9
+
10
+ def to_a
11
+ @association.load_collection.reject(&:destroyed?)
12
+ end
13
+
14
+ def <<(*records)
15
+ @association.concat(records) && self
16
+ end
17
+ alias_method :push, :<<
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,41 @@
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
+ 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
+ CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
24
+ def define_callbacks(options)
25
+ Hash[CALLBACKS.map { |callback_name| [callback_name, Array(options[callback_name.to_sym])] }]
26
+ end
27
+
28
+ def touch_column
29
+ if options[:touch]
30
+ options[:touch] == true ? :updated_at : options[:touch].to_sym
31
+ end
32
+ end
33
+
34
+ def counter_cache_column
35
+ if options[:counter_cache]
36
+ (options[:counter_cache] == true ? "#{name}_count" : options[:counter_cache]).to_sym
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,91 @@
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
+ #
47
+ # === Example
48
+ #
49
+ # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
50
+ # * <tt>Firm#clients</tt>
51
+ # * <tt>Firm#clients=(objects)</tt>
52
+ # * <tt>Firm#client_params=(params)</tt>
53
+ def searches_many(name, options = {})
54
+ ElasticRecord::SearchesMany::Builder.build(self, name, options)
55
+ end
56
+
57
+ def generated_searches_many_methods
58
+ @generated_searches_many_methods ||= begin
59
+ mod = const_set(:GeneratedSearchesManyMethods, Module.new)
60
+ include mod
61
+ mod
62
+ end
63
+ end
64
+ end
65
+
66
+ # Returns the searches_many instance for the given name, instantiating it if it doesn't already exist
67
+ def searches_many_association(name)
68
+ association = searches_many_instance_get(name)
69
+
70
+ if association.nil?
71
+ association = ElasticRecord::SearchesMany::Association.new(self, searches_many_reflections[name])
72
+ searches_many_instance_set(name, association)
73
+ end
74
+
75
+ association
76
+ end
77
+
78
+ private
79
+ def searches_many_cache
80
+ @searches_many_cache ||= {}
81
+ end
82
+
83
+ def searches_many_instance_get(name)
84
+ searches_many_cache[name.to_sym]
85
+ end
86
+
87
+ def searches_many_instance_set(name, association)
88
+ searches_many_cache[name.to_sym] = association
89
+ end
90
+ end
91
+ end
@@ -1,5 +1,6 @@
1
1
  require 'arelastic'
2
2
  require 'active_support/core_ext/object/blank' # required because ActiveModel depends on this but does not require it
3
+ require 'active_support/concern'
3
4
  require 'active_model'
4
5
 
5
6
  module ElasticRecord
@@ -10,6 +11,7 @@ module ElasticRecord
10
11
  autoload :Lucene, 'elastic_record/lucene'
11
12
  autoload :Model, 'elastic_record/model'
12
13
  autoload :Relation, 'elastic_record/relation'
14
+ autoload :SearchesMany, 'elastic_record/searches_many'
13
15
  autoload :Searching, 'elastic_record/searching'
14
16
 
15
17
  class << self
@@ -19,4 +21,4 @@ module ElasticRecord
19
21
  end
20
22
  end
21
23
 
22
- require 'elastic_record/railtie' if defined?(Rails)
24
+ require 'elastic_record/railtie' if defined?(Rails)
@@ -1,16 +1,11 @@
1
1
  require 'helper'
2
2
 
3
3
  class ElasticRecord::CallbacksTest < MiniTest::Spec
4
- def setup
5
- super
6
- Widget.elastic_index.reset
7
- end
8
-
9
4
  def test_added_to_index
10
5
  widget = Widget.new id: '10', color: 'green'
11
6
  refute Widget.elastic_index.record_exists?(widget.id)
12
7
 
13
- widget.run_callbacks :save
8
+ widget.save
14
9
 
15
10
  assert Widget.elastic_index.record_exists?(widget.id)
16
11
  end
@@ -21,7 +16,7 @@ class ElasticRecord::CallbacksTest < MiniTest::Spec
21
16
 
22
17
  assert Widget.elastic_index.record_exists?(widget.id)
23
18
 
24
- widget.run_callbacks :destroy
19
+ widget.destroy
25
20
 
26
21
  refute Widget.elastic_index.record_exists?(widget.id)
27
22
  end
@@ -4,6 +4,6 @@ class ElasticRecord::ConfigTest < MiniTest::Spec
4
4
  def test_models
5
5
  ElasticRecord::Config.model_names = %w(Widget)
6
6
 
7
- assert_equal [Widget], ElasticRecord::Config.models
7
+ assert_equal [Warehouse, Widget], ElasticRecord::Config.models
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ require 'helper'
3
3
  class ElasticRecord::Index::DocumentsTest < MiniTest::Spec
4
4
  def setup
5
5
  super
6
-
6
+ index.disable_deferring!
7
7
  index.reset
8
8
  end
9
9
 
@@ -1,9 +1,13 @@
1
1
  require 'helper'
2
2
 
3
+ # class Cat
4
+ # include TestModel
5
+ # end
6
+
3
7
  class ElasticRecord::Index::ManageTest < MiniTest::Spec
4
8
  def setup
5
9
  super
6
-
10
+ index.disable_deferring!
7
11
  index.delete_all
8
12
  end
9
13
 
@@ -1,6 +1,11 @@
1
1
  require 'helper'
2
2
 
3
3
  class ElasticRecord::Index::PercolatorTest < MiniTest::Spec
4
+ def setup
5
+ super
6
+ index.disable_deferring!
7
+ end
8
+
4
9
  def test_create_percolator
5
10
  index.delete(index.percolator_index_name) if index.exists?(index.percolator_index_name)
6
11
 
@@ -2,7 +2,7 @@ require 'helper'
2
2
 
3
3
  class ElasticRecord::Relation::BatchesTest < MiniTest::Spec
4
4
  def setup
5
- Widget.elastic_index.reset
5
+ super
6
6
  create_widgets
7
7
  end
8
8
 
@@ -37,7 +37,5 @@ class ElasticRecord::Relation::BatchesTest < MiniTest::Spec
37
37
  Widget.new(id: 10, color: 'blue'),
38
38
  Widget.new(id: 15, color: 'green'),
39
39
  ]
40
-
41
- Widget.elastic_index.refresh
42
40
  end
43
41
  end
@@ -1,13 +1,8 @@
1
1
  require 'helper'
2
2
 
3
3
  class ElasticRecord::Relation::DelegationTest < MiniTest::Spec
4
- def setup
5
- Widget.elastic_index.reset
6
- end
7
-
8
4
  def test_delegate_to_array
9
5
  Widget.elastic_index.index_document('5', color: 'red')
10
- Widget.elastic_index.refresh
11
6
 
12
7
  records = []
13
8
  Widget.elastic_relation.each do |record|
@@ -2,7 +2,7 @@ require 'helper'
2
2
 
3
3
  class ElasticRecord::Relation::FinderMethodsTest < MiniTest::Spec
4
4
  def setup
5
- Widget.elastic_index.reset
5
+ super
6
6
  create_widgets
7
7
  end
8
8
 
@@ -37,8 +37,6 @@ class ElasticRecord::Relation::FinderMethodsTest < MiniTest::Spec
37
37
  Widget.elastic_index.bulk_add [
38
38
  Widget.new(color: 'red', id: '05'),
39
39
  Widget.new(color: 'blue', id: '10'),
40
- ]
41
-
42
- Widget.elastic_index.refresh
40
+ ]
43
41
  end
44
42
  end
@@ -1,11 +1,6 @@
1
1
  require 'helper'
2
2
 
3
3
  class ElasticRecord::Relation::NoneTest < MiniTest::Spec
4
- def setup
5
- super
6
- Widget.elastic_index.reset
7
- end
8
-
9
4
  def test_none
10
5
  none = Widget.elastic_relation.none
11
6
 
@@ -57,6 +57,25 @@ class ElasticRecord::Relation::SearchMethodsTest < MiniTest::Spec
57
57
  assert_equal expected, relation.as_elastic['query']
58
58
  end
59
59
 
60
+ def test_filter_with_another_relation
61
+ relation.filter! Widget.elastic_search.query('red')
62
+
63
+ expected = {
64
+ "constant_score" => {
65
+ "filter" => {
66
+ "has_child" => {
67
+ "type" => "widget",
68
+ "query" => {
69
+ "query_string" => {"query"=>"red"}
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ assert_equal expected, relation.as_elastic['query']
77
+ end
78
+
60
79
  def test_query_with_only_query
61
80
  relation.query!('foo')
62
81
 
@@ -1,11 +1,6 @@
1
1
  require 'helper'
2
2
 
3
3
  class ElasticRecord::RelationTest < MiniTest::Spec
4
- def setup
5
- super
6
- Widget.elastic_index.reset
7
- end
8
-
9
4
  def test_count
10
5
  create_widgets [Widget.new(id: 5, color: 'red'), Widget.new(id: 10, color: 'blue')]
11
6
 
@@ -32,7 +27,6 @@ class ElasticRecord::RelationTest < MiniTest::Spec
32
27
  create_widgets [Widget.new(id: 10, color: 'blue')]
33
28
 
34
29
  # explain = Widget.elastic_relation.filter(color: 'blue').explain('10')
35
- # p "explain = #{explain}"
36
30
  end
37
31
 
38
32
  def test_to_hits
@@ -70,6 +64,5 @@ class ElasticRecord::RelationTest < MiniTest::Spec
70
64
  private
71
65
  def create_widgets(widgets)
72
66
  Widget.elastic_index.bulk_add(widgets)
73
- Widget.elastic_index.refresh
74
67
  end
75
68
  end
@@ -0,0 +1,31 @@
1
+ require 'helper'
2
+
3
+ class ElasticRecord::SearchesMany::AutosaveTest < MiniTest::Spec
4
+ def test_save_associations_callback
5
+ warehouse = Warehouse.new
6
+ widget = Widget.new
7
+ warehouse.widgets = [widget]
8
+ assert widget.new_record?
9
+
10
+ warehouse.save
11
+
12
+ assert widget.persisted?
13
+ end
14
+
15
+ def test_validate_associations_callback
16
+ warehouse = Warehouse.new
17
+ widget = Widget.new color: 123
18
+ warehouse.widgets = [widget]
19
+
20
+ assert warehouse.invalid?
21
+ assert_equal ["is invalid"], warehouse.errors['widgets.color']
22
+ end
23
+
24
+ def test_mark_for_destruction
25
+ widget = Widget.new
26
+
27
+ widget.mark_for_destruction
28
+
29
+ assert widget.marked_for_destruction?
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ require 'helper'
2
+
3
+ class ElasticRecord::SearchesMany::CollectionProxyTest < MiniTest::Spec
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
@@ -0,0 +1,20 @@
1
+ require 'helper'
2
+
3
+ class ElasticRecord::SearchesMany::ReflectionTest < MiniTest::Spec
4
+ def test_touch_column
5
+ assert_nil reflection_class.new(Warehouse, :widgets, {}).touch_column
6
+ assert_equal :updated_at, reflection_class.new(Warehouse, :widgets, touch: true).touch_column
7
+ assert_equal :my_column, reflection_class.new(Warehouse, :widgets, touch: :my_column).touch_column
8
+ end
9
+
10
+ def test_counter_cache_column
11
+ assert_nil reflection_class.new(Warehouse, :widgets, {}).counter_cache_column
12
+ assert_equal :widgets_count, reflection_class.new(Warehouse, :widgets, counter_cache: true).counter_cache_column
13
+ assert_equal :my_column, reflection_class.new(Warehouse, :widgets, counter_cache: :my_column).counter_cache_column
14
+ end
15
+
16
+ private
17
+ def reflection_class
18
+ ElasticRecord::SearchesMany::Reflection
19
+ end
20
+ end
@@ -0,0 +1,65 @@
1
+ require 'helper'
2
+
3
+ class ElasticRecord::SearchesManyTest < MiniTest::Spec
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
+ end
data/test/helper.rb CHANGED
@@ -3,8 +3,13 @@ Bundler.require
3
3
 
4
4
  require 'minitest/autorun'
5
5
 
6
- require 'support/widget'
7
6
  require 'support/connect'
7
+ require 'support/models/test_model'
8
+ require 'support/models/warehouse'
9
+ require 'support/models/widget'
10
+ Widget.elastic_index.reset
11
+
12
+ ElasticRecord::Config.model_names = %w(Warehouse Widget)
8
13
 
9
14
  FakeWeb.allow_net_connect = %r[^https?://127.0.0.1]
10
15
 
@@ -13,6 +18,16 @@ module MiniTest
13
18
  def setup
14
19
  super
15
20
  FakeWeb.clean_registry
21
+
22
+ ElasticRecord::Config.models.each do |model|
23
+ model.elastic_index.enable_deferring!
24
+ end
25
+ end
26
+
27
+ def teardown
28
+ ElasticRecord::Config.models.each do |model|
29
+ model.elastic_index.reset_deferring!
30
+ end
16
31
  end
17
32
  end
18
33
  end
@@ -1 +1 @@
1
- ElasticRecord::Config.servers = '127.0.0.1:9200'
1
+ ElasticRecord::Config.servers = '127.0.0.1:9200'
@@ -0,0 +1,98 @@
1
+ module TestModel
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ extend ActiveModel::Naming
6
+ extend ActiveModel::Callbacks
7
+ define_model_callbacks :save, :destroy
8
+
9
+ include ActiveModel::Dirty
10
+ include ActiveModel::Validations
11
+
12
+ include ElasticRecord::Model
13
+ include ElasticRecord::Callbacks
14
+ end
15
+
16
+ module ClassMethods
17
+ def find(ids)
18
+ ids.map { |id| new(id: id, color: 'red') }
19
+ end
20
+
21
+ def base_class
22
+ self
23
+ end
24
+
25
+ def create(attributes = {})
26
+ record = new(attributes)
27
+ record.save
28
+ record
29
+ end
30
+
31
+ def define_attributes(attributes)
32
+ define_attribute_methods attributes
33
+
34
+ attributes.each do |attribute|
35
+ define_method attribute do
36
+ instance_variable_get("@#{attribute}")
37
+ end
38
+
39
+ define_method "#{attribute}=" do |value|
40
+ send("#{attribute}_will_change!")
41
+ instance_variable_set("@#{attribute}", value)
42
+ end
43
+ end
44
+
45
+ define_method 'attributes' do
46
+ Hash[attributes.map { |attr| [attr.to_s, send(attr)] }]
47
+ end
48
+ end
49
+ end
50
+
51
+ def initialize(attrs = {})
52
+ self.attributes = attrs
53
+ end
54
+
55
+ def attributes=(attrs)
56
+ attrs.each do |key, val|
57
+ send("#{key}=", val)
58
+ end
59
+ end
60
+
61
+ def id=(value)
62
+ @id = value
63
+ end
64
+
65
+ def id
66
+ @id ||= rand(10000).to_s
67
+ end
68
+
69
+ def save
70
+ @persisted = true
71
+ run_callbacks :save
72
+ end
73
+
74
+ def destroy
75
+ @destroyed = true
76
+ run_callbacks :destroy
77
+ end
78
+
79
+ def ==(other)
80
+ id == other.id
81
+ end
82
+
83
+ def changed?
84
+ true
85
+ end
86
+
87
+ def new_record?
88
+ !@persisted
89
+ end
90
+
91
+ def persisted?
92
+ @persisted
93
+ end
94
+
95
+ def destroyed?
96
+ @destroyed
97
+ end
98
+ end
@@ -0,0 +1,6 @@
1
+ class Warehouse
2
+ include TestModel
3
+
4
+ define_attributes [:name]
5
+ searches_many :widgets
6
+ end
@@ -1,10 +1,9 @@
1
1
  class Widget
2
- extend ActiveModel::Naming
3
- extend ActiveModel::Callbacks
4
- define_model_callbacks :save, :destroy
2
+ include TestModel
5
3
 
6
- include ElasticRecord::Model
7
- include ElasticRecord::Callbacks
4
+ validates :color, format: {with: /[a-z]/}
5
+
6
+ define_attributes [:name, :color, :warehouse_id]
8
7
 
9
8
  self.elastic_index.mapping[:properties].update(
10
9
  name: {
@@ -16,14 +15,13 @@ class Widget
16
15
  },
17
16
  color: {
18
17
  type: 'string', index: 'not_analyzed'
18
+ },
19
+ warehouse_id: {
20
+ type: 'string', index: 'not_analyzed'
19
21
  }
20
22
  )
21
-
22
- class << self
23
- def find(ids)
24
- ids.map { |id| new(id: id, color: 'red') }
25
- end
26
23
 
24
+ class << self
27
25
  def anon(&block)
28
26
  Class.new(self) do
29
27
  def self.name
@@ -33,16 +31,9 @@ class Widget
33
31
  instance_eval(&block)
34
32
  end
35
33
  end
36
-
37
- def base_class
38
- self
39
- end
40
34
  end
41
35
 
42
- attr_accessor :id, :name, :color
43
- def initialize(attributes = {})
44
- attributes.each do |key, val|
45
- send("#{key}=", val)
46
- end
36
+ def warehouse=(other)
37
+ self.warehouse_id = other.id
47
38
  end
48
39
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.9.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-12 00:00:00.000000000 Z
12
+ date: 2012-10-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: arelastic
@@ -62,6 +62,7 @@ files:
62
62
  - lib/elastic_record/config.rb
63
63
  - lib/elastic_record/connection.rb
64
64
  - lib/elastic_record/index.rb
65
+ - lib/elastic_record/index/deferred.rb
65
66
  - lib/elastic_record/index/documents.rb
66
67
  - lib/elastic_record/index/manage.rb
67
68
  - lib/elastic_record/index/mapping.rb
@@ -79,6 +80,12 @@ files:
79
80
  - lib/elastic_record/relation/none.rb
80
81
  - lib/elastic_record/relation/search_methods.rb
81
82
  - lib/elastic_record/relation/value_methods.rb
83
+ - lib/elastic_record/searches_many.rb
84
+ - lib/elastic_record/searches_many/association.rb
85
+ - lib/elastic_record/searches_many/autosave.rb
86
+ - lib/elastic_record/searches_many/builder.rb
87
+ - lib/elastic_record/searches_many/collection_proxy.rb
88
+ - lib/elastic_record/searches_many/reflection.rb
82
89
  - lib/elastic_record/searching.rb
83
90
  - lib/elastic_record/tasks/index.rake
84
91
  - test/elastic_record/callbacks_test.rb
@@ -99,10 +106,16 @@ files:
99
106
  - test/elastic_record/relation/none_test.rb
100
107
  - test/elastic_record/relation/search_methods_test.rb
101
108
  - test/elastic_record/relation_test.rb
109
+ - test/elastic_record/searches_many/autosave_test.rb
110
+ - test/elastic_record/searches_many/collection_proxy_test.rb
111
+ - test/elastic_record/searches_many/reflection_test.rb
112
+ - test/elastic_record/searches_many_test.rb
102
113
  - test/elastic_record/searching_test.rb
103
114
  - test/helper.rb
104
115
  - test/support/connect.rb
105
- - test/support/widget.rb
116
+ - test/support/models/test_model.rb
117
+ - test/support/models/warehouse.rb
118
+ - test/support/models/widget.rb
106
119
  homepage: http://github.com/matthuhiggins/elastic_record
107
120
  licenses:
108
121
  - MIT