gsa 0.5.0

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/Gemfile +2 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +3 -0
  6. data/Rakefile +1 -0
  7. data/fixtures/cassette_library/delete_many_records.yml +44 -0
  8. data/fixtures/cassette_library/delete_single_record.yml +44 -0
  9. data/fixtures/cassette_library/many_records.yml +44 -0
  10. data/fixtures/cassette_library/many_results_no_filters.yml +90 -0
  11. data/fixtures/cassette_library/many_results_with_filters.yml +134 -0
  12. data/fixtures/cassette_library/no_result_no_filters.yml +55 -0
  13. data/fixtures/cassette_library/no_result_with_filters.yml +57 -0
  14. data/fixtures/cassette_library/single_record.yml +44 -0
  15. data/fixtures/cassette_library/single_result_no_filters.yml +483 -0
  16. data/fixtures/cassette_library/single_result_with_filters.yml +185 -0
  17. data/fixtures/facets/many.txt +1 -0
  18. data/fixtures/facets/single.txt +1 -0
  19. data/fixtures/feeds/15_records.txt +17 -0
  20. data/fixtures/feeds/1_records.txt +1 -0
  21. data/fixtures/queries/many.txt +1 -0
  22. data/fixtures/queries/none.txt +1 -0
  23. data/fixtures/queries/single.txt +1 -0
  24. data/fixtures/result_sets/many.txt +1 -0
  25. data/fixtures/result_sets/many_facets.txt +1 -0
  26. data/fixtures/result_sets/single.txt +1 -0
  27. data/fixtures/result_sets/single_facets.txt +1 -0
  28. data/fixtures/uids/many.txt +1 -0
  29. data/fixtures/uids/single.txt +1 -0
  30. data/gsa.gemspec +39 -0
  31. data/lib/defaults.rb +41 -0
  32. data/lib/facade.rb +48 -0
  33. data/lib/gsa.rb +27 -0
  34. data/lib/gsa/exceptions.rb +3 -0
  35. data/lib/gsa/faceter.rb +41 -0
  36. data/lib/gsa/feeder.rb +19 -0
  37. data/lib/gsa/modules/filer.rb +26 -0
  38. data/lib/gsa/modules/injector.rb +16 -0
  39. data/lib/gsa/modules/xmlizer.rb +24 -0
  40. data/lib/gsa/records_converter.rb +61 -0
  41. data/lib/gsa/search_converter.rb +41 -0
  42. data/lib/gsa/searcher.rb +32 -0
  43. data/lib/gsa/uid_extractor.rb +24 -0
  44. data/lib/gsa/version.rb +3 -0
  45. data/spec/fixtures.rb +73 -0
  46. data/spec/lib/gsa_spec.rb +318 -0
  47. data/spec/spec_helper.rb +15 -0
  48. metadata +209 -0
@@ -0,0 +1,19 @@
1
+ module GSA
2
+ class Feeder
3
+ extend Filer
4
+
5
+ def self.feed(file_name, datasource_name)
6
+
7
+ file = open_file(file_name)
8
+
9
+ RestClient.post(
10
+ "#{GSA.base_uri}#{GSA::FEED_EXTENSION}",
11
+ {
12
+ :feedtype => GSA::FEED_TYPE,
13
+ :datasource => datasource_name,
14
+ :data => file.read
15
+ }
16
+ )
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module GSA
2
+ module Filer
3
+
4
+ def build_xml_file(file_name, contents)
5
+ close_file(
6
+ add_content_to_file(new_file(file_name), contents)
7
+ ).path
8
+ end
9
+
10
+ def new_file(file_name)
11
+ File.new("#{file_name}.xml", "w")
12
+ end
13
+
14
+ def open_file(file_name)
15
+ File.open(file_name)
16
+ end
17
+
18
+ def add_content_to_file(file, content)
19
+ file.puts(content); file
20
+ end
21
+
22
+ def close_file(file)
23
+ file.close; file
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module GSA
2
+ module Injector
3
+
4
+ def inject_s(items, &block)
5
+ inject("", items, &block)
6
+ end
7
+
8
+ def inject_a(items, &block)
9
+ inject([], items, &block)
10
+ end
11
+
12
+ def inject(injected, items, &block)
13
+ items.each.inject(injected) {|result, item| result << block.call(item)}
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ module GSA
2
+ module XMLizer
3
+ include Injector
4
+
5
+ def xml(content)
6
+ GSA::XML_TYPE << "\n" << GSA::DOC_TYPE << "\n" << content
7
+ end
8
+
9
+ # for single-line tags
10
+ def tag(tag, attributes={})
11
+ "<#{tag.to_s}#{attributor(attributes)}/>\n"
12
+ end
13
+
14
+ # for container tags
15
+ def block(tag, value, attributes={})
16
+ "<#{tag.to_s}#{attributor(attributes)}>\n" << value.to_s << "</#{tag.to_s}>\n"
17
+ end
18
+
19
+ # for creating attributes on tags
20
+ def attributor(attributes)
21
+ inject_s(attributes) {|key, value| " #{key.to_s}='#{value.to_s}'"}
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,61 @@
1
+ module GSA
2
+ class RecordsConverter
3
+ extend XMLizer
4
+ extend Injector
5
+ extend Filer
6
+
7
+ def self.convert(args)
8
+ records = args[:records]
9
+ file_name = args[:file_name]
10
+ searchable = args[:searchable]
11
+ datasource_name = args[:datasource_name]
12
+ datasource_uri = args[:datasource_uri]
13
+ datasource_uid = args[:datasource_uid]
14
+ action = args[:delete?] ? GSA::DELETE_ACTION : GSA::ADD_ACTION
15
+
16
+ # if there is only one record, convert to the expected array.
17
+ records = [records] if records.is_a? Hash
18
+
19
+ build_xml_file(file_name, layout(records, searchable, datasource_name, datasource_uri, datasource_uid, action))
20
+ end
21
+
22
+ #######
23
+ private
24
+ #######
25
+
26
+ def self.layout(records, searchable, datasource_name, datasource_uri, datasource_uid, action)
27
+ xml(
28
+ block(:gsafeed,
29
+ block(:header,
30
+ block(:datasource, datasource_name) <<
31
+ block(:feedtype, GSA::FEED_TYPE)
32
+ ) <<
33
+ block(:group,
34
+ record_blocks(records, searchable, datasource_uri, datasource_uid),
35
+ {:action => action}
36
+ )
37
+ )
38
+ )
39
+ end
40
+
41
+ def self.record_blocks(records, searchable, datasource_uri, datasource_uid)
42
+ inject_s(records) {|record| record_block(record, searchable, datasource_uri, datasource_uid)}
43
+ end
44
+
45
+ def self.record_block(record, searchable, datasource_uri, datasource_uid)
46
+ block(:record,
47
+ block(:metadata, record_metadata(record)) <<
48
+ block(:content, record_searchable(record, searchable)),
49
+ {:url => "#{datasource_uri}/#{record[datasource_uid]}", :mimetype => GSA::MIME_TYPE} # ABSTRACT URL STRING VALUE (id)
50
+ )
51
+ end
52
+
53
+ def self.record_searchable(record, searchable)
54
+ inject_s(searchable) {|search| " #{record[search.to_s]}"}
55
+ end
56
+
57
+ def self.record_metadata(record)
58
+ inject_s(record) {|key, value| tag(:meta, {:name => key, :content => value})}
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,41 @@
1
+ module GSA
2
+ class SearchConverter
3
+ extend Injector
4
+
5
+ def self.convert(convertable)
6
+ parse result_sets_from results_param_in convertable
7
+ end
8
+
9
+ def self.results_param_in(convertable)
10
+ convertable[GSA::GOOGLE_SEARCH_PROTOCOL][GSA::RESULTS]
11
+ end
12
+
13
+ def self.result_sets_from(results)
14
+ (results.keep_if {|key, value| key == GSA::RESULT })[GSA::RESULT]
15
+ end
16
+
17
+ def self.parse(result_sets)
18
+
19
+ # if there is only one result, convert to the expected array.
20
+ result_sets = [result_sets] if result_sets.is_a? Hash
21
+
22
+ inject_a(result_sets) {|set|
23
+ {
24
+ :result_number => set[GSA::RESULT_NUMBER],
25
+ :url => set[GSA::URL],
26
+ :title => set[GSA::TITLE],
27
+ :metatags => parse_metatags(set[GSA::METATAGS]),
28
+ :search_result_snippet => set[GSA::SEARCH_RESULT_SNIPPET]
29
+ }
30
+ }
31
+ end
32
+
33
+ def self.parse_metatags(metatags)
34
+ inject_a(metatags) {|meta_pair| convert_meta_pair(meta_pair) }
35
+ end
36
+
37
+ def self.convert_meta_pair(meta_pair)
38
+ {:meta_name => meta_pair[GSA::META_NAME], :meta_value => meta_pair[GSA::META_VALUE]}
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,32 @@
1
+ module GSA
2
+ class Searcher
3
+ def self.search(query, args={})
4
+ query = clean_query(query)
5
+ filter = args[:filter] || GSA::DEFAULT_FILTER
6
+ getfields = args[:getfields] || GSA::DEFAULT_GETFIELDS
7
+ sort = args[:sort] || GSA::DEFAULT_SORT
8
+ num = args[:num] || GSA::DEFAULT_NUM
9
+ output = args[:output] || GSA::DEFAULT_OUTPUT
10
+ requiredfields = args[:filters] || nil
11
+
12
+ search = "#{GSA.base_uri}/search?q=#{query}&filter=#{filter}&getfields=#{getfields}&sort=#{sort}&num=#{num}&output=#{output}"
13
+ search = "#{search}&requiredfields=#{clean_query(requiredfields)}" if requiredfields
14
+
15
+ search_results = parsed_json( RestClient.get(search) )
16
+
17
+ results_found?(search_results) ? search_results : GSA::NO_RESULTS
18
+ end
19
+
20
+ def self.clean_query(query)
21
+ query.tr(' ', '+')
22
+ end
23
+
24
+ def self.parsed_json(xml)
25
+ JSON.parse(Hash.from_xml(xml).to_json)
26
+ end
27
+
28
+ def self.results_found?(search_results)
29
+ search_results[GSA::GOOGLE_SEARCH_PROTOCOL].has_key? GSA::RESULTS
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ module GSA
2
+ class UIDExtractor
3
+ def self.extract(search_results, uid)
4
+ iterate_results([], search_results, uid)
5
+ end
6
+
7
+ def self.iterate_results(uids, search_results, uid)
8
+ search_results.each {|result| get_uids_from_record(uids, result, uid)}
9
+ uids
10
+ end
11
+
12
+ def self.get_uids_from_record(uids, record, uid)
13
+ record[:metatags].each {|tag| add_uid(uids, tag) if uid?(tag, uid)}
14
+ end
15
+
16
+ def self.add_uid(uids, meta_tag)
17
+ uids << meta_tag[:meta_value]
18
+ end
19
+
20
+ def self.uid?(tag, uid)
21
+ tag[:meta_name] == uid
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module GSA
2
+ VERSION = "0.5.0"
3
+ end
@@ -0,0 +1,73 @@
1
+ module Fixtures
2
+ def many_records
3
+ read 'fixtures/feeds/15_records.txt'
4
+ end
5
+
6
+ def one_records
7
+ read 'fixtures/feeds/1_records.txt'
8
+ end
9
+
10
+ def many_query
11
+ read 'fixtures/queries/many.txt'
12
+ end
13
+
14
+ def one_query
15
+ read 'fixtures/queries/single.txt'
16
+ end
17
+
18
+ def none_query
19
+ read 'fixtures/queries/none.txt'
20
+ end
21
+
22
+ def many_results
23
+ read 'fixtures/result_sets/many.txt'
24
+ end
25
+
26
+ def one_results
27
+ read 'fixtures/result_sets/single.txt'
28
+ end
29
+
30
+ def many_facets
31
+ read 'fixtures/facets/many.txt'
32
+ end
33
+
34
+ def one_facets
35
+ read 'fixtures/facets/single.txt'
36
+ end
37
+
38
+ def many_facet_results
39
+ read 'fixtures/result_sets/many_facets.txt'
40
+ end
41
+
42
+ def one_facet_results
43
+ read 'fixtures/result_sets/single_facets.txt'
44
+ end
45
+
46
+ def many_uids
47
+ read 'fixtures/uids/many.txt'
48
+ end
49
+
50
+ def one_uids
51
+ read 'fixtures/uids/single.txt'
52
+ end
53
+
54
+ def success_text
55
+ "Success"
56
+ end
57
+
58
+ def uid
59
+ "id"
60
+ end
61
+
62
+ def gsa_base_uri
63
+ "http://dev-gsa.1000bulbs.com"
64
+ end
65
+
66
+ #######
67
+ private
68
+ #######
69
+
70
+ def read(file_path)
71
+ eval(File.read(file_path))
72
+ end
73
+ end
@@ -0,0 +1,318 @@
1
+ require 'spec_helper'
2
+
3
+ describe GSA do
4
+ include Fixtures
5
+
6
+ before(:each) do
7
+ GSA.base_uri = gsa_base_uri
8
+ end
9
+
10
+ describe "#feed" do
11
+
12
+ context "add" do
13
+
14
+ context "many records" do
15
+
16
+ it "successfully adds the records to the gsa index" do
17
+ VCR.use_cassette("many_records") do
18
+ results = GSA.feed(
19
+ :file_name => "out", :records => many_records,
20
+ :searchable => [:name, :description],
21
+ :datasource_name => "products",
22
+ :datasource_uri => "http://0.0.0.0:3000/products",
23
+ :datasource_uid => "id"
24
+ )
25
+ results.should eq success_text
26
+ end
27
+ end
28
+ end
29
+
30
+ context "a single record" do
31
+
32
+ it "successfully adds the records to the gsa index" do
33
+ VCR.use_cassette("single_record") do
34
+ results = GSA.feed(
35
+ :file_name => "out", :records => many_records,
36
+ :searchable => [:name, :description],
37
+ :datasource_name => "products",
38
+ :datasource_uri => "http://0.0.0.0:3000/products",
39
+ :datasource_uid => "id"
40
+ )
41
+ results.should eq success_text
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ context "delete" do
48
+
49
+ context "many records" do
50
+
51
+ it "successfully deletes the records from the gsa index" do
52
+ VCR.use_cassette("delete_many_records") do
53
+ results = GSA.feed(
54
+ :file_name => "out", :records => many_records,
55
+ :searchable => [:name, :description],
56
+ :datasource_name => "products",
57
+ :datasource_uri => "http://0.0.0.0:3000/products",
58
+ :datasource_uid => "id",
59
+ :delete? => true
60
+ )
61
+ results.should eq success_text
62
+ end
63
+ end
64
+ end
65
+
66
+ context "a single record" do
67
+
68
+ it "successfully deletes the record from the gsa index" do
69
+ VCR.use_cassette("delete_single_record") do
70
+ results = GSA.feed(
71
+ :file_name => "out",
72
+ :records => one_records,
73
+ :searchable => [:name, :description],
74
+ :datasource_name => "products",
75
+ :datasource_uri => "http://0.0.0.0:3000/products",
76
+ :datasource_uid => "id",
77
+ :delete? => true
78
+ )
79
+ results.should eq success_text
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ context "without a base_uri set" do
86
+
87
+ before(:each) do
88
+ GSA.base_uri = nil
89
+ end
90
+
91
+ it "raises an error" do
92
+ expect {
93
+
94
+ GSA.feed(
95
+ :file_name => "out",
96
+ :records => one_records,
97
+ :searchable => [:name, :description],
98
+ :datasource_name => "products",
99
+ :datasource_uri => "http://0.0.0.0:3000/products",
100
+ :datasource_uid => "id",
101
+ :delete? => true
102
+ )
103
+
104
+ }.to raise_error GSA::URINotSetError
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "#search" do
110
+
111
+ context "with no filters" do
112
+
113
+ context "with a query yielding many matches" do
114
+
115
+ let(:query) { many_query }
116
+ let(:results_set) { many_results }
117
+
118
+ it "returns many records" do
119
+ VCR.use_cassette("many_results_no_filters") do
120
+ results = GSA.search(query)
121
+ results.count.should eq results_set.count
122
+ end
123
+ end
124
+
125
+ it "returns results in the expected 'pretty' format" do
126
+ VCR.use_cassette("many_results_no_filters") do
127
+ results = GSA.search(query)
128
+ results.should eq results_set
129
+ end
130
+ end
131
+ end
132
+
133
+ context "with a query yielding a single match" do
134
+
135
+ let(:query) { one_query }
136
+ let(:result_set) { one_results }
137
+
138
+ it "returns a single record" do
139
+ VCR.use_cassette("single_result_no_filters") do
140
+ results = GSA.search(query)
141
+ results.count.should eq 1
142
+ end
143
+ end
144
+
145
+ it "returns the single result in the expected 'pretty' format" do
146
+ VCR.use_cassette("single_result_no_filters") do
147
+ results = GSA.search(query)
148
+ results.should eq result_set
149
+ end
150
+ end
151
+ end
152
+
153
+ context "with a query yielding no matches" do
154
+
155
+ let(:query) { none_query }
156
+
157
+ it "returns the no record flag" do
158
+ VCR.use_cassette("no_result_no_filters") do
159
+ results = GSA.search(query)
160
+ results.should eq GSA::NO_RESULTS
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ context "with filters" do
167
+
168
+ context "with a query yielding many results" do
169
+
170
+ let(:query) { many_query }
171
+ let(:results_set) { many_results }
172
+ let(:filter_name) { "brand" }
173
+ let(:filter_value) { "Philips" }
174
+ let(:filters) { "#{filter_name}:#{filter_value}" }
175
+
176
+ it "returns less than the unfiltered results" do
177
+ VCR.use_cassette("many_results_with_filters") do
178
+ results = GSA.search(query, :filters => filters)
179
+ results.count.should be < results_set.count
180
+ end
181
+ end
182
+
183
+ it "should only contain results matched in the unfiltered results" do
184
+ VCR.use_cassette("many_results_with_filters") do
185
+ results = GSA.search(query, :filters => filters)
186
+
187
+ filtered_results = []
188
+ results_set.each {|result|
189
+ result[:metatags].each {|tag|
190
+ if tag[:meta_name] == filter_name && tag[:meta_value] == filter_value
191
+ filtered_results << result
192
+ end
193
+ }
194
+ }
195
+
196
+ result_uids = GSA.uids(results, uid)
197
+ filtered_uids = GSA.uids(filtered_results, uid)
198
+ result_uids.should eq filtered_uids
199
+ end
200
+ end
201
+ end
202
+
203
+ context "with a query yielding a single result" do
204
+
205
+ let(:query) { many_query }
206
+ let(:results_set) { many_results }
207
+ let(:filter_1_name) { "brand" }
208
+ let(:filter_1_value) { "Philips" }
209
+ let(:filter_2_name) { "material" }
210
+ let(:filter_2_value) { "Brass" }
211
+ let(:filters) { "#{filter_1_name}:#{filter_1_value}.#{filter_2_name}:#{filter_2_value}" }
212
+
213
+ it "returns a single filtered result" do
214
+ VCR.use_cassette("single_result_with_filters") do
215
+ results = GSA.search(query, :filters => filters)
216
+ results.count.should eq 1
217
+ end
218
+ end
219
+
220
+ it "returns a result with the expected matching filters" do
221
+ VCR.use_cassette("single_result_with_filters") do
222
+ results = GSA.search(query, :filters => filters)
223
+
224
+ results.each.inject([]) {|flags, result| result[:metatags].each {|tag|
225
+ flags << 1 if tag[:meta_name] == filter_1_name && tag[:meta_value] == filter_1_value
226
+ flags << 1 if tag[:meta_name] == filter_2_name && tag[:meta_value] == filter_2_value
227
+ }
228
+ flags
229
+ }.should eq [1, 1]
230
+ end
231
+ end
232
+ end
233
+
234
+ context "with a query yielding no results" do
235
+
236
+ let(:query) { many_query }
237
+ let(:filters) { "brand:FooBar" }
238
+
239
+ it "returns the no record flag" do
240
+ VCR.use_cassette("no_result_with_filters") do
241
+ results = GSA.search(query, :filters => filters)
242
+ results.should eq GSA::NO_RESULTS
243
+ end
244
+ end
245
+ end
246
+
247
+ context "without a base_uri set" do
248
+
249
+ let(:query) { many_query }
250
+ let(:filters) { "brand:Philips" }
251
+
252
+ before(:each) do
253
+ GSA.base_uri = nil
254
+ end
255
+
256
+ it "raises an error" do
257
+ expect {
258
+
259
+ GSA.search(query, :filters => filters)
260
+
261
+ }.to raise_error GSA::URINotSetError
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ describe "#facet" do
268
+
269
+ let(:results_set) { many_results }
270
+
271
+ context "with multiple facets" do
272
+
273
+ let(:facetables) { many_facets }
274
+ let(:facet_results) { many_facet_results }
275
+
276
+ it "returns multiple facets in the expected form" do
277
+ results = GSA.facet(results_set, facetables)
278
+ results.should eq facet_results
279
+ end
280
+ end
281
+
282
+ context "with a single facet" do
283
+
284
+ let(:facetables) { one_facets }
285
+ let(:facet_results) { one_facet_results }
286
+
287
+ it "returns a single facet in the expected form" do
288
+ results = GSA.facet(results_set, facetables)
289
+ results.should eq facet_results
290
+ end
291
+ end
292
+ end
293
+
294
+ describe "#uids" do
295
+
296
+ context "with multiple records passed in" do
297
+
298
+ let(:results_set) { many_results }
299
+ let(:uids) { many_uids }
300
+
301
+ it "returns multiple uids" do
302
+ results = GSA.uids(results_set, uid)
303
+ results.should eq uids
304
+ end
305
+ end
306
+
307
+ context "with a single record passed in" do
308
+
309
+ let(:results_set) { one_results }
310
+ let(:uids) { one_uids }
311
+
312
+ it "returns a single uid" do
313
+ results = GSA.uids(results_set, uid)
314
+ results.should eq uids
315
+ end
316
+ end
317
+ end
318
+ end