api_resource 0.6.18 → 0.6.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -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