rawscsi 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +13 -0
  4. data/Gemfile +5 -0
  5. data/LICENSE.txt +23 -0
  6. data/README.md +288 -0
  7. data/Rakefile +11 -0
  8. data/lib/rawscsi/base.rb +13 -0
  9. data/lib/rawscsi/index.rb +64 -0
  10. data/lib/rawscsi/index_helpers/connection.rb +23 -0
  11. data/lib/rawscsi/index_helpers/sdf_add.rb +47 -0
  12. data/lib/rawscsi/index_helpers/sdf_delete.rb +19 -0
  13. data/lib/rawscsi/query/compound.rb +114 -0
  14. data/lib/rawscsi/query/simple.rb +16 -0
  15. data/lib/rawscsi/search.rb +46 -0
  16. data/lib/rawscsi/search_helpers/results_active_record.rb +28 -0
  17. data/lib/rawscsi/search_helpers/results_hash.rb +14 -0
  18. data/lib/rawscsi/version.rb +3 -0
  19. data/lib/rawscsi.rb +46 -0
  20. data/rawscsi.gemspec +41 -0
  21. data/spec/fixtures/vcr/index_spec/delete.yml +38 -0
  22. data/spec/fixtures/vcr/index_spec/handle_batch_limit.yml +73 -0
  23. data/spec/fixtures/vcr/index_spec/upload_hash.yml +39 -0
  24. data/spec/fixtures/vcr/search_spec/and_diff_field.yml +32 -0
  25. data/spec/fixtures/vcr/search_spec/and_or_combo.yml +35 -0
  26. data/spec/fixtures/vcr/search_spec/and_same_field.yml +31 -0
  27. data/spec/fixtures/vcr/search_spec/date.yml +34 -0
  28. data/spec/fixtures/vcr/search_spec/disjunction.yml +44 -0
  29. data/spec/fixtures/vcr/search_spec/fields.yml +35 -0
  30. data/spec/fixtures/vcr/search_spec/limit_sort.yml +32 -0
  31. data/spec/fixtures/vcr/search_spec/no_result.yml +30 -0
  32. data/spec/fixtures/vcr/search_spec/numeric_range.yml +32 -0
  33. data/spec/fixtures/vcr/search_spec/simple_search.yml +153 -0
  34. data/spec/lib/rawscsi/index_helpers/connection_spec.rb +25 -0
  35. data/spec/lib/rawscsi/index_helpers/sdf_add_spec.rb +44 -0
  36. data/spec/lib/rawscsi/index_helpers/sdf_delete_spec.rb +11 -0
  37. data/spec/lib/rawscsi/index_spec.rb +62 -0
  38. data/spec/lib/rawscsi/query/compound_spec.rb +80 -0
  39. data/spec/lib/rawscsi/query/simple_spec.rb +11 -0
  40. data/spec/lib/rawscsi/search_spec.rb +170 -0
  41. data/spec/lib/rawscsi/version_spec.rb +8 -0
  42. data/spec/rawscsi_spec.rb +33 -0
  43. data/spec/spec_helper.rb +17 -0
  44. metadata +250 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 335008cad4298d74c3a008b6dc3d9e6ab0f52588
4
+ data.tar.gz: b1dae029204a1a1db586a9018f8e2d7c314d96f2
5
+ SHA512:
6
+ metadata.gz: 34f19cb0137d62bf6d5ded203ae279f3d885aa42b1e3536865aff772e84f70a925f23d2add0a4ecf3125747aa25b870a91eab97c2aa9c8a7b16cc8d300023d7e
7
+ data.tar.gz: e4d1196c9b3473289cd69c42ee586739f0fd317873f2492e7e8aaec59721a10cf08dfac551b6f2045d180d5b31b892856c46a071cab1c3aa2d22bab63e79290c
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - 2.1.2
7
+ branches:
8
+ only:
9
+ - master
10
+ env:
11
+ global:
12
+ - secure: "J2/UOjDgJNI9qZ0sEQUKDb3iLctyvsAPb3s+pSxDF0FVG5nPx3kDUU0p5yYzr7xcvxlpWCVyjKOYJdA7eM9mupml1muwOH5adcU0nIxBtd8zshRw8SIvyUpjL1q7CeABHyi2QpbvwbYLE8bRngWMk3SqRHrqLEIFUb6mpyfZsy8="
13
+
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rawscsi.gemspec
4
+ gem "codeclimate-test-reporter", :group => :test, :require => nil
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2014 Steven Li
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
data/README.md ADDED
@@ -0,0 +1,288 @@
1
+ # Rawscsi
2
+
3
+ [![Build Status](https://travis-ci.org/StevenJL/rawscsi.svg)](https://travis-ci.org/StevenJL/rawscsi)
4
+ [![Code Climate](https://codeclimate.com/github/StevenJL/rawscsi/badges/gpa.svg)](https://codeclimate.com/github/StevenJL/rawscsi)
5
+ [![Test Coverage](https://codeclimate.com/github/StevenJL/rawscsi/badges/coverage.svg)](https://codeclimate.com/github/StevenJL/rawscsi)
6
+
7
+ Ruby Amazon Web Services Cloud Search Interface (Rawscsi) is a flexible gem for searching and indexing AWS Cloud Search with the following characteristics:
8
+
9
+ 1) Maximal Flexibility: Rawscsi can construct very complex queries. For example, using Cloud Search's default movies search domain, Rawscsi can find the top three James Bond movies excluding 'Casino', staring either 'Connery', 'Craig', or 'Moore', but not 'Brosnan', released after 1970. This is how `rawscsi` would construct this query:
10
+
11
+ ```ruby
12
+ search_object.search(q: {
13
+ and: [
14
+ { plot: "James Bond" },
15
+ { not: {
16
+ title: "Casino"
17
+ },
18
+ },
19
+ { or: [
20
+ { actor: "Connery" },
21
+ { actor: "Craig" },
22
+ { actor: "Moore"}
23
+ ]
24
+ },
25
+ { not: {
26
+ actor: "Brosnan"
27
+ }
28
+ }]
29
+ },
30
+ date: {release_date: "[1970-01-01,}"},
31
+ sort: "rating desc",
32
+ limit: 3
33
+ )
34
+ ```
35
+ In theory, if the query is allowed on Cloudsearch, this gem can construct it.
36
+
37
+ 2) Supports Cloud Search Version api 2013-01-01.
38
+
39
+ 3) Smart indexing of data to a search domain. Namely, it calculates the size of your batch and breaks it up into chunks, each of which is less than 5Mb (the upload limit).
40
+
41
+ 4) Has some nice Active Record integration features.
42
+
43
+ 5) Supports Ruby 1.8.7, 1.9.3, 2.0.0, 2.1.0, and 2.1.2
44
+
45
+ Here's the official aws cloud search documentation: http://docs.aws.amazon.com/cloudsearch/latest/developerguide/what-is-cloudsearch.html
46
+
47
+ ## Installation
48
+
49
+ Add this line to your application's Gemfile:
50
+
51
+ gem 'rawscsi'
52
+
53
+ And then execute:
54
+
55
+ $ bundle
56
+
57
+ Or install it yourself as:
58
+
59
+ $ gem install rawscsi
60
+
61
+ ### Registering Search Domains
62
+
63
+ Suppose we have two search domains: Songs and Books. Let's say Song is an `active_record` model in our project. We first register them to `Rawscsi`.
64
+
65
+ ```ruby
66
+ Rawscsi.register 'Song' do |config|
67
+ config.domain_name = 'good-songs'
68
+ config.domain_id = 'akldfjakljf3894fjeaf9df'
69
+ config.region = 'us-east-1'
70
+ config.api_version = '2013-01-01' # rawscsi only supports api version 2013-01-01
71
+ config.attributes = [:title, :album, :artist, :release_date] # since Song is an active model, we can specify the attributes we want indexed
72
+ end
73
+
74
+ Rawscsi.register 'Book' do |config|
75
+ config.name = 'good-books'
76
+ config.id = 'dj43g6i77dof86lk34fsf2s'
77
+ config.region = 'us-east-1'
78
+ config.api_version = '2013-01-01' # rawscsi only supports api version 2013-01-01
79
+ end
80
+ ```
81
+
82
+ ### Uploading indices
83
+ ```ruby
84
+ song_indexer = Rawscsi::Index.new('Song', :active_record => true)
85
+ # Since Song is an active record object and we specified the attributes in the config, we can use the active record option
86
+ # Then we can just upload active record objects directly. Note this will use the active record id as the cloud search record id.
87
+ song_indexer.upload([
88
+ # active record objects
89
+ <id: 4567, title: "Saturdays Reprise", artist: "Cut Copy", album: "Bright Like Neon Love">
90
+ <id: 5456, title: "Common Burn", artist: "Mazzy Star", album: "Seasons of Your Day">
91
+ <id: 5346, title: "Honey Power", artist: "My Bloody Valentine">
92
+ <id: 5762, title: "Introduction", artist: "Nick Drake">
93
+ ])
94
+
95
+ book_indexer = Rawscsi::Index.new('Book')
96
+ # since Book is not an active record object, we upload hashes instead
97
+ # Note the id key in the hash will be used as the cloud search record id.
98
+ book_indexer.upload([
99
+ {id: 1234, title: "Surely you're Joking, Mr. Feynman", author: "Richard Feynman"},
100
+ {id: 5546, title: "Zen and the Art of Motorcycle Maintanence", author: "Robert Pirsig"}
101
+ ])
102
+ ```
103
+
104
+ ### Deleting indices
105
+ ```ruby
106
+ song_indexer.delete([3244, 53452, 5435, 64545, 34342, 4545])
107
+ # To delete records in the search domain, you just pass an array of ids (the cloud search record id)
108
+ ```
109
+
110
+ ##### Automatically Batches on cloud search's 5Mb Upload Limit
111
+ Rawscsi is smart enough to break up large batch uploads into chunks of 5 Mb (cloud search's upload size limit per post request).
112
+
113
+ ### Searching
114
+ Instantiate a search object
115
+
116
+ ```ruby
117
+ book_search_helper = Rawscsi::Search.new('Book')
118
+
119
+ song_search_helper = Rawscsi::Search.new('Song', :active_record => true)
120
+ # use the 'active record' option if 'Song' is a Active Record model in your project.
121
+ # Calling search will return an array of active record objects ordered by cloud search's rank score.
122
+ ```
123
+
124
+ #### Simple Search
125
+ Simple search queries take one string as an argument and searches all text and text-array fields on the search domain.
126
+
127
+ ```ruby
128
+ search_books_helper.search('Richard Feynman')
129
+ => [{id: 546, author: "Richard Feynman" title: "Surely, You're Joking Mr. Feynman" },
130
+ {id: 657, author: "Richard Feynman", title: "What Do You Care What Other People Think"}]
131
+
132
+ search_songs_helper.search('Air')
133
+ # since we initiated this search helper with the active record option
134
+ # it returns an array of active record objects
135
+ => [<Song id:156, artist: "White Stripes", title: "The Air Near My Fingers">,
136
+ <Song id:342, artist: "Air", title: "Mer du Japon">]
137
+ ```
138
+ #### Compound Boolean Searches
139
+ Compound search queries are queries consisting of multiple constraints, linked together by either `and` or `or` or some combination of the two. Rawscsi's philosophy in this aspect is maximal flexibility: Any query that is possible on cloud search, should be possible to construct in Rawscsi.
140
+
141
+ Lets look at some sample compound boolean queries.
142
+
143
+ ```ruby
144
+ search_songs_helper.search(q: {
145
+ and: [{artist: "Daft Punk"}]
146
+ }
147
+ )
148
+ # Even though we're searching only one term, it's not a simple query because we're looking
149
+ # at just the artist field, not all text fields. Also note, even though its only one constrainst, we still use 'and'.
150
+ ```
151
+
152
+ ```ruby
153
+ search_songs_helper.search(q: {
154
+ and: [{ title: 'Angel' }, { artist: 'Smith' }]
155
+ })
156
+ # Conjunction of two constraints is done using the `and` key
157
+ => [{song_id: 12345, title: "Angel in the Snow", artist: "Elliot Smith"},
158
+ {song_id: 43534, title: "Angel, Angel Down We Go Together", artist: "The Smiths"}]
159
+ ```
160
+ By default, the search returns all the fields in the domain. But you can specify which fields to return.
161
+
162
+ ```ruby
163
+ search_songs.search(q: {and: [ {artist: "Cold Play"} ],
164
+ fields: [:title])
165
+
166
+ => [{ title: "Warning Sign"},
167
+ { title: "God Put a Smile Upon Your Face"},
168
+ { title: "Sparks"}]
169
+
170
+ ```
171
+ The default sort order is Amazon Cloudsearch's rank-score but you can use your own sort as well as a limiting the number of results.
172
+ ```ruby
173
+ search_songs.search(q: {and: [{genres: "Rock"}]}, sort: "rating desc", limit: 100)
174
+ # Top 100 Rock songs.
175
+ ```
176
+
177
+ Here is an example of using a date constraint in conjunction ("and") with other constraints:
178
+ ```ruby
179
+ search_songs.search(q: {
180
+ and: [
181
+ { genres: "Hip Hop" },
182
+ { title: "Street"}
183
+ ]
184
+ },
185
+ date: { release_date: "[1995-01-01,}"}
186
+ limit: 5,
187
+ sort: "rating desc"
188
+ )
189
+ # Conjunction of two constraints and date constraint (Top 5 Hip Hop songs with Street in title released after 1995)
190
+ # Note date syntax is working in conjunction (and) with the frist two constraints. This is always the case.
191
+ # Also note syntax for searching date ranges: "[1995-01-01,}" is all dates after Jan 1 1995 while "{,1995-01-01]" is all dates before.
192
+ # Note we're limiting only 5 results and sorting by rating in descending order.
193
+ => [{song_id: 54642, title: "Street Dreams", artist: "Nas"},
194
+ {song_id: 98786, title: "Street Struck", artist: "Big L"},
195
+ {song_id: 54645, title: "Street Disputes", artist: "Wu-Tang Clan"},
196
+ {song_id: 54542, title: "Streets is Watching", artist: "Jay-Z"},
197
+ {song_id: 54644, title: "Street Corners", artist: "Wu-Tang Clan"}]
198
+ ```
199
+
200
+ So far, we've only seen 'and' examples. Now let's look at some 'or' examples.
201
+
202
+ ```ruby
203
+ search_songs.search(q: {
204
+ or: [
205
+ { artist: "Digitalism"},
206
+ { artist: "Daft Punk"},
207
+ { artist: "Justice"}
208
+ ]
209
+ }
210
+ )
211
+ # Find the union of all the songs by these awesome electro-house bands
212
+ ```
213
+ ```ruby
214
+ # you can also combine `and` and `or` constraints
215
+ search_songs.search(q: {
216
+ and: [
217
+ { range: "rating:['9',}"},
218
+ { or: [
219
+ { artist: "Bob Dylan" },
220
+ { artist: "Lorde"}
221
+ ]
222
+ }
223
+ ]
224
+ }
225
+ )
226
+ # You only want highly rated songs by either Bob Dylan or Lorde (cuz you know, Lorde is the new Bob Dylan)
227
+ ```
228
+
229
+ And of course, negation:
230
+
231
+ ```ruby
232
+ # You love the song "All Along the Watchtower" but you didn't like the Dave Matthews Band cover
233
+ search_songs.search(q: {
234
+ and: [
235
+ { title: "All Along the Watchtower"},
236
+ not: {
237
+ artist: "Dave Matthews Band"
238
+ }
239
+ ]
240
+ }
241
+ )
242
+ ```
243
+
244
+ By default, the search returns all the fields in the domain. But you can specify which fields to return.
245
+
246
+ ```ruby
247
+ search_songs.search(q: {and: [ {artist: "Cold Play"} ],
248
+ fields: [:title])
249
+
250
+ => [{ title: "Warning Sign"},
251
+ { title: "God Put a Smile Upon Your Face"},
252
+ { title: "Sparks"}]
253
+
254
+ ```
255
+
256
+ ## Contributing
257
+
258
+ 1. Fork it
259
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
260
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
261
+ 4. Push to the branch (`git push origin my-new-feature`)
262
+ 5. Create new Pull Request
263
+
264
+ ## License
265
+
266
+ (The MIT License)
267
+
268
+ Copyright © 2014 Steven Li
269
+
270
+ Permission is hereby granted, free of charge, to any person obtaining
271
+ a copy of this software and associated documentation files (the
272
+ ‘Software’), to deal in the Software without restriction, including
273
+ without limitation the rights to use, copy, modify, merge, publish,
274
+ distribute, sublicense, and/or sell copies of the Software, and to
275
+ permit persons to whom the Software is furnished to do so, subject to
276
+ the following conditions:
277
+
278
+ The above copyright notice and this permission notice shall be
279
+ included in all copies or substantial portions of the Software.
280
+
281
+ THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND,
282
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
283
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
284
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
285
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
286
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
287
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
288
+
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ task :default => :spec
5
+
6
+ RSpec::Core::RakeTask.new
7
+
8
+ namespace :spec do
9
+ Rake::Task["spec"].invoke
10
+ end
11
+
@@ -0,0 +1,13 @@
1
+ module Rawscsi
2
+ class Base
3
+ attr_accessor :model
4
+ attr_reader :config, :is_active_record
5
+
6
+ def initialize(model_name, options={})
7
+ @is_active_record = options[:active_record]
8
+ @model = model_name
9
+ @config = Rawscsi.registered_models[model_name]
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,64 @@
1
+ require "json"
2
+
3
+ module Rawscsi
4
+ class Index < Rawscsi::Base
5
+ def upload(obj_array)
6
+ batch_size = 0
7
+ current_batch = []
8
+
9
+ obj_array.each do |obj|
10
+ sdf = format_to_sdf(obj)
11
+ size = sdf.to_json.bytesize
12
+ if (batch_size + size) < max_size
13
+ current_batch << sdf
14
+ batch_size = batch_size + size
15
+ else
16
+ post_to_amazon(current_batch)
17
+ current_batch = []
18
+ batch_size = 0
19
+ end
20
+ end
21
+ post_to_amazon(current_batch)
22
+ end
23
+
24
+ def delete(id_array)
25
+ if id_array.length < 20000
26
+ delete_from_amazon(id_array)
27
+ else
28
+ id_array.slice(20000) do |sub_array|
29
+ delete_from_amazon(sub_array)
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+ def max_size
36
+ 5240000 # bytes
37
+ end
38
+
39
+ def format_to_sdf(obj)
40
+ Rawscsi::IndexHelpers::SdfAdd.new(obj, config.attributes).build
41
+ end
42
+
43
+ def delete_from_amazon(id_array)
44
+ sdf_del_array = id_array.map do |id|
45
+ Rawscsi::IndexHelpers::SdfDelete.new(id).build
46
+ end
47
+ post_to_amazon(sdf_del_array)
48
+ end
49
+
50
+ def post_to_amazon(payload)
51
+ resp = connection.post do |req|
52
+ req.url "#{config.api_version}/documents/batch"
53
+ req.headers["Content-type"] = "application/json"
54
+ req.body = payload.to_json
55
+ end
56
+ resp.body
57
+ end
58
+
59
+ def connection
60
+ @connection ||= Rawscsi::IndexHelpers::Connection.new(config).build
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,23 @@
1
+ require "faraday"
2
+ require "faraday_middleware"
3
+
4
+ module Rawscsi
5
+ module IndexHelpers
6
+ class Connection
7
+ attr_reader :url
8
+
9
+ def initialize(config)
10
+ @url= "http://doc-#{config.domain_name}-#{config.domain_id}.#{config.region}.cloudsearch.amazonaws.com"
11
+ end
12
+
13
+ def build
14
+ connection = Faraday.new url do |builder|
15
+ builder.use FaradayMiddleware::EncodeJson
16
+ builder.adapter Faraday.default_adapter
17
+ end
18
+ connection
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,47 @@
1
+ module Rawscsi
2
+ module IndexHelpers
3
+ class SdfAdd
4
+ attr_reader :doc, :attributes
5
+
6
+ def initialize(doc, attributes=nil)
7
+ @doc = doc
8
+ @attributes = attributes || doc.keys
9
+ end
10
+
11
+ def build
12
+ {
13
+ :id => doc_id,
14
+ :type => "add",
15
+ :fields => fields
16
+ }
17
+ end
18
+
19
+ private
20
+ def doc_id
21
+ if doc.is_a?(Hash)
22
+ doc[:id]
23
+ else
24
+ "#{doc.class}_#{doc.id}"
25
+ end
26
+ end
27
+
28
+ def fields
29
+ output = {}
30
+ attributes.each do |attr|
31
+ next if attr == :id
32
+ output[attr] = get_attr(doc, attr)
33
+ end
34
+ output
35
+ end
36
+
37
+ def get_attr(doc, attr)
38
+ if doc.is_a?(Hash)
39
+ doc[attr]
40
+ else
41
+ doc.send(attr)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,19 @@
1
+ module Rawscsi
2
+ module IndexHelpers
3
+ class SdfDelete
4
+ attr_reader :id
5
+
6
+ def initialize(id)
7
+ @id = id
8
+ end
9
+
10
+ def build
11
+ {
12
+ :type => "delete",
13
+ :id => id
14
+ }
15
+ end
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,114 @@
1
+ module Rawscsi
2
+ module Query
3
+ class Compound
4
+ attr_reader :query_hash
5
+ def initialize(query_hash)
6
+ @query_hash = query_hash
7
+ end
8
+
9
+ def build
10
+ [
11
+ query,
12
+ date,
13
+ sort,
14
+ limit,
15
+ fields,
16
+ "q.parser=structured"
17
+ ].compact.join("&")
18
+ end
19
+
20
+ private
21
+ def query
22
+ "q=" + compound_bool(query_hash[:q])
23
+ end
24
+
25
+ def date
26
+ return nil unless date_hash = query_hash[:date]
27
+ output_str = "fq="
28
+ date_hash.each do |k,v|
29
+ output_str << "#{k}:#{FRC3339(v)}"
30
+ end
31
+ encode(output_str)
32
+ end
33
+
34
+ def FRC3339(date_str)
35
+ return date_str if /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/ =~ date_str
36
+ date_str.gsub(/\d{4}-\d{2}-\d{2}/) do |dt|
37
+ "#{dt}T00:00:00Z"
38
+ end
39
+ end
40
+
41
+ def sort
42
+ return nil unless query_hash[:sort]
43
+ encode("sort=#{query_hash[:sort]}")
44
+ end
45
+
46
+ def limit
47
+ return nil unless query_hash[:limit]
48
+ "size=#{query_hash[:limit]}"
49
+ end
50
+
51
+ def fields
52
+ return nil unless fields_array = query_hash[:fields]
53
+ output = []
54
+ fields_array.each do |field_sym|
55
+ output << field_sym.to_s
56
+ end
57
+ "return=" + output.join(",")
58
+ end
59
+
60
+ def compound_bool(hash)
61
+ if compound?(hash)
62
+ stringify_compound(hash)
63
+ else
64
+ stringify_noncompound(hash)
65
+ end
66
+ end
67
+
68
+ def compound?(hash)
69
+ ar = hash.keys
70
+ ar.include?(:and) || ar.include?(:or)
71
+ end
72
+
73
+ def stringify_compound(hash)
74
+ bool_op = hash.keys.first
75
+ ar = hash[bool_op]
76
+ "(#{bool_op}" + encode(" #{bool_map(ar)}") + ")"
77
+ end
78
+
79
+ def stringify_noncompound(hash)
80
+ if not_hash = hash[:not]
81
+ "(not" + encode(" #{stringify(not_hash)}") + ")"
82
+ elsif range = hash[:range]
83
+ range
84
+ else
85
+ encode(stringify(hash))
86
+ end
87
+ end
88
+
89
+ def bool_map(array)
90
+ output = ""
91
+ array.each do |pred|
92
+ output << compound_bool(pred)
93
+ end
94
+ output
95
+ end
96
+
97
+ def stringify(hash)
98
+ output_str = ""
99
+ hash.each do |k,v|
100
+ output_str << "#{k}:'#{v}'"
101
+ end
102
+ output_str
103
+ end
104
+
105
+ def encode(str)
106
+ # URI and CGI.escape don't quite work here
107
+ # For example, I need blank space as %20, but they encode it as +
108
+ # So I have to write my own
109
+ str.gsub(' ', '%20').gsub("'", '%27').gsub("[", '%5B').gsub("]",'%5D').gsub("{", '%7B').gsub("}", '%7D')
110
+ end
111
+ end
112
+ end
113
+ end
114
+
@@ -0,0 +1,16 @@
1
+ require "cgi"
2
+
3
+ module Rawscsi
4
+ module Query
5
+ class Simple
6
+ def initialize(query_string)
7
+ @query_string = query_string
8
+ end
9
+
10
+ def build
11
+ "q=#{CGI.escape(@query_string.to_s)}"
12
+ end
13
+ end
14
+ end
15
+ end
16
+