CloudSesame 0.6.4 → 0.6.5

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