api_resource 0.6.18 → 0.6.19

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.
@@ -1,5 +1,5 @@
1
1
  require 'active_support/core_ext/benchmark'
2
- require 'rest_client'
2
+ require 'httpclient'
3
3
  require 'net/https'
4
4
  require 'date'
5
5
  require 'time'
@@ -51,7 +51,7 @@ module ApiResource
51
51
  @timeout = timeout
52
52
  end
53
53
 
54
- # make a put request
54
+ # make a get request
55
55
  # @return [String] response.body raises an
56
56
  # ApiResource::ConnectionError if we
57
57
  # have a timeout, general exception, or
@@ -62,7 +62,7 @@ module ApiResource
62
62
  headers = build_request_headers(headers, :get, site)
63
63
 
64
64
  self.with_caching(path, headers) do
65
- format.decode(request(:get, path, headers))
65
+ format.decode(request(:get, path, {}, headers))
66
66
  end
67
67
  end
68
68
 
@@ -84,7 +84,7 @@ module ApiResource
84
84
  response = request(
85
85
  :put,
86
86
  path,
87
- body,
87
+ format.encode(body),
88
88
  build_request_headers(headers, :put, self.site.merge(path))
89
89
  )
90
90
  # handle blank response and return true
@@ -111,7 +111,7 @@ module ApiResource
111
111
  request(
112
112
  :post,
113
113
  path,
114
- body,
114
+ format.encode(body),
115
115
  build_request_headers(headers, :post, self.site.merge(path))
116
116
  )
117
117
  )
@@ -143,13 +143,18 @@ module ApiResource
143
143
  # if result.code is not within 200..399
144
144
  def request(method, path, *arguments)
145
145
  handle_response(path) do
146
+ unless path =~ /\./
147
+ path += ".#{self.format.extension}"
148
+ end
146
149
  ActiveSupport::Notifications.instrument("request.api_resource") do |payload|
147
-
148
150
  # debug logging
149
151
  ApiResource.logger.info("#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}")
150
152
  payload[:method] = method
151
153
  payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}"
152
- payload[:result] = http(path).send(method, *arguments)
154
+ payload[:result] = http.send(
155
+ method,
156
+ "#{site.scheme}://#{site.host}:#{site.port}#{path}",
157
+ *arguments)
153
158
  end
154
159
  end
155
160
  end
@@ -158,7 +163,7 @@ module ApiResource
158
163
  def handle_response(path, &block)
159
164
  begin
160
165
  result = yield
161
- rescue RestClient::RequestTimeout
166
+ rescue HTTPClient::TimeoutError
162
167
  raise ApiResource::RequestTimeout.new("Request Time Out - Accessing #{path}}")
163
168
  rescue Exception => error
164
169
  if error.respond_to?(:http_code)
@@ -207,11 +212,16 @@ module ApiResource
207
212
 
208
213
  # Creates new Net::HTTP instance for communication with the
209
214
  # remote service and resources.
210
- def http(path)
211
- unless path =~ /\./
212
- path += ".#{self.format.extension}"
215
+ def http
216
+ # TODO: Deal with proxies and such
217
+ unless @http
218
+ @http = HTTPClient.new
219
+ # TODO: This should be on the class level
220
+ @http.connect_timeout = ApiResource::Base.open_timeout
221
+ @http.receive_timeout = ApiResource::Base.timeout
213
222
  end
214
- RestClient::Resource.new("#{site.scheme}://#{site.host}:#{site.port}#{path}", {:timeout => ApiResource::Base.timeout, :open_timeout => ApiResource::Base.open_timeout})
223
+
224
+ return @http
215
225
  end
216
226
 
217
227
  def build_request_headers(headers, verb, uri)
@@ -13,9 +13,9 @@ module ApiResource
13
13
 
14
14
  module ClassMethods
15
15
 
16
- # This decides which finder method to call.
16
+ # This decides which finder method to call.
17
17
  # It accepts arguments of the form "scope", "options={}"
18
- # where options can be standard rails options or :expires_in.
18
+ # where options can be standard rails options or :expires_in.
19
19
  # If :expires_in is set, it caches it for expires_in seconds.
20
20
 
21
21
  # Need to support the following cases
@@ -36,13 +36,12 @@ module ApiResource
36
36
  expiry = @expiry
37
37
  ApiResource.with_ttl(expiry.to_f) do
38
38
  if numeric_find
39
- if single_find && (@conditions.blank_conditions? || include_associations_only?)
39
+ if single_find && (@conditions.blank_conditions? || nested_find_only?)
40
40
  # If we have no conditions or they are only prefixes or
41
- # includes, and only one argument (not a word) then we
41
+ # includes, and only one argument (not a word) then we
42
42
  # only have a single item to find.
43
43
  # e.g. Class.includes(:association).find(1)
44
44
  # Class.find(1)
45
- @scope = @scope.first if @scope.is_a?(Array)
46
45
  final_cond = @conditions.merge!(ApiResource::Conditions::ScopeCondition.new({:id => @scope}, self))
47
46
 
48
47
  ApiResource::Finders::SingleFinder.new(self, final_cond).load
@@ -51,17 +50,12 @@ module ApiResource
51
50
  # Class.includes(:association).find(1,2)
52
51
  # Class.find(1,2)
53
52
  # Class.active.find(1)
54
- if Array.wrap(@scope).size == 1 && @scope.is_a?(Array)
55
- @scope = @scope.first
56
- end
57
-
58
53
  fnd = @conditions.merge!(ApiResource::Conditions::ScopeCondition.new({:find => {:ids => @scope}}, self))
59
54
  fnd.send(:all)
60
55
  end
61
56
  else
62
57
  # e.g. Class.scope(1).first
63
58
  # Class.first
64
- @scope = @scope.first if @scope.is_a?(Array)
65
59
  new_condition = @scope == :all ? {} : {@scope => true}
66
60
 
67
61
  final_cond = @conditions.merge!ApiResource::Conditions::ScopeCondition.new(new_condition, self)
@@ -69,7 +63,6 @@ module ApiResource
69
63
  fnd = ApiResource::Finders::ResourceFinder.new(self, final_cond)
70
64
  fnd.send(@scope)
71
65
  end
72
-
73
66
  end
74
67
  end
75
68
 
@@ -95,7 +88,7 @@ module ApiResource
95
88
  end
96
89
 
97
90
  def instantiate_collection(collection)
98
- collection.collect{|record|
91
+ collection.collect{|record|
99
92
  instantiate_record(record)
100
93
  }
101
94
  end
@@ -151,23 +144,29 @@ module ApiResource
151
144
 
152
145
  # Conditions sometimes call find, passing themselves as the last arg.
153
146
  if args.last.is_a?(ApiResource::Conditions::AbstractCondition)
154
- cond = args.slice!(args.length - 1)
147
+ cond = args.slice!(args.length - 1)
155
148
  else
156
- cond = nil
149
+ cond = nil
157
150
  end
158
151
 
159
152
  # Support options being passed in as a hash.
160
- options = args.extract_options!
153
+ options = args.extract_options!
161
154
  if options.blank?
162
155
  options = nil
163
156
  end
164
157
 
165
- @expiry = (options.is_a?(Hash) ? options.delete(:expires_in) : nil) || ApiResource::Base.ttl || 0
158
+ @expiry = (options.is_a?(Hash) ? options.delete(:expires_in) : nil) || ApiResource::Base.ttl || 0
166
159
 
167
160
  combine_conditions(options, cond)
168
161
 
169
162
  # Remaining args are the scope.
170
- @scope = args
163
+ @scope = args
164
+
165
+ if Array.wrap(@scope).size == 1 && @scope.is_a?(Array)
166
+ @scope = @scope.first
167
+ end
168
+
169
+ true
171
170
  end
172
171
 
173
172
  def combine_conditions(options, condition)
@@ -192,7 +191,7 @@ module ApiResource
192
191
  @conditions = final_cond
193
192
  end
194
193
 
195
- def include_associations_only?
194
+ def nested_find_only?
196
195
  if @conditions.blank_conditions?
197
196
  return false
198
197
  else
@@ -31,7 +31,7 @@ module ApiResource
31
31
  protected
32
32
 
33
33
  def build_load_path
34
- if self.condition.to_hash["id"].nil?
34
+ if self.condition.to_hash["id"].blank?
35
35
  raise "Invalid evaluation of a SingleFinder without an ID"
36
36
  end
37
37
 
@@ -1,49 +1,55 @@
1
1
  require 'api_resource'
2
2
 
3
3
  module ApiResource
4
-
4
+
5
5
  module Mocks
6
-
6
+
7
7
  @@endpoints = {}
8
8
  @@path = nil
9
9
 
10
- # A simple interface class to change the new connection to look like the
10
+ # A simple interface class to change the new connection to look like the
11
11
  # old activeresource connection
12
12
  class Interface
13
-
14
- def initialize(path)
15
- @path = path
13
+
14
+
15
+ def get(path, *args, &block)
16
+ uri = URI.parse(path)
17
+ path = uri.path + (uri.query.present? ? "?#{uri.query}" : '')
18
+ Connection.get(path, *args, &block)
16
19
  end
17
-
18
- def get(*args, &block)
19
- Connection.send(:get, @path, *args, &block)
20
+ def post(path, *args, &block)
21
+ Connection.post(process_path(path), *args, &block)
20
22
  end
21
- def post(*args, &block)
22
- Connection.send(:post, @path, *args, &block)
23
+ def put(path, *args, &block)
24
+ Connection.put(process_path(path), *args, &block)
23
25
  end
24
- def put(*args, &block)
25
- Connection.send(:put, @path, *args, &block)
26
+ def delete(path, *args, &block)
27
+ Connection.delete(process_path(path), *args, &block)
26
28
  end
27
- def delete(*args, &block)
28
- Connection.send(:delete, @path, *args, &block)
29
+ def head(path, *args, &block)
30
+ Connection.head(process_path(path), *args, &block)
29
31
  end
30
- def head(*args, &block)
31
- Connection.send(:head, @path, *args, &block)
32
+
33
+ protected
34
+
35
+ def process_path(path)
36
+ uri = URI.parse(path)
37
+ return uri.path
32
38
  end
33
39
  end
34
40
 
35
- # set ApiResource's http
41
+ # set ApiResource's http
36
42
  def self.init
37
43
  ::ApiResource::Connection.class_eval do
38
44
  private
39
45
  alias_method :http_without_mock, :http
40
- def http(path)
41
- Interface.new(path)
46
+ def http
47
+ Interface.new
42
48
  end
43
49
  end
44
50
  end
45
-
46
- # set ApiResource's http
51
+
52
+ # set ApiResource's http
47
53
  def self.remove
48
54
  ::ApiResource::Connection.class_eval do
49
55
  private
@@ -114,7 +120,7 @@ module ApiResource
114
120
 
115
121
  return {:responses => nil, :params => nil}
116
122
  end
117
-
123
+
118
124
 
119
125
  private
120
126
  def self.with_path_and_format(path, format, &block)
@@ -123,13 +129,13 @@ module ApiResource
123
129
  @@path, @@format = nil, nil
124
130
  ret
125
131
  end
126
- # define the
132
+ # define the
127
133
  [:post, :put, :get, :delete, :head].each do |verb|
128
134
  instance_eval <<-EOE, __FILE__, __LINE__ + 1
129
135
  def #{verb}(response_body, opts = {}, &block)
130
136
 
131
137
  raise Exception.new("Must be called from within an endpoint block") unless @@path
132
- opts = opts.reverse_merge({:status_code => 200, :response_headers => {}, :params => {}})
138
+ opts = opts.reverse_merge({:status_code => 200, :response_headers => {}, :params => {}})
133
139
 
134
140
  @@endpoints[@@path] << [MockRequest.new(:#{verb}, @@path, :params => opts[:params], :format => @@format), MockResponse.new(response_body, :status_code => opts[:status_code], :headers => opts[:response_headers], :format => @@format, &block)]
135
141
  end
@@ -138,7 +144,7 @@ module ApiResource
138
144
 
139
145
  class MockResponse
140
146
  attr_reader :body, :headers, :code, :format, :block
141
- def initialize(body, opts = {}, &block)
147
+ def initialize(body, opts = {}, &block)
142
148
  opts = opts.reverse_merge({:headers => {}, :status_code => 200})
143
149
  @body = body
144
150
  @headers = opts[:headers]
@@ -197,7 +203,7 @@ module ApiResource
197
203
  @headers["Content-Length"] = @body.blank? ? "0" : @body.size.to_s
198
204
  end
199
205
 
200
- #
206
+ #
201
207
  def typecast_values(data)
202
208
  if data.is_a?(Hash)
203
209
  data.each_pair do |k,v|
@@ -240,8 +246,8 @@ module ApiResource
240
246
  self.requests = []
241
247
 
242
248
  # body? methods
243
- { true => %w(post put),
244
- false => %w(get delete head) }.each do |has_body, methods|
249
+ { true => %w(post put get),
250
+ false => %w(delete head) }.each do |has_body, methods|
245
251
  methods.each do |method|
246
252
  # def post(path, body, headers)
247
253
  # request = ApiResource::Request.new(:post, path, body, headers)
@@ -249,7 +255,7 @@ module ApiResource
249
255
  # if response = LifebookerClient::Mocks.find_response(request)
250
256
  # response
251
257
  # else
252
- # raise InvalidRequestError.new("Could not find a response
258
+ # raise InvalidRequestError.new("Could not find a response
253
259
  # recorded for #{request.to_s} - Responses recorded are: -
254
260
  # #{inspect_responses}")
255
261
  # end
@@ -261,7 +267,7 @@ module ApiResource
261
267
  request = MockRequest.new(:#{method}, path, opts)
262
268
  self.requests << request
263
269
  if response = Mocks.find_response(request)
264
- response[:response].tap{|resp|
270
+ response[:response].tap{|resp|
265
271
  resp.generate_response(
266
272
  request.params
267
273
  .with_indifferent_access
@@ -2,17 +2,17 @@ module ApiResource
2
2
  module Scopes
3
3
 
4
4
  extend ActiveSupport::Concern
5
-
5
+
6
6
  module ClassMethods
7
7
  # TODO: calling these methods should force loading of the resource definition
8
8
  def scopes
9
9
  return self.related_objects[:scopes]
10
10
  end
11
-
11
+
12
12
  def scope?(name)
13
13
  self.related_objects[:scopes].has_key?(name.to_sym)
14
14
  end
15
-
15
+
16
16
  def scope_attributes(name)
17
17
  raise "No such scope #{name}" unless self.scope?(name)
18
18
  self.related_objects[:scopes][name.to_sym]
@@ -27,15 +27,15 @@ module ApiResource
27
27
  def scope(scope_name, scope_definition)
28
28
 
29
29
  unless scope_definition.is_a?(Hash)
30
- raise ArgumentError, "Expecting an attributes hash given #{scope_definition.inspect}"
30
+ raise ArgumentError, "Expecting an attributes hash given #{scope_definition.inspect}"
31
31
  end
32
-
32
+
33
33
  self.related_objects[:scopes][scope_name.to_sym] = scope_definition
34
34
 
35
35
  self.class_eval do
36
36
 
37
37
  define_singleton_method(scope_name) do |*args|
38
-
38
+
39
39
  arg_names = scope_definition.keys
40
40
  arg_types = scope_definition.values
41
41
 
@@ -44,10 +44,10 @@ module ApiResource
44
44
  }
45
45
 
46
46
  arg_names.each_with_index do |arg_name, i|
47
-
47
+
48
48
  # If we are dealing with a scope with multiple args
49
49
  if arg_types[i] == :rest
50
- finder_opts[scope_name][arg_name] =
50
+ finder_opts[scope_name][arg_name] =
51
51
  args.slice(i, args.count)
52
52
  # Else we are only dealing with a single argument
53
53
  else
@@ -67,19 +67,77 @@ module ApiResource
67
67
  end
68
68
  end
69
69
  end
70
+
71
+ #
72
+ # Apply scopes from params based on our resource
73
+ # definition
74
+ #
75
+ def add_scopes(params, base = self)
76
+ # scopes are stored as strings but we want to allow
77
+ params = params.with_indifferent_access
78
+ base = self.add_static_scopes(params, base)
79
+ return self.add_dynamic_scopes(params, base)
80
+ end
81
+
82
+ protected
83
+
84
+ def add_static_scopes(params, base)
85
+ self.static_scopes.each do |name|
86
+ if params[name].present?
87
+ base = base.send(name)
88
+ end
89
+ end
90
+ return base
91
+ end
92
+
93
+ def add_dynamic_scopes(params, base)
94
+ self.dynamic_scopes.each_pair do |name, args|
95
+ next if params[name].blank?
96
+ caller_args = []
97
+ args.each_pair do |subkey, type|
98
+ if type == :req || params[name][subkey].present?
99
+ caller_args << params[name][subkey]
100
+ end
101
+ end
102
+ base = base.send(name, *caller_args)
103
+ end
104
+ return base
105
+ end
106
+
107
+ #
108
+ # Wrapper method to define all scopes from the resource definition
109
+ #
110
+ # @return [Boolean] true
111
+ def define_all_scopes
112
+ if self.resource_definition["scopes"]
113
+ self.resource_definition["scopes"].each_pair do |name, opts|
114
+ self.scope(name, opts)
115
+ end
116
+ end
117
+ true
118
+ end
119
+
120
+ def dynamic_scopes
121
+ self.scopes.select { |name, args| args.present? }
122
+ end
123
+
124
+ def static_scopes
125
+ self.scopes.select { |name, args| args.blank? }.keys
126
+ end
127
+
70
128
  end
71
-
129
+
72
130
  def scopes
73
131
  return self.class.scopes
74
132
  end
75
-
133
+
76
134
  def scope?(name)
77
135
  return self.class.scope?(name)
78
136
  end
79
-
137
+
80
138
  def scope_attributes(name)
81
139
  return self.class.scope_attributes(name)
82
140
  end
83
-
141
+
84
142
  end
85
143
  end