CloudSesame 0.6.4 → 0.6.5

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -3
  3. data/.rubocop.yml +1158 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile.lock +28 -1
  6. data/Guardfile +6 -1
  7. data/README.md +6 -1
  8. data/cloud_sesame.gemspec +4 -2
  9. data/coverage/.last_run.json +5 -0
  10. data/coverage/.resultset.json +2423 -0
  11. data/coverage/.resultset.json.lock +0 -0
  12. data/lib/cloud_sesame.rb +4 -5
  13. data/lib/cloud_sesame/domain/base.rb +46 -33
  14. data/lib/cloud_sesame/domain/client_module/caching/base.rb +1 -1
  15. data/lib/cloud_sesame/query/ast/near.rb +1 -1
  16. data/lib/cloud_sesame/query/ast/operator.rb +1 -1
  17. data/lib/cloud_sesame/query/ast/range_value.rb +23 -30
  18. data/lib/cloud_sesame/query/ast/single_expression_operator.rb +1 -1
  19. data/lib/cloud_sesame/query/ast/value.rb +18 -4
  20. data/lib/cloud_sesame/query/domain/block.rb +2 -2
  21. data/lib/cloud_sesame/query/domain/literal.rb +1 -1
  22. data/lib/cloud_sesame/query/dsl/applied_filter_query.rb +21 -23
  23. data/lib/cloud_sesame/query/dsl/field_accessors.rb +8 -3
  24. data/lib/cloud_sesame/query/dsl/response_methods.rb +2 -2
  25. data/lib/cloud_sesame/query/dsl/scope_accessors.rb +1 -1
  26. data/lib/cloud_sesame/query/node/fuzziness.rb +11 -13
  27. data/lib/cloud_sesame/query/node/query.rb +9 -10
  28. data/lib/cloud_sesame/query/node/request.rb +2 -2
  29. data/lib/cloud_sesame/query/node/sloppiness.rb +23 -0
  30. data/spec/cloud_sesame/config/credential_spec.rb +76 -0
  31. data/spec/cloud_sesame/domain/base_spec.rb +274 -9
  32. data/spec/cloud_sesame/domain/client_spec.rb +0 -1
  33. data/spec/cloud_sesame/query/domain/block_spec.rb +0 -1
  34. data/spec/cloud_sesame/query/node/query_spec.rb +18 -7
  35. data/spec/cloud_sesame_spec.rb +10 -10
  36. data/spec/spec_helper.rb +3 -0
  37. metadata +38 -3
  38. data/lib/cloud_sesame/domain/context.rb +0 -39
@@ -8,7 +8,7 @@ module CloudSesame
8
8
 
9
9
  defined_scopes = _scope.context[:scopes]
10
10
  if defined_scopes && (block = defined_scopes[name.to_sym])
11
- instance_exec *args, &block
11
+ instance_exec(*args, &block)
12
12
  _return
13
13
  else
14
14
  raise Error::ScopeNotDefined
@@ -3,6 +3,8 @@ module CloudSesame
3
3
  module Node
4
4
  class Fuzziness
5
5
 
6
+ EXCLUDING_TERMS = /^\-/
7
+
6
8
  def initialize(&block)
7
9
 
8
10
  # default fuzziness
@@ -10,7 +12,7 @@ module CloudSesame
10
12
  @min_char_size = 6
11
13
  @fuzzy_percent = 0.17
12
14
 
13
- instance_eval &block if block_given?
15
+ instance_eval(&block) if block_given?
14
16
  end
15
17
 
16
18
  def max_fuzziness(int)
@@ -25,32 +27,28 @@ module CloudSesame
25
27
  @fuzzy_percent = float.to_f
26
28
  end
27
29
 
28
- def parse(string)
29
- join_by_and each_with(string) { |word| fuzziness word }
30
+ def compile(string)
31
+ "(#{ each_word_in(string) { |word| fuzziness(word) }.compact.join('+') })"
30
32
  end
31
33
 
32
34
  private
33
35
 
34
- def each_with(string, &block)
35
- string.split(' ').map &block
36
+ def each_word_in(string, &block)
37
+ string.split(' ').map(&block)
36
38
  end
37
39
 
38
40
  def fuzziness(word)
39
- if word.length >= @min_char_size && !excluding_term?(word)
40
- fuzziness = (word.length * @fuzzy_percent).round
41
+ if (length = word.length) >= @min_char_size && !excluding_term?(word)
42
+ fuzziness = (length * @fuzzy_percent).round
41
43
  fuzziness = [fuzziness, @max_fuzziness].min
42
- "#{word}~#{fuzziness}"
44
+ "#{ word }~#{ fuzziness }"
43
45
  else
44
46
  word
45
47
  end
46
48
  end
47
49
 
48
- def join_by_and(args = [])
49
- (args = args.compact).size > 1 ? "(#{ args.join('+') })" : args[0]
50
- end
51
-
52
50
  def excluding_term?(word)
53
- !!word.match(/^\-/)
51
+ !!(EXCLUDING_TERMS =~ word)
54
52
  end
55
53
 
56
54
  end
@@ -10,24 +10,23 @@ module CloudSesame
10
10
  end
11
11
 
12
12
  def compile
13
- compiled = [query]
14
- compiled << fuzziness if context[:fuzziness]
15
- compiled << sloppiness if context[:sloppiness]
16
- { query: join_by_or(compiled) }
13
+ { query: "(#{
14
+ query
15
+ })#{
16
+ '|' << fuzziness.compile(query) if fuzziness
17
+ }#{
18
+ '|' << sloppiness.compile(query) if sloppiness
19
+ }" }
17
20
  end
18
21
 
19
22
  private
20
23
 
21
24
  def fuzziness
22
- context[:fuzziness] && query && !query.empty? ? context[:fuzziness].parse(query) : nil
25
+ context[:fuzziness]
23
26
  end
24
27
 
25
28
  def sloppiness
26
- context[:sloppiness] && query && query.include?(' ') ? "\"#{ query }\"~#{ context[:sloppiness] }" : nil
27
- end
28
-
29
- def join_by_or(args = [])
30
- (args = args.compact).size > 1 ? "(#{ args.join('|') })" : args[0]
29
+ context[:sloppiness]
31
30
  end
32
31
 
33
32
  end
@@ -51,8 +51,8 @@ module CloudSesame
51
51
  page,
52
52
  sort,
53
53
  return_field
54
- ].each_with_object({}) do |node, compiled|
55
- compiled.merge!(node.compile || {})
54
+ ].each_with_object({}) do |node, object|
55
+ object.merge!(node.compile || {})
56
56
  end
57
57
 
58
58
  if compiled[:filter_query].empty?
@@ -0,0 +1,23 @@
1
+ module CloudSesame
2
+ module Query
3
+ module Node
4
+ class Sloppiness
5
+
6
+ def initialize(value)
7
+ @value = value.to_i
8
+ end
9
+
10
+ def compile(string)
11
+ "\"#{ string }\"~#{ @value }" if more_than_one_word?(string)
12
+ end
13
+
14
+ private
15
+
16
+ def more_than_one_word?(string)
17
+ string.include?(' ')
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,76 @@
1
+ module CloudSesame
2
+ module Config
3
+ describe Credential do
4
+
5
+ describe '#initialize' do
6
+ it 'should accept access_key_id' do
7
+ credential = Credential.new(access_key_id: 123)
8
+ expect(credential.to_hash[:access_key_id]).to eq 123
9
+ end
10
+ it 'should accept access_key as an alias for access_key_id' do
11
+ credential = Credential.new(access_key: 123)
12
+ expect(credential.to_hash[:access_key_id]).to eq 123
13
+ end
14
+ it 'should accept secret_access_key' do
15
+ credential = Credential.new(secret_access_key: 'secret')
16
+ expect(credential.to_hash[:secret_access_key]).to eq 'secret'
17
+ end
18
+ it 'should accept secret_key as an alias for secret_access_key' do
19
+ credential = Credential.new(secret_key: 'secret')
20
+ expect(credential.to_hash[:secret_access_key]).to eq 'secret'
21
+ end
22
+ end
23
+
24
+ describe 'access_key_id' do
25
+ context 'getter' do
26
+ subject { Credential.new(access_key: 123) }
27
+ it 'should be defined' do
28
+ expect(subject).to respond_to(:access_key_id)
29
+ expect(subject.access_key_id).to eq 123
30
+ end
31
+ it 'should have an alias getter defined' do
32
+ expect(subject).to respond_to(:access_key)
33
+ expect(subject.access_key).to eq 123
34
+ end
35
+ end
36
+ context 'writer' do
37
+ let(:attributes) { subject.instance_variable_get(:@attributes) }
38
+ it 'should be defined' do
39
+ expect(subject).to respond_to(:access_key_id=)
40
+ expect{ subject.access_key_id = 123 }.to change{ attributes[:access_key_id] }.from(nil).to(123)
41
+ end
42
+ it 'should have an alias writer defined' do
43
+ expect(subject).to respond_to(:access_key=)
44
+ expect{ subject.access_key = 123 }.to change{ attributes[:access_key_id] }.from(nil).to(123)
45
+ end
46
+ end
47
+ end
48
+
49
+ describe 'secret_access_key' do
50
+ context 'getter' do
51
+ subject { Credential.new(secret_key: 123) }
52
+ it 'should be defined' do
53
+ expect(subject).to respond_to(:secret_access_key)
54
+ expect(subject.secret_access_key).to eq 123
55
+ end
56
+ it 'should have an alias getter defined' do
57
+ expect(subject).to respond_to(:secret_key)
58
+ expect(subject.secret_key).to eq 123
59
+ end
60
+ end
61
+ context 'writer' do
62
+ let(:attributes) { subject.instance_variable_get(:@attributes) }
63
+ it 'should be defined' do
64
+ expect(subject).to respond_to(:secret_access_key=)
65
+ expect{ subject.secret_access_key = 123 }.to change{ attributes[:secret_access_key] }.from(nil).to(123)
66
+ end
67
+ it 'should have an alias writer defined' do
68
+ expect(subject).to respond_to(:secret_key=)
69
+ expect{ subject.secret_key = 123 }.to change{ attributes[:secret_access_key] }.from(nil).to(123)
70
+ end
71
+ end
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -1,25 +1,290 @@
1
- require 'spec_helper'
2
-
3
1
  module CloudSesame
4
2
  module Domain
5
3
  describe Base do
6
4
 
7
- let(:searchable_class) { "Test" }
8
- subject { Base.new(searchable_class) }
5
+ class BaseSearchable; end
6
+
7
+ let(:searchable) { BaseSearchable }
8
+ subject { Base.new(searchable) }
9
9
 
10
- describe '#initalize' do
11
- it 'should set searchable class' do
12
- expect(subject.searchable).to eq "Test"
10
+ shared_examples 'delegation to client' do |method|
11
+ it 'should be delegated to #client' do
12
+ expect(subject.client).to receive(method)
13
+ subject.send(method)
13
14
  end
14
15
  end
15
16
 
16
- describe '#field' do
17
+ describe '#config' do
18
+ it_behaves_like 'delegation to client', :config
19
+ end
20
+
21
+ describe '#caching_with' do
22
+ it_behaves_like 'delegation to client', :caching_with
23
+ end
24
+
25
+ describe '#builder' do
26
+ it 'should initialize an new builder each time' do
27
+ number = 2
28
+ expect(Query::Builder).to receive(:new).exactly(number).times.and_call_original
29
+ number.times { subject.builder }
30
+ end
31
+ it 'should receive the context from base when called' do
32
+ c = Context.new
33
+ expect(subject).to receive(:context).and_return(c)
34
+ subject.builder
35
+ end
36
+ it 'should receive the searchable form base when called' do
37
+ expect(subject).to receive(:searchable).and_return(searchable)
38
+ subject.builder
39
+ end
17
40
  end
18
41
 
19
42
  describe '#client' do
43
+ it 'should initialize and return a domain client with searchable' do
44
+ expect(Client).to receive(:new).with(searchable)
45
+ subject.client
46
+ end
47
+ end
48
+
49
+ describe '#context' do
50
+ it 'should initialize and return a context' do
51
+ expect(Context).to receive(:new)
52
+ subject.context
53
+ end
54
+ end
55
+
56
+ describe '#default_size' do
57
+ it 'should store the value to context page size' do
58
+ size = 99
59
+ subject.default_size size
60
+ expect(subject.context[:page][:size]).to eq(size)
61
+ end
62
+ it 'should convert value to integer' do
63
+ size = "99"
64
+ subject.default_size size
65
+ expect(subject.context[:page][:size]).to eq(size.to_i)
66
+ end
67
+ it 'should add page size to context' do
68
+ expect{ subject.default_size 99 }.to change{ subject.context[:page] }.from(nil).to(hash_including(size: 99))
69
+ end
70
+ end
71
+
72
+ describe '#define_sloppiness' do
73
+ it 'should create a sloppiness node' do
74
+ value = 3
75
+ expect(Query::Node::Sloppiness).to receive(:new).with(value)
76
+ subject.define_sloppiness(value)
77
+ end
78
+ it 'should add query sloppiness to context' do
79
+ expect{ subject.define_sloppiness 3 }.to change{ subject.context[:query] }.from(nil).to(hash_including(sloppiness: Query::Node::Sloppiness))
80
+ end
81
+ end
82
+
83
+ describe '#define_fuzziness' do
84
+ let(:block) { Proc.new {} }
85
+ it 'should create a fuzziness node' do
86
+ expect(Query::Node::Fuzziness).to receive(:new) { |&b|
87
+ expect(b).to eq block
88
+ }
89
+ subject.define_fuzziness(&block)
90
+ end
91
+ it 'should add query fuzziness to context' do
92
+ expect{ subject.define_fuzziness {} }.to change{ subject.context[:query] }.from(nil).to(hash_including(fuzziness: Query::Node::Fuzziness))
93
+ end
94
+ end
95
+
96
+ describe '#field' do
97
+ let(:field_name) { :name }
98
+
99
+ shared_examples 'and options :as is also passed' do
100
+ let(:real_name) { :text1 }
101
+ before { options[:as] = real_name }
102
+ it 'should use the real name as field name' do
103
+ subject.field field_name, options
104
+ expect(custom_options).to include(real_name)
105
+ end
106
+ end
107
+
108
+ context 'when options :query is passed in' do
109
+ let(:options) { { query: true } }
110
+
111
+ context 'and query is set to true' do
112
+ it 'should add query options for field' do
113
+ subject.field(field_name, options)
114
+ expect(subject.context[:query_options][:fields][field_name]).to eq({})
115
+ end
116
+ end
117
+ context 'and query has options' do
118
+ it 'should use the query options for field' do
119
+ query_options = { weight: 2 }
120
+ options[:query] = query_options
121
+ subject.field(field_name, options)
122
+ expect(subject.context[:query_options][:fields][field_name]).to eq(query_options)
123
+ end
124
+ end
125
+
126
+ it_behaves_like 'and options :as is also passed' do
127
+ let(:options) { { query: true } }
128
+ let(:custom_options) { subject.context[:query_options][:fields] }
129
+ end
130
+ end
131
+
132
+ context 'when options :facet is passed in' do
133
+ let(:options) {{ facet: true }}
134
+ context 'and facet is set to true' do
135
+ it 'should add facet options for field' do
136
+ subject.field(field_name, options)
137
+ expect(subject.context[:facet][field_name]).to eq({})
138
+ end
139
+ end
140
+ context 'and facet has options' do
141
+ it 'should use the facet options defined' do
142
+ facet_options = { size: 2 }
143
+ options[:facet] = facet_options
144
+ subject.field(field_name, options)
145
+ expect(subject.context[:facet][field_name]).to eq(facet_options)
146
+ end
147
+ end
148
+
149
+ it_behaves_like 'and options :as is also passed' do
150
+ let(:options) { { facet: true } }
151
+ let(:custom_options) { subject.context[:facet] }
152
+ end
153
+ end
154
+
155
+ context 'when options :as is passed in' do
156
+ let(:options) {{ override: false }}
157
+ context 'and filter_query fields contains an options for field :as' do
158
+ let(:original_option) {{ hello: 'world', override: true }}
159
+ let(:real_name) { :text1 }
160
+ before {
161
+ options[:as] = real_name
162
+ ((subject.context[:filter_query] ||= {})[:fields] ||= {})[:text1] = original_option
163
+ }
164
+ it 'should delete the filter_query fields options for :as field' do
165
+ expect(subject.context[:filter_query][:fields]).to receive(:delete).with(real_name)
166
+ subject.field field_name, options
167
+ end
168
+ it 'should merge the original options into the new options' do
169
+ subject.field field_name, options
170
+ expect(subject.context[:filter_query][:fields][field_name]).to include({hello: 'world' })
171
+ end
172
+ it 'should not override the new options' do
173
+ subject.field field_name, options
174
+ expect(subject.context[:filter_query][:fields][field_name]).to include(override: false)
175
+ end
176
+ end
177
+ end
178
+
179
+ context 'when options :default is passed in' do
180
+ let(:proc) { Proc.new { } }
181
+ let(:options) {{ default: proc }}
182
+ it 'should remove the default lambda or proc from the options' do
183
+ expect(options).to receive(:delete).with(:default)
184
+ subject.field field_name, options
185
+ end
186
+ it 'should create a literal node using Query::Domain::Literal' do
187
+ domain = Query::Domain::Literal.new(field_name, {}, self)
188
+ expect(Query::Domain::Literal).to receive(:new).with(field_name, {}, self).and_return(domain)
189
+ expect(domain).to receive(:_eval) { |&block| expect(block).to eq proc }
190
+ subject.field field_name, options
191
+ end
192
+
193
+ context 'when default proc/lambda returns value' do
194
+ let(:proc) { Proc.new { "name" } }
195
+ it 'should store the literal node in filter query defaults of context' do
196
+ node = Query::Domain::Literal.new(field_name, {}, self)._eval(&proc)
197
+ allow_any_instance_of(Query::Domain::Literal).to receive(:_eval).and_return(node)
198
+ expect{ subject.field field_name, options }.to change{ subject.context[:filter_query] }.from(nil).to(hash_including(defaults: include(node)))
199
+ end
200
+ end
201
+
202
+ context 'when default proc/lambda returns nothing' do
203
+ it 'should not create any literal node in filter query defaults of context' do
204
+ node = Query::Domain::Literal.new(field_name, {}, self)._eval(&proc)
205
+ allow_any_instance_of(Query::Domain::Literal).to receive(:_eval).and_return(node)
206
+ expect{ subject.field field_name, options }.to_not change{ subject.send(:filter_query_defaults) }.from([])
207
+ end
208
+ end
209
+ end
210
+
211
+ it 'should create an field accessor' do
212
+ field_name = :an_indexed_field
213
+ expect{ subject.field field_name, {} }.to change{ Query::DSL::FieldAccessors.instance_methods }.by([field_name])
214
+ end
215
+
216
+ context 'when options is passed in' do
217
+ let(:options) {{ hello: "world" }}
218
+ it 'should store the options in filter query fields' do
219
+ subject.field field_name, options
220
+ expect(subject.context[:filter_query][:fields][field_name]).to eq options
221
+ end
222
+ end
223
+
224
+ context 'when no options is passed in' do
225
+ it 'should create a options in filter query fields' do
226
+ subject.field field_name
227
+ expect(subject.context[:filter_query][:fields][field_name]).to eq({})
228
+ end
229
+ end
20
230
  end
21
231
 
22
- describe '#query' do
232
+ describe '#scope' do
233
+ let(:scope_name) { :test_scope }
234
+ context 'when no proc or block is given' do
235
+ it 'should not add anything to filter query scopes' do
236
+ expect{ subject.scope(scope_name) }.to_not change{ subject.send(:filter_query_scopes) }
237
+ end
238
+ end
239
+ context 'when proc is given' do
240
+ let(:proc) { Proc.new {} }
241
+ it 'should add the proc to the filter query scopes' do
242
+ expect{ subject.scope(scope_name, proc) }.to change{ subject.send(:filter_query_scopes) }.from({}).to(hash_including( scope_name => proc))
243
+ end
244
+ end
245
+ context 'when block is given' do
246
+ let(:block) { -> { "block" } }
247
+ it 'should add the block to the filter query scopes' do
248
+ expect{ subject.scope(scope_name, &block) }.to change{ subject.send(:filter_query_scopes) }.from({}).to(scope_name => eq(block))
249
+ end
250
+ end
251
+ end
252
+
253
+ context 'when method missing' do
254
+ let(:undefined_method) { :undefined_method }
255
+ let(:args) { [1,2,3] }
256
+ context 'and builder resposnds to the method' do
257
+ let(:builder) { subject.builder }
258
+ before { allow(subject).to receive(:builder).and_return(builder) }
259
+ it 'should check if builder responds to the method' do
260
+ expect(builder).to receive(:respond_to?).with(undefined_method)
261
+ subject.undefined_method rescue nil
262
+ end
263
+ it 'should call method on builder with parameters passed in' do
264
+ allow(builder).to receive(:respond_to?).and_return(true)
265
+ expect(builder).to receive(undefined_method).with(*args)
266
+ subject.undefined_method(*args)
267
+ end
268
+ end
269
+
270
+ context 'and builder not responds to the methods but searchable do' do
271
+ it 'should check if callee existing and responds to the method' do
272
+ expect(searchable).to receive(:respond_to?).with(undefined_method)
273
+ subject.undefined_method rescue nil
274
+ end
275
+ it 'should call method on searchable with parameters passed in' do
276
+ allow(searchable).to receive(:respond_to?).and_return(true)
277
+ expect(searchable).to receive(undefined_method).with(*args)
278
+ subject.undefined_method(*args)
279
+ end
280
+ end
281
+
282
+ context 'both builder and searchable do not respond to the method' do
283
+ it 'should raise NoMethodError on subject' do
284
+ expect{ subject.undefined_method }.to raise_error(NoMethodError)
285
+ end
286
+ end
287
+
23
288
  end
24
289
 
25
290
  end