Class: RageController::API

Inherits:
Object
  • Object
show all
Defined in:
lib/rage/controller/api.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.after_action(action_name = nil, **opts, &block) ⇒ Object

Register a new after_action hook. Calls with the same action_name will overwrite the previous ones.

Examples:

after_action :log_detailed_metrics, only: :create

Parameters:

  • action_name (String, nil) (defaults to: nil)

    the name of the callback to add

  • opts (Hash)

    action options

Options Hash (**opts):

  • :only (Symbol, Array<Symbol>)

    restrict the callback to run only for specific actions

  • :except (Symbol, Array<Symbol>)

    restrict the callback to run for all actions except specified

  • :if (Symbol, Proc)

    only run the callback if the condition is true

  • :unless (Symbol, Proc)

    only run the callback if the condition is false



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/rage/controller/api.rb', line 235

def after_action(action_name = nil, **opts, &block)
  action = prepare_action_params(action_name, **opts, &block)

  if @__after_actions && @__after_actions.frozen?
    @__after_actions = @__after_actions.dup
  end

  if @__after_actions.nil?
    @__after_actions = [action]
  elsif i = @__after_actions.find_index { |a| a[:name] == action_name }
    @__after_actions[i] = action
  else
    @__after_actions << action
  end
end

.before_action(action_name = nil, **opts, &block) ⇒ Object

Note:

The block form doesn’t receive an argument and is executed on the controller level as if it was a regular method.

Register a new before_action hook. Calls with the same action_name will overwrite the previous ones.

Examples:

before_action :find_photo, only: :show

def find_photo
  Photo.first
end
before_action :require_user, unless: :logged_in?
before_action :set_locale, if: -> { params[:locale] != "en-US" }
before_action do
  unless logged_in? # would be `controller.send(:logged_in?)` in Rails
    head :unauthorized
  end
end

Parameters:

  • action_name (String, nil) (defaults to: nil)

    the name of the callback to add

  • opts (Hash)

    action options

Options Hash (**opts):

  • :only (Symbol, Array<Symbol>)

    restrict the callback to run only for specific actions

  • :except (Symbol, Array<Symbol>)

    restrict the callback to run for all actions except specified

  • :if (Symbol, Proc)

    only run the callback if the condition is true

  • :unless (Symbol, Proc)

    only run the callback if the condition is false



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/rage/controller/api.rb', line 209

def before_action(action_name = nil, **opts, &block)
  action = prepare_action_params(action_name, **opts, &block)

  if @__before_actions && @__before_actions.frozen?
    @__before_actions = @__before_actions.dup
  end

  if @__before_actions.nil?
    @__before_actions = [action]
  elsif i = @__before_actions.find_index { |a| a[:name] == action_name }
    @__before_actions[i] = action
  else
    @__before_actions << action
  end
end

.rescue_from(*klasses, with: nil, &block) ⇒ Object

Note:

Unlike in Rails, the handler must always take an argument. Use _ if you don’t care about the actual exception.

Register a global exception handler. Handlers are inherited and matched from bottom to top.

Examples:

rescue_from User::NotAuthorized, with: :deny_access

def deny_access(exception)
  head :forbidden
end
rescue_from User::NotAuthorized do |_|
  head :forbidden
end

Parameters:

  • klasses (Class, Array<Class>)

    exception classes to watch on

  • with (Symbol) (defaults to: nil)

    the name of a handler method. The method must take one argument, which is the raised exception. Alternatively, you can pass a block, which must also take one argument.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/rage/controller/api.rb', line 166

def rescue_from(*klasses, with: nil, &block)
  unless with
    if block_given?
      with = define_tmp_method(block)
    else
      raise "No handler provided. Pass the `with` keyword argument or provide a block."
    end
  end

  if @__rescue_handlers.nil?
    @__rescue_handlers = []
  elsif @__rescue_handlers.frozen?
    @__rescue_handlers = @__rescue_handlers.dup
  end

  @__rescue_handlers.unshift([klasses, with])
end

.skip_before_action(action_name, only: nil, except: nil) ⇒ Object

Prevent a before_action hook from running.

Examples:

skip_before_action :find_photo, only: :create

Parameters:

  • action_name (String)

    the name of the callback to skip

  • only (Symbol, Array<Symbol>) (defaults to: nil)

    restrict the callback to be skipped only for specific actions

  • except (Symbol, Array<Symbol>) (defaults to: nil)

    restrict the callback to be skipped for all actions except specified



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/rage/controller/api.rb', line 258

def skip_before_action(action_name, only: nil, except: nil)
  i = @__before_actions&.find_index { |a| a[:name] == action_name }
  raise "The following action was specified to be skipped but couldn't be found: #{self}##{action_name}" unless i

  @__before_actions = @__before_actions.dup if @__before_actions.frozen?

  if only.nil? && except.nil?
    @__before_actions.delete_at(i)
    return
  end

  action = @__before_actions[i].dup
  if only
    action[:except] ? action[:except] |= Array(only) : action[:except] = Array(only)
  end
  if except
    action[:only] = Array(except)
  end

  @__before_actions[i] = action
end

Instance Method Details

#append_info_to_payload(payload) ⇒ Object

Define this method to add more information to request logs.

Examples:

def append_info_to_payload(payload)
  payload[:response] = response.body
end

Parameters:

  • payload (Hash)

    the payload to add additional information to



# File 'lib/rage/controller/api.rb', line 507

#authenticate_or_request_with_http_token {|token| ... } ⇒ Object

Authenticate using an HTTP Bearer token, or otherwise render an HTTP header requesting the client to send a Bearer token. For the authentication to be considered successful, the block should return a non-nil value.

Examples:

before_action :authenticate

def authenticate
  authenticate_or_request_with_http_token do |token|
    ApiToken.find_by(token: token)
  end
end

Yields:

  • (token)

    token value extracted from the Authorization header



443
444
445
# File 'lib/rage/controller/api.rb', line 443

def authenticate_or_request_with_http_token
  authenticate_with_http_token { |token| yield(token) } || request_http_token_authentication
end

#authenticate_with_http_token {|token| ... } ⇒ Object

Authenticate using an HTTP Bearer token. Returns the value of the block if a token is found. Returns nil if no token is found.

Examples:

user = authenticate_with_http_token do |token|
  User.find_by(key: token)
end

Yields:

  • (token)

    token value extracted from the Authorization header



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/rage/controller/api.rb', line 408

def authenticate_with_http_token
  auth_header = @__env["HTTP_AUTHORIZATION"]

  payload = if auth_header&.start_with?("Bearer")
    auth_header[7..]
  elsif auth_header&.start_with?("Token")
    auth_header[6..]
  end

  return unless payload

  token = if payload.start_with?("token=")
    payload[6..]
  else
    payload
  end

  token.delete_prefix!('"')
  token.delete_suffix!('"')

  yield token
end

#cookiesRage::Cookies

Get the cookie object. See Rage::Cookies.

Returns:



329
330
331
# File 'lib/rage/controller/api.rb', line 329

def cookies
  @cookies ||= Rage::Cookies.new(@__env, self)
end

#head(status) ⇒ Object

Send a response with no body.

Examples:

head :unauthorized
head 429

Parameters:

  • status (Integer, Symbol)

    set a response status



382
383
384
385
386
387
388
389
390
# File 'lib/rage/controller/api.rb', line 382

def head(status)
  @__rendered = true

  @__status = if status.is_a?(Symbol)
    ::Rack::Utils::SYMBOL_TO_STATUS_CODE[status]
  else
    status
  end
end

#headersHash

Set response headers.

Examples:

headers["Content-Type"] = "application/pdf"

Returns:

  • (Hash)


397
398
399
# File 'lib/rage/controller/api.rb', line 397

def headers
  @__headers
end

#paramsHash{Symbol=>String,Array,Hash,Numeric,NilClass,TrueClass,FalseClass}

Get the request data. The keys inside the hash are symbols, so params.keys returns an array of Symbol.
You can also load Strong Params to have Rage automatically wrap params in an instance of ActionController::Parameters.
At the same time, if you are not implementing complex filtering rules or working with nested structures, consider using native Hash#fetch and Hash#slice instead.

For multipart file uploads, the uploaded files are represented by an instance of Rage::UploadedFile.

Examples:

# make sure to load strong params before the `require "rage/all"` call
require "active_support/all"
require "action_controller/metal/strong_parameters"

params.permit(:user).require(:full_name, :dob)
# without strong params
params.fetch(:user).slice(:full_name, :dob)

Returns:

  • (Hash{Symbol=>String,Array,Hash,Numeric,NilClass,TrueClass,FalseClass})


470
471
472
# File 'lib/rage/controller/api.rb', line 470

def params
  @__params
end

#render(json: nil, plain: nil, status: nil) ⇒ Object

Note:

render doesn’t terminate execution of the action, so if you want to exit an action after rendering, you need to do something like render(...) and return.

Send a response to the client.

Examples:

render json: { hello: "world" }
render status: :ok
render plain: "hello world", status: 201

Parameters:

  • json (String, Object) (defaults to: nil)

    send a json response to the client; objects like arrays will be serialized automatically

  • plain (String) (defaults to: nil)

    send a text response to the client

  • status (Integer, Symbol) (defaults to: nil)

    set a response status



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/rage/controller/api.rb', line 351

def render(json: nil, plain: nil, status: nil)
  raise "Render was called multiple times in this action" if @__rendered
  @__rendered = true

  if json || plain
    @__body << if json
      json.is_a?(String) ? json : json.to_json
    else
      headers["content-type"] = "text/plain; charset=utf-8"
      plain.to_s
    end

    @__status = 200
  end

  if status
    @__status = if status.is_a?(Symbol)
      ::Rack::Utils::SYMBOL_TO_STATUS_CODE[status]
    else
      status
    end
  end
end

#requestRage::Request

Get the request object. See Rage::Request.

Returns:



317
318
319
# File 'lib/rage/controller/api.rb', line 317

def request
  @request ||= Rage::Request.new(@__env)
end

#request_http_token_authenticationObject

Render an HTTP header requesting the client to send a Bearer token for authentication.



448
449
450
451
# File 'lib/rage/controller/api.rb', line 448

def request_http_token_authentication
  headers["Www-Authenticate"] = "Token"
  render plain: "HTTP Token: Access denied.", status: 401
end

#reset_sessionObject

Reset the entire session. See Rage::Session.



516
517
518
# File 'lib/rage/controller/api.rb', line 516

def reset_session
  session.clear
end

#responseRage::Response

Get the response object. See Rage::Response.

Returns:



323
324
325
# File 'lib/rage/controller/api.rb', line 323

def response
  @response ||= Rage::Response.new(@__headers, @__body)
end

#sessionRage::Session

Get the session object. See Rage::Session.

Returns:



335
336
337
# File 'lib/rage/controller/api.rb', line 335

def session
  @session ||= Rage::Session.new(self)
end

#stale?(etag: nil, last_modified: nil) ⇒ Boolean

Note:

stale? will set the response status to 304 if the request is fresh. This side effect will cause a double render error, if render gets called after this method. Make sure to implement a proper conditional in your action to prevent this from happening:

if stale?(etag: "123")
  render json: { hello: "world" }
end

Checks if the request is stale to decide if the action has to be rendered or the cached version is still valid. Use this method to implement conditional GET.

Examples:

stale?(etag: "123", last_modified: Time.utc(2023, 12, 15))
stale?(last_modified: Time.utc(2023, 12, 15))
stale?(etag: "123")

Parameters:

  • etag (String) (defaults to: nil)

    The etag of the requested resource.

  • last_modified (Time) (defaults to: nil)

    The last modified time of the requested resource.

Returns:

  • (Boolean)

    True if the response is stale, false otherwise.



494
495
496
497
498
499
# File 'lib/rage/controller/api.rb', line 494

def stale?(etag: nil, last_modified: nil)
  still_fresh = request.fresh?(etag:, last_modified:)

  head :not_modified if still_fresh
  !still_fresh
end