remote_api 0.1.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.
- data/History.txt +0 -0
- data/Manifest.txt +20 -0
- data/README.txt +3 -0
- data/Rakefile +49 -0
- data/lib/dsl_accessor.rb +50 -0
- data/lib/remote_api/base.rb +120 -0
- data/lib/remote_api/version.rb +9 -0
- data/lib/remote_api/xml.rb +19 -0
- data/lib/remote_api/xml_response.rb +34 -0
- data/lib/remote_api.rb +13 -0
- data/setup.rb +1585 -0
- data/test/base_test.rb +58 -0
- data/test/test_helper.rb +7 -0
- data/test/xml_response_test.rb +77 -0
- data/test/xml_test.rb +63 -0
- metadata +70 -0
data/History.txt
ADDED
File without changes
|
data/Manifest.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
|
5
|
+
Rakefile
|
6
|
+
setup.rb
|
7
|
+
|
8
|
+
lib/dsl_accessor.rb
|
9
|
+
lib/remote_api.rb
|
10
|
+
|
11
|
+
lib/remote_api/base.rb
|
12
|
+
lib/remote_api/xml.rb
|
13
|
+
lib/remote_api/xml_response.rb
|
14
|
+
|
15
|
+
lib/remote_api/version.rb
|
16
|
+
|
17
|
+
test/test_helper.rb
|
18
|
+
test/base_test.rb
|
19
|
+
test/xml_test.rb
|
20
|
+
test/xml_response_test.rb
|
data/README.txt
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'hoe'
|
11
|
+
include FileUtils
|
12
|
+
require File.join(File.dirname(__FILE__), 'lib', 'remote_api', 'version')
|
13
|
+
|
14
|
+
AUTHOR = "Alex Wayne"
|
15
|
+
EMAIL = "rubyonrails@beautifulpixel.com"
|
16
|
+
DESCRIPTION = "Provides a basic framework for easily creating classes that access remote APIs."
|
17
|
+
GEM_NAME = "remote_api" # what ppl will type to install your gem
|
18
|
+
RUBYFORGE_PROJECT = "remote-api" # The unix name for your project
|
19
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
+
RELEASE_TYPES = %w( gem ) # can use: gem, tar, zip
|
21
|
+
|
22
|
+
|
23
|
+
NAME = "remote_api"
|
24
|
+
REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
25
|
+
VERS = ENV['VERSION'] || (RemoteAPI::VERSION::STRING + (REV ? ".#{REV}" : ""))
|
26
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
27
|
+
RDOC_OPTS = ['--quiet', '--title', "remote_api documentation",
|
28
|
+
"--opname", "index.html",
|
29
|
+
"--line-numbers",
|
30
|
+
"--main", "README",
|
31
|
+
"--inline-source"]
|
32
|
+
|
33
|
+
# Generate all the Rake tasks
|
34
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
35
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
36
|
+
p.author = AUTHOR
|
37
|
+
p.description = DESCRIPTION
|
38
|
+
p.email = EMAIL
|
39
|
+
p.summary = DESCRIPTION
|
40
|
+
p.url = HOMEPATH
|
41
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
42
|
+
p.test_globs = ["test/**/*_test.rb"]
|
43
|
+
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
44
|
+
|
45
|
+
# == Optional
|
46
|
+
#p.changes - A description of the release's latest changes.
|
47
|
+
p.extra_deps = %w( activesupport )
|
48
|
+
#p.spec_extras - A hash of extra values to set in the gemspec.
|
49
|
+
end
|
data/lib/dsl_accessor.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
class Class
|
2
|
+
def dsl_accessor(name, options = {})
|
3
|
+
raise TypeError, "DSL Error: options should be a hash. but got `#{options.class}'" unless options.is_a?(Hash)
|
4
|
+
writer = options[:writer] || options[:setter]
|
5
|
+
writer =
|
6
|
+
case writer
|
7
|
+
when NilClass then Proc.new{|value| value}
|
8
|
+
when Symbol then Proc.new{|value| __send__(writer, value)}
|
9
|
+
when Proc then writer
|
10
|
+
else raise TypeError, "DSL Error: writer should be a symbol or proc. but got `#{options[:writer].class}'"
|
11
|
+
end
|
12
|
+
write_inheritable_attribute(:"#{name}_writer", writer)
|
13
|
+
|
14
|
+
default =
|
15
|
+
case options[:default]
|
16
|
+
when NilClass then nil
|
17
|
+
when [] then Proc.new{[]}
|
18
|
+
when {} then Proc.new{{}}
|
19
|
+
when Symbol then Proc.new{__send__(options[:default])}
|
20
|
+
when Proc then options[:default]
|
21
|
+
else Proc.new{options[:default]}
|
22
|
+
end
|
23
|
+
write_inheritable_attribute(:"#{name}_default", default)
|
24
|
+
|
25
|
+
self.class.class_eval do
|
26
|
+
define_method("#{name}=") do |value|
|
27
|
+
writer = read_inheritable_attribute(:"#{name}_writer")
|
28
|
+
value = writer.call(value) if writer
|
29
|
+
write_inheritable_attribute(:"#{name}", value)
|
30
|
+
end
|
31
|
+
|
32
|
+
define_method(name) do |*values|
|
33
|
+
if values.empty?
|
34
|
+
# getter method
|
35
|
+
key = :"#{name}"
|
36
|
+
if !inheritable_attributes.has_key?(key)
|
37
|
+
default = read_inheritable_attribute(:"#{name}_default")
|
38
|
+
value = default ? default.call(self) : nil
|
39
|
+
__send__("#{name}=", value)
|
40
|
+
end
|
41
|
+
read_inheritable_attribute(key)
|
42
|
+
else
|
43
|
+
# setter method
|
44
|
+
__send__("#{name}=", *values)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
class RemoteAPI
|
2
|
+
class ResponseFailure < RuntimeError; end
|
3
|
+
|
4
|
+
dsl_accessor :debug
|
5
|
+
dsl_accessor :url
|
6
|
+
dsl_accessor :content_type
|
7
|
+
|
8
|
+
class ResponseFailure < RuntimeError; end
|
9
|
+
|
10
|
+
# Eeverything happens on instantiation. Pass in a hash, and each key in the hash will
|
11
|
+
# become an instance variable for use in the rest of the instance methods.
|
12
|
+
#
|
13
|
+
# Then we send the request, assert that is the response is successful, and process the
|
14
|
+
# response.
|
15
|
+
def initialize(params = {})
|
16
|
+
params.each do |k, v|
|
17
|
+
instance_variable_set "@#{k}", v
|
18
|
+
end
|
19
|
+
|
20
|
+
send_request
|
21
|
+
assert_success
|
22
|
+
process
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Send the request to the UPS servers
|
28
|
+
def send_request(request_body = nil)
|
29
|
+
request_body ||= request
|
30
|
+
debug_request(request_body)
|
31
|
+
|
32
|
+
# Setup HTTP objects
|
33
|
+
url = URI.parse(server_url)
|
34
|
+
http = Net::HTTP.new(url.host, url.port)
|
35
|
+
|
36
|
+
if url.is_a?(URI::HTTPS)
|
37
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
38
|
+
http.use_ssl = true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Setup request options
|
42
|
+
options = {}
|
43
|
+
options['ContentType'] = self.class.content_type if self.class.content_type
|
44
|
+
|
45
|
+
# Send Request
|
46
|
+
response = http.post(url.path, request_body, options)
|
47
|
+
debug_response(response.body, url)
|
48
|
+
|
49
|
+
# Convert response to proper format
|
50
|
+
@response = prepare_response(response)
|
51
|
+
|
52
|
+
# Code hook to raise execption if error conditions are met
|
53
|
+
assert_success
|
54
|
+
end
|
55
|
+
|
56
|
+
def assert_success
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def process(*args)
|
61
|
+
raise 'You must define a "process" method! This should should parse the @response and' +
|
62
|
+
'extract relevant data.'
|
63
|
+
end
|
64
|
+
|
65
|
+
def request(*args)
|
66
|
+
raise 'You must define a "request" method! This should create the body of your API request'
|
67
|
+
end
|
68
|
+
|
69
|
+
def prepare_response(response)
|
70
|
+
response.body
|
71
|
+
end
|
72
|
+
|
73
|
+
def server_url
|
74
|
+
url = self.class.url
|
75
|
+
|
76
|
+
case url.class.to_s
|
77
|
+
when 'String'
|
78
|
+
url
|
79
|
+
when 'Hash'
|
80
|
+
url.symbolize_keys[ENV['RAILS_ENV'].to_sym]
|
81
|
+
when 'Proc'
|
82
|
+
url.call
|
83
|
+
else
|
84
|
+
raise "url attribute must be a String, Proc or Hash. Got #{url.class}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def format_response(response)
|
89
|
+
response
|
90
|
+
end
|
91
|
+
|
92
|
+
def debug_request(request_body)
|
93
|
+
return unless self.class.debug
|
94
|
+
puts <<DEBUG
|
95
|
+
|
96
|
+
-----------
|
97
|
+
- REQUEST -
|
98
|
+
-----------
|
99
|
+
|
100
|
+
#{request_body}
|
101
|
+
DEBUG
|
102
|
+
end
|
103
|
+
|
104
|
+
def debug_response(response, url)
|
105
|
+
return unless self.class.debug
|
106
|
+
|
107
|
+
parsed_response = format_response(response)
|
108
|
+
|
109
|
+
puts <<DEBUG
|
110
|
+
|
111
|
+
------------"
|
112
|
+
- RESPONSE -"
|
113
|
+
------------"
|
114
|
+
--- From: #{url}"
|
115
|
+
|
116
|
+
#{parsed_response}
|
117
|
+
|
118
|
+
DEBUG
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class RemoteAPI
|
2
|
+
class XML < RemoteAPI
|
3
|
+
|
4
|
+
include REXML
|
5
|
+
|
6
|
+
content_type 'application/xml'
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def prepare_response(response)
|
11
|
+
Response.new(response.body)
|
12
|
+
end
|
13
|
+
|
14
|
+
def format_response(response)
|
15
|
+
Response.new(response).to_formatted_s
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class RemoteAPI
|
2
|
+
class XML
|
3
|
+
class Response
|
4
|
+
|
5
|
+
def initialize(data)
|
6
|
+
@data = REXML::Document.new(data.to_s).root
|
7
|
+
end
|
8
|
+
|
9
|
+
# equivalent to:
|
10
|
+
# xml.elements[xpath].text
|
11
|
+
def [](xpath)
|
12
|
+
if element = @data.elements[xpath]
|
13
|
+
element.text
|
14
|
+
else
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# equivalent to:
|
20
|
+
# xml.each_element(xpath) { |node| ... }
|
21
|
+
def each(xpath)
|
22
|
+
raise ArgumentError, 'You must supply a block to the "each" method' unless block_given?
|
23
|
+
@data.each_element(xpath) do |node|
|
24
|
+
yield self.class.new(node)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_formatted_s
|
29
|
+
output = ''
|
30
|
+
@data.write(output, 0)
|
31
|
+
end
|
32
|
+
end # Response
|
33
|
+
end # XML
|
34
|
+
end # RemoteAPI
|
data/lib/remote_api.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'rexml/document'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
require 'active_support'
|
6
|
+
|
7
|
+
# Why isn't this a gem?
|
8
|
+
require File.join(File.dirname(__FILE__), 'dsl_accessor')
|
9
|
+
|
10
|
+
# remote_api files
|
11
|
+
require 'remote_api/base'
|
12
|
+
require 'remote_api/xml'
|
13
|
+
require 'remote_api/xml_response'
|