Module: OceanApplicationController
- Included in:
- ApplicationController
- Defined in:
- lib/ocean/ocean_application_controller.rb
Constant Summary collapse
- @@extra_actions =
Class variable to hold any extra controller actions defined in the
ocean_resource_controller
declaration in the resource controller. {}
Instance Method Summary collapse
-
#api_render(x, new: false, href: x.present? && url_for(params.permit!), override_partial: false) ⇒ Object
This is the main rendering function in Ocean.
-
#authorize_action ⇒ Object
Performs authorisation of the current action.
-
#collection_etag(coll, klass_name = "_unknown_") ⇒ Object
Cache values for collections.
-
#default_url_options(options = nil) ⇒ Object
Sets the default URL generation options to the HTTPS protocol, and the host to the OCEAN_API_HOST, that is, to the external URL of the Ocean API.
-
#filtered_params(klass) ⇒ Object
Filters away all non-accessible attributes from params.
-
#find_connectee ⇒ Object
This method finds the other resource for connect/disconnect, given the value of the param
href
, which should be a complete resource URI. -
#render_api_error(status_code, *messages) ⇒ Object
Renders an API level error.
-
#render_head_204 ⇒ Object
Renders a
HEAD
response with HTTP status 204 No Content. -
#render_validation_errors(r, except: []) ⇒ Object
Renders a HTTP 422 Unprocessable Entity response with a body enumerating each invalid Rails resource attribute and all their errors.
-
#require_x_api_token ⇒ Object
Ensures that there is an
X-API-Token
HTTP header in the request. -
#set_updater(obj) ⇒ Object
Updates
created_by
andupdated_by
to the ApiUser for which the current request is authorised.
Instance Method Details
#api_render(x, new: false, href: x.present? && url_for(params.permit!), override_partial: false) ⇒ Object
This is the main rendering function in Ocean. The argument x
can be a resource or a collection of resources (which need not be of the same type).
The keyword arg new
, if true, sets the response HTTP status to 201 and also adds a Location
HTTP header with the URI of the resource.
Rendering is done using partials only. These should by convention be located in their standard position, begin with an underscore, etc. The ocean
gem generator for resources creates a partial in the proper location.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/ocean/ocean_application_controller.rb', line 162 def api_render(x, new: false, href: x.present? && url_for(params.permit!), override_partial: false ) if !x.is_a?(Array) && !(defined?(ActiveRecord) && x.is_a?(ActiveRecord::Relation)) partial = override_partial || x.to_partial_path if new render partial: partial, object: x, status: 201, location: x else render partial: partial, object: x end return else resources = x.dup.collect { |m| render_to_string(partial: m.to_partial_path, locals: {m.class.model_name.i18n_key => m}) } count = resources.count total_count = x.respond_to?(:unscope) ? x.unscope(:limit, :offset).count : count resource_type = x.is_a?(Array) ? x.first && x.first.class.table_name.singularize : x.table_name.singularize attrs = {count: count, total_count: total_count, resource_type: resource_type } links = {} if href links['self'] = {href: href, type: 'application/json'} if params[:page] page = params[:page].to_i page_size = params[:page_size].to_i || x.collection_page_size total_pages = (total_count.to_f / page_size.to_f).ceil last_page = total_pages - 1 prev_page = page > 0 && page - 1 next_page = page < last_page && page + 1 links['first_page'] = {href: url_for(params.merge(page: 0)), type: 'application/json'} if page > 0 links['last_page'] = {href: url_for(params.merge(page: last_page)), type: 'application/json'} if page < last_page links['previous_page'] = {href: url_for(params.merge(page: prev_page)), type: 'application/json'} if prev_page links['next_page'] = {href: url_for(params.merge(page: next_page)), type: 'application/json'} if next_page end end attrs['_links'] = links if links attrs['page'] = page if page attrs['page_size'] = page_size if page_size attrs['total_pages'] = total_pages if total_pages render plain: '{"_collection":{"resources":[' + resources.join(',') + ']' + (attrs.collect do |k, v| ',"' + k.to_s + '":' + v.to_json end).join('') + '}}', content_type: 'application/json' end end |
#authorize_action ⇒ Object
Performs authorisation of the current action. Returns true if allowed, false if not. Calls the Auth service using a GET
, which means previous authorisations using the same token and args will be cached in Varnish.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/ocean/ocean_application_controller.rb', line 58 def return true if ENV['NO_OCEAN_AUTH'] # Obtain any nonstandard actions @@extra_actions[controller_name] ||= begin extra_actions rescue NameError => e {} end # Create a query string and call Auth qs = Api.(@@extra_actions, controller_name, action_name) response = Api.permitted?(@x_api_token, query: qs) if response.status == 200 a = response.body['authentication'] @auth_api_user_id = a['user_id'] # Deprecate and remove @auth_api_user_uri = a['_links']['creator']['href'] # Keep Thread.current[:username] = a['username'] @right_restrictions = a['right'] params['_right_restrictions'] = @right_restrictions if @right_restrictions @group_names = a['group_names'].to_set if a['group_names'] return true end = response.body['_api_error'] rescue ["Not JSON: #{response.raw_body}"] render_api_error response.status, * expires_in 0, must_revalidate: true false end |
#collection_etag(coll, klass_name = "_unknown_") ⇒ Object
Cache values for collections. Accepts a class, a scope, or an array. The cache value is based on three components:
(1) the name of the class,
(2) the number of members in the collection, and
(3) the modification time of the last updated member.
If an array is given, the class of the first member will be used to create a scope. If the array is empty, the optional second argument will be used as the class indicator.
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/ocean/ocean_application_controller.rb', line 242 def collection_etag(coll, klass_name="_unknown_") if coll.is_a? Array return { etag: "#{klass_name}:0:0" } if coll == [] # Construct a new scope and fall through coll = coll[0].class.where(id: coll.collect(&:id)) end coll = add_right_restrictions(coll, @right_restrictions) klass = coll.name.constantize # Force a load of the class (for secondary collections) = klass. || klass. if () last_item = coll.reorder().last last_updated = last_item ? last_item.send().utc : 0 else last_updated = 0 end { etag: "#{coll.name}:#{coll.count}:#{last_updated}" } end |
#default_url_options(options = nil) ⇒ Object
Sets the default URL generation options to the HTTPS protocol, and the host to the OCEAN_API_HOST, that is, to the external URL of the Ocean API. We always generate external URIs, even for internal calls. It’s the responsibility of the other service to rewrite external to internal URIs when calling the internal API point.
10 11 12 |
# File 'lib/ocean/ocean_application_controller.rb', line 10 def ( = nil) { :protocol => "https", :host => OCEAN_API_HOST } end |
#filtered_params(klass) ⇒ Object
Filters away all non-accessible attributes from params. Thus, we still are using pre-Rails 4.0 protected attributes. This will eventually be replaced by strong parameters. Takes a class and returns a new hash containing only the model attributes which may be modified.
223 224 225 226 227 228 229 |
# File 'lib/ocean/ocean_application_controller.rb', line 223 def filtered_params(klass) result = {} params.each do |k, v| result[k] = v if klass.accessible_attributes.include?(k) end result end |
#find_connectee ⇒ Object
This method finds the other resource for connect/disconnect, given the value of the param href
, which should be a complete resource URI.
Renders API errors if the href
arg is missing, can’t be parsed, or the resource can’t be found.
Sets @connectee_class to the class of the resource pointed to by href
, and @connectee to the resource itself.
271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/ocean/ocean_application_controller.rb', line 271 def find_connectee href = params[:href] render_api_error(422, "href query arg is missing") and return if href.blank? begin routing = Rails.application.routes.recognize_path(href) rescue ActionController::RoutingError render_api_error(422, "href query arg isn't parseable") return end @connectee_class = routing[:controller].classify.constantize @connectee = @connectee_class.find_by_id(routing[:id]) render_api_error(404, "Resource to connect not found") and return unless @connectee true end |
#render_api_error(status_code, *messages) ⇒ Object
Renders an API level error. The body will be a JSON hash with a single key, _api_error
. The value is an array containing the messages
.
render_api_error(500, "An unforeseen error occurred")
results in a response with HTTP status 500 and the following body:
{"_api_error": ["An unforeseen error occurred"]}
Resource consumers should always examine the body when an error is returned, as _api_error
always will give additional information which may be required to process the error properly.
117 118 119 |
# File 'lib/ocean/ocean_application_controller.rb', line 117 def render_api_error(status_code, *) render json: {_api_error: }, status: status_code end |
#render_head_204 ⇒ Object
Renders a HEAD
response with HTTP status 204 No Content.
124 125 126 |
# File 'lib/ocean/ocean_application_controller.rb', line 124 def render_head_204 render plain: '', status: 204, content_type: 'application/json' end |
#render_validation_errors(r, except: []) ⇒ Object
Renders a HTTP 422 Unprocessable Entity response with a body enumerating each invalid Rails resource attribute and all their errors. This is usually done in response to detecting a resource is invalid during POST
(create) and PUT/PATCH
(update). E.g.:
{"name": ["must be specified"],
"email": ["must be specified", "must contain a @ character"]}
The messages are intended for presentation to an end user.
The keyword argument except
, if present, must be a string, symbol or an array of strings or symbols and will suppress error information for the enumerated attributes of the same names. This is sometimes useful when internal attributes which never appear in external resource representations depend on user-provided data, such as password hashes and salts.
145 146 147 148 149 |
# File 'lib/ocean/ocean_application_controller.rb', line 145 def render_validation_errors(r, except: []) except = [except] unless except.is_a?(Array) except = except.collect(&:to_sym) render json: r.errors..except(*except), status: 422 end |
#require_x_api_token ⇒ Object
Ensures that there is an X-API-Token
HTTP header in the request. Stores the token in @x_api_token for use during authorisation of the current controller action. If there’s no X-API-Token
header, the request is aborted and an API error with status 400 is returned.
400 error responses will always contain a body with error information explaining the API error:
{"_api_error": ["X-API-Token missing"]}
or
{"_api_error": ["Authentication expired"]}
30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/ocean/ocean_application_controller.rb', line 30 def require_x_api_token return true if ENV['NO_OCEAN_AUTH'] @x_api_token = request.headers['X-API-Token'] Thread.current[:x_api_token] = @x_api_token @x_metadata = request.headers['X-Metadata'] @x_metadata = @x_metadata.to_s[0, 128] if @x_metadata Thread.current[:metadata] = @x_metadata return true if @x_api_token.present? logger.info "X-API-Token missing" render_api_error 400, "X-API-Token missing" expires_in 0, must_revalidate: true false end |
#set_updater(obj) ⇒ Object
Updates created_by
and updated_by
to the ApiUser for which the current request is authorised. The attributes can be declared either String (recommended) or Integer (deprecated). If String, they will be set to the URI of the ApiUser. (If Integer, to their internal SQL ID.)
92 93 94 95 96 97 98 99 100 |
# File 'lib/ocean/ocean_application_controller.rb', line 92 def set_updater(obj) if obj.created_by.is_a?(Integer) obj.created_by = @auth_api_user_id if obj.created_by.blank? || obj.created_by == 0 obj.updated_by = @auth_api_user_id else obj.created_by = @auth_api_user_uri if obj.created_by.blank? || obj.created_by == "0" obj.updated_by = @auth_api_user_uri end end |