chewy 0.6.2 → 0.7.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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +35 -29
  4. data/Appraisals +37 -0
  5. data/CHANGELOG.md +115 -4
  6. data/Gemfile +2 -3
  7. data/README.md +135 -40
  8. data/chewy.gemspec +4 -3
  9. data/gemfiles/rails.3.2.activerecord.gemfile +13 -0
  10. data/gemfiles/rails.3.2.activerecord.kaminari.gemfile +14 -0
  11. data/gemfiles/rails.3.2.activerecord.will_paginate.gemfile +14 -0
  12. data/gemfiles/rails.4.0.activerecord.gemfile +13 -0
  13. data/gemfiles/rails.4.0.activerecord.kaminari.gemfile +14 -0
  14. data/gemfiles/rails.4.0.activerecord.will_paginate.gemfile +14 -0
  15. data/gemfiles/rails.4.0.mongoid.gemfile +13 -0
  16. data/gemfiles/rails.4.0.mongoid.kaminari.gemfile +14 -0
  17. data/gemfiles/rails.4.0.mongoid.will_paginate.gemfile +14 -0
  18. data/gemfiles/rails.4.1.activerecord.gemfile +13 -0
  19. data/gemfiles/rails.4.1.activerecord.kaminari.gemfile +14 -0
  20. data/gemfiles/rails.4.1.activerecord.will_paginate.gemfile +14 -0
  21. data/gemfiles/rails.4.1.mongoid.gemfile +13 -0
  22. data/gemfiles/rails.4.1.mongoid.kaminari.gemfile +14 -0
  23. data/gemfiles/rails.4.1.mongoid.will_paginate.gemfile +14 -0
  24. data/gemfiles/rails.4.2.activerecord.gemfile +13 -0
  25. data/gemfiles/rails.4.2.activerecord.kaminari.gemfile +14 -0
  26. data/gemfiles/rails.4.2.activerecord.will_paginate.gemfile +14 -0
  27. data/gemfiles/rails.4.2.mongoid.gemfile +13 -0
  28. data/gemfiles/rails.4.2.mongoid.kaminari.gemfile +14 -0
  29. data/gemfiles/rails.4.2.mongoid.will_paginate.gemfile +14 -0
  30. data/lib/chewy.rb +65 -0
  31. data/lib/chewy/config.rb +44 -93
  32. data/lib/chewy/errors.rb +14 -5
  33. data/lib/chewy/fields/base.rb +8 -7
  34. data/lib/chewy/fields/root.rb +2 -2
  35. data/lib/chewy/index.rb +7 -9
  36. data/lib/chewy/log_subscriber.rb +34 -0
  37. data/lib/chewy/query.rb +41 -27
  38. data/lib/chewy/query/criteria.rb +28 -23
  39. data/lib/chewy/query/scoping.rb +20 -0
  40. data/lib/chewy/railtie.rb +51 -13
  41. data/lib/chewy/repository.rb +61 -0
  42. data/lib/chewy/rspec/update_index.rb +3 -6
  43. data/lib/chewy/search.rb +28 -7
  44. data/lib/chewy/strategy.rb +60 -0
  45. data/lib/chewy/strategy/atomic.rb +31 -0
  46. data/lib/chewy/strategy/base.rb +27 -0
  47. data/lib/chewy/strategy/bypass.rb +15 -0
  48. data/lib/chewy/strategy/urgent.rb +17 -0
  49. data/lib/chewy/type.rb +19 -5
  50. data/lib/chewy/type/adapter/active_record.rb +28 -117
  51. data/lib/chewy/type/adapter/base.rb +35 -0
  52. data/lib/chewy/type/adapter/mongoid.rb +23 -123
  53. data/lib/chewy/type/adapter/object.rb +41 -19
  54. data/lib/chewy/type/adapter/orm.rb +142 -0
  55. data/lib/chewy/type/import.rb +43 -16
  56. data/lib/chewy/type/observe.rb +8 -21
  57. data/lib/chewy/version.rb +1 -1
  58. data/lib/tasks/chewy.rake +8 -4
  59. data/spec/chewy/config_spec.rb +20 -97
  60. data/spec/chewy/fields/base_spec.rb +24 -11
  61. data/spec/chewy/fields/time_fields_spec.rb +27 -0
  62. data/spec/chewy/index/settings_spec.rb +2 -1
  63. data/spec/chewy/index_spec.rb +98 -79
  64. data/spec/chewy/query/criteria_spec.rb +14 -0
  65. data/spec/chewy/query_spec.rb +1 -1
  66. data/spec/chewy/repository_spec.rb +50 -0
  67. data/spec/chewy/search_spec.rb +100 -0
  68. data/spec/chewy/strategy_spec.rb +109 -0
  69. data/spec/chewy/type/adapter/active_record_spec.rb +110 -46
  70. data/spec/chewy/type/adapter/mongoid_spec.rb +123 -74
  71. data/spec/chewy/type/adapter/object_spec.rb +51 -34
  72. data/spec/chewy/type/import_spec.rb +21 -21
  73. data/spec/chewy/type/observe_spec.rb +26 -29
  74. data/spec/chewy/type_spec.rb +19 -0
  75. data/spec/chewy_spec.rb +19 -3
  76. data/spec/spec_helper.rb +1 -1
  77. data/spec/support/active_record.rb +2 -1
  78. data/spec/support/mongoid.rb +29 -38
  79. metadata +85 -55
  80. data/gemfiles/Gemfile.rails-3.2.active_record +0 -6
  81. data/gemfiles/Gemfile.rails-3.2.active_record.kaminari +0 -7
  82. data/gemfiles/Gemfile.rails-3.2.active_record.will_paginate +0 -7
  83. data/gemfiles/Gemfile.rails-4.0.active_record +0 -6
  84. data/gemfiles/Gemfile.rails-4.0.active_record.kaminari +0 -7
  85. data/gemfiles/Gemfile.rails-4.0.active_record.will_paginate +0 -7
  86. data/gemfiles/Gemfile.rails-4.0.mongoid +0 -6
  87. data/gemfiles/Gemfile.rails-4.0.mongoid.kaminari +0 -7
  88. data/gemfiles/Gemfile.rails-4.0.mongoid.will_paginate +0 -7
  89. data/gemfiles/Gemfile.rails-4.1.active_record +0 -6
  90. data/gemfiles/Gemfile.rails-4.1.active_record.kaminari +0 -7
  91. data/gemfiles/Gemfile.rails-4.1.active_record.will_paginate +0 -7
  92. data/gemfiles/Gemfile.rails-4.1.mongoid +0 -6
  93. data/gemfiles/Gemfile.rails-4.1.mongoid.kaminari +0 -7
  94. data/gemfiles/Gemfile.rails-4.1.mongoid.will_paginate +0 -7
  95. data/gemfiles/Gemfile.rails-4.2.active_record +0 -6
  96. data/gemfiles/Gemfile.rails-4.2.active_record.kaminari +0 -7
  97. data/gemfiles/Gemfile.rails-4.2.active_record.will_paginate +0 -7
  98. data/gemfiles/Gemfile.rails-4.2.mongoid +0 -6
  99. data/gemfiles/Gemfile.rails-4.2.mongoid.kaminari +0 -7
  100. data/gemfiles/Gemfile.rails-4.2.mongoid.will_paginate +0 -7
  101. data/spec/chewy/index/search_spec.rb +0 -46
@@ -26,8 +26,9 @@ module Chewy
26
26
 
27
27
  ActiveSupport::Notifications.instrument 'import_objects.chewy', type: self do |payload|
28
28
  adapter.import(*args, import_options) do |action_objects|
29
- indexed_objects = self.root_object.parent_id && fetch_indexed_objects(action_objects.values.flatten, bulk_options)
29
+ indexed_objects = self.root_object.parent_id && fetch_indexed_objects(action_objects.values.flatten)
30
30
  body = bulk_body(action_objects, indexed_objects)
31
+
31
32
  errors = bulk(bulk_options.merge(body: body)) if body.any?
32
33
 
33
34
  fill_payload_import payload, action_objects
@@ -76,33 +77,54 @@ module Chewy
76
77
 
77
78
  def bulk_body(action_objects, indexed_objects = nil)
78
79
  action_objects.inject([]) do |result, (action, objects)|
79
- result.concat(objects.map { |object| bulk_entries(action, object, indexed_objects) }.flatten)
80
+ method = "#{action}_bulk_entry"
81
+ result.concat(objects.map { |object| send(method, object, indexed_objects) }.flatten)
80
82
  end
81
83
  end
82
84
 
83
- def bulk_entries(action, object, indexed_objects = nil)
85
+ def delete_bulk_entry(object, indexed_objects = nil)
84
86
  entry = {}
85
87
 
86
- entry[:_id] = object.respond_to?(:id) ? object.id : object
87
- entry[:_id] = entry[:_id].to_s if defined?(BSON) && entry[:_id].is_a?(BSON::ObjectId)
88
- entry[:data] = object_data(object) unless action == :delete
88
+ if self.root_object.id
89
+ entry[:_id] = self.root_object.compose_id(object)
90
+ else
91
+ entry[:_id] = object.id if object.respond_to?(:id)
92
+ entry[:_id] ||= object[:id] || object['id'] if object.is_a?(Hash)
93
+ entry[:_id] ||= object
94
+ entry[:_id] = entry[:_id].to_s if defined?(BSON) && entry[:_id].is_a?(BSON::ObjectId)
95
+ end
89
96
 
90
97
  if self.root_object.parent_id
91
- entry[:parent] = self.root_object.compose_parent(object) if object.respond_to?(:id)
98
+ existing_object = entry[:_id].present? && indexed_objects && indexed_objects[entry[:_id].to_s]
99
+ entry.merge!(parent: existing_object[:parent]) if existing_object
92
100
  end
93
-
101
+
102
+ [{ delete: entry }]
103
+ end
104
+
105
+ def index_bulk_entry(object, indexed_objects = nil)
106
+ entry = {}
107
+
94
108
  if self.root_object.id
95
109
  entry[:_id] = self.root_object.compose_id(object)
110
+ else
111
+ entry[:_id] = object.id if object.respond_to?(:id)
112
+ entry[:_id] ||= object[:id] || object['id'] if object.is_a?(Hash)
113
+ entry[:_id] = entry[:_id].to_s if defined?(BSON) && entry[:_id].is_a?(BSON::ObjectId)
114
+ end
115
+ entry.delete(:_id) if entry[:_id].blank?
116
+
117
+ if self.root_object.parent_id
118
+ entry[:parent] = self.root_object.compose_parent(object)
119
+ existing_object = entry[:_id].present? && indexed_objects && indexed_objects[entry[:_id].to_s]
96
120
  end
97
121
 
98
- indexed_object = indexed_objects && indexed_objects[entry[:_id].to_s]
122
+ entry[:data] = object_data(object)
99
123
 
100
- if indexed_object && self.root_object.parent_id && action == :delete
101
- [{ action => entry.merge(parent: indexed_object[:parent]) }]
102
- elsif indexed_object && self.root_object.parent_id && entry[:parent].to_s != indexed_object[:parent]
103
- [{ :delete => entry.except(:data).merge(parent: indexed_object[:parent]) }, { action => entry }]
124
+ if existing_object && entry[:parent].to_s != existing_object[:parent]
125
+ [{ delete: entry.except(:data).merge(parent: existing_object[:parent]) }, { index: entry }]
104
126
  else
105
- [{ action => entry }]
127
+ [{ index: entry }]
106
128
  end
107
129
  end
108
130
 
@@ -143,9 +165,14 @@ module Chewy
143
165
  end.reduce(&:merge) || {}
144
166
  end
145
167
 
146
- def fetch_indexed_objects(objects, options = {})
168
+ def fetch_indexed_objects(objects)
147
169
  ids = objects.map { |object| object.respond_to?(:id) ? object.id : object }
148
- result = client.search index: index.build_index_name(suffix: options[:suffix]), type: type_name, fields: '_parent', body: { query: { ids: { values: ids } } }, search_type: 'scan', scroll: '1m'
170
+ result = client.search index: index_name,
171
+ type: type_name,
172
+ fields: '_parent',
173
+ body: { filter: { ids: { values: ids } } },
174
+ search_type: 'scan',
175
+ scroll: '1m'
149
176
 
150
177
  indexed_objects = {}
151
178
 
@@ -31,7 +31,7 @@ module Chewy
31
31
  method = args.first
32
32
 
33
33
  update = Proc.new do
34
- clear_association_cache if Chewy.urgent_update
34
+ # clear_association_cache if Chewy.strategy.current.name == :urgent
35
35
 
36
36
  backreference = if method && method.to_s == 'self'
37
37
  self
@@ -44,31 +44,18 @@ module Chewy
44
44
  Chewy.derive_type(type_name).update_index(backreference, options)
45
45
  end
46
46
 
47
- after_save &update
48
- after_destroy &update
47
+ if Chewy.use_after_commit_callbacks
48
+ after_commit &update
49
+ else
50
+ after_save &update
51
+ after_destroy &update
52
+ end
49
53
  end
50
54
  end
51
55
 
52
56
  module ClassMethods
53
57
  def update_index(objects, options = {})
54
- if Chewy.atomic?
55
- relation = (defined?(::ActiveRecord) && objects.is_a?(::ActiveRecord::Relation)) ||
56
- (defined?(::Mongoid) && objects.is_a?(::Mongoid::Criteria))
57
-
58
- ids = if relation
59
- objects.pluck(:id)
60
- else
61
- Array.wrap(objects).map { |object| object.respond_to?(:id) ? object.id : object.to_i }
62
- end
63
-
64
- Chewy.stash self, ids
65
- elsif options[:urgent]
66
- ActiveSupport::Deprecation.warn("`urgent: true` option is deprecated and will be removed soon, use `Chewy.atomic` block instead")
67
- import(objects)
68
- elsif Chewy.urgent_update
69
- import(objects)
70
- end if objects
71
-
58
+ Chewy.strategy.current.update(self, objects, options)
72
59
  true
73
60
  end
74
61
  end
@@ -1,3 +1,3 @@
1
1
  module Chewy
2
- VERSION = '0.6.2'
2
+ VERSION = '0.7.0'
3
3
  end
@@ -1,7 +1,7 @@
1
1
  def subscribe_task_stats!
2
2
  ActiveSupport::Notifications.subscribe('import_objects.chewy') do |name, start, finish, id, payload|
3
- duration = ((finish - start).to_f * 100).round / 100.0
4
- puts " Imported #{payload[:type]} for #{duration}s, documents total: #{payload[:import]}"
3
+ duration = (finish - start).round(2)
4
+ puts " Imported #{payload[:type]} for #{duration}s, documents total: #{payload[:import].try(:[], :index).to_i}"
5
5
  payload[:errors].each do |action, errors|
6
6
  puts " #{action.to_s.humanize} errors:"
7
7
  errors.each do |error, documents|
@@ -13,8 +13,12 @@ def subscribe_task_stats!
13
13
  end
14
14
 
15
15
  def eager_load_chewy!
16
- Rails.application.config.paths['app/chewy'].existent.each do |dir|
17
- Dir.glob(File.join(dir, '**/*.rb')).each { |file| require_dependency file }
16
+ dirs = Chewy::Railtie.all_engines.map { |engine| engine.paths['app/chewy'].existent }.flatten.uniq
17
+
18
+ dirs.each do |dir|
19
+ Dir.glob(File.join(dir, '**/*.rb')).each do |file|
20
+ require_dependency file
21
+ end
18
22
  end
19
23
  end
20
24
 
@@ -3,110 +3,33 @@ require 'spec_helper'
3
3
  describe Chewy::Config do
4
4
  subject { described_class.send(:new) }
5
5
 
6
+ its(:logger) { should be_nil }
7
+ its(:transport_logger) { should be_nil }
8
+ its(:transport_logger) { should be_nil }
6
9
  its(:query_mode) { should == :must }
7
10
  its(:filter_mode) { should == :and }
8
11
  its(:post_filter_mode) { should be_nil }
9
- its(:logger) { should be_nil }
10
- its(:configuration) { should_not have_key :logger }
11
- its(:analyzers) { should == {} }
12
- its(:tokenizers) { should == {} }
13
- its(:filters) { should == {} }
14
- its(:char_filters) { should == {} }
15
-
16
- describe '#analyzer' do
17
- specify { expect(subject.analyzer(:name)).to be_nil }
18
-
19
- context do
20
- before { subject.analyzer(:name, option: :foo) }
21
- specify { expect(subject.analyzer(:name)).to eq({option: :foo}) }
22
- specify { expect(subject.analyzers).to eq({name: {option: :foo}}) }
23
- end
24
- end
25
-
26
- describe '#tokenizer' do
27
- specify { expect(subject.tokenizer(:name)).to be_nil }
28
-
29
- context do
30
- before { subject.tokenizer(:name, option: :foo) }
31
- specify { expect(subject.tokenizer(:name)).to eq({option: :foo}) }
32
- specify { expect(subject.tokenizers).to eq({name: {option: :foo}}) }
33
- end
34
- end
35
-
36
- describe '#filter' do
37
- specify { expect(subject.filter(:name)).to be_nil }
38
-
39
- context do
40
- before { subject.filter(:name, option: :foo) }
41
- specify { expect(subject.filter(:name)).to eq({option: :foo}) }
42
- specify { expect(subject.filters).to eq({name: {option: :foo}}) }
43
- end
44
- end
12
+ its(:root_strategy) { should == :base }
13
+ its(:request_strategy) { should == :atomic }
14
+ its(:use_after_commit_callbacks) { should == true }
45
15
 
46
- describe '#char_filter' do
47
- specify { expect(subject.char_filter(:name)).to be_nil }
16
+ describe '#transport_logger=' do
17
+ let(:logger) { Logger.new('/dev/null') }
18
+ after { subject.transport_logger = nil }
48
19
 
49
- context do
50
- before { subject.char_filter(:name, option: :foo) }
51
- specify { expect(subject.char_filter(:name)).to eq({option: :foo}) }
52
- specify { expect(subject.char_filters).to eq({name: {option: :foo}}) }
53
- end
20
+ specify { expect { subject.transport_logger = logger }
21
+ .to change { Chewy.client.transport.logger }.to(logger) }
22
+ specify { expect { subject.transport_logger = logger }
23
+ .to change { subject.transport_logger }.to(logger) }
54
24
  end
55
25
 
56
- describe '#logger' do
57
- before { subject.logger = double(:logger) }
58
-
59
- its(:logger) { should_not be_nil }
60
- its(:configuration) { should have_key :logger }
61
- end
62
-
63
- describe '#atomic?' do
64
- its(:atomic?) { should eq(false) }
65
- specify { subject.atomic { expect(subject.atomic?).to eq(true) } }
66
- specify { subject.atomic { }; expect(subject.atomic?).to eq(false) }
67
- end
68
-
69
- describe '#atomic' do
70
- before do
71
- stub_index(:dummies) do
72
- define_type :dummy
73
- end
74
- end
75
- let(:dummy_type) { DummiesIndex::Dummy }
76
-
77
- specify { expect(subject.atomic { 42 }).to eq(42) }
78
- specify { expect { subject.atomic { subject.stash Class.new, 42 } }.to raise_error ArgumentError }
79
- specify { subject.atomic { subject.atomic { expect(subject.stash).to eq([{}, {}]) } } }
80
-
81
- specify do
82
- expect(dummy_type).to receive(:import).with([1, 2, 3]).once
83
- subject.atomic do
84
- subject.stash dummy_type, [1, 2]
85
- subject.stash dummy_type, [2, 3]
86
- end
87
- end
88
-
89
- specify do
90
- expect(dummy_type).to receive(:import).with([1, 2]).once
91
- subject.atomic do
92
- subject.stash dummy_type, [1, 2]
93
- raise
94
- end rescue nil
95
- end
96
-
97
- specify do
98
- expect(dummy_type).to receive(:import).with([2, 3]).once
99
- expect(dummy_type).to receive(:import).with([1, 2]).once
100
- subject.atomic do
101
- subject.stash dummy_type, [2, 3]
102
- subject.atomic do
103
- subject.stash dummy_type, [1, 2]
104
- end
105
- end
106
- end
107
- end
26
+ describe '#transport_tracer=' do
27
+ let(:logger) { Logger.new('/dev/null') }
28
+ after { subject.transport_tracer = nil }
108
29
 
109
- describe '#stash' do
110
- specify { subject.atomic { expect(subject.stash).to eq([{}]) } }
30
+ specify { expect { subject.transport_tracer = logger }
31
+ .to change { Chewy.client.transport.tracer }.to(logger) }
32
+ specify { expect { subject.transport_tracer = logger }
33
+ .to change { subject.transport_tracer }.to(logger) }
111
34
  end
112
35
  end
@@ -12,7 +12,7 @@ describe Chewy::Fields::Base do
12
12
 
13
13
  specify { expect(described_class.new(:name).compose(double(name: 'hello'))).to eq({name: 'hello'}) }
14
14
 
15
- context do
15
+ context 'nested fields' do
16
16
  before do
17
17
  field.nested(described_class.new(:subname1, value: ->(o){ o.subvalue1 }))
18
18
  field.nested(described_class.new(:subname2, value: ->{ subvalue2 }))
@@ -30,7 +30,22 @@ describe Chewy::Fields::Base do
30
30
  ]}) }
31
31
  end
32
32
 
33
- context do
33
+ context 'parent objects' do
34
+ let!(:country) { described_class.new(:name, value: ->(country){ country.cities }) }
35
+ let!(:city) { country.nested(described_class.new(:name, value: ->(city, country) { city.districts.map { |district| [district, country.name] } })) }
36
+ let!(:district) { city.nested(described_class.new(:name, value: ->(district, city, country) { [district, city.name, country.name] })) }
37
+
38
+ specify { expect(country.compose(double(name: 'Thailand', cities: [
39
+ double(name: 'Bangkok', districts: ['First', 'Second'])
40
+ ]))).to eq(name: [
41
+ { 'name' => [
42
+ { 'name' => [['First', 'Thailand'], 'Bangkok', 'Thailand'] },
43
+ { 'name' => [['Second', 'Thailand'], 'Bangkok', 'Thailand'] }
44
+ ] }
45
+ ]) }
46
+ end
47
+
48
+ context 'implicit values' do
34
49
  let(:field) { described_class.new(:name, type: 'string') }
35
50
  before do
36
51
  field.nested(described_class.new(:name))
@@ -40,7 +55,7 @@ describe Chewy::Fields::Base do
40
55
  specify { expect(field.compose(double(name: 'Alex'))).to eq({name: 'Alex'}) }
41
56
  end
42
57
 
43
- context do
58
+ context 'hash values' do
44
59
  let(:field) { described_class.new(:name, type: 'object') }
45
60
  let(:object) { double(name: { key1: 'value1', key2: 'value2' }) }
46
61
 
@@ -134,7 +149,7 @@ describe Chewy::Fields::Base do
134
149
  ])).to eq({
135
150
  event: { 'id' => 1, 'category' => [
136
151
  { 'id' => 2, 'licenses' => { 'id' => 3, 'name' => 'Name1' } },
137
- {'id' => 4, 'licenses' => nil }
152
+ {'id' => 4, 'licenses' => nil.as_json }
138
153
  ] }
139
154
  })
140
155
  end
@@ -180,7 +195,7 @@ describe Chewy::Fields::Base do
180
195
  ]))).to eq({
181
196
  event: { 'id' => 1, 'category' => [
182
197
  { 'id' => 2, 'licenses' => { 'id' => 3, 'name' => 'Name1' } },
183
- {'id' => 4, 'licenses' => nil }
198
+ {'id' => 4, 'licenses' => nil.as_json }
184
199
  ] }
185
200
  })
186
201
  end
@@ -298,13 +313,11 @@ describe Chewy::Fields::Base do
298
313
 
299
314
  context 'objects and scopes', :orm do
300
315
  before do
301
- stub_model(:city) do
302
- belongs_to :country
303
- end
316
+ stub_model(:city)
317
+ stub_model(:country)
304
318
 
305
- stub_model(:country) do
306
- has_many :cities
307
- end
319
+ City.belongs_to :country
320
+ Country.has_many :cities
308
321
 
309
322
  stub_index(:countries) do
310
323
  define_type Country do
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Time fields' do
4
+ before { Chewy.massacre }
5
+
6
+ before do
7
+ stub_index(:posts) do
8
+ define_type :post do
9
+ field :published_at, type: 'date'
10
+ end
11
+ end
12
+ end
13
+
14
+ before { PostsIndex::Post.import(
15
+ double(published_at: ActiveSupport::TimeZone[-28800].parse('2014/12/18 18:00')),
16
+ double(published_at: ActiveSupport::TimeZone[-21600].parse('2014/12/18 20:00')),
17
+ double(published_at: ActiveSupport::TimeZone[-21600].parse('2014/12/17 20:00')),
18
+ ) }
19
+
20
+ let(:time) { ActiveSupport::TimeZone[-14400].parse('2014/12/18 22:00') }
21
+ let(:range) { (time - 1.minute)..(time + 1.minute) }
22
+
23
+ specify { expect(PostsIndex.total).to eq(3) }
24
+ specify { expect(PostsIndex.filter { published_at == o{range} }.count).to eq(2) }
25
+ specify { expect(PostsIndex.filter { published_at == o{range.min.utc..range.max.utc} }.count).to eq(2) }
26
+ specify { expect(PostsIndex.filter { published_at == o{[range.min.to_date..range.max.to_date]} }.count).to eq(1) }
27
+ end
@@ -3,6 +3,7 @@ require 'spec_helper'
3
3
  describe Chewy::Index::Settings do
4
4
  describe '#to_hash' do
5
5
  before { allow(Chewy).to receive_messages(config: Chewy::Config.send(:new)) }
6
+ before { allow(Chewy).to receive_messages(repository: Chewy::Repository.send(:new)) }
6
7
 
7
8
  specify { expect(described_class.new.to_hash).to eq({}) }
8
9
  specify { expect(described_class.new(number_of_nodes: 3).to_hash).to eq({settings: {number_of_nodes: 3}}) }
@@ -70,7 +71,7 @@ describe Chewy::Index::Settings do
70
71
  .to eq({settings: {index: {number_of_shards: 3}}}) }
71
72
 
72
73
  context do
73
- before { Chewy.configuration = {index: {number_of_shards: 7, number_of_replicas: 2}} }
74
+ before { allow(Chewy).to receive_messages(configuration: {index: {number_of_shards: 7, number_of_replicas: 2}}) }
74
75
 
75
76
  specify { expect(described_class.new.to_hash)
76
77
  .to eq({settings: {index: {number_of_shards: 7, number_of_replicas: 2}}}) }
@@ -7,6 +7,41 @@ describe Chewy::Index do
7
7
  end
8
8
  end
9
9
 
10
+ describe '.import', :orm do
11
+ before do
12
+ stub_model(:city)
13
+ stub_model(:country)
14
+
15
+ stub_index(:places) do
16
+ define_type City
17
+ define_type Country
18
+ end
19
+ end
20
+
21
+ let!(:cities) { 2.times.map { |i| City.create! id: i + 1 } }
22
+ let!(:countries) { 2.times.map { |i| Country.create! id: i + 1 } }
23
+
24
+ specify do
25
+ expect { PlacesIndex.import }.to update_index(PlacesIndex::City).and_reindex(cities)
26
+ expect { PlacesIndex.import }.to update_index(PlacesIndex::Country).and_reindex(countries)
27
+ end
28
+
29
+ specify do
30
+ expect { PlacesIndex.import city: cities.first }.to update_index(PlacesIndex::City).and_reindex(cities.first).only
31
+ expect { PlacesIndex.import city: cities.first }.to update_index(PlacesIndex::Country).and_reindex(countries)
32
+ end
33
+
34
+ specify do
35
+ expect { PlacesIndex.import city: cities.first, country: countries.last }.to update_index(PlacesIndex::City).and_reindex(cities.first).only
36
+ expect { PlacesIndex.import city: cities.first, country: countries.last }.to update_index(PlacesIndex::Country).and_reindex(countries.last).only
37
+ end
38
+
39
+ specify do
40
+ expect(PlacesIndex.client).to receive(:bulk).with(hash_including(refresh: false)).twice
41
+ PlacesIndex.import city: cities.first, refresh: false
42
+ end
43
+ end
44
+
10
45
  describe '.client' do
11
46
  specify { expect(stub_index(:dummies1).client).to eq(stub_index(:dummies2).client) }
12
47
 
@@ -20,28 +55,17 @@ describe Chewy::Index do
20
55
  end
21
56
  end
22
57
 
23
- describe '.settings' do
24
- before do
25
- allow(Chewy).to receive_messages(config: Chewy::Config.send(:new))
58
+ describe '.index_name' do
59
+ specify { expect { Class.new(Chewy::Index).index_name }.to raise_error Chewy::UndefinedIndex }
60
+ specify { expect(Class.new(Chewy::Index) { index_name :myindex }.index_name).to eq('myindex') }
61
+ specify { expect(stub_const('DeveloperIndex', Class.new(Chewy::Index)).index_name).to eq('developer') }
62
+ specify { expect(stub_const('DevelopersIndex', Class.new(Chewy::Index)).index_name).to eq('developers') }
26
63
 
27
- Chewy.analyzer :name, filter: ['lowercase', 'icu_folding', 'names_nysiis']
28
- Chewy.analyzer :phone, tokenizer: 'ngram', char_filter: ['phone']
29
- Chewy.tokenizer :ngram, type: 'nGram', min_gram: 3, max_gram: 3
30
- Chewy.char_filter :phone, type: 'pattern_replace', pattern: '[^\d]', replacement: ''
31
- Chewy.filter :names_nysiis, type: 'phonetic', encoder: 'nysiis', replace: false
64
+ context do
65
+ before { allow(Chewy).to receive_messages(configuration: {prefix: 'testing'}) }
66
+ specify { expect(DummiesIndex.index_name).to eq('testing_dummies') }
67
+ specify { expect(stub_index(:dummies) { index_name :users }.index_name).to eq('testing_users') }
32
68
  end
33
-
34
- let(:documents) { stub_index(:documents) { settings analysis: {analyzer: [:name, :phone, {sorted: {option: :baz}}]} } }
35
-
36
- specify { expect { documents.settings_hash }.to_not change(documents._settings, :inspect) }
37
- specify { expect(documents.settings_hash).to eq({settings: {analysis: {
38
- analyzer: {name: {filter: ['lowercase', 'icu_folding', 'names_nysiis']},
39
- phone: {tokenizer: 'ngram', char_filter: ['phone']},
40
- sorted: {option: :baz}},
41
- tokenizer: {ngram: {type: 'nGram', min_gram: 3, max_gram: 3}},
42
- char_filter: {phone: {type: 'pattern_replace', pattern: '[^\d]', replacement: ''}},
43
- filter: {names_nysiis: {type: 'phonetic', encoder: 'nysiis', replace: false}}
44
- }}}) }
45
69
  end
46
70
 
47
71
  describe '.define_type' do
@@ -58,7 +82,6 @@ describe Chewy::Index do
58
82
  specify { expect(DummiesIndex.type_hash['country']).to eq(DummiesIndex::Country) }
59
83
  end
60
84
 
61
-
62
85
  context do
63
86
  before { stub_class('City') }
64
87
  before { stub_class('City::District', City) }
@@ -102,17 +125,48 @@ describe Chewy::Index do
102
125
  specify { expect(DummiesIndex.types(:user)).to be_a Chewy::Query }
103
126
  end
104
127
 
105
- describe '.index_name' do
106
- specify { expect { Class.new(Chewy::Index).index_name }.to raise_error Chewy::UndefinedIndex }
107
- specify { expect(Class.new(Chewy::Index) { index_name :myindex }.index_name).to eq('myindex') }
108
- specify { expect(stub_const('DeveloperIndex', Class.new(Chewy::Index)).index_name).to eq('developer') }
109
- specify { expect(stub_const('DevelopersIndex', Class.new(Chewy::Index)).index_name).to eq('developers') }
128
+ describe '.settings' do
129
+ before do
130
+ allow(Chewy).to receive_messages(config: Chewy::Config.send(:new))
110
131
 
111
- context do
112
- before { allow(Chewy).to receive_messages(configuration: {prefix: 'testing'}) }
113
- specify { expect(DummiesIndex.index_name).to eq('testing_dummies') }
114
- specify { expect(stub_index(:dummies) { index_name :users }.index_name).to eq('testing_users') }
132
+ Chewy.analyzer :name, filter: ['lowercase', 'icu_folding', 'names_nysiis']
133
+ Chewy.analyzer :phone, tokenizer: 'ngram', char_filter: ['phone']
134
+ Chewy.tokenizer :ngram, type: 'nGram', min_gram: 3, max_gram: 3
135
+ Chewy.char_filter :phone, type: 'pattern_replace', pattern: '[^\d]', replacement: ''
136
+ Chewy.filter :names_nysiis, type: 'phonetic', encoder: 'nysiis', replace: false
137
+ end
138
+
139
+ let(:documents) { stub_index(:documents) { settings analysis: {analyzer: [:name, :phone, {sorted: {option: :baz}}]} } }
140
+
141
+ specify { expect { documents.settings_hash }.to_not change(documents._settings, :inspect) }
142
+ specify { expect(documents.settings_hash).to eq({settings: {analysis: {
143
+ analyzer: {name: {filter: ['lowercase', 'icu_folding', 'names_nysiis']},
144
+ phone: {tokenizer: 'ngram', char_filter: ['phone']},
145
+ sorted: {option: :baz}},
146
+ tokenizer: {ngram: {type: 'nGram', min_gram: 3, max_gram: 3}},
147
+ char_filter: {phone: {type: 'pattern_replace', pattern: '[^\d]', replacement: ''}},
148
+ filter: {names_nysiis: {type: 'phonetic', encoder: 'nysiis', replace: false}}
149
+ }}}) }
150
+ end
151
+
152
+ describe '.scopes' do
153
+ before do
154
+ stub_index(:places) do
155
+ def self.by_rating
156
+ end
157
+
158
+ def self.by_name
159
+ end
160
+
161
+ define_type :city do
162
+ def self.by_id
163
+ end
164
+ end
165
+ end
115
166
  end
167
+
168
+ specify { expect(described_class.scopes).to eq([]) }
169
+ specify { expect(PlacesIndex.scopes).to match_array([:by_rating, :by_name]) }
116
170
  end
117
171
 
118
172
  describe '.build_index_name' do
@@ -123,24 +177,6 @@ describe Chewy::Index do
123
177
  specify { expect(stub_const('DevelopersIndex', Class.new(Chewy::Index)).build_index_name(:users, prefix: 'test', suffix: '2013')).to eq('test_users_2013') }
124
178
  end
125
179
 
126
- describe '.index_params' do
127
- before { allow(Chewy).to receive_messages(config: Chewy::Config.send(:new)) }
128
-
129
- specify { expect(stub_index(:documents).index_params).to eq({}) }
130
- specify { expect(stub_index(:documents) { settings number_of_shards: 1 }.index_params.keys).to eq([:settings]) }
131
- specify { expect(stub_index(:documents) do
132
- define_type :document do
133
- field :name, type: 'string'
134
- end
135
- end.index_params.keys).to eq([:mappings]) }
136
- specify { expect(stub_index(:documents) do
137
- settings number_of_shards: 1
138
- define_type :document do
139
- field :name, type: 'string'
140
- end
141
- end.index_params.keys).to match_array([:mappings, :settings]) }
142
- end
143
-
144
180
  describe '.settings_hash' do
145
181
  before { allow(Chewy).to receive_messages(config: Chewy::Config.send(:new)) }
146
182
 
@@ -166,38 +202,21 @@ describe Chewy::Index do
166
202
  end.mappings_hash[:mappings].keys).to match_array([:document, :document2]) }
167
203
  end
168
204
 
169
- describe '.import', :orm do
170
- before do
171
- stub_model(:city)
172
- stub_model(:country)
205
+ describe '.index_params' do
206
+ before { allow(Chewy).to receive_messages(config: Chewy::Config.send(:new)) }
173
207
 
174
- stub_index(:places) do
175
- define_type City
176
- define_type Country
208
+ specify { expect(stub_index(:documents).index_params).to eq({}) }
209
+ specify { expect(stub_index(:documents) { settings number_of_shards: 1 }.index_params.keys).to eq([:settings]) }
210
+ specify { expect(stub_index(:documents) do
211
+ define_type :document do
212
+ field :name, type: 'string'
177
213
  end
178
- end
179
-
180
- let!(:cities) { 2.times.map { |i| City.create! id: i + 1 } }
181
- let!(:countries) { 2.times.map { |i| Country.create! id: i + 1 } }
182
-
183
- specify do
184
- expect { PlacesIndex.import }.to update_index(PlacesIndex::City).and_reindex(cities)
185
- expect { PlacesIndex.import }.to update_index(PlacesIndex::Country).and_reindex(countries)
186
- end
187
-
188
- specify do
189
- expect { PlacesIndex.import city: cities.first }.to update_index(PlacesIndex::City).and_reindex(cities.first).only
190
- expect { PlacesIndex.import city: cities.first }.to update_index(PlacesIndex::Country).and_reindex(countries)
191
- end
192
-
193
- specify do
194
- expect { PlacesIndex.import city: cities.first, country: countries.last }.to update_index(PlacesIndex::City).and_reindex(cities.first).only
195
- expect { PlacesIndex.import city: cities.first, country: countries.last }.to update_index(PlacesIndex::Country).and_reindex(countries.last).only
196
- end
197
-
198
- specify do
199
- expect(PlacesIndex.client).to receive(:bulk).with(hash_including(refresh: false)).twice
200
- PlacesIndex.import city: cities.first, refresh: false
201
- end
214
+ end.index_params.keys).to eq([:mappings]) }
215
+ specify { expect(stub_index(:documents) do
216
+ settings number_of_shards: 1
217
+ define_type :document do
218
+ field :name, type: 'string'
219
+ end
220
+ end.index_params.keys).to match_array([:mappings, :settings]) }
202
221
  end
203
222
  end