CloudSesame 0.2.1 → 0.2.2

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 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