gsa 0.5.0

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