chewy 0.6.2 → 0.7.0

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