relax 0.0.1 → 0.0.2
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/README +152 -3
- data/lib/relax/query.rb +7 -0
- data/lib/relax/request.rb +16 -3
- data/lib/relax/response.rb +43 -4
- data/lib/relax/service.rb +101 -0
- data/lib/relax/symbolic_hash.rb +20 -6
- data/lib/relax.rb +2 -2
- data/spec/response_spec.rb +18 -2
- metadata +4 -4
- data/lib/relax/api.rb +0 -49
data/README
CHANGED
@@ -1,4 +1,153 @@
|
|
1
|
-
Relax
|
2
|
-
=====
|
1
|
+
= Relax
|
3
2
|
|
4
|
-
|
3
|
+
Relax is a simple library for creating REST consumers.
|
4
|
+
|
5
|
+
When used as a basis for writing REST consumer APIs, it provides a set of
|
6
|
+
functionality common to all REST consumers, including:
|
7
|
+
|
8
|
+
- building HTTP queries (Relax::Request)
|
9
|
+
- issuing HTTP requests (Relax::Service)
|
10
|
+
- parsing XML responses (Relax::Response)
|
11
|
+
|
12
|
+
|
13
|
+
== Tutorial
|
14
|
+
|
15
|
+
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.
|
16
|
+
|
17
|
+
=== Step 1
|
18
|
+
|
19
|
+
In the first step we're going to simply include Relax, and define the basis for
|
20
|
+
our Service class.
|
21
|
+
|
22
|
+
require 'rubygems'
|
23
|
+
require 'relax'
|
24
|
+
|
25
|
+
module Flickr
|
26
|
+
class Service < Relax::Service
|
27
|
+
ENDPOINT = 'http://api.flickr.com/services/rest/'
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
super(ENDPOINT)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
=== Step 2
|
37
|
+
|
38
|
+
Next we're going to define common Request and Response classes for use
|
39
|
+
throughout our API. This gives us a single point to add any shared
|
40
|
+
functionality. For Flickr, this means that each request will have a "method"
|
41
|
+
parameter, and each response will have a "stat" attribute that will equal "ok"
|
42
|
+
when the response comes back without any errors.
|
43
|
+
|
44
|
+
module Flickr
|
45
|
+
class Request < Relax::Request
|
46
|
+
parameter :method
|
47
|
+
end
|
48
|
+
|
49
|
+
class Response < Relax::Response
|
50
|
+
def successful?
|
51
|
+
root[:stat] == 'ok'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
While we're at it, we're also going to add a new line to the constructor from
|
57
|
+
our service to make sure that our Flickr API key gets passed along with each
|
58
|
+
request as well.
|
59
|
+
|
60
|
+
module Flickr
|
61
|
+
class Service < Relax::Service
|
62
|
+
ENDPOINT = 'http://api.flickr.com/services/rest/'
|
63
|
+
|
64
|
+
def initialize(api_key)
|
65
|
+
super(ENDPOINT)
|
66
|
+
Request[:api_key] = api_key
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
When we call our Request class as we have here, we're basically setting up a
|
72
|
+
value on our request that acts like a template. Each request we create now will
|
73
|
+
have the api_key property prepopulated for us.
|
74
|
+
|
75
|
+
|
76
|
+
=== Step 3
|
77
|
+
|
78
|
+
Next, we're going to need a basic Photo class to represent photos that Flickr
|
79
|
+
returns to us.
|
80
|
+
|
81
|
+
module Flickr
|
82
|
+
class Photo < Response
|
83
|
+
parameter :id, :attribute => true, :type => :integer
|
84
|
+
parameter :title, :attribute => true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
Here we're creating a Response class that extends our Flickr::Response, which
|
89
|
+
has two parameters: "id" and "title." By setting the attribute option to true,
|
90
|
+
we're telling Relax to look for an attribute by that name on the XML root
|
91
|
+
instead of checking for an element by that name. The type options can be used
|
92
|
+
to specify what type of data we're expecting the response to give us. The
|
93
|
+
default type is string.
|
94
|
+
|
95
|
+
|
96
|
+
=== Step 4
|
97
|
+
|
98
|
+
Now we arrive at the final piece of the puzzle: a service call module. To keep
|
99
|
+
things contained, a Relax best practice is to create a module for each call
|
100
|
+
on your service. The one we're creating here is the PhotoSearch module for the
|
101
|
+
"flickr.photos.search" call on the Flickr API.
|
102
|
+
|
103
|
+
There are three main pieces to every service call module:
|
104
|
+
|
105
|
+
1. a Relax::Request object
|
106
|
+
2. a Relax::Response object
|
107
|
+
3. a call method that calls Relax::Service#call
|
108
|
+
|
109
|
+
This module then gets included into the Service class, where the call method
|
110
|
+
can be easily utilized.
|
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 will get included into our Flickr::Service class, and we
|
141
|
+
can use it by calling either of the call methods.
|
142
|
+
|
143
|
+
flickr = Flickr::Service.new(ENV['FLICKR_API_KEY'])
|
144
|
+
relax = flickr.find_by_tag('relax', :per_page => 10)
|
145
|
+
|
146
|
+
if relax.successful?
|
147
|
+
relax.photos.each do |photo|
|
148
|
+
puts "[#{photo.id}] #{photo.title}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
This will output the IDs and titles for the first 10 photos on Flickr that have
|
153
|
+
the tag "relax."
|
data/lib/relax/query.rb
CHANGED
@@ -4,7 +4,10 @@ require 'uri'
|
|
4
4
|
require 'relax/symbolic_hash'
|
5
5
|
|
6
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.
|
7
9
|
class Query < SymbolicHash
|
10
|
+
# Converts the Query to a query string for use in a URL.
|
8
11
|
def to_s
|
9
12
|
keys.sort { |a, b| a.to_s <=> b.to_s }.collect do |key|
|
10
13
|
"#{key.to_s}=#{self.class.escape_value(fetch(key))}"
|
@@ -12,6 +15,7 @@ module Relax
|
|
12
15
|
end
|
13
16
|
|
14
17
|
class << self
|
18
|
+
# Parses a URL and returns a Query with its query portion.
|
15
19
|
def parse(uri)
|
16
20
|
query = uri.query.split('&').inject({}) do |query, parameter|
|
17
21
|
key, value = parameter.split('=')
|
@@ -21,10 +25,12 @@ module Relax
|
|
21
25
|
self.new(query)
|
22
26
|
end
|
23
27
|
|
28
|
+
# Escapes a query parameter value.
|
24
29
|
def escape_value(value)
|
25
30
|
ERB::Util.url_encode(value.to_s).gsub('%20', '+')
|
26
31
|
end
|
27
32
|
|
33
|
+
# Unescapes a query parameter value.
|
28
34
|
def unescape_value(value)
|
29
35
|
URI.unescape(value)
|
30
36
|
end
|
@@ -32,6 +38,7 @@ module Relax
|
|
32
38
|
|
33
39
|
protected
|
34
40
|
|
41
|
+
# Converts each value of the Query to a string as it's added.
|
35
42
|
def convert_value(value)
|
36
43
|
value.to_s
|
37
44
|
end
|
data/lib/relax/request.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'relax/query'
|
2
2
|
|
3
3
|
module Relax
|
4
|
+
# Request is intended to be a parent class for requests passed to
|
5
|
+
# Service#call.
|
4
6
|
class Request
|
5
7
|
def initialize(options = {})
|
6
8
|
self.class.class_variables.each do |variable|
|
@@ -12,6 +14,7 @@ module Relax
|
|
12
14
|
end
|
13
15
|
end
|
14
16
|
|
17
|
+
# Converts this request into a Query object.
|
15
18
|
def to_query
|
16
19
|
keys.inject(Query.new) do |parameters, key|
|
17
20
|
parameters[convert_key(key)] = send(key)
|
@@ -19,18 +22,23 @@ module Relax
|
|
19
22
|
end
|
20
23
|
end
|
21
24
|
|
25
|
+
# Converts this request into a query string for use in a URL.
|
22
26
|
def to_s
|
23
|
-
|
24
|
-
"#{key.to_s}=#{ERB::Util.url_encode(send('[]', key).to_s)}"
|
25
|
-
end.join('&')
|
27
|
+
to_query.to_s
|
26
28
|
end
|
27
29
|
|
28
30
|
class << self
|
31
|
+
# Specifies a parameter to create on the request class.
|
32
|
+
#
|
33
|
+
# Options:
|
34
|
+
# - <tt>:value</tt>: The default value for this parameter.
|
29
35
|
def parameter(name, options = {})
|
30
36
|
attr_accessor name
|
31
37
|
class_variable_set("@@#{name}", options.delete(:value)) if options[:value]
|
32
38
|
end
|
33
39
|
|
40
|
+
# Adds a template value to a request class. Equivalent to creating a
|
41
|
+
# parameter with a default value.
|
34
42
|
def []=(key, value)
|
35
43
|
parameter(key, {:value => value})
|
36
44
|
end
|
@@ -38,10 +46,15 @@ module Relax
|
|
38
46
|
|
39
47
|
protected
|
40
48
|
|
49
|
+
# Returns an array of the parameter names for this request.
|
41
50
|
def keys
|
42
51
|
instance_variables.collect { |v| v.sub('@', '') }
|
43
52
|
end
|
44
53
|
|
54
|
+
# Converts a key when the Request is converted to a query. By default, no
|
55
|
+
# conversion actually takes place, but this method can be overridden by
|
56
|
+
# child classes to perform standard manipulations, such as replacing
|
57
|
+
# underscores.
|
45
58
|
def convert_key(key)
|
46
59
|
key
|
47
60
|
end
|
data/lib/relax/response.rb
CHANGED
@@ -4,9 +4,18 @@ require 'hpricot'
|
|
4
4
|
require 'date'
|
5
5
|
|
6
6
|
module Relax
|
7
|
+
# Response is intended to be a parent class for responses passed to
|
8
|
+
# Service#call.
|
9
|
+
#
|
10
|
+
# A response is in essence an object used to facilitate XML parsing. It
|
11
|
+
# stores an XML document, and provides access to it through methods like
|
12
|
+
# #element and #attribute.
|
7
13
|
class Response
|
8
14
|
attr_accessor :xml
|
9
15
|
|
16
|
+
# New takes in the XML from the response. For the initial response, this
|
17
|
+
# will be the root element, but child elements may also be passed into
|
18
|
+
# Response objects.
|
10
19
|
def initialize(xml)
|
11
20
|
@xml = Hpricot.XML(xml.to_s)
|
12
21
|
|
@@ -29,7 +38,10 @@ module Relax
|
|
29
38
|
options[:collection].new(element)
|
30
39
|
end
|
31
40
|
else
|
32
|
-
case options[:type]
|
41
|
+
case type = options[:type]
|
42
|
+
when Response
|
43
|
+
value = type.new(node)
|
44
|
+
|
33
45
|
when :float
|
34
46
|
value = float_value(node)
|
35
47
|
|
@@ -47,62 +59,89 @@ module Relax
|
|
47
59
|
end
|
48
60
|
end
|
49
61
|
|
62
|
+
# Returns the root of the XML document.
|
50
63
|
def root
|
51
64
|
@xml.root
|
52
65
|
end
|
53
66
|
|
67
|
+
# Checks the name of the root node.
|
54
68
|
def is?(name)
|
55
69
|
root.name.gsub(/.*:(.*)/, '\1') == node_name(name)
|
56
70
|
end
|
57
71
|
|
72
|
+
# Returns an element of the specified name.
|
58
73
|
def element(name)
|
59
74
|
root.at(root_path(name))
|
60
75
|
end
|
76
|
+
alias :has? :element
|
61
77
|
|
78
|
+
# Returns an attribute on an element.
|
62
79
|
def attribute(element, name)
|
63
80
|
element[name]
|
64
81
|
end
|
65
82
|
|
83
|
+
# Returns a set of elements matching name.
|
66
84
|
def elements(name)
|
67
85
|
root.search(root_path(name))
|
68
86
|
end
|
69
87
|
|
88
|
+
# Gets the value of an element or attribute.
|
70
89
|
def value(value)
|
71
90
|
value.is_a?(Hpricot::Elem) ? value.inner_text : value.to_s
|
72
91
|
end
|
73
92
|
|
93
|
+
# Gets a text value.
|
74
94
|
def text_value(value)
|
75
95
|
value(value)
|
76
96
|
end
|
77
97
|
|
98
|
+
# Gets an integer value.
|
78
99
|
def integer_value(value)
|
79
100
|
value(value).to_i
|
80
101
|
end
|
81
102
|
|
103
|
+
# Gets a float value.
|
82
104
|
def float_value(value)
|
83
105
|
value(value).to_f
|
84
106
|
end
|
85
107
|
|
108
|
+
# Gets a date value.
|
86
109
|
def date_value(value)
|
87
110
|
Date.parse(value(value))
|
88
111
|
end
|
89
112
|
|
90
|
-
alias :has? :element
|
91
|
-
|
92
113
|
class << self
|
114
|
+
# Specifes a parameter that will be automatically parsed when the
|
115
|
+
# Response is instantiated.
|
116
|
+
#
|
117
|
+
# Options:
|
118
|
+
# - <tt>:attribute</tt>: An attribute name to use, or <tt>true</tt> to
|
119
|
+
# use the <tt>:element</tt> value as the attribute name on the root.
|
120
|
+
# - <tt>:collection</tt>: A class used to instantiate each item when
|
121
|
+
# selecting a collection of elements.
|
122
|
+
# - <tt>:element</tt>: The XML element name.
|
123
|
+
# - <tt>:object</tt>: A class used to instantiate an element.
|
124
|
+
# - <tt>:type</tt>: The type of the parameter. Should be one of
|
125
|
+
# <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>, or <tt>:date</tt>.
|
93
126
|
def parameter(name, options = {})
|
94
127
|
attr_accessor name
|
95
128
|
@parameters ||= {}
|
96
129
|
@parameters[name] = options
|
97
130
|
end
|
131
|
+
|
132
|
+
def ===(response)
|
133
|
+
response.is_a?(Class) ? response.ancestors.include?(self) : super
|
134
|
+
end
|
98
135
|
end
|
99
136
|
|
100
137
|
private
|
101
138
|
|
139
|
+
# Converts a name to a node name.
|
102
140
|
def node_name(name)
|
103
|
-
name.to_s
|
141
|
+
name.to_s
|
104
142
|
end
|
105
143
|
|
144
|
+
# Gets the XPath expression representing the root node.
|
106
145
|
def root_path(name)
|
107
146
|
"/#{node_name(name)}"
|
108
147
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'net/https'
|
3
|
+
require 'uri'
|
4
|
+
require 'date'
|
5
|
+
require 'base64'
|
6
|
+
require 'erb'
|
7
|
+
|
8
|
+
module Relax
|
9
|
+
# Service is the starting point for any REST consumer written with Relax. It
|
10
|
+
# is responsible for setting up the endpoint for the service, and issuing the
|
11
|
+
# HTTP requests for each call made.
|
12
|
+
#
|
13
|
+
# == Extending Service
|
14
|
+
#
|
15
|
+
# When writing consumers, you should start by extending Service by inheriting
|
16
|
+
# from it and calling its constructor with the endpoint for the service.
|
17
|
+
#
|
18
|
+
# === Example
|
19
|
+
#
|
20
|
+
# class Service < Relax::Service
|
21
|
+
# ENDPOINT = 'http://example.com/services/rest/'
|
22
|
+
#
|
23
|
+
# def initialize
|
24
|
+
# super(ENDPOINT)
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# == Calling a Service
|
29
|
+
#
|
30
|
+
# Each call made to the service goes through the #call method of Service,
|
31
|
+
# which takes in a Request object and a Response class. The Request object is
|
32
|
+
# used to generate the query string that will be passed to the endpoint. The
|
33
|
+
# Reponse class is instantiated with the body of the response from the HTTP
|
34
|
+
# request.
|
35
|
+
#
|
36
|
+
# === Example
|
37
|
+
#
|
38
|
+
# This example show how to create a barebones call. This module can be then
|
39
|
+
# included into your Service class.
|
40
|
+
#
|
41
|
+
# module Search
|
42
|
+
# class SearchRequest < Relax::Request
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# class SearchResponse < Relax::Response
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# def search(options = {})
|
49
|
+
# call(SearchRequest.new(options), SearchResponse)
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
class Service
|
54
|
+
attr_reader :endpoint
|
55
|
+
|
56
|
+
# This constructor should be called from your Service with the endpoint URL
|
57
|
+
# for the REST service.
|
58
|
+
def initialize(endpoint)
|
59
|
+
@endpoint = URI::parse(endpoint)
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# Calls the service using a query built from the Request object passed in
|
65
|
+
# as its first parameter. Once the response comes back from the service,
|
66
|
+
# the body of the response is used to instantiate the response class, and
|
67
|
+
# this response object is returned.
|
68
|
+
def call(request, response_class)
|
69
|
+
uri = @endpoint.clone
|
70
|
+
uri.query = query(request).to_s
|
71
|
+
response = request(uri)
|
72
|
+
puts "Response:\n#{response.body}\n\n" if $DEBUG
|
73
|
+
response_class.new(response.body)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def default_query
|
79
|
+
Query.new
|
80
|
+
end
|
81
|
+
|
82
|
+
def query(request)
|
83
|
+
Query.new(default_query.merge(request.to_query))
|
84
|
+
end
|
85
|
+
|
86
|
+
def request(uri)
|
87
|
+
puts "Request:\n#{uri.to_s}\n\n" if $DEBUG
|
88
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
89
|
+
|
90
|
+
if uri.scheme == 'https'
|
91
|
+
http.use_ssl = true
|
92
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
93
|
+
end
|
94
|
+
|
95
|
+
http.start do |http|
|
96
|
+
request = Net::HTTP::Get.new("#{uri.path}?#{uri.query}")
|
97
|
+
http.request(request)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/relax/symbolic_hash.rb
CHANGED
@@ -1,4 +1,20 @@
|
|
1
1
|
module Relax
|
2
|
+
# SymbolicHash provides an extension of Hash, but one that only supports keys
|
3
|
+
# that are symbols. This has been done in an effort to prevent the case where
|
4
|
+
# both a string key and a symbol key are set on the same hash, and espcially
|
5
|
+
# for dealing with this particular case when convert the hash to a string.
|
6
|
+
#
|
7
|
+
# === Example
|
8
|
+
#
|
9
|
+
# hash = Relax::SymbolicHash.new
|
10
|
+
# hash[:one] = 1
|
11
|
+
# hash['one'] = 2
|
12
|
+
# puts hash[:one] # => 2
|
13
|
+
#
|
14
|
+
# === Credits
|
15
|
+
#
|
16
|
+
# Some of the inspiration (and code) for this class comes from the
|
17
|
+
# HashWithIndifferentAccess that ships with Rails.
|
2
18
|
class SymbolicHash < Hash
|
3
19
|
def initialize(constructor = {})
|
4
20
|
if constructor.is_a?(Hash)
|
@@ -21,8 +37,7 @@ module Relax
|
|
21
37
|
other_hash.each_pair { |key, value| store(convert_key(key), convert_value(value)) }
|
22
38
|
self
|
23
39
|
end
|
24
|
-
|
25
|
-
alias_method :merge!, :update
|
40
|
+
alias :merge! :update
|
26
41
|
|
27
42
|
def fetch(key, *extras)
|
28
43
|
super(convert_key(key), *extras)
|
@@ -47,10 +62,9 @@ module Relax
|
|
47
62
|
def key?(key)
|
48
63
|
super(convert_key(key))
|
49
64
|
end
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
alias_method :member?, :key?
|
65
|
+
alias :include? :key?
|
66
|
+
alias :has_key? :key?
|
67
|
+
alias :member? :key?
|
54
68
|
|
55
69
|
protected
|
56
70
|
|
data/lib/relax.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
2
|
|
3
|
-
require 'relax/api'
|
4
|
-
require 'relax/symbolic_hash'
|
5
3
|
require 'relax/query'
|
6
4
|
require 'relax/request'
|
7
5
|
require 'relax/response'
|
6
|
+
require 'relax/service'
|
7
|
+
require 'relax/symbolic_hash'
|
data/spec/response_spec.rb
CHANGED
@@ -15,6 +15,10 @@ XML = <<EOF
|
|
15
15
|
</Tokens>
|
16
16
|
<Status>Success</Status>
|
17
17
|
<RequestId valid="true">44287</RequestId>
|
18
|
+
<Error>
|
19
|
+
<Code>1</Code>
|
20
|
+
<Message>Failed</Message>
|
21
|
+
</Error>
|
18
22
|
</RESTResponse>
|
19
23
|
EOF
|
20
24
|
|
@@ -24,10 +28,16 @@ class TestResponse < Relax::Response
|
|
24
28
|
parameter :status
|
25
29
|
end
|
26
30
|
|
31
|
+
class Error < Relax::Response
|
32
|
+
parameter :code, :type => :integer
|
33
|
+
parameter :message
|
34
|
+
end
|
35
|
+
|
27
36
|
parameter :status
|
28
37
|
parameter :request_id, :element => :requestid, :type => :integer
|
29
38
|
parameter :valid_request, :element => :requestid, :attribute => :valid
|
30
39
|
parameter :tokens, :collection => Token
|
40
|
+
parameter :error, :type => Error
|
31
41
|
end
|
32
42
|
|
33
43
|
describe 'a response' do
|
@@ -38,7 +48,7 @@ describe 'a response' do
|
|
38
48
|
it 'should allow access to the root' do
|
39
49
|
root = @response.root
|
40
50
|
root.should be_an_instance_of(Hpricot::Elem)
|
41
|
-
root.name.should eql('
|
51
|
+
root.name.should eql('RESTResponse')
|
42
52
|
end
|
43
53
|
|
44
54
|
it 'should be checkable by the name of its root' do
|
@@ -66,7 +76,7 @@ describe 'a response' do
|
|
66
76
|
@response.has?(:Errors).should be_nil
|
67
77
|
end
|
68
78
|
|
69
|
-
it 'should be able to define children of
|
79
|
+
it 'should be able to define children of Response without modifying parent' do
|
70
80
|
Relax::Response.new(XML).respond_to?(:status).should be_false
|
71
81
|
TestResponse.new(XML).respond_to?(:status).should be_true
|
72
82
|
end
|
@@ -78,5 +88,11 @@ describe 'a response' do
|
|
78
88
|
response.valid_request.should eql('true')
|
79
89
|
response.tokens.length.should eql(2)
|
80
90
|
response.tokens.first.status.should eql('Active')
|
91
|
+
response.error.code.should eql(1)
|
92
|
+
response.error.message.should eql('Failed')
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should be relationally equivalent to its children' do
|
96
|
+
(Relax::Response === TestResponse).should be_true
|
81
97
|
end
|
82
98
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
|
|
3
3
|
specification_version: 1
|
4
4
|
name: relax
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.0.2
|
7
|
+
date: 2007-10-10 00:00:00 -04:00
|
8
8
|
summary: A simple library for creating REST consumers.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -32,8 +32,8 @@ files:
|
|
32
32
|
- lib/relax.rb
|
33
33
|
- lib/relax
|
34
34
|
- lib/relax/response.rb
|
35
|
-
- lib/relax/api.rb
|
36
35
|
- lib/relax/request.rb
|
36
|
+
- lib/relax/service.rb
|
37
37
|
- lib/relax/query.rb
|
38
38
|
- lib/relax/symbolic_hash.rb
|
39
39
|
- README
|
@@ -62,5 +62,5 @@ dependencies:
|
|
62
62
|
requirements:
|
63
63
|
- - ">="
|
64
64
|
- !ruby/object:Gem::Version
|
65
|
-
version: "0.
|
65
|
+
version: "0.6"
|
66
66
|
version:
|
data/lib/relax/api.rb
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
require 'openssl'
|
2
|
-
require 'net/https'
|
3
|
-
require 'uri'
|
4
|
-
require 'date'
|
5
|
-
require 'base64'
|
6
|
-
require 'erb'
|
7
|
-
|
8
|
-
module Relax
|
9
|
-
class API
|
10
|
-
attr_reader :endpoint
|
11
|
-
|
12
|
-
def initialize(endpoint)
|
13
|
-
@endpoint = URI::parse(endpoint)
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def default_query
|
19
|
-
Query.new
|
20
|
-
end
|
21
|
-
|
22
|
-
def query(request)
|
23
|
-
Query.new(default_query.merge(request.to_query))
|
24
|
-
end
|
25
|
-
|
26
|
-
def call(request, response_class)
|
27
|
-
uri = @endpoint.clone
|
28
|
-
uri.query = query(request).to_s
|
29
|
-
response = request(uri)
|
30
|
-
puts "Response:\n#{response.body}\n\n" if $DEBUG
|
31
|
-
response_class.new(response.body)
|
32
|
-
end
|
33
|
-
|
34
|
-
def request(uri)
|
35
|
-
puts "Request:\n#{uri.to_s}\n\n" if $DEBUG
|
36
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
37
|
-
|
38
|
-
if uri.scheme == 'https'
|
39
|
-
http.use_ssl = true
|
40
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
41
|
-
end
|
42
|
-
|
43
|
-
http.start do |http|
|
44
|
-
request = Net::HTTP::Get.new("#{uri.path}?#{uri.query}")
|
45
|
-
http.request(request)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|