resthome 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem "httparty", ">= 0"
6
+
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development do
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.1"
12
+ gem "rcov", ">= 0"
13
+ gem "fakeweb", ">= 0"
14
+ gem "json", ">= 0"
15
+ gem "rspec", ">= 0"
16
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ crack (0.1.8)
5
+ fakeweb (1.2.8)
6
+ git (1.2.5)
7
+ httparty (0.6.1)
8
+ crack (= 0.1.8)
9
+ jeweler (1.5.1)
10
+ bundler (~> 1.0.0)
11
+ git (>= 1.2.5)
12
+ rake
13
+ json (1.2.4)
14
+ rake (0.8.7)
15
+ rcov (0.9.8)
16
+ rspec (1.3.0)
17
+
18
+ PLATFORMS
19
+ ruby
20
+
21
+ DEPENDENCIES
22
+ bundler (~> 1.0.0)
23
+ fakeweb
24
+ httparty
25
+ jeweler (~> 1.5.1)
26
+ json
27
+ rcov
28
+ rspec
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Doug Youch
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.rdoc ADDED
@@ -0,0 +1,80 @@
1
+ = RESTHome
2
+
3
+ Simple wrapper class generator for consuming RESTful web services
4
+
5
+ == RESTful Example
6
+
7
+ RESTHome's are used to communicate to RESTful Web Services.
8
+
9
+ Let's say you are working with B2B.dev. They provide a simple RESTful API for interacting with customer data.
10
+
11
+ API looks like
12
+ GET http://api.b2b.dev/customers.json - list of your customers
13
+ GET http://api.b2b.dev/customers/<id>.json - customer data
14
+ PUT http://api.b2b.dev/customers/<id>.json - edit customer data
15
+ DELETE http://api.b2b.dev/customers/<id>.json - delete customer
16
+ POST http://api.b2b.dev/customers.json - create a new customer
17
+
18
+ JSON response looks like {'customer': {'id': 99, 'first_name': 'Joe', 'last_name': 'Smith'}}
19
+
20
+ Create a simple RESTHome service to interact with B2B.dev api.
21
+
22
+ class B2BService < RESTHome
23
+ rest :customer, :customers, '/customers.json'
24
+
25
+ def initialize(api_key)
26
+ self.base_uri = 'http://api.b2b.dev'
27
+ self.basic_auth = {:username => api_key, :password => 'x'}
28
+ end
29
+ end
30
+
31
+ service = B2BService.new 'XXXXX'
32
+ service.customers # returns an array of customers
33
+ customer = service.customer 99 # returns the data for customer 99, i.e. {:first_name => 'Joe', :last_name => 'Smith'}
34
+ service.edit_customer 99, :first_name => 'Joesph', :last_name => 'Smithie' # edits customer 99
35
+ service.delete_customer 99 # deletes customer 99
36
+ service.create_customer :first_name => 'John', :last_name => 'Doe' # creates a new customer
37
+
38
+ == Lorem Lipsum Example
39
+
40
+ Create a simple lorem lipsum generator, using http://www.lipsum.com.
41
+
42
+ lipsum = RESTHome.new
43
+ lipsum.base_uri = 'http://www.lipsum.com'
44
+ lipsum.route :generate, '/feed/json', :method => :post
45
+ words = lipsum.generate(:what => 'words', :amount => 20) do |res|
46
+ res['feed']['lipsum']
47
+ end
48
+
49
+
50
+ class LoremLipsumService < RESTHome
51
+ route :generate, '/feed/json', :method => :post, :return => :parse_feed
52
+
53
+ def initialize
54
+ self.base_uri = 'http://www.lipsum.com'
55
+ end
56
+
57
+ def parse_feed(res)
58
+ res['feed']['lipsum']
59
+ end
60
+ end
61
+
62
+ service = LoremLipsumService.new
63
+ words = service.generate(:what => 'words', :amount => 20)
64
+
65
+
66
+ == Contributing to RESTHome
67
+
68
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
69
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
70
+ * Fork the project
71
+ * Start a feature/bugfix branch
72
+ * Commit and push until you are happy with your contribution
73
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
74
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
75
+
76
+ == Copyright
77
+
78
+ Copyright (c) 2010 Doug Youch. See LICENSE.txt for
79
+ further details.
80
+
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "resthome"
16
+ gem.homepage = "http://github.com/cykod/resthome"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{RESTful web services consumer}
19
+ gem.description = %Q{Simple wrapper class generator for consuming RESTful web services}
20
+ gem.email = "doug@cykod.com"
21
+ gem.authors = ["Doug Youch"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ gem.add_runtime_dependency 'httparty', '>= 0'
26
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
27
+ gem.add_development_dependency "bundler", "~> 1.0.0"
28
+ gem.add_development_dependency "jeweler", "~> 1.5.1"
29
+ gem.add_development_dependency "rcov", ">= 0"
30
+ gem.add_development_dependency "fakeweb", ">= 0"
31
+ gem.add_development_dependency "json", ">= 0"
32
+ gem.add_development_dependency "rspec", ">= 0"
33
+ end
34
+ Jeweler::RubygemsDotOrgTasks.new
35
+
36
+ task :default => :spec
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
41
+
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "resthome #{version}"
44
+ rdoc.rdoc_files.include('README*')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
47
+
48
+ for file in Dir['tasks/*.rake']
49
+ load file
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.6.0
@@ -0,0 +1,53 @@
1
+ require 'resthome'
2
+ require 'json'
3
+
4
+ class ChargifyWebService < RESTHome
5
+
6
+ headers 'Content-Type' => 'application/json'
7
+
8
+ rest :customer, :customers, '/customers.json'
9
+ route :find_customer, '/customers/lookup.json', :resource => 'customer'
10
+ # find_customer_by_reference
11
+ route :customer_subscriptions, '/customers/:customer_id/subscriptions.json', :resource => :subscription
12
+
13
+ route :product_families, '/product_families.xml', :resource => :product_families
14
+
15
+ route :products, '/products.json', :resource => :product
16
+ route :product, '/products/:product_id.json', :resource => :product
17
+ route :find_product_by_handle, '/products/handle/:handle.json', :resource => :product
18
+
19
+ rest :subscription, :subscriptions, '/subscriptions.json'
20
+ route :cancel_subscription, '/subscriptions/:subscription_id.json', :resource => :subscription, :method => :delete, :expected_status => [200, 204]
21
+ route :reactivate_subscription, '/subscriptions/:subscription_id/reactivate.xml', :resource => :subscription, :method => :put, :expected_status => 200
22
+ route :subscription_transactions, '/subscriptions/:subscription_id/transactions.json', :resource => :transaction
23
+ # Chargify offers the ability to upgrade or downgrade a Customer's subscription in the middle of a billing period.
24
+ route :create_subscription_migration, '/subscriptions/:subscription_id/migrations.json', :expected_status => 200
25
+ route :reactivate_subscription, '/subscriptions/:subscription_id/reactivate.xml', :resource => :subscription, :method => :put, :expected_status => 200
26
+ route :create_subscription_credit, '/subscriptions/:subscription_id/credits.json', :resource => :credit
27
+ route :reset_subscription_balance, '/subscriptions/:subscription_id/reset_balance.xml', :resource => :subscription, :method => :put, :expected_status => 200
28
+
29
+ route :transactions, '/transactions.json', :resource => :transaction
30
+
31
+ route :create_charge, '/subscriptions/:subscription_id/charges.json', :resource => :charge
32
+
33
+ route :components, '/product_families/:product_family_id/components.json', :resource => :component
34
+ route :component_usages, '/subscriptions/:subscription_id/components/:component_id/usages.json', :resource => :usage
35
+ route :create_component_usage, '/subscriptions/:subscription_id/components/:component_id/usages.json', :resource => :usage, :expected_status => 200
36
+
37
+ route :coupon, '/product_families/:product_family_id/coupons/:coupon_id.json', :resource => :coupon
38
+ route :find_coupon, '/product_families/:product_family_id/coupons/find.json', :resource => :coupon
39
+ # find_coupon_by_code
40
+
41
+ route :subscription_components, '/subscriptions/:subscription_id/components.json', :resource => :component
42
+ route :edit_subscription_component, '/subscriptions/:subscription_id/components/:component_id.json', :resource => :component
43
+
44
+ def initialize(api_key, subdomain)
45
+ self.base_uri = "https://#{subdomain}.chargify.com"
46
+ self.basic_auth = {:username => api_key, :password => 'x'}
47
+ end
48
+
49
+ def build_options!(options)
50
+ super
51
+ options[:body] = options[:body].to_json if options[:body]
52
+ end
53
+ end
@@ -0,0 +1,89 @@
1
+ require 'resthome'
2
+ require 'nokogiri'
3
+
4
+ class WordpressWebService < RESTHome
5
+ attr_accessor :error
6
+
7
+ headers 'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/4.0 (.NET CLR 3.5.30729)'
8
+
9
+ def initialize(url, username, password)
10
+ @uri = URI.parse(url.gsub(/\/$/, ''))
11
+ @username = username
12
+ @password = password
13
+ self.base_uri = "#{@uri.scheme}://#{@uri.host}:#{@uri.port}"
14
+
15
+ self.route :wp_login_get, "#{@uri.path}/wp-login.php", :method => :get, :return => :parse_login_page
16
+ self.route :wp_login_post, "#{@uri.path}/wp-login.php", :method => :post, :return => :parse_login_page
17
+
18
+ self.route :wp_export_get, "#{@uri.path}/wp-admin/export.php", :method => :get, :return => :parse_export_form
19
+ self.route :wp_export, "#{@uri.path}/wp-admin/export.php", :method => :get
20
+ end
21
+
22
+ def parse_login_page(response)
23
+ return @error = 'Login page not found' unless response.code == 200
24
+
25
+ parent = Nokogiri::HTML.parse(response.body).css('body').first
26
+ login_form = parent.css('#loginform')
27
+ return @error = 'Login form not found' unless login_form.size > 0
28
+
29
+ login_error = parent.css('#login_error')
30
+ return @error = 'Login failed' if login_error.size > 0
31
+
32
+ login_form = login_form.first
33
+ @inputs = {}
34
+ login_form.css('input').each do |input|
35
+ next unless input.attributes['name']
36
+ @inputs[input.attributes['name'].to_s] = (input.attributes['value'] || '').to_s
37
+ end
38
+ end
39
+
40
+ def login
41
+ self.wp_login_get
42
+ return false if @inputs.nil?
43
+ @inputs['log'] = @username
44
+ @inputs['pwd'] = @password
45
+ begin
46
+ self.wp_login_post @inputs, :no_follow => true
47
+ rescue HTTParty::RedirectionTooDeep => e
48
+ save_cookies e.response.header.to_hash['set-cookie']
49
+ end
50
+ @error ? false : true
51
+ end
52
+
53
+ def parse_export_form(response)
54
+ parent = Nokogiri::HTML.parse(response.body).css('body').first
55
+ forms = parent.css('form')
56
+ return @error = 'Export form not found' unless forms.size > 0
57
+ export_form = forms.shift
58
+ while export_form && export_form.css('#mm_start').length == 0
59
+ export_form = forms.shift
60
+ end
61
+ return @error = 'Export form not found' unless export_form
62
+
63
+ @inputs = {}
64
+ export_form.css('input').each do |input|
65
+ next unless input.attributes['name']
66
+ @inputs[input.attributes['name'].to_s] = (input.attributes['value'] || '').to_s
67
+ end
68
+
69
+ export_form.css('select').each do |input|
70
+ next unless input.attributes['name']
71
+ value = ''
72
+ input.css('option').each do |option|
73
+ value = option.attributes['value'] if option.attributes['selected']
74
+ end
75
+
76
+ @inputs[input.attributes['name'].to_s] = (value || '').to_s
77
+ end
78
+
79
+ @inputs
80
+ end
81
+
82
+ def export
83
+ self.wp_export_get
84
+ return false if @error
85
+ self.wp_export :query => @inputs, :format => :plain
86
+ return self.response.body if self.response.headers['content-type'].to_s =~ /xml/
87
+ false
88
+ end
89
+ end
data/lib/resthome.rb ADDED
@@ -0,0 +1,287 @@
1
+ # Copyright (C) Doug Youch
2
+
3
+ require 'httparty'
4
+
5
+ class RESTHome
6
+ class Error < Exception; end
7
+ class InvalidResponse < Error; end
8
+ class MethodMissing < Error; end
9
+
10
+ include HTTParty
11
+
12
+ attr_accessor :base_uri, :basic_auth, :cookies
13
+ attr_reader :response, :request_url, :request_options, :request_method
14
+
15
+ # Defines a web service route
16
+ #
17
+ # === Arguments
18
+ # *name* of the method to create
19
+ # name has special meaning.
20
+ # * If starts with create or add the method will be set to POST.
21
+ # * If starts with edit or update the method will be set to PUT.
22
+ # * If starts with delete the method will be set to DELETE.
23
+ # * Else by default the method is GET.
24
+ #
25
+ # *path* is the path to the web service
26
+ #
27
+ # === Options
28
+ #
29
+ # [:method]
30
+ # The request method get/post/put/delete. Default is get.
31
+ # [:expected_status]
32
+ # Expected status code of the response, will raise InvalidResponse. Can be an array of codes.
33
+ # [:return]
34
+ # The method to call or the class to create before method returns.
35
+ # [:resource]
36
+ # The name of the element to return from the response.
37
+ # [:no_body]
38
+ # Removes the body argument from a post/put route
39
+ def self.route(name, path, options={})
40
+ args = path.scan /:[a-z_]+/
41
+ function_args = args.collect{ |arg| arg[1..-1] }
42
+
43
+ method = options[:method]
44
+ expected_status = options[:expected_status]
45
+ if method.nil?
46
+ if name.to_s =~ /^(create|add|edit|update|delete)_/
47
+ case $1
48
+ when 'create'
49
+ method = 'post'
50
+ expected_status ||= [200, 201]
51
+ when 'add'
52
+ method = 'post'
53
+ expected_status ||= [200, 201]
54
+ when 'edit'
55
+ method = 'put'
56
+ expected_status ||= 200
57
+ when 'update'
58
+ method = 'put'
59
+ expected_status ||= 200
60
+ when 'delete'
61
+ method = 'delete'
62
+ expected_status ||= [200, 204]
63
+ end
64
+ else
65
+ method = 'get'
66
+ expected_status ||= 200
67
+ end
68
+ end
69
+
70
+ method = method.to_s
71
+ function_args << 'body' if (method == 'post' || method == 'put') && options[:no_body].nil?
72
+ function_args << 'options={}, &block'
73
+
74
+ method_src = <<-METHOD
75
+ def #{name}(#{function_args.join(',')})
76
+ path = "#{path}"
77
+ METHOD
78
+
79
+ args.each_with_index do |arg, idx|
80
+ method_src << "path.sub! '#{arg}', #{function_args[idx]}.to_s\n"
81
+ end
82
+
83
+ if options[:no_body].nil?
84
+ if method == 'post' || method == 'put'
85
+ if options[:resource]
86
+ method_src << "options[:body] = {'#{options[:resource].to_s}' => body}\n"
87
+ else
88
+ method_src << "options[:body] = body\n"
89
+ end
90
+ end
91
+ end
92
+
93
+ method_src << "request :#{method}, path, options\n"
94
+
95
+ if expected_status
96
+ if expected_status.is_a?(Array)
97
+ method_src << 'raise InvalidResponse.new "Invalid response code #{response.code}" if ! [' + expected_status.join(',') + "].include?(response.code)\n"
98
+ else
99
+ method_src << 'raise InvalidResponse.new "Invalid response code #{response.code}" if response.code != ' + expected_status.to_s + "\n"
100
+ end
101
+ end
102
+
103
+ return_method = 'nil'
104
+ if options[:return].nil?
105
+ return_method = 'nil'
106
+ elsif options[:return].is_a?(Class)
107
+ return_method = options[:return].to_s
108
+ else
109
+ return_method = ":#{options[:return]}"
110
+ end
111
+
112
+ resource = options[:resource] ? "'#{options[:resource]}'" : 'nil'
113
+
114
+ method_src << "parse_response!\n"
115
+
116
+ method_src << "_handle_response response, :resource => #{resource}, :return => #{return_method}, &block\n"
117
+
118
+ method_src << "end\n"
119
+
120
+ if options[:instance]
121
+ options[:instance].instance_eval method_src, __FILE__, __LINE__
122
+ elsif options[:class]
123
+ options[:class].class_eval method_src, __FILE__, __LINE__
124
+ else
125
+ self.class_eval method_src, __FILE__, __LINE__
126
+ end
127
+ end
128
+
129
+ # Adds a route to the current object
130
+ def route(name, path, options={})
131
+ self.class.route name, path, options.merge(:instance => self)
132
+ end
133
+
134
+ # Creates routes for a RESTful API
135
+ #
136
+ # *resource_name* is the name of the items returned by the API,
137
+ # *collection_name* is the plural name of the items,
138
+ # *base_path* is the path to the collection
139
+ #
140
+ # Sets up 5 most common RESTful routes
141
+ #
142
+ # Example
143
+ # /customers.json GET list of customers, POST to create a customer
144
+ # /customers/1.json GET a customers, PUT to edit a customer, DELETE to delete a customer
145
+ # JSON response returns {'customer': {'id':1, 'name':'Joe', ...}}
146
+ #
147
+ # Setup the RESTful routes
148
+ # rest :customer, :customers, '/customers.json'
149
+ # # same as
150
+ # route :customers, '/customers.json', :resource => :customer
151
+ # route :create_customer, '/customers.json', :resource => :customer
152
+ # route :customer, '/customers/:customer_id.json', :resource => :customer
153
+ # route :edit_customer, '/customers/:customer_id.json', :resource => :customer
154
+ # route :delete_customer, '/customers/:customer_id.json', :resource => :customer
155
+ #
156
+ # Following methods are created
157
+ # customers # return an array of customers
158
+ # create_customer :name => 'Smith' # returns {'id' => 2, 'name' => 'Smith'}
159
+ # customer 1 # return data for customer 1
160
+ # edit_customer 1, :name => 'Joesph'
161
+ # delete_customer 1
162
+ def self.rest(resource_name, collection_name, base_path, options={})
163
+ options[:resource] ||= resource_name
164
+ self.route collection_name, base_path, options
165
+ self.route resource_name, base_path.sub(/(\.[a-zA-Z0-9]+)$/, "/:#{resource_name}_id\\1"), options
166
+ self.route "edit_#{resource_name}", base_path.sub(/(\.[a-zA-Z0-9]+)$/, "/:#{resource_name}_id\\1"), options
167
+ self.route "create_#{resource_name}", base_path, options
168
+ self.route "delete_#{resource_name}", base_path.sub(/(\.[a-zA-Z0-9]+)$/, "/:#{resource_name}_id\\1"), options
169
+ end
170
+
171
+ # Creates the url
172
+ def build_url(path)
173
+ "#{self.base_uri}#{path}"
174
+ end
175
+
176
+ # Adds the basic_auth and cookie options
177
+ # This method should be overwritten as needed.
178
+ def build_options!(options)
179
+ options[:basic_auth] = self.basic_auth if self.basic_auth
180
+ if @cookies
181
+ options[:headers] ||= {}
182
+ options[:headers]['cookie'] = @cookies.to_a.collect{|c| "#{c[0]}=#{c[1]}"}.join('; ') + ';'
183
+ end
184
+ end
185
+
186
+ # Makes the request using HTTParty. Saves the method, path and options used.
187
+ def request(method, path, options)
188
+ build_options! options
189
+ url = build_url path
190
+ @request_method = method
191
+ @request_url = url
192
+ @request_options = options
193
+
194
+ @response = self.class.send(method, url, options)
195
+ end
196
+
197
+ # Will either call edit_<name> or add_<name> based on wether or not the body[:id] exists.
198
+ def save(name, body, options={})
199
+ id = body[:id] || body['id']
200
+ if id
201
+ if self.class.method_defined?("edit_#{name}")
202
+ self.send("edit_#{name}", id, body, options)
203
+ elsif self.class.method_defined?("update_#{name}")
204
+ self.send("update_#{name}", id, body, options)
205
+ else
206
+ raise MethodMissing.new "No edit/update method found for #{name}"
207
+ end
208
+ else
209
+ if self.class.method_defined?("add_#{name}")
210
+ self.send("add_#{name}", body, options)
211
+ elsif self.class.method_defined?("create_#{name}")
212
+ self.send("create_#{name}", body, options)
213
+ else
214
+ raise MethodMissing.new "No add/create method found for #{name}"
215
+ end
216
+ end
217
+ end
218
+
219
+ def method_missing(method, *args) #:nodoc:
220
+ if method.to_s =~ /^find_(.*?)_by_(.*)$/
221
+ find_method = "find_#{$1}"
222
+ find_args = $2.split '_and_'
223
+ raise MethodMissing.new "Missing method #{find_method}" unless self.class.method_defined?(find_method)
224
+ start = (self.method(find_method).arity + 1).abs
225
+ options = args[-1].is_a?(Hash) ? args[-1] : {}
226
+ options[:query] ||= {}
227
+ find_args.each_with_index do |find_arg, idx|
228
+ options[:query][find_arg] = args[start+idx]
229
+ end
230
+
231
+ if start > 0
232
+ send_args = args[0..(start-1)]
233
+ send_args << options
234
+ return self.send(find_method, *send_args)
235
+ else
236
+ return self.send(find_method, options)
237
+ end
238
+ else
239
+ super
240
+ end
241
+ end
242
+
243
+ # Convenience method for saving all cookies by default called from parse_response!.
244
+ def save_cookies!
245
+ return unless @response.headers.to_hash['set-cookie']
246
+ save_cookies @response.headers.to_hash['set-cookie']
247
+ end
248
+
249
+ # Parse an array of Set-cookie headers
250
+ def save_cookies(data)
251
+ @cookies ||= {}
252
+ data.collect { |cookie| cookie.split("\; ")[0].split('=') }.each do |c|
253
+ @cookies[c[0]] = c[1]
254
+ end
255
+ end
256
+
257
+ # Called after every valid request. Useful for parsing response headers.
258
+ # This method should be overwritten as needed.
259
+ def parse_response!
260
+ save_cookies!
261
+ end
262
+
263
+ protected
264
+
265
+ def _handle_response(response, opts={}, &block) #:nodoc:
266
+ if response.is_a?(Array)
267
+ response.to_a.collect do |obj|
268
+ _handle_response_object obj, opts, &block
269
+ end
270
+ else
271
+ _handle_response_object response, opts, &block
272
+ end
273
+ end
274
+
275
+ def _handle_response_object(obj, opts={}) #:nodoc:
276
+ obj = obj[opts[:resource]] unless opts[:resource].blank?
277
+ if opts[:return]
278
+ if opts[:return].is_a?(Class)
279
+ obj = opts[:return].new obj
280
+ else
281
+ obj = send opts[:return], obj
282
+ end
283
+ end
284
+ obj = yield(obj) if block_given?
285
+ obj
286
+ end
287
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+
12
+ require 'spec/autorun'
13
+ require 'fakeweb'
14
+ require 'json'
15
+
16
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
17
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
18
+ require 'resthome'
19
+
@@ -0,0 +1,317 @@
1
+ require 'helper'
2
+
3
+ describe RESTHome do
4
+
5
+ before(:each) do
6
+ @service_class = Class.new RESTHome
7
+ FakeWeb.allow_net_connect = false
8
+ end
9
+
10
+ class DummyProduct
11
+ attr_accessor :data
12
+ def initialize(resource)
13
+ raise 'Missing id' unless resource['id']
14
+ @data = resource
15
+ end
16
+ end
17
+
18
+ def fakeweb_response(method, url, code, value)
19
+ FakeWeb.clean_registry
20
+ status = [code.to_s, 'Ok']
21
+ FakeWeb.register_uri(method, url, :status => status, :body => value.to_json, :content_type => 'application/jsonrequest')
22
+ end
23
+
24
+ it "should build the url and options" do
25
+ @service = @service_class.new
26
+ @service.base_uri = 'http://test.dev'
27
+ @service.basic_auth = {:username => 'user', :password => 'x'}
28
+
29
+ @service.build_url('/items.json').should == 'http://test.dev/items.json'
30
+ options = {}
31
+ @service.build_options!(options)
32
+ options.should == {:basic_auth => {:username => 'user', :password => 'x'}}
33
+
34
+ options = {}
35
+ fakeweb_response(:get, 'http://user:x@test.dev/items.json', 200, {})
36
+ @service.request :get, '/items.json', options
37
+ @service.request_method.should == :get
38
+ @service.request_url.should == 'http://test.dev/items.json'
39
+ @service.request_options.should == {:basic_auth => {:username => 'user', :password => 'x'}}
40
+ end
41
+
42
+ it "should be able to create a route using GET" do
43
+ @service_class.route :products, '/products.json'
44
+ @service_class.method_defined?(:products).should be_true
45
+
46
+ @service = @service_class.new
47
+ @service.base_uri = 'http://test.dev'
48
+
49
+ fakeweb_response(:get, 'http://test.dev/products.json', 200,
50
+ [{'product' => {'id' => 1, 'name' => 'Item1'}}, {'product' => {'id' => 2, 'name' => 'Item2'}}])
51
+ @service.products
52
+ @service.request_method.should == :get
53
+ @service.request_url.should == 'http://test.dev/products.json'
54
+ @service.request_options.should == {}
55
+ end
56
+
57
+ it "should be able to create a route using POST" do
58
+ @service_class.route :create_product, '/products.json', :resource => 'product'
59
+ @service_class.route :add_products, '/products.json'
60
+ @service_class.method_defined?(:create_product).should be_true
61
+ @service_class.method_defined?(:add_products).should be_true
62
+
63
+ @service = @service_class.new
64
+ @service.base_uri = 'http://test.dev'
65
+
66
+ # Create
67
+ fakeweb_response(:post, 'http://test.dev/products.json', 201, {'product' => {'id' => 1, 'name' => 'Item1'}})
68
+ @service.create_product :name => 'Item1'
69
+ @service.request_method.should == :post
70
+ @service.request_url.should == 'http://test.dev/products.json'
71
+ @service.request_options.should == {:body => {'product' => {:name => 'Item1'}}}
72
+
73
+ # Add
74
+ fakeweb_response(:post, 'http://test.dev/products.json', 201, {'product' => {'id' => 2, 'name' => 'Item2'}})
75
+ @service.add_products 'product' => {:name => 'Item2'}
76
+ @service.request_method.should == :post
77
+ @service.request_url.should == 'http://test.dev/products.json'
78
+ @service.request_options.should == {:body => {'product' => {:name => 'Item2'}}}
79
+ end
80
+
81
+ it "should be able to create a route using PUT" do
82
+ @service_class.route :edit_product, '/products/:product_id.json', :resource => 'product'
83
+ @service_class.route :update_products, '/products/:product_id.json'
84
+ @service_class.method_defined?(:edit_product).should be_true
85
+ @service_class.method_defined?(:update_products).should be_true
86
+
87
+ @service = @service_class.new
88
+ @service.base_uri = 'http://test.dev'
89
+
90
+ # Edit
91
+ fakeweb_response(:put, 'http://test.dev/products/1.json', 200, {'product' => {'id' => 1, 'name' => 'Item1'}})
92
+ @service.edit_product 1, :name => 'Item1'
93
+ @service.request_method.should == :put
94
+ @service.request_url.should == 'http://test.dev/products/1.json'
95
+ @service.request_options.should == {:body => {'product' => {:name => 'Item1'}}}
96
+
97
+ # Update
98
+ fakeweb_response(:put, 'http://test.dev/products/2.json', 200, {'product' => {'id' => 2, 'name' => 'Item2'}})
99
+ @service.update_products 2, 'product' => {:name => 'Item2'}
100
+ @service.request_method.should == :put
101
+ @service.request_url.should == 'http://test.dev/products/2.json'
102
+ @service.request_options.should == {:body => {'product' => {:name => 'Item2'}}}
103
+ end
104
+
105
+ it "should be able to create a route using DELETE" do
106
+ @service_class.route :delete_product, '/products/:product_id.json'
107
+ @service_class.method_defined?(:delete_product).should be_true
108
+
109
+ @service = @service_class.new
110
+ @service.base_uri = 'http://test.dev'
111
+
112
+ # Delete
113
+ fakeweb_response(:delete, 'http://test.dev/products/1.json', 200, {})
114
+ @service.delete_product 1
115
+ @service.request_method.should == :delete
116
+ @service.request_url.should == 'http://test.dev/products/1.json'
117
+ @service.request_options.should == {}
118
+ end
119
+
120
+ it "should be able to setup RESTful routes" do
121
+ @service_class.rest :product, :products, '/products.json'
122
+ @service_class.method_defined?(:products).should be_true
123
+ @service_class.method_defined?(:product).should be_true
124
+ @service_class.method_defined?(:create_product).should be_true
125
+ @service_class.method_defined?(:edit_product).should be_true
126
+ @service_class.method_defined?(:delete_product).should be_true
127
+
128
+ @service = @service_class.new
129
+ @service.base_uri = 'http://test.dev'
130
+
131
+ # Get products
132
+ fakeweb_response(:get, 'http://test.dev/products.json', 200,
133
+ [{'product' => {'id' => 1, 'name' => 'Item1'}},{'product' => {'id' => 2, 'name' => 'Item2'}}])
134
+ @service.products
135
+ @service.request_method.should == :get
136
+ @service.request_url.should == 'http://test.dev/products.json'
137
+ @service.request_options.should == {}
138
+
139
+ # Get a product
140
+ fakeweb_response(:get, 'http://test.dev/products/1.json', 200, {'product' => {'id' => 1, 'name' => 'Item1'}})
141
+ @service.product 1
142
+ @service.request_method.should == :get
143
+ @service.request_url.should == 'http://test.dev/products/1.json'
144
+ @service.request_options.should == {}
145
+
146
+ # Create
147
+ fakeweb_response(:post, 'http://test.dev/products.json', 201, {'product' => {'id' => 1, 'name' => 'Item1'}})
148
+ @service.create_product :name => 'Item1'
149
+ @service.request_method.should == :post
150
+ @service.request_url.should == 'http://test.dev/products.json'
151
+ @service.request_options.should == {:body => {'product' => {:name => 'Item1'}}}
152
+
153
+ # Edit
154
+ fakeweb_response(:put, 'http://test.dev/products/1.json', 200, {'product' => {'id' => 1, 'name' => 'Item1'}})
155
+ @service.edit_product 1, :name => 'Item1'
156
+ @service.request_method.should == :put
157
+ @service.request_url.should == 'http://test.dev/products/1.json'
158
+ @service.request_options.should == {:body => {'product' => {:name => 'Item1'}}}
159
+
160
+ # Delete
161
+ fakeweb_response(:delete, 'http://test.dev/products/1.json', 200, {})
162
+ @service.delete_product 1
163
+ @service.request_method.should == :delete
164
+ @service.request_url.should == 'http://test.dev/products/1.json'
165
+ @service.request_options.should == {}
166
+ end
167
+
168
+ it "should be able to save resources" do
169
+ @service_class.rest :product, :products, '/products.json'
170
+ @service_class.method_defined?(:create_product).should be_true
171
+ @service_class.method_defined?(:edit_product).should be_true
172
+
173
+ @service = @service_class.new
174
+ @service.base_uri = 'http://test.dev'
175
+
176
+ # Create
177
+ fakeweb_response(:post, 'http://test.dev/products.json', 201, {'product' => {'id' => 1, 'name' => 'Item1'}})
178
+ @service.save :product, :name => 'Item1'
179
+ @service.request_method.should == :post
180
+ @service.request_url.should == 'http://test.dev/products.json'
181
+ @service.request_options.should == {:body => {'product' => {:name => 'Item1'}}}
182
+
183
+ # Edit
184
+ fakeweb_response(:put, 'http://test.dev/products/1.json', 200, {'product' => {'id' => 1, 'name' => 'Item1'}})
185
+ @service.save :product, :id => 1, :name => 'Item1'
186
+ @service.request_method.should == :put
187
+ @service.request_url.should == 'http://test.dev/products/1.json'
188
+ @service.request_options.should == {:body => {'product' => {:id => 1, :name => 'Item1'}}}
189
+ end
190
+
191
+ it "should be able to change the return value using a class" do
192
+ @service_class.rest :product, :products, '/products.json', :return => DummyProduct
193
+ @service_class.method_defined?(:products).should be_true
194
+ @service_class.method_defined?(:product).should be_true
195
+
196
+ @service = @service_class.new
197
+ @service.base_uri = 'http://test.dev'
198
+
199
+ # Get products
200
+ fakeweb_response(:get, 'http://test.dev/products.json', 200,
201
+ [{'product' => {'id' => 1, 'name' => 'Item1'}}, {'product' => {'id' => 2, 'name' => 'Item2'}}])
202
+ products = @service.products
203
+ @service.request_method.should == :get
204
+ @service.request_url.should == 'http://test.dev/products.json'
205
+ @service.request_options.should == {}
206
+ products.is_a?(Array).should be_true
207
+ products[1].data['id'].should == 2
208
+
209
+ # Get a product
210
+ fakeweb_response(:get, 'http://test.dev/products/1.json', 200, {'product' => {'id' => 1, 'name' => 'Item1'}})
211
+ product = @service.product 1
212
+ @service.request_method.should == :get
213
+ @service.request_url.should == 'http://test.dev/products/1.json'
214
+ @service.request_options.should == {}
215
+ product.is_a?(DummyProduct).should be_true
216
+ product.data['id'].should == 1
217
+ end
218
+
219
+ it "should be able to change the return value using a function" do
220
+ @service_class.send(:define_method, :handle_product) { |resource| resource['name'] }
221
+ @service_class.rest :product, :products, '/products.json', :return => :handle_product
222
+ @service_class.method_defined?(:products).should be_true
223
+ @service_class.method_defined?(:product).should be_true
224
+
225
+ @service = @service_class.new
226
+ @service.base_uri = 'http://test.dev'
227
+
228
+ # Get products
229
+ fakeweb_response(:get, 'http://test.dev/products.json', 200, [{'product' => {'id' => 1, 'name' => 'Item1'}},
230
+ {'product' => {'id' => 2, 'name' => 'Item2'}}])
231
+ products = @service.products
232
+ @service.request_method.should == :get
233
+ @service.request_url.should == 'http://test.dev/products.json'
234
+ @service.request_options.should == {}
235
+ products.is_a?(Array).should be_true
236
+ products.should == ['Item1', 'Item2']
237
+
238
+ # Get a product
239
+ fakeweb_response(:get, 'http://test.dev/products/1.json', 200, {'product' => {'id' => 1, 'name' => 'Item1'}})
240
+ product = @service.product 1
241
+ @service.request_method.should == :get
242
+ @service.request_url.should == 'http://test.dev/products/1.json'
243
+ @service.request_options.should == {}
244
+ product.is_a?(String).should be_true
245
+ product.should == 'Item1'
246
+ end
247
+
248
+ it "should be able to use a find_<resource> method to adjust the query option" do
249
+ @service_class.route :find_product, '/products/lookup.json', :resource => 'product'
250
+ @service_class.route :find_component, '/products/:product_id/components/lookup.json', :resource => 'component'
251
+ @service_class.method_defined?(:find_product).should be_true
252
+ @service_class.method_defined?(:find_component).should be_true
253
+
254
+ @service = @service_class.new
255
+ @service.base_uri = 'http://test.dev'
256
+
257
+ # Get a product
258
+ fakeweb_response(:get, 'http://test.dev/products/lookup.json?name=Item1', 200, {'product' => {'id' => 1, 'name' => 'Item1'}})
259
+ @service.find_product_by_name 'Item1'
260
+ @service.request_method.should == :get
261
+ @service.request_url.should == 'http://test.dev/products/lookup.json'
262
+ @service.request_options.should == {:query => {'name' => 'Item1'}}
263
+
264
+ # Get a component
265
+ fakeweb_response(:get, 'http://test.dev/products/1/components/lookup.json?name=C2', 200, {'component' => {'id' => 100, 'name' => 'C2'}})
266
+ @service.find_component_by_name 1, 'C2'
267
+ @service.request_method.should == :get
268
+ @service.request_url.should == 'http://test.dev/products/1/components/lookup.json'
269
+ @service.request_options.should == {:query => {'name' => 'C2'}}
270
+ end
271
+
272
+ it "should raise invalid response" do
273
+ @service_class.rest :product, :products, '/products.json'
274
+ @service_class.method_defined?(:product).should be_true
275
+
276
+ @service = @service_class.new
277
+ @service.base_uri = 'http://test.dev'
278
+
279
+ # Get a product
280
+ fakeweb_response(:get, 'http://test.dev/products/1.json', 400, {})
281
+ lambda{ @service.product 1 }.should raise_error(RESTHome::InvalidResponse)
282
+ @service.request_method.should == :get
283
+ @service.request_url.should == 'http://test.dev/products/1.json'
284
+ @service.request_options.should == {}
285
+ end
286
+
287
+ it "should raise error if find function is not found" do
288
+ @service_class.route :find_product, '/products/lookup.json', :resource => 'product'
289
+ @service_class.method_defined?(:find_product).should be_true
290
+
291
+ @service = @service_class.new
292
+ @service.base_uri = 'http://test.dev'
293
+
294
+ lambda{ @service.find_component_by_name 'Item1' }.should raise_error(RESTHome::MethodMissing)
295
+ lambda{ @service.save :product, {:name => 'Item1'} }.should raise_error(RESTHome::MethodMissing)
296
+ lambda{ @service.save :product, {:id => 1, :name => 'Item1'} }.should raise_error(RESTHome::MethodMissing)
297
+ end
298
+
299
+ it "should be able to use blocks on the response" do
300
+ @service_class.route :products, '/products.json', :resource => 'product'
301
+ @service_class.method_defined?(:products).should be_true
302
+
303
+ @service = @service_class.new
304
+ @service.base_uri = 'http://test.dev'
305
+
306
+ fakeweb_response(:get, 'http://test.dev/products.json', 200,
307
+ [{'product' => {'id' => 1, 'name' => 'Item1'}}, {'product' => {'id' => 2, 'name' => 'Item2'}}])
308
+ @names = @service.products do |product|
309
+ product['name']
310
+ end
311
+
312
+ @names.should == ['Item1', 'Item2']
313
+ @service.request_method.should == :get
314
+ @service.request_url.should == 'http://test.dev/products.json'
315
+ @service.request_options.should == {}
316
+ end
317
+ end
data/tasks/spec.rake ADDED
@@ -0,0 +1,9 @@
1
+ begin
2
+ require 'spec/rake/spectask'
3
+ Spec::Rake::SpecTask.new(:spec) do |spec|
4
+ spec.libs << 'lib' << 'spec'
5
+ spec.spec_files = FileList['spec/**/*_spec.rb']
6
+ end
7
+ rescue LoadError => e
8
+ puts "unable to run specs from rake. gem install rspec"
9
+ end
metadata ADDED
@@ -0,0 +1,286 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resthome
3
+ version: !ruby/object:Gem::Version
4
+ hash: 7
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 6
9
+ - 0
10
+ version: 0.6.0
11
+ platform: ruby
12
+ authors:
13
+ - Doug Youch
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-29 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ prerelease: false
23
+ name: httparty
24
+ version_requirements: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ requirement: *id001
34
+ type: :runtime
35
+ - !ruby/object:Gem::Dependency
36
+ prerelease: false
37
+ name: bundler
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 23
44
+ segments:
45
+ - 1
46
+ - 0
47
+ - 0
48
+ version: 1.0.0
49
+ requirement: *id002
50
+ type: :development
51
+ - !ruby/object:Gem::Dependency
52
+ prerelease: false
53
+ name: jeweler
54
+ version_requirements: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 1
60
+ segments:
61
+ - 1
62
+ - 5
63
+ - 1
64
+ version: 1.5.1
65
+ requirement: *id003
66
+ type: :development
67
+ - !ruby/object:Gem::Dependency
68
+ prerelease: false
69
+ name: rcov
70
+ version_requirements: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ requirement: *id004
80
+ type: :development
81
+ - !ruby/object:Gem::Dependency
82
+ prerelease: false
83
+ name: fakeweb
84
+ version_requirements: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ requirement: *id005
94
+ type: :development
95
+ - !ruby/object:Gem::Dependency
96
+ prerelease: false
97
+ name: json
98
+ version_requirements: &id006 !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ requirement: *id006
108
+ type: :development
109
+ - !ruby/object:Gem::Dependency
110
+ prerelease: false
111
+ name: rspec
112
+ version_requirements: &id007 !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ requirement: *id007
122
+ type: :development
123
+ - !ruby/object:Gem::Dependency
124
+ prerelease: false
125
+ name: httparty
126
+ version_requirements: &id008 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ hash: 3
132
+ segments:
133
+ - 0
134
+ version: "0"
135
+ requirement: *id008
136
+ type: :runtime
137
+ - !ruby/object:Gem::Dependency
138
+ prerelease: false
139
+ name: bundler
140
+ version_requirements: &id009 !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ~>
144
+ - !ruby/object:Gem::Version
145
+ hash: 23
146
+ segments:
147
+ - 1
148
+ - 0
149
+ - 0
150
+ version: 1.0.0
151
+ requirement: *id009
152
+ type: :development
153
+ - !ruby/object:Gem::Dependency
154
+ prerelease: false
155
+ name: jeweler
156
+ version_requirements: &id010 !ruby/object:Gem::Requirement
157
+ none: false
158
+ requirements:
159
+ - - ~>
160
+ - !ruby/object:Gem::Version
161
+ hash: 1
162
+ segments:
163
+ - 1
164
+ - 5
165
+ - 1
166
+ version: 1.5.1
167
+ requirement: *id010
168
+ type: :development
169
+ - !ruby/object:Gem::Dependency
170
+ prerelease: false
171
+ name: rcov
172
+ version_requirements: &id011 !ruby/object:Gem::Requirement
173
+ none: false
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ hash: 3
178
+ segments:
179
+ - 0
180
+ version: "0"
181
+ requirement: *id011
182
+ type: :development
183
+ - !ruby/object:Gem::Dependency
184
+ prerelease: false
185
+ name: fakeweb
186
+ version_requirements: &id012 !ruby/object:Gem::Requirement
187
+ none: false
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ hash: 3
192
+ segments:
193
+ - 0
194
+ version: "0"
195
+ requirement: *id012
196
+ type: :development
197
+ - !ruby/object:Gem::Dependency
198
+ prerelease: false
199
+ name: json
200
+ version_requirements: &id013 !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ">="
204
+ - !ruby/object:Gem::Version
205
+ hash: 3
206
+ segments:
207
+ - 0
208
+ version: "0"
209
+ requirement: *id013
210
+ type: :development
211
+ - !ruby/object:Gem::Dependency
212
+ prerelease: false
213
+ name: rspec
214
+ version_requirements: &id014 !ruby/object:Gem::Requirement
215
+ none: false
216
+ requirements:
217
+ - - ">="
218
+ - !ruby/object:Gem::Version
219
+ hash: 3
220
+ segments:
221
+ - 0
222
+ version: "0"
223
+ requirement: *id014
224
+ type: :development
225
+ description: Simple wrapper class generator for consuming RESTful web services
226
+ email: doug@cykod.com
227
+ executables: []
228
+
229
+ extensions: []
230
+
231
+ extra_rdoc_files:
232
+ - LICENSE.txt
233
+ - README.rdoc
234
+ files:
235
+ - .document
236
+ - Gemfile
237
+ - Gemfile.lock
238
+ - LICENSE.txt
239
+ - README.rdoc
240
+ - Rakefile
241
+ - VERSION
242
+ - examples/chargify_web_service.rb
243
+ - examples/wordpress_web_service.rb
244
+ - lib/resthome.rb
245
+ - spec/helper.rb
246
+ - spec/lib/resthome_spec.rb
247
+ - tasks/spec.rake
248
+ has_rdoc: true
249
+ homepage: http://github.com/cykod/resthome
250
+ licenses:
251
+ - MIT
252
+ post_install_message:
253
+ rdoc_options: []
254
+
255
+ require_paths:
256
+ - lib
257
+ required_ruby_version: !ruby/object:Gem::Requirement
258
+ none: false
259
+ requirements:
260
+ - - ">="
261
+ - !ruby/object:Gem::Version
262
+ hash: 3
263
+ segments:
264
+ - 0
265
+ version: "0"
266
+ required_rubygems_version: !ruby/object:Gem::Requirement
267
+ none: false
268
+ requirements:
269
+ - - ">="
270
+ - !ruby/object:Gem::Version
271
+ hash: 3
272
+ segments:
273
+ - 0
274
+ version: "0"
275
+ requirements: []
276
+
277
+ rubyforge_project:
278
+ rubygems_version: 1.3.7
279
+ signing_key:
280
+ specification_version: 3
281
+ summary: RESTful web services consumer
282
+ test_files:
283
+ - examples/chargify_web_service.rb
284
+ - examples/wordpress_web_service.rb
285
+ - spec/helper.rb
286
+ - spec/lib/resthome_spec.rb