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 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
@@ -0,0 +1,3 @@
1
+ README for remote_api
2
+ =====================
3
+
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
@@ -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,9 @@
1
+ class RemoteAPI #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ 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'