tylerhunt-relax 0.0.5
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.
- data/LICENSE +20 -0
- data/README +171 -0
- data/lib/relax/parsers/base.rb +34 -0
- data/lib/relax/parsers/factory.rb +43 -0
- data/lib/relax/parsers/hpricot.rb +145 -0
- data/lib/relax/parsers/rexml.rb +158 -0
- data/lib/relax/parsers.rb +13 -0
- data/lib/relax/query.rb +46 -0
- data/lib/relax/request.rb +95 -0
- data/lib/relax/response.rb +78 -0
- data/lib/relax/service.rb +101 -0
- data/lib/relax/symbolic_hash.rb +79 -0
- data/lib/relax.rb +13 -0
- data/spec/parsers/factory_spec.rb +29 -0
- data/spec/parsers/hpricot_spec.rb +35 -0
- data/spec/parsers/rexml_spec.rb +40 -0
- data/spec/query_spec.rb +60 -0
- data/spec/request_spec.rb +108 -0
- data/spec/response_spec.rb +98 -0
- data/spec/symbolic_hash_spec.rb +67 -0
- metadata +89 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007-2008 Tyler Hunt
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
= Relax
|
2
|
+
|
3
|
+
Relax is a simple library that provides a foundation for writing REST consumer
|
4
|
+
APIs, including the logic to handle the HTTP requests, build URLs with query
|
5
|
+
parameters, and parse XML responses.
|
6
|
+
|
7
|
+
It provides a basic set of functionality common to most REST consumers:
|
8
|
+
|
9
|
+
- building HTTP queries (Relax::Request)
|
10
|
+
- issuing HTTP requests (Relax::Service)
|
11
|
+
- parsing XML responses (Relax::Response)
|
12
|
+
|
13
|
+
|
14
|
+
== Tutorial
|
15
|
+
|
16
|
+
This short tutorial will walk you through the basic steps of creating a simple Flickr API that supports a single call to search for photos by tags.
|
17
|
+
|
18
|
+
=== Step 1
|
19
|
+
|
20
|
+
In the first step we're going to simply include Relax, and define the basis for
|
21
|
+
our Service class.
|
22
|
+
|
23
|
+
require 'rubygems'
|
24
|
+
require 'relax'
|
25
|
+
|
26
|
+
module Flickr
|
27
|
+
class Service < Relax::Service
|
28
|
+
ENDPOINT = 'http://api.flickr.com/services/rest/'
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
super(ENDPOINT)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
=== Step 2
|
38
|
+
|
39
|
+
Next we're going to define common Request and Response classes for use
|
40
|
+
throughout our API. This gives us a single point to add any shared
|
41
|
+
functionality. For Flickr, this means that each request will have a "method"
|
42
|
+
parameter, and each response will have a "stat" attribute that will equal "ok"
|
43
|
+
when the response comes back without any errors.
|
44
|
+
|
45
|
+
module Flickr
|
46
|
+
class Request < Relax::Request
|
47
|
+
parameter :method
|
48
|
+
end
|
49
|
+
|
50
|
+
class Response < Relax::Response
|
51
|
+
def successful?
|
52
|
+
root[:stat] == 'ok'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
While we're at it, we're also going to add a new line to the constructor from
|
58
|
+
our service to make sure that our Flickr API key gets passed along with each
|
59
|
+
request as well.
|
60
|
+
|
61
|
+
module Flickr
|
62
|
+
class Service < Relax::Service
|
63
|
+
ENDPOINT = 'http://api.flickr.com/services/rest/'
|
64
|
+
|
65
|
+
def initialize(api_key)
|
66
|
+
super(ENDPOINT)
|
67
|
+
Request[:api_key] = api_key
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
When we call our Request class as we have here, we're basically setting up a
|
73
|
+
value on our request that acts like a template. Each request we create now will
|
74
|
+
have the api_key property prepopulated for us.
|
75
|
+
|
76
|
+
|
77
|
+
=== Step 3
|
78
|
+
|
79
|
+
Next, we're going to need a basic Photo class to represent photos that Flickr
|
80
|
+
returns to us.
|
81
|
+
|
82
|
+
module Flickr
|
83
|
+
class Photo < Response
|
84
|
+
parameter :id, :attribute => true, :type => :integer
|
85
|
+
parameter :title, :attribute => true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
Here we're creating a Response class that extends our Flickr::Response, which
|
90
|
+
has two parameters: "id" and "title." By setting the attribute option to true,
|
91
|
+
we're telling Relax to look for an attribute by that name on the XML root
|
92
|
+
instead of checking for an element by that name. The type options can be used
|
93
|
+
to specify what type of data we're expecting the response to give us. The
|
94
|
+
default type is string.
|
95
|
+
|
96
|
+
|
97
|
+
=== Step 4
|
98
|
+
|
99
|
+
Now we arrive at the final piece of the puzzle: a service call module. To keep
|
100
|
+
things contained, a Relax best practice is to create a module for each call
|
101
|
+
on your service. The one we're creating here is the PhotoSearch module for the
|
102
|
+
"flickr.photos.search" call on the Flickr API.
|
103
|
+
|
104
|
+
There are three main pieces to every service call module:
|
105
|
+
|
106
|
+
1. a Relax::Request object
|
107
|
+
2. a Relax::Response object
|
108
|
+
3. a call method that calls Relax::Service#call
|
109
|
+
|
110
|
+
Here's what the PhotoSearch module looks like:
|
111
|
+
|
112
|
+
module Flickr
|
113
|
+
module PhotoSearch
|
114
|
+
class PhotoSearchRequest < Flickr::Request
|
115
|
+
parameter :per_page
|
116
|
+
parameter :tags
|
117
|
+
|
118
|
+
def initialize(options = {})
|
119
|
+
super
|
120
|
+
@method = 'flickr.photos.search'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class PhotoSearchResponse < Flickr::Response
|
125
|
+
parameter :photos, :element => 'photos/photo', :collection => Photo
|
126
|
+
end
|
127
|
+
|
128
|
+
def search(options = {})
|
129
|
+
call(PhotoSearchRequest.new(options), PhotoSearchResponse)
|
130
|
+
end
|
131
|
+
|
132
|
+
def find_by_tag(tags, options = {})
|
133
|
+
search(options.merge(:tags => tags))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
As you can see, we have our request (PhotoSearchRequest), response
|
139
|
+
(PhotoSearchResponse), and call method (actually, two in this case: search and
|
140
|
+
find_by_tag). This now needs to be included into our Flickr::Service class,
|
141
|
+
and then we'll be able to use it by calling either of the call methods.
|
142
|
+
|
143
|
+
module Flickr
|
144
|
+
class Service < Relax::Service
|
145
|
+
include Flickr::PhotoSearch
|
146
|
+
|
147
|
+
ENDPOINT = 'http://api.flickr.com/services/rest/'
|
148
|
+
|
149
|
+
def initialize(api_key)
|
150
|
+
super(ENDPOINT)
|
151
|
+
Request[:api_key] = api_key
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
Now we're ready to make a call against the API:
|
157
|
+
|
158
|
+
flickr = Flickr::Service.new(ENV['FLICKR_API_KEY'])
|
159
|
+
relax = flickr.find_by_tag('relax', :per_page => 10)
|
160
|
+
|
161
|
+
if relax.successful?
|
162
|
+
relax.photos.each do |photo|
|
163
|
+
puts "[#{photo.id}] #{photo.title}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
This will output the IDs and titles for the first 10 photos on Flickr that have
|
168
|
+
the tag "relax."
|
169
|
+
|
170
|
+
|
171
|
+
Copyright (c) 2007-2008 Tyler Hunt, released under the MIT license
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Relax
|
2
|
+
module Parsers
|
3
|
+
|
4
|
+
class Base
|
5
|
+
|
6
|
+
attr_reader :parent
|
7
|
+
attr_reader :parameters
|
8
|
+
|
9
|
+
def initialize(raw, parent)
|
10
|
+
@parent = parent
|
11
|
+
@parameters = parent.class.instance_variable_get('@parameters')
|
12
|
+
parse!
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse!; end
|
16
|
+
|
17
|
+
def root; end
|
18
|
+
def is?(name); end
|
19
|
+
def has?(name); end
|
20
|
+
def element(name); end
|
21
|
+
def elements(name); end
|
22
|
+
|
23
|
+
def attribute(element, name); end
|
24
|
+
def value(value); end
|
25
|
+
def text_value(value); end
|
26
|
+
def integer_value(value); end
|
27
|
+
def float_value(value); end
|
28
|
+
def date_value(value); end
|
29
|
+
def time_value(value); end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Relax
|
2
|
+
module Parsers
|
3
|
+
|
4
|
+
##
|
5
|
+
# Manages the Relax::Parsers in the library.
|
6
|
+
#
|
7
|
+
module Factory
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
##
|
12
|
+
# Returns the parser class which has been registered for the given
|
13
|
+
# +name+.
|
14
|
+
#
|
15
|
+
def get(name)
|
16
|
+
@@parsers ||= {}
|
17
|
+
@@parsers[name] || raise(UnrecognizedParser, "Given parser name not recognized: #{name.inspect}. Expected one of: #{@@parsers.keys.inspect}")
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Registers a new parser with the factory. The +name+ should be unique,
|
22
|
+
# but if not, it will override the previously defined parser for the
|
23
|
+
# given +name+.
|
24
|
+
#
|
25
|
+
def register(name, klass)
|
26
|
+
@@parsers ||= {}
|
27
|
+
@@parsers[:default] = klass if @@parsers.empty?
|
28
|
+
@@parsers[name] = klass
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Removes all registered parsers from the factory.
|
33
|
+
#
|
34
|
+
def clear!
|
35
|
+
@@parsers = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hpricot'
|
3
|
+
|
4
|
+
module Relax
|
5
|
+
module Parsers
|
6
|
+
|
7
|
+
##
|
8
|
+
# Parses the server's raw response using the Hpricot library.
|
9
|
+
#
|
10
|
+
class Hpricot < Base
|
11
|
+
|
12
|
+
FACTORY_NAME = :hpricot
|
13
|
+
|
14
|
+
def initialize(raw, parent)
|
15
|
+
@xml = ::Hpricot.XML(raw)
|
16
|
+
super(raw, parent)
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse!
|
20
|
+
if parameters
|
21
|
+
parameters.each do |parameter, options|
|
22
|
+
begin
|
23
|
+
element = options[:element] || parameter
|
24
|
+
|
25
|
+
if attribute = options[:attribute] and attribute == true
|
26
|
+
node = attribute(root, element)
|
27
|
+
elsif attribute
|
28
|
+
node = attribute(element(element), attribute)
|
29
|
+
elsif options[:collection]
|
30
|
+
node = elements(element)
|
31
|
+
else
|
32
|
+
node = element(element)
|
33
|
+
end
|
34
|
+
|
35
|
+
if options[:collection]
|
36
|
+
value = node.collect do |element|
|
37
|
+
options[:collection].new(element)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
case type = options[:type]
|
41
|
+
when Response
|
42
|
+
value = type.new(node)
|
43
|
+
|
44
|
+
when :date
|
45
|
+
value = date_value(node)
|
46
|
+
|
47
|
+
when :time
|
48
|
+
value = time_value(node)
|
49
|
+
|
50
|
+
when :float
|
51
|
+
value = float_value(node)
|
52
|
+
|
53
|
+
when :integer
|
54
|
+
value = integer_value(node)
|
55
|
+
|
56
|
+
when :text
|
57
|
+
else
|
58
|
+
value = text_value(node)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
parent.instance_variable_set("@#{parameter}", value)
|
63
|
+
rescue ::Hpricot::Error
|
64
|
+
raise Relax::MissingParameter if options[:required]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the root of the XML document.
|
71
|
+
def root
|
72
|
+
@xml.root
|
73
|
+
end
|
74
|
+
|
75
|
+
# Checks the name of the root node.
|
76
|
+
def is?(name)
|
77
|
+
root.name.gsub(/.*:(.*)/, '\1') == node_name(name)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a set of elements matching name.
|
81
|
+
def elements(name)
|
82
|
+
root.search(root_path(name))
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns an element of the specified name.
|
86
|
+
def element(name)
|
87
|
+
root.at(root_path(name))
|
88
|
+
end
|
89
|
+
alias :has? :element
|
90
|
+
|
91
|
+
# Returns an attribute on an element.
|
92
|
+
def attribute(element, name)
|
93
|
+
element[name]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Gets the value of an element or attribute.
|
97
|
+
def value(value)
|
98
|
+
value.is_a?(::Hpricot::Elem) ? value.inner_text : value.to_s
|
99
|
+
end
|
100
|
+
|
101
|
+
# Gets a text value.
|
102
|
+
def text_value(value)
|
103
|
+
value(value)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Gets an integer value.
|
107
|
+
def integer_value(value)
|
108
|
+
value(value).to_i
|
109
|
+
end
|
110
|
+
|
111
|
+
# Gets a float value.
|
112
|
+
def float_value(value)
|
113
|
+
value(value).to_f
|
114
|
+
end
|
115
|
+
|
116
|
+
# Gets a date value.
|
117
|
+
def date_value(value)
|
118
|
+
Date.parse(value(value))
|
119
|
+
end
|
120
|
+
|
121
|
+
# Gets a time value.
|
122
|
+
def time_value(value)
|
123
|
+
Time.parse(value(value))
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
|
130
|
+
# Converts a name to a node name.
|
131
|
+
def node_name(name)
|
132
|
+
name.to_s
|
133
|
+
end
|
134
|
+
|
135
|
+
# Gets the XPath expression representing the root node.
|
136
|
+
def root_path(name)
|
137
|
+
"/#{node_name(name)}"
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
Factory.register(Hpricot::FACTORY_NAME, Hpricot)
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rexml/document'
|
3
|
+
|
4
|
+
module Relax
|
5
|
+
module Parsers
|
6
|
+
|
7
|
+
##
|
8
|
+
# Parsers the server's response using the REXML library.
|
9
|
+
#
|
10
|
+
# Benefits:
|
11
|
+
#
|
12
|
+
# * XML Namespace support (parameter :foo, :namespace => 'bar')
|
13
|
+
#
|
14
|
+
# Drawbacks:
|
15
|
+
#
|
16
|
+
# * Case sensitive field names (<Status>..</> != parameter :status)
|
17
|
+
#
|
18
|
+
class REXML < Base
|
19
|
+
|
20
|
+
FACTORY_NAME = :rexml
|
21
|
+
|
22
|
+
def initialize(raw, parent)
|
23
|
+
@xml = ::REXML::Document.new(raw)
|
24
|
+
super(raw, parent)
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse!
|
28
|
+
if parameters
|
29
|
+
parameters.each do |parameter, options|
|
30
|
+
begin
|
31
|
+
element = options[:element] || parameter
|
32
|
+
namespace = options[:namespace]
|
33
|
+
|
34
|
+
if attribute = options[:attribute] and attribute == true
|
35
|
+
node = attribute(root, element, namespace)
|
36
|
+
elsif attribute
|
37
|
+
node = attribute(element(element), attribute, namespace)
|
38
|
+
elsif options[:collection]
|
39
|
+
node = elements(element, namespace)
|
40
|
+
else
|
41
|
+
node = element(element, namespace)
|
42
|
+
end
|
43
|
+
|
44
|
+
if options[:collection]
|
45
|
+
value = node.collect do |element|
|
46
|
+
options[:collection].new(element.deep_clone)
|
47
|
+
end
|
48
|
+
else
|
49
|
+
case type = options[:type]
|
50
|
+
when Response
|
51
|
+
value = type.new(node)
|
52
|
+
|
53
|
+
when :date
|
54
|
+
value = date_value(node)
|
55
|
+
|
56
|
+
when :time
|
57
|
+
value = time_value(node)
|
58
|
+
|
59
|
+
when :float
|
60
|
+
value = float_value(node)
|
61
|
+
|
62
|
+
when :integer
|
63
|
+
value = integer_value(node)
|
64
|
+
|
65
|
+
when :text
|
66
|
+
else
|
67
|
+
value = text_value(node)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
parent.instance_variable_set("@#{parameter}", value)
|
72
|
+
rescue
|
73
|
+
raise(Relax::MissingParameter) if node.nil? && options[:required]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the root of the XML document.
|
80
|
+
def root
|
81
|
+
@xml.root
|
82
|
+
end
|
83
|
+
|
84
|
+
# Checks the name of the root node.
|
85
|
+
def is?(name, namespace = nil)
|
86
|
+
root.name == node_name(name, nil)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a set of elements matching name.
|
90
|
+
def elements(name, namespace = nil)
|
91
|
+
root.get_elements(node_path(name, namespace))
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns an element of the specified name.
|
95
|
+
def element(name, namespace = nil)
|
96
|
+
root.elements[node_path(name, namespace)]
|
97
|
+
end
|
98
|
+
alias :has? :element
|
99
|
+
|
100
|
+
# Returns an attribute on an element.
|
101
|
+
def attribute(element, name, namespace = nil)
|
102
|
+
element.attribute(name)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Gets the value of an element or attribute.
|
106
|
+
def value(value)
|
107
|
+
value.is_a?(::REXML::Element) ? value.text : value.to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
# Gets a text value.
|
111
|
+
def text_value(value)
|
112
|
+
value(value)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Gets an integer value.
|
116
|
+
def integer_value(value)
|
117
|
+
value(value).to_i
|
118
|
+
end
|
119
|
+
|
120
|
+
# Gets a float value.
|
121
|
+
def float_value(value)
|
122
|
+
value(value).to_f
|
123
|
+
end
|
124
|
+
|
125
|
+
# Gets a date value.
|
126
|
+
def date_value(value)
|
127
|
+
Date.parse(value(value))
|
128
|
+
end
|
129
|
+
|
130
|
+
# Gets a time value.
|
131
|
+
def time_value(value)
|
132
|
+
Time.parse(value(value))
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
|
139
|
+
# Converts a name to a node name.
|
140
|
+
def node_name(name, namespace = nil)
|
141
|
+
"#{namespace.to_s + ':' if namespace}#{name}"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Gets the XPath expression representing the root node.
|
145
|
+
def root_path(name)
|
146
|
+
"/#{node_name(name)}"
|
147
|
+
end
|
148
|
+
|
149
|
+
def node_path(name, namespace = nil)
|
150
|
+
"#{node_name(name, namespace)}"
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
Factory.register(REXML::FACTORY_NAME, REXML)
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
data/lib/relax/query.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
require 'relax/symbolic_hash'
|
5
|
+
|
6
|
+
module Relax
|
7
|
+
# Query is used to represent the query portion of a URL. It's basically just
|
8
|
+
# a hash, where each key/value pair is a query parameter.
|
9
|
+
class Query < SymbolicHash
|
10
|
+
# Converts the Query to a query string for use in a URL.
|
11
|
+
def to_s
|
12
|
+
keys.sort { |a, b| a.to_s <=> b.to_s }.collect do |key|
|
13
|
+
"#{key.to_s}=#{self.class.escape_value(fetch(key))}"
|
14
|
+
end.join('&')
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Parses a URL and returns a Query with its query portion.
|
19
|
+
def parse(uri)
|
20
|
+
query = uri.query.split('&').inject({}) do |query, parameter|
|
21
|
+
key, value = parameter.split('=', 2)
|
22
|
+
query[unescape_value(key)] = unescape_value(value)
|
23
|
+
query
|
24
|
+
end
|
25
|
+
self.new(query)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Escapes a query parameter value.
|
29
|
+
def escape_value(value)
|
30
|
+
CGI.escape(value.to_s).gsub('%20', '+')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Unescapes a query parameter value.
|
34
|
+
def unescape_value(value)
|
35
|
+
CGI.unescape(value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
# Converts each value of the Query to a string as it's added.
|
42
|
+
def convert_value(value)
|
43
|
+
value.to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|