Class: WellRested::API
- Inherits:
-
Object
- Object
- WellRested::API
- Includes:
- WellRested, Utils
- Defined in:
- lib/well_rested/api.rb
Overview
All REST requests are made through an API object. API objects store cross-resource settings such as user and password (for HTTP basic auth).
Instance Attribute Summary collapse
-
#client ⇒ Object
Returns the value of attribute client.
-
#default_path_parameters ⇒ Object
Returns the value of attribute default_path_parameters.
-
#last_response ⇒ Object
readonly
Returns the value of attribute last_response.
-
#password ⇒ Object
Returns the value of attribute password.
-
#unique_id ⇒ Object
Returns the value of attribute unique_id.
-
#user ⇒ Object
Returns the value of attribute user.
-
#version ⇒ Object
Returns the value of attribute version.
Class Method Summary collapse
-
.fill_path(path_template, params) ⇒ Object
TODO: Move this into a utility module? It can then be called from Base#fill_path or directly if needed.
-
.request_headers(unique_id, version) ⇒ Object
Return the default headers sent with all HTTP requests.
Instance Method Summary collapse
-
#create(klass, attributes = {}, url = nil) ⇒ Object
Create the resource of klass from the given attributes.
-
#delete(klass_or_object, path_params_or_url = {}) ⇒ Object
DELETE a resource.
-
#find(klass, path_params_or_url = {}, query_params = {}) ⇒ Object
GET a single resource.
-
#find_many(klass, path_params_or_url = {}, query_params = {}) ⇒ Object
GET a collection of resources.
-
#get(url, json = true) ⇒ Object
Issue a GET request to the given url.
-
#initialize(path_params = {}, session_params = {}, version = "") ⇒ API
constructor
A new instance of API.
-
#post(url, payload, json = true) ⇒ Object
Issue a POST request to the given url.
-
#put(url, payload, options = {}) ⇒ Object
Issue a PUT request to the given url.
-
#request(klass, method, path, payload_hash = {}, headers = {}) ⇒ Object
Issue a request of method ‘method’ (:get, :put, :post, :delete) for the resource identified by ‘klass’.
-
#request_headers ⇒ Object
Convenience method.
-
#save(resource, url = nil) ⇒ Object
Save a resource.
-
#url_for(klass, path_params_or_url = {}, query_params = {}) ⇒ Object
Generate a full URL for the class klass with the given path_params and query_params In the case of an update, path params will usually be resource.attributes_for_api.
Methods included from WellRested
Constructor Details
#initialize(path_params = {}, session_params = {}, version = "") ⇒ API
Returns a new instance of API.
24 25 26 27 28 29 |
# File 'lib/well_rested/api.rb', line 24 def initialize(path_params = {}, session_params = {}, version = "") self.default_path_parameters = path_params.with_indifferent_access self.client = RestClient self.unique_id = session_params.try(:uid) || 'unauthorized' self.version = version end |
Instance Attribute Details
#client ⇒ Object
Returns the value of attribute client.
19 20 21 |
# File 'lib/well_rested/api.rb', line 19 def client @client end |
#default_path_parameters ⇒ Object
Returns the value of attribute default_path_parameters.
18 19 20 |
# File 'lib/well_rested/api.rb', line 18 def default_path_parameters @default_path_parameters end |
#last_response ⇒ Object (readonly)
Returns the value of attribute last_response.
20 21 22 |
# File 'lib/well_rested/api.rb', line 20 def last_response @last_response end |
#password ⇒ Object
Returns the value of attribute password.
17 18 19 |
# File 'lib/well_rested/api.rb', line 17 def password @password end |
#unique_id ⇒ Object
Returns the value of attribute unique_id.
21 22 23 |
# File 'lib/well_rested/api.rb', line 21 def unique_id @unique_id end |
#user ⇒ Object
Returns the value of attribute user.
16 17 18 |
# File 'lib/well_rested/api.rb', line 16 def user @user end |
#version ⇒ Object
Returns the value of attribute version.
22 23 24 |
# File 'lib/well_rested/api.rb', line 22 def version @version end |
Class Method Details
.fill_path(path_template, params) ⇒ Object
TODO: Move this into a utility module? It can then be called from Base#fill_path or directly if needed.
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/well_rested/api.rb', line 274 def self.fill_path(path_template, params) raise "Cannot fill nil path" if path_template.nil? params = params.with_indifferent_access # substitute marked params path = path_template.gsub(/\:\w+/) do |match| sym = match[1..-1].to_sym val = params.include?(sym) ? params[sym] : match raise ArgumentError.new "Blank parameter #{sym} in path #{path}!" if val.blank? val end # Raise an error if we have un-filled parameters if path.match(/(\:\w+)/) raise ArgumentError.new "Unfilled parameter in path: #{$1} (path: #{path} params: #{params.inspect})" end # ID goes on the end of the resource path but isn't spec'd there path += "/#{params[:id]}" unless params[:id].blank? path end |
.request_headers(unique_id, version) ⇒ Object
Return the default headers sent with all HTTP requests.
251 252 253 254 255 256 257 258 259 |
# File 'lib/well_rested/api.rb', line 251 def self.request_headers(unique_id, version) # Accept necessary for fetching results by result ID, but not in most places. { :content_type => 'application/json', :accept => 'application/json', "Authentication" => unique_id, :version => version } end |
Instance Method Details
#create(klass, attributes = {}, url = nil) ⇒ Object
Create the resource of klass from the given attributes. The class will be instantiated, and its new_from_api and attributes_for_api methods will be used to determine which attributes actually get sent. If url is specified, it overrides the default url.
125 126 127 128 129 |
# File 'lib/well_rested/api.rb', line 125 def create(klass, attributes = {}, url = nil) obj = klass.new(default_path_parameters.merge(attributes)) create_or_update_resource(obj, url) end |
#delete(klass_or_object, path_params_or_url = {}) ⇒ Object
DELETE a resource. There are two main ways to call delete. 1) The first argument is a class, and the second argument is an array of path_params that resolve to a path to the resource to delete.
(e.g. for klass Post with path '/users/:user_id/posts', :user_id and :id would be required in path_params_or_url to delete /users/x/posts/y)
2) The first argument can be an object to delete. It should include all of the path params in its attributes.
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/well_rested/api.rb', line 147 def delete(klass_or_object, path_params_or_url = {}) if klass_or_object.respond_to?(:attributes_for_api) # klass_or_object is an object klass = klass_or_object.class if path_params_or_url.kind_of?(String) url = url_for(klass, path_params_or_url) else params = default_path_parameters.merge(klass_or_object.attributes_for_api) url = url_for(klass, params) end else # klass_or_object is a class klass = klass_or_object #logger.debug "Calling delete with class #{klass.name} and params: #{path_params.inspect}" if path_params_or_url.kind_of?(String) url = url_for(klass, path_params_or_url) else params = default_path_parameters.merge(path_params_or_url) url = url_for(klass, params) end end #logger.info "DELETE #{url}" response = client.delete(url, request_headers) do |response, request, result, &block| @last_response = response response.return!(request, result, &block) end end |
#find(klass, path_params_or_url = {}, query_params = {}) ⇒ Object
GET a single resource. ‘klass’ is a class that descends from WellRested::Base ‘path_params_or_url’ is either a url string or a hash of params to substitute into the url pattern specified in klass.path
e.g. if klass.path is '/accounts/:account_id/users', then the path_params hash should include 'account_id'
‘query_params’ is an optional hash of query parameters
If path_params includes ‘id’, it will be added to the end of the path (e.g. /accounts/1/users/1) If path_params_or_url is a hash, query_params will be added on the end (e.g. { :option => ‘x’ }) produces a url with ?option=x If it is a string, query_params is ignored.
Returns an object of class klass representing that resource. If the resource is not found, raises a RestClient::ResourceNotFound exception.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/well_rested/api.rb', line 76 def find(klass, path_params_or_url = {}, query_params = {}) if klass.respond_to?(:path_parameters) path_params_or_url = klass.path_parameters klass = klass.class end url = url_for(klass, path_params_or_url, query_params) #logger.info "GET #{url}" response = client.get(url, request_headers) do |response, request, result, &block| @last_response = response response.return!(request, result, &block) # default RestClient response handling (raise exceptions on errors, etc.) end raise "Invalid body formatter for #{klass.name}!" if klass.body_formatter.nil? or !klass.body_formatter.respond_to?(:decode) hash = klass.body_formatter.decode(response) decoded_hash = klass.attribute_formatter.nil? ? hash : klass.attribute_formatter.decode(hash) klass.new_from_api(decoded_hash) end |
#find_many(klass, path_params_or_url = {}, query_params = {}) ⇒ Object
GET a collection of resources. This works the same as find, except it expects and returns an array of resources instead of a single resource.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/well_rested/api.rb', line 100 def find_many(klass, path_params_or_url = {}, query_params = {}) url = url_for(klass, path_params_or_url, query_params) logger.info "GET #{url}" # response = client.get(url, request_headers) do |response, request, result, &block| response = client.get(url, request_headers) do |response, request, result, &block| @last_response = response response.return!(request, result, &block) end raise "Invalid body formatter for #{klass.name}!" if klass.body_formatter.nil? or !klass.body_formatter.respond_to?(:decode) array = klass.body_formatter.decode(response) processed_array = klass.attribute_formatter.nil? ? array : klass.attribute_formatter.decode(array) raise "Response did not parse to an array" unless array.is_a?(Array) processed_array.map { |e| klass.new_from_api(e) } end |
#get(url, json = true) ⇒ Object
Issue a GET request to the given url. If json is passed as true, it will be interpreted as JSON and converted into a hash / array of hashes. Otherwise, the body is returned as a string. TODO: Same issue as with put. def get(url, body_formatter, attribute_formatter) ?
212 213 214 215 216 217 |
# File 'lib/well_rested/api.rb', line 212 def get(url, json = true) response = client.get(url, request_headers) return response unless json parsed = JSON.parse(response) KeyTransformer.underscore_keys(parsed) end |
#post(url, payload, json = true) ⇒ Object
Issue a POST request to the given url. The post body is specified by ‘payload’, which can either be a string, an object, a hash, or an array of hashes. If it is not a string, it will be recurisvely converted into JSON using any objects’ attributes_for_api methods. TODO: Same issue as with put, get, etc.
200 201 202 203 204 205 |
# File 'lib/well_rested/api.rb', line 200 def post(url, payload, json = true) response = client.post(url, payload, request_headers) return response unless json parsed = JSON.parse(response) KeyTransformer.underscore_keys(parsed) end |
#put(url, payload, options = {}) ⇒ Object
Issue a PUT request to the given url. The post body is specified by ‘payload’, which can either be a string, an object, a hash, or an array of hashes. If it is not a string, it will be recurisvely converted into JSON using any objects’ attributes_for_api methods. TODO: Update this to do something that makes more sense with the formatters. e.g. def put(url, payload, formatter)
180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/well_rested/api.rb', line 180 def put(url, payload, = {}) = { :json => true } opts = .merge() payload = payload.kind_of?(String) ? payload : KeyTransformer.camelize_keys(objects_to_attributes(payload)).to_json response = run_update(:put, url, payload) if opts[:json] and !response.blank? objs = JSON.parse(response) return KeyTransformer.underscore_keys(objs) end return response end |
#request(klass, method, path, payload_hash = {}, headers = {}) ⇒ Object
Issue a request of method ‘method’ (:get, :put, :post, :delete) for the resource identified by ‘klass’. If it is a PUT or a POST, the payload_hash should be specified.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/well_rested/api.rb', line 34 def request(klass, method, path, payload_hash = {}, headers = {}) auth = (self.user or self.password) ? "#{CGI.escape(user)}:#{CGI.escape(password)}@" : '' # If path starts with a slash, assume it is relative to the default server. if path[0..0] == '/' url = "#{klass.protocol}://#{auth}#{klass.server}#{path}" else # Otherwise, treat it as a fully qualified URL and do not modify it. url = path end hash = klass.attribute_formatter.encode(payload_hash) payload = klass.body_formatter.encode(hash) #logger.info "#{method.to_s.upcase} #{url} (#{payload.inspect})" if [:put, :post].include?(method) # RestClient.put and .post take an extra payload argument. client.send(method, url, payload, request_headers.merge(headers)) do |response, request, result, &block| @last_response = response response.return!(request, result, &block) end else client.send(method, url, request_headers.merge(headers)) do |response, request, result, &block| @last_response = response response.return!(request, result, &block) end end end |
#request_headers ⇒ Object
Convenience method. Also allows request_headers to be can be set on a per-instance basis.
246 247 248 |
# File 'lib/well_rested/api.rb', line 246 def request_headers self.class.request_headers(self.unique_id, self.version) end |
#save(resource, url = nil) ⇒ Object
Save a resource. Return false if doesn’t pass validation. If the update succeeds, return the resource. Otherwise, return a hash containing whatever the server returned (usually includes an array of errors).
135 136 137 138 139 140 |
# File 'lib/well_rested/api.rb', line 135 def save(resource, url = nil) # convert any hashes that should be objects into objects before saving, # so that we can use their attributes_for_api methods in case they need to override what gets sent resource.convert_attributes_to_objects create_or_update_resource(resource, url) end |
#url_for(klass, path_params_or_url = {}, query_params = {}) ⇒ Object
Generate a full URL for the class klass with the given path_params and query_params In the case of an update, path params will usually be resource.attributes_for_api. In the case of a find(many), query_params might be count, start, etc.
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/well_rested/api.rb', line 222 def url_for(klass, path_params_or_url = {}, query_params = {}) # CONSIDERATION: Defaults should be settable at the global level on the @api object. # They should be overrideable at the class-level (e.g. User) and again at the time of the method call. # url_for is currently not overrideable at the class level. auth = (self.user or self.password) ? "#{CGI.escape(user)}:#{CGI.escape(password)}@" : '' if path_params_or_url.kind_of?(String) # if it starts with a slash, we assume its part of a if path_params_or_url[0..0] == '/' url = "#{klass.protocol}://#{auth}#{klass.server}#{path_params_or_url}#{klass.extension}" else # if not, we treat it as fully qualified and do not modify it url = path_params_or_url end else path = self.class.fill_path(klass.path, default_path_parameters.merge(path_params_or_url).with_indifferent_access) url = "#{klass.protocol}://#{auth}#{klass.server}#{path}" end url += '?' + klass.attribute_formatter.encode(query_params).to_query unless query_params.empty? url end |