sunspot 2.5.0 → 2.6.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.
- checksums.yaml +4 -4
- data/lib/sunspot/adapters.rb +14 -3
- data/lib/sunspot/data_extractor.rb +1 -1
- data/lib/sunspot/dsl/fulltext.rb +1 -1
- data/lib/sunspot/dsl/standard_query.rb +29 -1
- data/lib/sunspot/dsl.rb +2 -2
- data/lib/sunspot/field.rb +15 -4
- data/lib/sunspot/indexer.rb +37 -8
- data/lib/sunspot/query/abstract_fulltext.rb +7 -3
- data/lib/sunspot/query/abstract_json_field_facet.rb +3 -0
- data/lib/sunspot/query/composite_fulltext.rb +21 -2
- data/lib/sunspot/query/date_field_json_facet.rb +2 -16
- data/lib/sunspot/query/dismax.rb +10 -4
- data/lib/sunspot/query/function_query.rb +25 -1
- data/lib/sunspot/query/join.rb +1 -1
- data/lib/sunspot/query/range_json_facet.rb +5 -2
- data/lib/sunspot/query/restriction.rb +10 -9
- data/lib/sunspot/query/standard_query.rb +12 -0
- data/lib/sunspot/search/field_json_facet.rb +14 -3
- data/lib/sunspot/session.rb +6 -4
- data/lib/sunspot/setup.rb +38 -0
- data/lib/sunspot/util.rb +4 -11
- data/lib/sunspot/version.rb +1 -1
- data/lib/sunspot.rb +9 -1
- data/spec/api/indexer/removal_spec.rb +87 -0
- data/spec/api/query/connective_boost_examples.rb +85 -0
- data/spec/api/query/join_spec.rb +2 -2
- data/spec/api/query/standard_spec.rb +10 -0
- data/spec/api/setup_spec.rb +99 -0
- data/spec/helpers/indexer_helper.rb +22 -0
- data/spec/integration/atomic_updates_spec.rb +169 -5
- data/spec/integration/faceting_spec.rb +68 -34
- data/spec/integration/join_spec.rb +22 -3
- data/spec/integration/scoped_search_spec.rb +78 -0
- data/spec/mocks/connection.rb +6 -0
- data/spec/mocks/photo.rb +11 -7
- data/spec/mocks/post.rb +35 -1
- data/sunspot.gemspec +0 -2
- metadata +10 -6
data/lib/sunspot.rb
CHANGED
@@ -40,6 +40,12 @@ module Sunspot
|
|
40
40
|
NoSetupError = Class.new(StandardError)
|
41
41
|
IllegalSearchError = Class.new(StandardError)
|
42
42
|
NotImplementedError = Class.new(StandardError)
|
43
|
+
AtomicUpdateRequireInstanceForCompositeIdMessage = lambda do |class_name|
|
44
|
+
"WARNING: `id_prefix` is defined for #{class_name}. Use instance as key for `atomic_update` instead of ID."
|
45
|
+
end
|
46
|
+
RemoveByIdNotSupportCompositeIdMessage = lambda do |class_name|
|
47
|
+
"WARNING: `id_prefix` is defined for #{class_name}. `remove_by_id` does not support it. Use `remove` instead."
|
48
|
+
end
|
43
49
|
|
44
50
|
autoload :Installer, File.join(File.dirname(__FILE__), 'sunspot', 'installer')
|
45
51
|
|
@@ -208,6 +214,8 @@ module Sunspot
|
|
208
214
|
#
|
209
215
|
# post1, post2 = new Array(2) { Post.create }
|
210
216
|
# Sunspot.atomic_update(Post, post1.id => {title: 'New Title'}, post2.id => {description: 'new description'})
|
217
|
+
# Or
|
218
|
+
# Sunspot.atomic_update(Post, post1 => {title: 'New Title'}, post2 => {description: 'new description'})
|
211
219
|
#
|
212
220
|
# Note that indexed objects won't be reflected in search until a commit is
|
213
221
|
# sent - see Sunspot.index! and Sunspot.commit
|
@@ -223,7 +231,7 @@ module Sunspot
|
|
223
231
|
# ==== Parameters
|
224
232
|
#
|
225
233
|
# clazz<Class>:: the class of the objects to be updated
|
226
|
-
# updates<Hash>:: hash of updates where keys are model ids
|
234
|
+
# updates<Hash>:: hash of updates where keys are models or model ids
|
227
235
|
# and values are hash with property name/values to be updated
|
228
236
|
#
|
229
237
|
def atomic_update!(clazz, updates = {})
|
@@ -60,4 +60,91 @@ describe 'document removal', :type => :indexer do
|
|
60
60
|
end
|
61
61
|
expect(connection).to have_delete_by_query("(type:Post AND title_ss:monkeys)")
|
62
62
|
end
|
63
|
+
|
64
|
+
context 'when call #remove_by_id' do
|
65
|
+
let(:post) { clazz.new(title: 'A Title') }
|
66
|
+
before(:each) { index_post(post) }
|
67
|
+
|
68
|
+
context 'and `id_prefix` is defined on model' do
|
69
|
+
context 'as Proc' do
|
70
|
+
let(:clazz) { PostWithProcPrefixId }
|
71
|
+
let(:id_prefix) { lambda { |post| "USERDATA-#{post.id}!" } }
|
72
|
+
|
73
|
+
it 'prints warning' do
|
74
|
+
expect do
|
75
|
+
session.remove_by_id(clazz, post.id)
|
76
|
+
end.to output(Sunspot::RemoveByIdNotSupportCompositeIdMessage.call(clazz) + "\n").to_stderr
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'does not remove record' do
|
80
|
+
session.remove_by_id(clazz, post.id)
|
81
|
+
expect(connection).to have_no_delete(post_solr_id)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'as Symbol' do
|
86
|
+
let(:clazz) { PostWithSymbolPrefixId }
|
87
|
+
let(:id_prefix) { lambda { |post| "#{post.title}!" } }
|
88
|
+
|
89
|
+
it 'prints warning' do
|
90
|
+
expect do
|
91
|
+
session.remove_by_id(clazz, post.id)
|
92
|
+
end.to output(Sunspot::RemoveByIdNotSupportCompositeIdMessage.call(clazz) + "\n").to_stderr
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'does not remove record' do
|
96
|
+
session.remove_by_id(clazz, post.id)
|
97
|
+
expect(connection).to have_no_delete(post_solr_id)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'as String' do
|
102
|
+
let(:clazz) { PostWithStringPrefixId }
|
103
|
+
let(:id_prefix) { 'USERDATA!' }
|
104
|
+
|
105
|
+
it 'does not print warning' do
|
106
|
+
expect do
|
107
|
+
session.remove_by_id(clazz, post.id)
|
108
|
+
end.to_not output(Sunspot::RemoveByIdNotSupportCompositeIdMessage.call(clazz) + "\n").to_stderr
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'removes record' do
|
112
|
+
session.remove_by_id(clazz, post.id)
|
113
|
+
expect(connection).to have_delete(post_solr_id)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'and `id_prefix` is not defined on model' do
|
119
|
+
let(:clazz) { PostWithoutPrefixId }
|
120
|
+
let(:id_prefix) { nil }
|
121
|
+
|
122
|
+
it 'does not print warning' do
|
123
|
+
expect do
|
124
|
+
session.remove_by_id(clazz, post.id)
|
125
|
+
end.to_not output(Sunspot::RemoveByIdNotSupportCompositeIdMessage.call(clazz) + "\n").to_stderr
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'removes record' do
|
129
|
+
session.remove_by_id(clazz, post.id)
|
130
|
+
expect(connection).to have_delete(post_solr_id)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'and `id_prefix` is passed along with `class_name`' do
|
135
|
+
let(:clazz) { PostWithProcPrefixId }
|
136
|
+
let(:id_prefix) { lambda { |post| "USERDATA-#{post.id}!" } }
|
137
|
+
|
138
|
+
it 'does not print warning' do
|
139
|
+
expect do
|
140
|
+
session.remove_by_id("USERDATA-#{post.id}!#{clazz.name}", post.id)
|
141
|
+
end.to_not output(Sunspot::RemoveByIdNotSupportCompositeIdMessage.call(clazz) + "\n").to_stderr
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'removes record' do
|
145
|
+
session.remove_by_id("USERDATA-#{post.id}!#{clazz.name}", post.id)
|
146
|
+
expect(connection).to have_delete(post_solr_id)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
63
150
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
shared_examples_for "query with connective scope and boost" do
|
2
|
+
it 'creates a boost query' do
|
3
|
+
search do
|
4
|
+
boost(10) do
|
5
|
+
any_of do
|
6
|
+
with(:coordinates_new).in_bounding_box([23, -46], [25, -44])
|
7
|
+
with(:coordinates_new).in_bounding_box([42, 56], [43, 58])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
expect(connection).to have_last_search_including(
|
13
|
+
:bq, '(coordinates_new_ll:[23,-46 TO 25,-44] OR coordinates_new_ll:[42,56 TO 43,58])^10'
|
14
|
+
)
|
15
|
+
|
16
|
+
expect(connection).to have_last_search_including(
|
17
|
+
:defType, 'edismax'
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'creates a boost function' do
|
22
|
+
search do
|
23
|
+
boost(function() { field(:average_rating) })
|
24
|
+
end
|
25
|
+
|
26
|
+
expect(connection).to have_last_search_including(
|
27
|
+
:bf, 'field(average_rating_ft)'
|
28
|
+
)
|
29
|
+
|
30
|
+
expect(connection).to have_last_search_including(
|
31
|
+
:defType, 'edismax'
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'creates a multiplicative boost function' do
|
36
|
+
search do
|
37
|
+
boost_multiplicative(function() { field(:average_rating) })
|
38
|
+
end
|
39
|
+
|
40
|
+
expect(connection).to have_last_search_including(
|
41
|
+
:boost, 'field(average_rating_ft)'
|
42
|
+
)
|
43
|
+
|
44
|
+
expect(connection).to have_last_search_including(
|
45
|
+
:defType, 'edismax'
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'creates combined boost search' do
|
50
|
+
search do
|
51
|
+
boost(10) do
|
52
|
+
any_of do
|
53
|
+
with(:coordinates_new).in_bounding_box([23, -46], [25, -44])
|
54
|
+
with(:coordinates_new).in_bounding_box([42, 56], [43, 58])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
boost(function() { field(:average_rating) })
|
59
|
+
boost_multiplicative(function() { field(:average_rating) })
|
60
|
+
end
|
61
|
+
|
62
|
+
expect(connection).to have_last_search_including(
|
63
|
+
:bq, '(coordinates_new_ll:[23,-46 TO 25,-44] OR coordinates_new_ll:[42,56 TO 43,58])^10'
|
64
|
+
)
|
65
|
+
|
66
|
+
expect(connection).to have_last_search_including(
|
67
|
+
:bf, 'field(average_rating_ft)'
|
68
|
+
)
|
69
|
+
|
70
|
+
expect(connection).to have_last_search_including(
|
71
|
+
:boost, 'field(average_rating_ft)'
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'avoids duplicate boost functions' do
|
76
|
+
search do
|
77
|
+
boost(function() { field(:average_rating) })
|
78
|
+
boost(function() { field(:average_rating) })
|
79
|
+
boost_multiplicative(function() { field(:average_rating) })
|
80
|
+
end
|
81
|
+
|
82
|
+
expect(connection.searches.last[:bf]).to eq ['field(average_rating_ft)']
|
83
|
+
expect(connection.searches.last[:boost]).to eq ['field(average_rating_ft)']
|
84
|
+
end
|
85
|
+
end
|
data/spec/api/query/join_spec.rb
CHANGED
@@ -6,7 +6,7 @@ describe 'join' do
|
|
6
6
|
with(:caption, 'blah')
|
7
7
|
end
|
8
8
|
expect(connection).to have_last_search_including(
|
9
|
-
:fq, "{!join from=photo_container_id_i to=id_i
|
9
|
+
:fq, "{!join from=photo_container_id_i to=id_i v='type:\"Photo\" AND caption_s:\"blah\"'}")
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'should greater_than search by join' do
|
@@ -14,6 +14,6 @@ describe 'join' do
|
|
14
14
|
with(:photo_rating).greater_than(3)
|
15
15
|
end
|
16
16
|
expect(connection).to have_last_search_including(
|
17
|
-
:fq, "{!join from=photo_container_id_i to=id_i
|
17
|
+
:fq, "{!join from=photo_container_id_i to=id_i v='type:\"Photo\" AND average_rating_ft:{3\\.0 TO *}'}")
|
18
18
|
end
|
19
19
|
end
|
@@ -4,6 +4,7 @@ describe 'standard query', :type => :query do
|
|
4
4
|
it_should_behave_like "scoped query"
|
5
5
|
it_should_behave_like "query with advanced manipulation"
|
6
6
|
it_should_behave_like "query with connective scope"
|
7
|
+
it_should_behave_like "query with connective scope and boost"
|
7
8
|
it_should_behave_like "query with dynamic field support"
|
8
9
|
it_should_behave_like "facetable query"
|
9
10
|
it_should_behave_like "fulltext query"
|
@@ -22,6 +23,15 @@ describe 'standard query', :type => :query do
|
|
22
23
|
expect(connection).to have_last_search_with(:q => '*:*')
|
23
24
|
end
|
24
25
|
|
26
|
+
it 'adds a no-op query to :q parameter when only a boost query provided' do
|
27
|
+
session.search Post do
|
28
|
+
boost(2) do
|
29
|
+
with :title, 'My Pet Post'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
expect(connection).to have_last_search_with(:q => '*:*')
|
33
|
+
end
|
34
|
+
|
25
35
|
private
|
26
36
|
|
27
37
|
def search(*classes, &block)
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Sunspot::Setup do
|
4
|
+
context '#id_prefix_for_class' do
|
5
|
+
subject { Sunspot::Setup.for(clazz).id_prefix_for_class }
|
6
|
+
|
7
|
+
context 'when `id_prefix` is defined on model' do
|
8
|
+
context 'as Proc' do
|
9
|
+
let(:clazz) { PostWithProcPrefixId }
|
10
|
+
|
11
|
+
it 'returns nil' do
|
12
|
+
is_expected.to be_nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'as Symbol' do
|
17
|
+
let(:clazz) { PostWithSymbolPrefixId }
|
18
|
+
|
19
|
+
it 'returns nil' do
|
20
|
+
is_expected.to be_nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'as String' do
|
25
|
+
let(:clazz) { PostWithStringPrefixId }
|
26
|
+
|
27
|
+
it 'returns `id_prefix` value' do
|
28
|
+
is_expected.to eq('USERDATA!')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when `id_prefix` is not defined on model' do
|
34
|
+
let(:clazz) { PostWithoutPrefixId }
|
35
|
+
|
36
|
+
it 'returns nil' do
|
37
|
+
is_expected.to be_nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context '#id_prefix_defined?' do
|
43
|
+
subject { Sunspot::Setup.for(clazz).id_prefix_defined? }
|
44
|
+
|
45
|
+
context 'when `id_prefix` is defined on model' do
|
46
|
+
let(:clazz) { PostWithProcPrefixId }
|
47
|
+
|
48
|
+
it 'returns true' do
|
49
|
+
is_expected.to be_truthy
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when `id_prefix` is not defined on model' do
|
54
|
+
let(:clazz) { PostWithoutPrefixId }
|
55
|
+
|
56
|
+
it 'returns false' do
|
57
|
+
is_expected.to be_falsey
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context '#id_prefix_requires_instance?' do
|
63
|
+
subject { Sunspot::Setup.for(clazz).id_prefix_requires_instance? }
|
64
|
+
|
65
|
+
context 'when `id_prefix` is defined on model' do
|
66
|
+
context 'as Proc' do
|
67
|
+
let(:clazz) { PostWithProcPrefixId }
|
68
|
+
|
69
|
+
it 'returns true' do
|
70
|
+
is_expected.to be_truthy
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'as Symbol' do
|
75
|
+
let(:clazz) { PostWithSymbolPrefixId }
|
76
|
+
|
77
|
+
it 'returns true' do
|
78
|
+
is_expected.to be_truthy
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'as String' do
|
83
|
+
let(:clazz) { PostWithStringPrefixId }
|
84
|
+
|
85
|
+
it 'returns false' do
|
86
|
+
is_expected.to be_falsey
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'when `id_prefix` is not defined on model' do
|
92
|
+
let(:clazz) { PostWithoutPrefixId }
|
93
|
+
|
94
|
+
it 'returns false' do
|
95
|
+
is_expected.to be_falsey
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -14,4 +14,26 @@ module IndexerHelper
|
|
14
14
|
def values_in_last_document_for(field_name)
|
15
15
|
@connection.adds.last.last.fields_by_name(field_name).map { |field| field.value }
|
16
16
|
end
|
17
|
+
|
18
|
+
def index_post(post)
|
19
|
+
Sunspot.index!(post)
|
20
|
+
hit = find_post(post)
|
21
|
+
expect(hit).not_to be_nil
|
22
|
+
hit
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_post(post)
|
26
|
+
Sunspot.search(clazz).hits.find { |h| h.primary_key.to_i == post.id && h.id_prefix == id_prefix_value(post, id_prefix) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def id_prefix_value(post, id_prefix)
|
30
|
+
return unless id_prefix
|
31
|
+
return id_prefix if id_prefix.is_a?(String)
|
32
|
+
|
33
|
+
id_prefix.call(post)
|
34
|
+
end
|
35
|
+
|
36
|
+
def post_solr_id
|
37
|
+
"#{id_prefix_value(post, id_prefix)}#{clazz} #{post.id}"
|
38
|
+
end
|
17
39
|
end
|
@@ -1,6 +1,79 @@
|
|
1
1
|
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
2
|
|
3
|
-
|
3
|
+
shared_examples 'atomic update with instance as key' do
|
4
|
+
it 'updates record' do
|
5
|
+
post = clazz.new(title: 'A Title', featured: true)
|
6
|
+
Sunspot.index!(post)
|
7
|
+
|
8
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
|
9
|
+
|
10
|
+
Sunspot.atomic_update!(clazz, post => { title: 'A New Title' })
|
11
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A New Title', featured: true)
|
12
|
+
|
13
|
+
Sunspot.atomic_update!(clazz, post => { featured: false })
|
14
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A New Title', featured: false)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'does not print warning' do
|
18
|
+
post = clazz.new(title: 'A Title', featured: true)
|
19
|
+
Sunspot.index!(post)
|
20
|
+
|
21
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
|
22
|
+
|
23
|
+
expect do
|
24
|
+
Sunspot.atomic_update!(clazz, post => { title: 'A New Title' })
|
25
|
+
end.to_not output(Sunspot::AtomicUpdateRequireInstanceForCompositeIdMessage.call(clazz)).to_stderr
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'does not create duplicate document' do
|
29
|
+
post = clazz.new(title: 'A Title', featured: true)
|
30
|
+
Sunspot.index!(post)
|
31
|
+
|
32
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
|
33
|
+
|
34
|
+
Sunspot.atomic_update!(clazz, post => { title: 'A New Title' })
|
35
|
+
hit = find_indexed_post_with_prefix_id(post, nil)
|
36
|
+
expect(hit).to be_nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
shared_examples 'atomic update with id as key' do
|
41
|
+
it 'does not update record' do
|
42
|
+
post = clazz.new(title: 'A Title', featured: true)
|
43
|
+
Sunspot.index!(post)
|
44
|
+
|
45
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
|
46
|
+
|
47
|
+
Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
|
48
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A Title', featured: true)
|
49
|
+
|
50
|
+
Sunspot.atomic_update!(clazz, post.id => { featured: false })
|
51
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A Title', featured: true)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'prints warning' do
|
55
|
+
post = clazz.new(title: 'A Title', featured: true)
|
56
|
+
Sunspot.index!(post)
|
57
|
+
|
58
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
|
59
|
+
|
60
|
+
expect do
|
61
|
+
Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
|
62
|
+
end.to output(Sunspot::AtomicUpdateRequireInstanceForCompositeIdMessage.call(clazz) + "\n").to_stderr
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'creates duplicate document that have only fields provided for update' do
|
66
|
+
post = clazz.new(title: 'A Title', featured: true)
|
67
|
+
Sunspot.index!(post)
|
68
|
+
|
69
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
|
70
|
+
|
71
|
+
Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
|
72
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, nil), title: 'A New Title', featured: nil)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'Atomic Update feature' do
|
4
77
|
before :all do
|
5
78
|
Sunspot.remove_all
|
6
79
|
end
|
@@ -18,10 +91,27 @@ describe 'atomic updates' do
|
|
18
91
|
hit
|
19
92
|
end
|
20
93
|
|
21
|
-
|
94
|
+
def find_and_validate_indexed_post_with_prefix_id(post, id_prefix)
|
95
|
+
hit = find_indexed_post_with_prefix_id(post, id_prefix_value(post, id_prefix))
|
96
|
+
expect(hit).not_to be_nil
|
97
|
+
hit
|
98
|
+
end
|
99
|
+
|
100
|
+
def find_indexed_post_with_prefix_id(post, id_prefix)
|
101
|
+
Sunspot.search(post.class).hits.find { |h| h.primary_key.to_i == post.id && h.id_prefix == id_prefix }
|
102
|
+
end
|
103
|
+
|
104
|
+
def id_prefix_value(post, id_prefix)
|
105
|
+
return unless id_prefix
|
106
|
+
return id_prefix if id_prefix.is_a?(String)
|
107
|
+
|
108
|
+
id_prefix.call(post)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'updates single record fields one by one' do
|
22
112
|
post = Post.new(title: 'A Title', featured: true)
|
23
113
|
Sunspot.index!(post)
|
24
|
-
|
114
|
+
|
25
115
|
validate_hit(find_indexed_post(post.id), title: post.title, featured: post.featured)
|
26
116
|
|
27
117
|
Sunspot.atomic_update!(Post, post.id => {title: 'A New Title'})
|
@@ -31,7 +121,7 @@ describe 'atomic updates' do
|
|
31
121
|
validate_hit(find_indexed_post(post.id), title: 'A New Title', featured: false)
|
32
122
|
end
|
33
123
|
|
34
|
-
it '
|
124
|
+
it 'updates fields for multiple records' do
|
35
125
|
post1 = Post.new(title: 'A First Title', featured: true)
|
36
126
|
post2 = Post.new(title: 'A Second Title', featured: false)
|
37
127
|
Sunspot.index!(post1, post2)
|
@@ -44,7 +134,7 @@ describe 'atomic updates' do
|
|
44
134
|
validate_hit(find_indexed_post(post2.id), title: 'A Second Title', featured: true)
|
45
135
|
end
|
46
136
|
|
47
|
-
it '
|
137
|
+
it 'clears field value properly' do
|
48
138
|
post = Post.new(title: 'A Title', tags: %w(tag1 tag2), featured: true)
|
49
139
|
Sunspot.index!(post)
|
50
140
|
validate_hit(find_indexed_post(post.id), title: post.title, tag_list: post.tags, featured: true)
|
@@ -55,4 +145,78 @@ describe 'atomic updates' do
|
|
55
145
|
Sunspot.atomic_update!(Post, post.id => {featured: nil})
|
56
146
|
validate_hit(find_indexed_post(post.id), title: post.title, tag_list: nil, featured: nil)
|
57
147
|
end
|
148
|
+
|
149
|
+
context 'when `id_prefix` is defined on model' do
|
150
|
+
context 'as Proc' do
|
151
|
+
let(:clazz) { PostWithProcPrefixId }
|
152
|
+
let(:id_prefix) { lambda { |post| "USERDATA-#{post.id}!" } }
|
153
|
+
|
154
|
+
context 'and instance passed as key' do
|
155
|
+
include_examples 'atomic update with instance as key'
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'and id passed as key' do
|
159
|
+
include_examples 'atomic update with id as key'
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'as Symbol' do
|
164
|
+
let(:clazz) { PostWithSymbolPrefixId }
|
165
|
+
let(:id_prefix) { lambda { |post| "#{post.title}!" } }
|
166
|
+
|
167
|
+
context 'and instance passed as key' do
|
168
|
+
include_examples 'atomic update with instance as key'
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'and id passed as key' do
|
172
|
+
include_examples 'atomic update with id as key'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'as String' do
|
177
|
+
let(:clazz) { PostWithStringPrefixId }
|
178
|
+
let(:id_prefix) { 'USERDATA!' }
|
179
|
+
|
180
|
+
context 'and instance passed as key' do
|
181
|
+
include_examples 'atomic update with instance as key'
|
182
|
+
end
|
183
|
+
|
184
|
+
context 'and id passed as key' do
|
185
|
+
it 'updates record' do
|
186
|
+
post = clazz.new(title: 'A Title', featured: true)
|
187
|
+
Sunspot.index!(post)
|
188
|
+
|
189
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
|
190
|
+
|
191
|
+
Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
|
192
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A New Title', featured: true)
|
193
|
+
|
194
|
+
Sunspot.atomic_update!(clazz, post.id => { featured: false })
|
195
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: 'A New Title', featured: false)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'does not print warning' do
|
199
|
+
post = clazz.new(title: 'A Title', featured: true)
|
200
|
+
Sunspot.index!(post)
|
201
|
+
|
202
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
|
203
|
+
|
204
|
+
expect do
|
205
|
+
Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
|
206
|
+
end.to_not output(Sunspot::AtomicUpdateRequireInstanceForCompositeIdMessage.call(clazz) + "\n").to_stderr
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'does not create duplicate document' do
|
210
|
+
post = clazz.new(title: 'A Title', featured: true)
|
211
|
+
Sunspot.index!(post)
|
212
|
+
|
213
|
+
validate_hit(find_and_validate_indexed_post_with_prefix_id(post, id_prefix), title: post.title, featured: post.featured)
|
214
|
+
|
215
|
+
Sunspot.atomic_update!(clazz, post.id => { title: 'A New Title' })
|
216
|
+
hit = find_indexed_post_with_prefix_id(post, nil)
|
217
|
+
expect(hit).to be_nil
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
58
222
|
end
|