summon 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.specification +58 -0
  2. data/History.txt +4 -0
  3. data/Manifest.txt +52 -0
  4. data/PostInstall.txt +4 -0
  5. data/README.rdoc +77 -0
  6. data/Rakefile +25 -0
  7. data/bin/summon +10 -0
  8. data/bin/summonh +19 -0
  9. data/ispec/integration_spec.rb +61 -0
  10. data/lib/summon.rb +41 -0
  11. data/lib/summon/cli.rb +136 -0
  12. data/lib/summon/log.rb +40 -0
  13. data/lib/summon/schema.rb +109 -0
  14. data/lib/summon/schema/availability.rb +14 -0
  15. data/lib/summon/schema/citation.rb +11 -0
  16. data/lib/summon/schema/date.rb +23 -0
  17. data/lib/summon/schema/document.rb +84 -0
  18. data/lib/summon/schema/error.rb +4 -0
  19. data/lib/summon/schema/facet.rb +51 -0
  20. data/lib/summon/schema/query.rb +96 -0
  21. data/lib/summon/schema/range.rb +52 -0
  22. data/lib/summon/schema/search.rb +37 -0
  23. data/lib/summon/schema/suggestion.rb +5 -0
  24. data/lib/summon/service.rb +44 -0
  25. data/lib/summon/transport.rb +13 -0
  26. data/lib/summon/transport/canned.json +2327 -0
  27. data/lib/summon/transport/canned.rb +9 -0
  28. data/lib/summon/transport/errors.rb +12 -0
  29. data/lib/summon/transport/headers.rb +55 -0
  30. data/lib/summon/transport/http.rb +114 -0
  31. data/lib/summon/transport/qstring.rb +49 -0
  32. data/script/console +10 -0
  33. data/script/destroy +14 -0
  34. data/script/generate +14 -0
  35. data/spec/spec.opts +1 -0
  36. data/spec/spec_helper.rb +19 -0
  37. data/spec/summon/log_spec.rb +28 -0
  38. data/spec/summon/schema/availability_spec.rb +31 -0
  39. data/spec/summon/schema/citation_spec.rb +34 -0
  40. data/spec/summon/schema/date_spec.rb +12 -0
  41. data/spec/summon/schema/document_spec.rb +235 -0
  42. data/spec/summon/schema/facet_spec.rb +115 -0
  43. data/spec/summon/schema/query_spec.rb +163 -0
  44. data/spec/summon/schema/range_spec.rb +45 -0
  45. data/spec/summon/schema/search_spec.rb +62 -0
  46. data/spec/summon/schema_spec.rb +143 -0
  47. data/spec/summon/service_spec.rb +18 -0
  48. data/spec/summon/transport/headers_spec.rb +47 -0
  49. data/spec/summon/transport/http_spec.rb +29 -0
  50. data/spec/summon/transport/qstring_spec.rb +24 -0
  51. data/spec/summon_spec.rb +48 -0
  52. data/summon.gemspec +41 -0
  53. metadata +145 -0
@@ -0,0 +1,115 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Summon::Facet do
4
+
5
+ it "should map" do
6
+ facet = Summon::Facet.new(JSON.parse(EXAMPLE_FACET_JSON))
7
+ facet.remove_src
8
+ facet.counts.each {|f| f.remove_src }
9
+ facet.to_yaml.should == EXPECTED_FACET_YAML
10
+
11
+ first = facet.counts.first
12
+ first.apply_command.should == "addFacetValueFilter(ContentType_sfacet,Book,false)"
13
+ first.apply_negated_command.should == "addFacetValueFilter(ContentType_sfacet,Book,true)"
14
+ first.remove_command.should == "eatMyShorts()"
15
+ end
16
+
17
+ it "should now how to escape values" do
18
+ count = Summon::FacetCount.new(:value => "the quick, brown, fox")
19
+ count.value.should == "the quick, brown, fox"
20
+ count.escaped_value.should == 'the quick\, brown\, fox'
21
+
22
+ Summon::FacetCount.new(:value => ': everything (else) and $1 or {is} it\ ').escaped_value.should ==
23
+ '\: everything \(else\) and \$1 or \{is\} it\\ '
24
+ end
25
+
26
+ EXAMPLE_FACET_JSON = <<-JSON
27
+ {
28
+ "pageSize": 10,
29
+ "displayName": "ContentType",
30
+ "combineMode": "or",
31
+ "pageNumber": 1,
32
+ "listValuesCommand": "listFacetValues(ContentType_sfacet,or)",
33
+ "hasAppliedValue": false,
34
+ "fieldName": "ContentType_sfacet",
35
+ "hasLimitingValue": true,
36
+ "removeCommand": "removeFacetField(ContentType_sfacet)",
37
+ "removeValueFiltersCommand": "removeFacetValueFilters(ContentType)",
38
+ "counts": [
39
+ {
40
+ "isFurtherLimiting": true,
41
+ "isNegated": false,
42
+ "value": "Book",
43
+ "count": 799602,
44
+ "applyCommand": "addFacetValueFilter(ContentType_sfacet,Book,false)",
45
+ "removeCommand": "eatMyShorts()",
46
+ "applyNegatedCommand": "addFacetValueFilter(ContentType_sfacet,Book,true)",
47
+ "isApplied": false
48
+ },
49
+ {
50
+ "isFurtherLimiting": true,
51
+ "isNegated": false,
52
+ "value": "JournalArticle",
53
+ "count": 49765,
54
+ "applyCommand": "addFacetValueFilter(ContentType_sfacet,JournalArticle,false)",
55
+ "removeCommand": "eatMyShorts()",
56
+ "applyNegatedCommand": "addFacetValueFilter(ContentType_sfacet,JournalArticle,true)",
57
+ "isApplied": false
58
+ },
59
+ {
60
+ "isFurtherLimiting": true,
61
+ "isNegated": false,
62
+ "value": "Journal Article",
63
+ "count": 179002,
64
+ "removeCommand": "eatMyShorts()",
65
+ "applyCommand": "addFacetValueFilter(ContentType_sfacet,Journal Article,false)",
66
+ "applyNegatedCommand": "addFacetValueFilter(ContentType_sfacet,Journal Article,true)",
67
+ "isApplied": false
68
+ }
69
+ ]
70
+ }
71
+ JSON
72
+
73
+ EXPECTED_FACET_YAML = <<-YAML
74
+ --- !ruby/object:Summon::Facet
75
+ combine_mode: or
76
+ counts:
77
+ - !ruby/object:Summon::FacetCount
78
+ applied: false
79
+ apply_command: addFacetValueFilter(ContentType_sfacet,Book,false)
80
+ apply_negated_command: addFacetValueFilter(ContentType_sfacet,Book,true)
81
+ count: 799602
82
+ further_limiting: true
83
+ negated: false
84
+ remove_command: eatMyShorts()
85
+ src:
86
+ value: Book
87
+ - !ruby/object:Summon::FacetCount
88
+ applied: false
89
+ apply_command: addFacetValueFilter(ContentType_sfacet,JournalArticle,false)
90
+ apply_negated_command: addFacetValueFilter(ContentType_sfacet,JournalArticle,true)
91
+ count: 49765
92
+ further_limiting: true
93
+ negated: false
94
+ remove_command: eatMyShorts()
95
+ src:
96
+ value: JournalArticle
97
+ - !ruby/object:Summon::FacetCount
98
+ applied: false
99
+ apply_command: addFacetValueFilter(ContentType_sfacet,Journal Article,false)
100
+ apply_negated_command: addFacetValueFilter(ContentType_sfacet,Journal Article,true)
101
+ count: 179002
102
+ further_limiting: true
103
+ negated: false
104
+ remove_command: eatMyShorts()
105
+ src:
106
+ value: Journal Article
107
+ display_name: ContentType
108
+ field_name: ContentType_sfacet
109
+ page_number: 1
110
+ page_size: 10
111
+ remove_command: removeFacetField(ContentType_sfacet)
112
+ remove_value_filters_command: removeFacetValueFilters(ContentType)
113
+ src:
114
+ YAML
115
+ end
@@ -0,0 +1,163 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Summon::Query do
4
+ it "maps" do
5
+ raw = JSON.parse(<<-JSON)
6
+ {
7
+ "searchTerms": [1],
8
+ "facetValueGroupFilters": [],
9
+ "queryString": "4",
10
+ "facetValueFilters": [],
11
+ "textQueries": [6],
12
+ "textFilters": [7],
13
+ "rangeFilters": [],
14
+ "facetFields": [8],
15
+ "rangeFacetFields": [9],
16
+ "sort": [
17
+ {
18
+ "fieldName": "PublicationDate_dt",
19
+ "sortOrder": "desc"
20
+ }
21
+ ],
22
+ "params": [11]
23
+ }
24
+ JSON
25
+
26
+
27
+ query(raw).with {
28
+ search_terms.should == [1]
29
+ range_filters.should == []
30
+ facet_value_group_filters.should == []
31
+ query_string.should == "4"
32
+ facet_value_filters.should == []
33
+ text_queries.should == [6]
34
+ text_filters.should == [7]
35
+ facet_fields.should == [8]
36
+ sorts.length.should == 1
37
+ sort.field_name.should == "PublicationDate_dt"
38
+ sort.sort_order.should == "desc"
39
+ }
40
+ end
41
+
42
+ it "should map facet value fields and facet group fields" do
43
+ q = query(
44
+ "searchTerms"=>[], "rangeFilters"=>[],
45
+ "facetValueGroupFilters"=>[
46
+ {
47
+ "combineMode"=>"or", "tag"=>"3",
48
+ "values"=>[
49
+ {"value"=>"history", "removeCommand"=>"removeFacetValueGroupFilter(3,history)"},
50
+ {"value"=>"uk", "removeCommand"=>"removeFacetValueGroupFilter(3,uk)"}
51
+ ],
52
+ "removeCommand"=>"removeFacetValueGroupFilter(3)",
53
+ "fieldName"=>"SubjectTerms"
54
+ }
55
+ ],
56
+ "facetValueFilters"=>[
57
+ {
58
+ "isNegated"=>false,
59
+ "value"=>"law",
60
+ "removeCommand"=>"removeFacetValueFilter(SubjectTerms,law)",
61
+ "fieldName"=>"SubjectTerms",
62
+ "negateCommand"=>"negateFacetValueFilter(SubjectTerms,law)"
63
+ }
64
+ ],
65
+ "queryString"=>"s.fvgf%3A3=SubjectTerms%2Cor%2Chistory%2Cuk&s.ff=SubjectTerms%2Cand%2C1%2C10&s.fvf=SubjectTerms%2Claw%2Cfalse",
66
+ "facetFields"=>[{
67
+ "pageSize"=>10, "pageNumber"=>1, "combineMode"=>"and", "removeCommand"=>"removeFacetField(SubjectTerms)", "fieldName"=>"SubjectTerms"
68
+ }],
69
+ "textFilters"=>[], "textQueries"=>[], "sort"=>[], "rangeFacetFields"=>[]
70
+ )
71
+ filter = q.facet_value_filters[0]
72
+ filter.should_not be_negated
73
+ filter.remove_command.should == "removeFacetValueFilter(SubjectTerms,law)"
74
+ filter.field_name.should == "SubjectTerms"
75
+ filter.negate_command.should == "negateFacetValueFilter(SubjectTerms,law)"
76
+
77
+ group = q.facet_value_group_filters[0]
78
+ group.combine_mode.should == "or"
79
+ group.tag.should == "3"
80
+ group.remove_command.should == "removeFacetValueGroupFilter(3)"
81
+ group.field_name.should == "SubjectTerms"
82
+
83
+ group.values[0].value.should == "history"
84
+ group.values[0].remove_command.should == "removeFacetValueGroupFilter(3,history)"
85
+ end
86
+
87
+ it "should be able to deescape a string" do
88
+ query_hash("").should == {}
89
+ query_hash("s.fq=Author_t%3A%28Linster%2C+Richard+L%29").should == {"s.fq" => "Author_t:(Linster, Richard L)"}
90
+ query_hash("s.fq=Balthazar&s.fq=Author_t%3A%28Linster%2C+Richard+L%29").should == {"s.fq" => ["Balthazar", "Author_t:(Linster, Richard L)"]}
91
+ query_hash("s.fq=Author_t%3A%28Linster%2C+Richard+L%29&s.q=Probability+models+of+recidivism%5C%3A+an+exploration").should ==
92
+ {"s.fq" => "Author_t:(Linster, Richard L)", "s.q" => "Probability models of recidivism\\: an exploration"}
93
+ end
94
+
95
+ it "has params" do
96
+ query({
97
+ :params => [
98
+ {
99
+ "key" => "holdingsOnly",
100
+ "value" => "t",
101
+ "removeCommand" => "removeParameter(holdingsOnly)"
102
+ }
103
+ ]
104
+ }).tap do |q|
105
+ q.params.should_not be(nil)
106
+ # what's the deal w/ holdings??? should we delete this test? - jl
107
+ # q.should be_holdings_only_enabled
108
+ end
109
+ end
110
+
111
+ it "knows it's own date_min and date_max" do
112
+ query({
113
+ :rangeFilters => [
114
+ {
115
+ "fieldName" => "PublicationDate",
116
+ "range" => {
117
+ "minValue" => "2000",
118
+ "maxValue" => "2008"
119
+ }
120
+ }
121
+ ]
122
+ }).tap do |q|
123
+ q.date_min.should == 2000
124
+ q.date_max.should == 2008
125
+ end
126
+ end
127
+
128
+ it "doesn't get phased when one of the ranges in unbounded" do
129
+ query({
130
+ :rangeFilters => [
131
+ {
132
+ "fieldName" => "PublicationDate",
133
+ "range" => {
134
+ "minValue" => "*",
135
+ "maxValue" => "2008"
136
+ }
137
+ }
138
+ ]
139
+ }).tap do |q|
140
+ q.date_min.should be_nil
141
+ q.date_max.should == 2008
142
+ end
143
+ end
144
+
145
+ it "should know about holdings only" do
146
+ query({}).should_not be_holdings_only_enabled
147
+ query(:isHoldingsOnlyEnabled => true).should be_holdings_only_enabled
148
+ query(:isHoldingsOnlyEnabled => false).should_not be_holdings_only_enabled
149
+ end
150
+
151
+ def query_hash(str)
152
+ query(:queryString => str).to_hash
153
+ end
154
+
155
+ def query(params)
156
+ Summon::Query.new(params)
157
+ end
158
+
159
+ end
160
+
161
+ class Object
162
+ alias_method :with, :instance_eval
163
+ end
@@ -0,0 +1,45 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Summon::RangeFacet do
4
+ it "maps" do
5
+ range = Summon::RangeFacet.new(JSON.parse(<<-JSON))
6
+ {
7
+ "displayName": "PublicationDate",
8
+ "removeCommand": "removeFacetField(PublicationDate)",
9
+ "fieldName": "PublicationDate_dt",
10
+ "counts": [
11
+ {
12
+ "count": 119795,
13
+ "applyCommand": "addRangeFilter(PublicationDate,1999:1999)",
14
+ "range": {
15
+ "minValue": "1999",
16
+ "maxValue": "2000"
17
+ },
18
+ "isApplied": true
19
+ },
20
+ {
21
+ "count": 122712,
22
+ "applyCommand": "addRangeFilter(PublicationDate,2000:2000)",
23
+ "range": {
24
+ "minValue": "2000",
25
+ "maxValue": "2001"
26
+ },
27
+ "isApplied": false
28
+ }
29
+ ]
30
+ }
31
+ JSON
32
+ range.display_name.should == "PublicationDate"
33
+ range.remove_command.should == "removeFacetField(PublicationDate)"
34
+ range.field_name.should == "PublicationDate_dt"
35
+ range.counts.should_not be_nil
36
+ range.counts.length.should be(2)
37
+ range.counts[0].tap do |c|
38
+ c.count.should == 119795
39
+ c.apply_command.should == "addRangeFilter(PublicationDate,1999:1999)"
40
+ c.min.should == "1999"
41
+ c.max.should == "2000"
42
+ c.should be_applied
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,62 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Summon::Search do
4
+ it "maps" do
5
+ search = Summon::Search.new(JSON.parse(<<-JSON))
6
+ {
7
+ "pageCount": 0,
8
+ "didYouMeanSuggestions": [
9
+
10
+ ],
11
+ "fullTextCount": 0,
12
+ "queryTime": 124,
13
+ "documents": [],
14
+ "totalRequestTime": 129,
15
+ "elapsedQueryTime": 126,
16
+ "version": "1.0.0",
17
+ "facetFields": [
18
+ ],
19
+ "rangeFacetFields": [
20
+
21
+ ],
22
+ "recordCount": 4589937,
23
+ "sessionId": "cfaa4020-1abe-4a9d-ae6e-e433a36c1069",
24
+ "query": {}
25
+ }
26
+ JSON
27
+ search.page_count.should == 0
28
+ search.record_count.should == 4589937
29
+ search.query_time.should == 124
30
+ search.session_id.should == "cfaa4020-1abe-4a9d-ae6e-e433a36c1069"
31
+ search.documents.should == []
32
+ search.version.should == "1.0.0"
33
+ search.query.should be_kind_of(Summon::Query)
34
+ end
35
+
36
+ it "should handle an error case" do
37
+ search = Summon::Search.new(
38
+ "version" => "1.0.0",
39
+ "errors" => [
40
+ {
41
+ "suggestion" => {
42
+ "applySuggestionCommand" => "removeTextQuery(foo\:bar) addTextQuery(foo\\\:bar)",
43
+ "suggestedQuery" => "foo\:bar",
44
+ "originalQuery" => "foo:bar"
45
+ },
46
+ "message" => "Unknown search field(s): foo."
47
+ }])
48
+ search.version.should == "1.0.0"
49
+ search.record_count.should == 0
50
+ search.errors.size.should == 1
51
+ search.errors.first.message.should == "Unknown search field(s): foo."
52
+ search.errors.first.suggestion.apply_suggestion_command.should == "removeTextQuery(foo\:bar) addTextQuery(foo\\\:bar)"
53
+ search.errors.first.suggestion.suggested_query.should == "foo\:bar"
54
+ search.errors.first.suggestion.original_query.should == "foo:bar"
55
+ end
56
+
57
+ it "should be empty w/ no docs" do
58
+ Summon::Search.new({}).should be_empty
59
+ Summon::Search.new({"documents" => [{}]}).should_not be_empty
60
+ end
61
+
62
+ end
@@ -0,0 +1,143 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Summon::Schema do
4
+
5
+ before(:each) do
6
+ @class = Class.new(Summon::Schema)
7
+ end
8
+
9
+ def class_eval(&block)
10
+ @class.class_eval(&block)
11
+ end
12
+
13
+ def init(values)
14
+ @class.new(values)
15
+ end
16
+
17
+ it "pulls its attributes from a hash" do
18
+ class_eval do
19
+ attr :foo
20
+ attr :baz
21
+ end
22
+
23
+ init(:foo => "bar", :baz => "bang").foo.should == "bar"
24
+ end
25
+
26
+ it "can pull its attributes from string keys as well as symbol keys" do
27
+ class_eval do
28
+ attr :foo
29
+ end
30
+
31
+ init("foo" => "bar").foo.should == "bar"
32
+ end
33
+
34
+ it "knows how to atomagically map between camel case and perl case" do
35
+ class_eval do
36
+ attr :foo_bar_baz
37
+ end
38
+
39
+ init("fooBarBaz" => "Hello").foo_bar_baz.should == "Hello"
40
+ end
41
+
42
+ it "can also magically convert from Pascal case as well" do
43
+ class_eval do
44
+ attr :foo_bar_baz
45
+ end
46
+ init("FooBarBaz" => "Hello").foo_bar_baz.should == "Hello"
47
+ end
48
+
49
+ it "can have an overridden field name for names that don't map so cleanly" do
50
+ class_eval do
51
+ attr :isbn, :json_name => "ISBN"
52
+ attr :eissn, :json_name => "eISSN"
53
+ end
54
+
55
+ o = init("ISBN" => 123456, "eISSN" => 98765)
56
+ o.isbn.should == 123456
57
+ o.eissn.should == 98765
58
+ end
59
+
60
+ describe "transforms" do
61
+ it "can transform subobjects" do
62
+ class Summon::DogPile < Summon::Schema
63
+ attr :type
64
+ end
65
+ class_eval do
66
+ attr :steaming, :transform => :DogPile
67
+ end
68
+
69
+ o = init("steaming" => {:type => "swirled"})
70
+ o.steaming.should_not be_nil
71
+ o.steaming.type.should == "swirled"
72
+ end
73
+
74
+ it "will apply a transform across an array if the field is an array" do
75
+ class Summon::DogPatch < Summon::Schema
76
+ attr :foo
77
+ end
78
+ class_eval do
79
+ attr :patches, :transform => :DogPatch
80
+ end
81
+ patches = init("patches" => [{:foo => 'bar'},{:foo => 'baz'}, {:foo => 'bang'}]).patches
82
+ patches.should_not be_nil
83
+ patches.should be_kind_of(Array)
84
+ patches.collect{|p| p.foo}.should == ['bar', 'baz', 'bang']
85
+ end
86
+
87
+ it "will ignore a transform if the element is not present" do
88
+ class Summon::DogFood < Summon::Schema
89
+ attr :foo
90
+ end
91
+ class_eval do
92
+ attr :kibbles, :transform => :DogPatch
93
+ attr :bits, :single => true, :transform => :DogPatch
94
+ end
95
+ foo = init({})
96
+ foo.kibbles.should == []
97
+ foo.bits.should == nil
98
+ end
99
+ end
100
+
101
+ it "will create a predicate if the field is marked as boolean" do
102
+ class_eval do
103
+ attr :boollocks, :boolean => true
104
+ end
105
+
106
+ init("isBoollocks" => true).boollocks?.should be(true)
107
+ end
108
+
109
+ it "will automatically do a bean styles lookup on attributes that end in a question mark" do
110
+ class_eval do
111
+ attr :awesome?
112
+ end
113
+ init("isAwesome" => true).should be_awesome
114
+ init("isAwesome" => false).should_not be_awesome
115
+ end
116
+
117
+ it "automatically unwraps arrays from values if the value is marked as being single" do
118
+ class_eval do
119
+ attr :just_one, :single => true
120
+ end
121
+ init(:justOne => ["foo"]).just_one.should == 'foo'
122
+ end
123
+
124
+ it "defaults to automatically unwrap arrays if the field doesn't end in s" do
125
+ class_eval do
126
+ attr :foo
127
+ attr :foos
128
+ end
129
+ o = init(:foo => ["bar"], :foos => ["bar"])
130
+ o.foo.should == "bar"
131
+ o.foos.should == ["bar"]
132
+ end
133
+
134
+ it "initializes multi-valued fields to the empty array if it is not present" do
135
+ class_eval do
136
+ attr :foos
137
+ end
138
+ init({}).foos.should == []
139
+ end
140
+
141
+
142
+
143
+ end