CloudSesame 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b03fae31220d970463fd33113b6a59c9ab0e7954
4
- data.tar.gz: 91253e2158baac1a5927f9391c10af5dd2ce3184
3
+ metadata.gz: a4760f588f53d52c647ac43ab20eb4e2f1fc0cee
4
+ data.tar.gz: 0ab550a8cdadd3cf77002a4b39c7b556c782a479
5
5
  SHA512:
6
- metadata.gz: 8167fb00a861a2d9a9fd9796aae670e43dc17d1ec88d895a137935336b32f921d16e3155b44a2d940caa3b723af40d627ac14a797ca125986d3e9287482dd2a4
7
- data.tar.gz: 6eb69016ada8ff183a69219284d7ea9408b648182c4b6b15bc00450b09b7a25b591273dec100530d22fa0e0885bd56e0cd862f8f6cc9d0c9f3f089cd70a5e634
6
+ metadata.gz: 85982cd6bccb2d8a74da708ce5e30000916dab8d8e97bdd23ec77b056e3fa451b2f4e4bfa92bd69f7b5d53092bb12b304245e6bdfe204af667938863796f8d40
7
+ data.tar.gz: 10c0e7f35306648b13fa936e4baaaf9aefc2d06b220c3dec8aac399b79d6750906c50efa1106921d60a38b02a57a167ebd72f8e68a124f5482065a616bb23588
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- CloudSesame (0.2.1)
4
+ CloudSesame (0.2.2)
5
5
  aws-sdk (~> 2)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -2,56 +2,251 @@
2
2
  Light and Flexible CloudSearch Query Interface
3
3
 
4
4
  #Install
5
- * In terminal
6
- ```gem install CloudSesame```
7
- * In Gemfile
8
- ```gem 'CloudSesame```
9
-
10
- #Setup
11
-
12
- 2. Initalize the gem
13
- * Inside the Rails `config/initializers` folder, create a file called `cloud_sesame.rb`
14
- * In the file, enter in your AWS credentials
5
+ * In terminal type:
6
+ ```
7
+ gem install CloudSesame
8
+ ```
9
+ * Or add this line to the application in Gemfile:
10
+ ```
11
+ gem 'CloudSesame
12
+ ```
15
13
 
14
+ #Setup AWS Credentials
15
+ *Create a initializer file, example: `config/initializers/cloud_sesame.rb`
16
16
  ```
17
17
  require 'cloud_sesame'
18
+
18
19
  CloudSesame::Domain::Client.configure do |config|
19
20
  config.access_key = ENV['AWS_ACCESS_KEY_ID']
20
21
  config.secret_key = ENV['AWS_SECRET_ACCESS_KEY']
21
22
  end
22
23
  ```
24
+ #Usage
25
+ ##1. Mix CloudSesame into any class or model
26
+ ```
27
+ class Product < ActiveRecord::Base
28
+ include CloudSesame
29
+
30
+ # call `define_cloudsearch` to setup your CloudSearch config, default size (optional), fields, and scopes (optional)
31
+ define_cloudsearch do
32
+
33
+ # REQUIRED: class specific config
34
+ config.endpoint = ENV['AWS_PRODUCT_SEARCH_ENDPOINT']
35
+ config.region = ENV['AWS_PRODUCT_SEARCH_REGION']
36
+
37
+ # default query size is 10
38
+ default_size <integer>
39
+
40
+ # turn on sloppy query with distance
41
+ define_sloppiness 3
42
+
43
+ # turn on fuzzy search
44
+ define_fuzziness {
45
+ max_fuzziness <integer> # => maximum fuzziness per word, DEFAULT: 3
46
+ min_char_size <integer> # => minimum word size to trigger fuzziness, DEFAULT: 6
47
+ fuzzy_percent <float> # => [(word.size * fuzzy_percent).round, max_fuzziness].min, DEFAULT: 0.17
48
+ }
49
+
50
+ # field config
51
+ field :product_name, query: { weight: <integer> } # => query_options[:fields] = ["product_name^<integer>"]
52
+ field :description, query: true # => query_options[:fields] = ["description"]
53
+
54
+ field :currency, facet: true # => enable facet
55
+ field :discount, facet: { # => enable facet with buckets
56
+ buckets: %w([10,100] [25,100] [50,100] [70,100]),
57
+ method: 'interval'
58
+ }
59
+ field :manufacturer, facet: { size: 50 } # => enable facet with max size
60
+ field :category, facet: { # => enable facet with sorting
61
+ sort: 'bucket', size: 10_000
62
+ }
63
+
64
+ field :created_at
65
+
66
+ # scope examples
67
+
68
+ # INPUT: "puma",
69
+ # OUPUT: query="shoes", filter_query="(and manufacturer:'puma')"
70
+ scope :shoes_by_brand, ->(brand = nil) { query("shoes").and { manufacturer brand } if brand }
71
+
72
+ # INPUT: 1
73
+ # OUPUT: filter_query="(and created_at:{'2016-01-17T00:00:00Z',})"
74
+ scope :created_in, ->(days = nil) { and { created_at r.gt(Date.today - days) } if days }
75
+
76
+ end
77
+ end
78
+ ```
79
+ ##2. Inheriting from another class or model
80
+ ```
81
+ # Inheriting Definitions from another class
82
+ class ExclusiveProduct < Product
23
83
 
24
- 3. Setup your searchable model
25
-
26
- * `include CloudSesame`
27
- * call `define_cloudsearch` to setup your CloudSearch config, default size (optional), fields, and scopes (optional).
28
-
29
- ```
30
- class Product
31
- include CloudSesame
32
-
33
- define_cloudsearch do
34
- config.endpoint = ENV[AWS_ENDPOINT]
35
- config.region = END[AWS_REGION]
36
-
37
- default_size 100 #will default to 10 if not defined
38
-
39
- field :description, query: true
40
- field :name, query: { weight: 2 }
41
- field :currency, facet: true
42
- field :manufacturer, facet: { size: 50 }
43
- field :price, facet: { buckets: %w([0, 25], [25, 50], [50, 100]), method: 'interval'}
44
-
45
- scope :puma_shoes, -> { query("shoes").and { manufacturer "Puma" } }
46
- scope :puma_shoes do
47
- query("shoes").and { manufacturer "Puma" }
48
- end
49
-
50
- end
84
+ # load previous define cloudsearch definition
85
+ load_definition_from Product
86
+
87
+ # call define_cloudsearch again to override config
88
+ # or map field to a different name
89
+ define_cloudsearch {
90
+ field :name, as: :product_name # => it will use name as product_name's alias, so user can query with
91
+ # NewProduct.cloudsearch.name("shoes") instead of .product_name("shoes")
92
+ # but the output will still be filter_query="product_name:'shoes'"
93
+ }
51
94
  end
52
95
  ```
53
96
 
54
- 4. How to define search fields
55
- * to add a field to query_options, set `query: true`
56
- * to add a field to query_options with a weight, set `query: { weight: <any integer> }`
57
- * to add a field to filter_query, set `facet : true`
97
+ ##3. Query DSL
98
+
99
+ ###Simple Query
100
+ ```
101
+ # Simple Query String
102
+ Product.cloudsearch.query("shoes")
103
+ # OUTPUT: "shoes"
104
+
105
+ Product.cloudsearch.query("shoes -puma")
106
+ # OUTPUT: "shoes -puma"
107
+
108
+ # With Sloppy Query
109
+ Product.cloudsearch.query("shoes")
110
+ # OUTPUT: "(shoes|\"shoes\"~3)"
111
+
112
+ Product.cloudsearch.query("shoes -puma")
113
+ # OUTPUT: "(shoes -puma|\"shoes -puma\"~3)"
114
+
115
+ # With Fuzzy Search
116
+ Product.cloudsearch.query("white shoes")
117
+ # OUTPUT: "(shoes|(white~3+shoes~3))"
118
+
119
+ Product.cloudsearch.query("white shoes -puma")
120
+ # OUTPUT: "(shoes -puma|(white~3+shoes~3+-puma))"
121
+ ```
122
+
123
+ ###Pagination
124
+ ```
125
+ Product.cloudsearch.page(3).size(100)
126
+ # OUTPUT: { start: 200, size: 100 }
127
+
128
+ Product.cloudsearch.start(99).size(100)
129
+ # OUTPUT: { start: 99, size: 100 }
130
+ ```
131
+
132
+ ###Sorting
133
+ ```
134
+ Product.cloudsearch.sort(name: :desc)
135
+ # OUTPUT: { sort: "name desc" }
136
+
137
+ Product.cloudsearch.sort(name: :desc, price: :asc)
138
+ # OUTPUT: { sort: "name desc,price asc" }
139
+ ```
140
+
141
+ ###Return
142
+ ```
143
+ Product.cloudsearch.all_fields
144
+ # OUTPUT: { return: "_all_fields" }
145
+
146
+ Product.cloudsearch.no_fields
147
+ # OUTPUT: { return: "_no_fields" }
148
+
149
+ Product.cloudsearch.score
150
+ # OUTPUT: { return: "_score" }
151
+ ```
152
+
153
+ ###Scope
154
+ ```
155
+ Product.coudsearch.shoes_by_brand("puma")
156
+ # OUTPUT: { query:"shoes" filter_query:"(and manufacturer:'puma')" }
157
+
158
+ Product.cloudsearch.created_in(7)
159
+ # OUTPUT: { filter_query:"(and created_at:{Date.today - 7,}) }
160
+
161
+ Product.cloudsearch.or {
162
+ created_in(7)
163
+ discount 25..100
164
+ }
165
+ # OUTPUT: { filter_query:"(or (and created_at:{Date.today - 7,}) discount:{25,100] }
166
+ ```
167
+
168
+ ###AND & OR Block
169
+ ```
170
+ Product.cloudsearch.and { ... }
171
+ # OUTPUT: "(and ...)"
172
+
173
+ Product.cloudsearch.and {
174
+ or! { ... }
175
+ or! { ... }
176
+ }
177
+ # OUTPUT: "(and (or ...) (or ...))"
178
+ # NOTE: AND & OR are a ruby syntax, can not be used directly,
179
+ # so aliased them to (and!, all) and (or!, any)
180
+
181
+ Product.cloudsearch.and.not { ... }
182
+ # OUTPUT: "(not (and ...))"
183
+
184
+ Product.cloudsearch.or { and!.not { ...} }
185
+ # OUTPUT: "(or (not (and ...)))"
186
+ ```
187
+
188
+ ###Field Methods
189
+ * field can be set by calling #<field_name> *values
190
+ ```
191
+ Product.cloudsearch.name("shoes")
192
+ # OUTPUT: "name:'shoes'"
193
+
194
+ Product.cloudsearch.name "shoes", "sneaker"
195
+ # OUTPUT: name:'shoes' name:'sneaker'
196
+
197
+ Product.cloudsearch.name("shoes").price(100)
198
+ # OUTPUT: "(and name:'shoes' price:100)"
199
+
200
+ Product.cloudsearch.and { name "shoes"; price 25..100 }
201
+ # OUTPUT: "(and name:'shoes' price:[25,100])"
202
+ ```
203
+ * #not, #prefix (#start_with, #begin_with), #near can be chained after #<field_name> and takes multiple values
204
+ ```
205
+ Product.cloudsearch.and { name.not "shoes"; ... }
206
+ # OUTPUT: "(and (not name:'shoes') ...)"
207
+
208
+ Product.cloudsearch.and { name.start_with "shoes" }
209
+ # OUTPUT: "(and (prefix field='name' 'shoes'))"
210
+
211
+ Product.cloudsearch.and { name.near "shoes" }
212
+ # OUTPUT: "(and (near field='name' 'shoes'))"
213
+
214
+ Product.cloudsearch.and { name.not.start_with "shoes" }
215
+ # OUTPUT: "(and (not (near field='name' 'shoes')))"
216
+
217
+ Product.cloudsearch.and { name(start_with("shoes"), near("puma")).not("nike") }
218
+ # OUTPUT: "(and (prefix field='name' 'shoes') (near field='name' 'puma') (not name:'nike'))"
219
+ ```
220
+ * #prefix (#start_with, #begin_with), #near can be called directly to generate a single field value
221
+ ```
222
+ Product.cloudsearch.and { name.not start_with("shoes"), near("something") }
223
+ # OUTPUT: "(not (prefix field='name' 'shoes') (not (near field='name' 'something')))"
224
+ ```
225
+
226
+ ###Date, Time and Rage
227
+ * field method accepts Ruby Date and Range Object and will automatically parse them into the CloudSearch format
228
+ ```
229
+ Date.today => "'2016-01-18T00:00:00Z'"
230
+ Time.now => "'2016-01-18T14:36:57Z'"
231
+ 25..100 => "[25,100]"
232
+ 25...100 => "[25,100}"
233
+ ```
234
+ * use #range or #r for more complicated range, range object accepts Date/Time object as well
235
+ ```
236
+ r.gt(100) => "{100,}"
237
+ r.gte(100) => "[100,}"
238
+ r.lt(100) => "{,100}"
239
+ r.lte(100) => "{,100]"
240
+ r.gte(100).lt(200) => "[100,200}"
241
+ r.gt(Date.today) => "{'2016-01-18T00:00:00Z',}"
242
+ ```
243
+
244
+ ###Search Related Methods
245
+ * #search => send off the request, save and returns the response and clear the request
246
+ * #found => returns the hits found from the response
247
+ * #results => returns the hits.hit from the response
248
+ * #each => Calls the given block once for each result, passing that result as a parameter. Returns the results itself.
249
+ * #map => Creates a new array containing the results returned by the block.
250
+
251
+ ###Other Methods
252
+ * #
data/cloud_sesame.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'CloudSesame'
3
- s.version = '0.2.1'
3
+ s.version = '0.2.2'
4
4
  s.date = '2016-01-14'
5
5
  s.summary = "AWS CloudSearch Query Interface"
6
6
  s.description = "AWS CloudSearch Query Interface"
@@ -41,7 +41,7 @@ module CloudSesame
41
41
 
42
42
  def define_fuzziness(proc = nil, &block)
43
43
  block = proc unless block_given?
44
- context[:query, true][:fuzziness] = Query::Fuzziness.new(&block)
44
+ context[:query, true][:fuzziness] = Query::Node::Fuzziness.new(&block)
45
45
  end
46
46
 
47
47
  def default_scope(proc, &block)
@@ -36,7 +36,7 @@ module CloudSesame
36
36
  end
37
37
 
38
38
  def fuzziness(word)
39
- if word.length >= @min_char_size
39
+ if word.length >= @min_char_size && !excluding_term?(word)
40
40
  fuzziness = (word.length * @fuzzy_percent).round
41
41
  fuzziness = [fuzziness, @max_fuzziness].min
42
42
  "#{word}~#{fuzziness}"
@@ -49,6 +49,10 @@ module CloudSesame
49
49
  (args = args.flatten.compact).size > 1 ? "(#{ args.join('+') })" : args[0]
50
50
  end
51
51
 
52
+ def excluding_term?(word)
53
+ !!word.match(/^\-/)
54
+ end
55
+
52
56
  end
53
57
  end
54
58
  end
@@ -0,0 +1,60 @@
1
+ module CloudSesame
2
+ module Query
3
+ module Node
4
+ class Fuzziness
5
+
6
+ def initialize(&block)
7
+
8
+ # default fuzziness
9
+ @max_fuzziness = 3
10
+ @min_char_size = 6
11
+ @fuzzy_percent = 0.17
12
+
13
+ instance_eval &block if block_given?
14
+ end
15
+
16
+ def max_fuzziness(int)
17
+ @max_fuzziness = int.to_i
18
+ end
19
+
20
+ def min_char_size(int)
21
+ @min_char_size = int.to_i
22
+ end
23
+
24
+ def fuzzy_percent(float)
25
+ @fuzzy_percent = float.to_f
26
+ end
27
+
28
+ def parse(string)
29
+ result = each_with(string) { |word| fuzziness word }
30
+ join_by_and result
31
+ end
32
+
33
+ private
34
+
35
+ def each_with(string, &block)
36
+ string.split(' ').map &block
37
+ end
38
+
39
+ def fuzziness(word)
40
+ if word.length >= @min_char_size && !excluding_term?(word)
41
+ fuzziness = (word.length * @fuzzy_percent).round
42
+ fuzziness = [fuzziness, @max_fuzziness].min
43
+ "#{word}~#{fuzziness}"
44
+ else
45
+ word
46
+ end
47
+ end
48
+
49
+ def join_by_and(*args)
50
+ (args = args.flatten.compact).size > 1 ? "(#{ args.join('+') })" : args[0]
51
+ end
52
+
53
+ def excluding_term?(word)
54
+ !!word.match(/^\-/)
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+ end
@@ -16,11 +16,11 @@ module CloudSesame
16
16
  private
17
17
 
18
18
  def fuzziness
19
- context[:fuzziness] ? context[:fuzziness].parse(query) : nil
19
+ context[:fuzziness] && query && !query.empty? ? context[:fuzziness].parse(query) : nil
20
20
  end
21
21
 
22
22
  def sloppiness
23
- context[:sloppiness] && query.include?(' ') ? "\"#{ query }\"~#{ context[:sloppiness] }" : nil
23
+ context[:sloppiness] && query && query.include?(' ') ? "\"#{ query }\"~#{ context[:sloppiness] }" : nil
24
24
  end
25
25
 
26
26
  def join_by_or(*args)
@@ -57,7 +57,7 @@ module CloudSesame
57
57
 
58
58
  if compiled[:filter_query].empty?
59
59
  compiled.delete(:filter_query)
60
- elsif compiled[:query].empty?
60
+ elsif compiled[:query].nil? || compiled[:query].empty?
61
61
  convert_to_structured_query(compiled)
62
62
  end
63
63
 
@@ -67,7 +67,7 @@ module CloudSesame
67
67
  private
68
68
 
69
69
  def convert_to_structured_query(compiled)
70
- replace(compiled, :query, :filter_query).merge!(query_parser.structured.compile)
70
+ replace(compiled, :query, :filter_query).merge! query_parser.structured.compile
71
71
  end
72
72
 
73
73
  def replace(hash, key1, key2)
data/lib/cloud_sesame.rb CHANGED
@@ -31,6 +31,7 @@ require 'cloud_sesame/query/dsl/operator_methods'
31
31
  require 'cloud_sesame/query/dsl/scope_methods'
32
32
  require 'cloud_sesame/query/dsl/value_methods'
33
33
 
34
+
34
35
  # Query Query Filter Query AST Tree
35
36
  # ===============================================
36
37
  require 'cloud_sesame/query/ast/operator'
@@ -56,6 +57,7 @@ require 'cloud_sesame/query/ast/root'
56
57
  require 'cloud_sesame/query/node/abstract'
57
58
  require 'cloud_sesame/query/node/request'
58
59
  require 'cloud_sesame/query/node/query'
60
+ require 'cloud_sesame/query/node/fuzziness'
59
61
  require 'cloud_sesame/query/node/query_options'
60
62
  require 'cloud_sesame/query/node/query_options_field'
61
63
  require 'cloud_sesame/query/node/query_parser'
@@ -68,7 +70,6 @@ require 'cloud_sesame/query/node/return'
68
70
  # Query Builder Interface
69
71
  # ===============================================
70
72
  require 'cloud_sesame/query/builder'
71
- require 'cloud_sesame/query/fuzziness'
72
73
 
73
74
  # Domain Objects
74
75
  # ===============================================
@@ -47,7 +47,7 @@ describe CloudSesame do
47
47
  # field :category_string, facet: { sort: 'bucket', size: 10_000 }
48
48
  # field :created_at
49
49
 
50
- # scope :men_tag, ->(date) { created_at date }
50
+ # scope :shoes_by_brand, ->(brand = nil) { query("shoes").and { manufacturer_name brand } if brand }
51
51
  # scope :and_mens do
52
52
  # and! { tags "men"}
53
53
  # end
@@ -66,21 +66,25 @@ describe CloudSesame do
66
66
  # end
67
67
 
68
68
  # # Example Query
69
- # query = Product.cloudsearch.query("children panasonic")
70
- # .and {
71
- # or!(boost: 2) {
72
- # # tags.not start_with "flash_deal"
73
- # tags.not.start_with "flash_deal"
74
- # # tags.near"sales"
75
- # # tags start_with("flash_deal"), near("sales")
76
- # }
77
- # or!.not {
78
- # currency "CAD", "EUR"
79
- # }
80
- # and! {
81
- # price r.gte(100).lt(200)
82
- # }
83
- # }.created_at(Date.today).description("wasup")
69
+ # query = Product.cloudsearch.query("shoes").and {
70
+ # searchable_text(start_with("puma")).not("nike")
71
+ # }
72
+ # .created_at(Time.now).description("wasup")
73
+
74
+ # # .and {
75
+ # # or!(boost: 2) {
76
+ # # # tags.not start_with "flash_deal"
77
+ # # tags.not.start_with "flash_deal"
78
+ # # # tags.near"sales"
79
+ # # # tags start_with("flash_deal"), near("sales")
80
+ # # }
81
+ # # or!.not {
82
+ # # currency "CAD", "EUR"
83
+ # # }
84
+ # # and! {
85
+ # # price r.gte(100).lt(200)
86
+ # # }
87
+
84
88
 
85
89
  # binding.pry
86
90
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: CloudSesame
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chu
@@ -145,6 +145,7 @@ files:
145
145
  - lib/cloud_sesame/query/node/abstract.rb
146
146
  - lib/cloud_sesame/query/node/facet.rb
147
147
  - lib/cloud_sesame/query/node/filter_query.rb
148
+ - lib/cloud_sesame/query/node/fuzziness.rb
148
149
  - lib/cloud_sesame/query/node/page.rb
149
150
  - lib/cloud_sesame/query/node/query.rb
150
151
  - lib/cloud_sesame/query/node/query_options.rb