Class: Cybertrain::Controller

Inherits:
Object
  • Object
show all
Defined in:
cybertrain/controller.rb

Overview

Base class of every application controller, through the app’s ApplicationController. One instance handles one request: the router builds it, runs the before callbacks, the action, the implicit render and the after callbacks.

class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]

  def index
    @articles = Article.all.to_a
  end

  def new_action   # GET /articles/new: a method named `new` would shadow ArticlesController.new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)
    if @article.save
      flash[:notice] = "Article was successfully created."
      redirect_to article_path(@article), status: :see_other
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def set_article
    @article = Article.find(params[:id])
  end

  def article_params
    params.require(:article).permit(:title, :body)
  end
end

Rules that differ from Rails, all because Spinel compiles ahead of time with no send or instance_exec:

  • Actions are public methods; the new action is def new_action (its view is still new.html.erb, its action name "new").
  • Templates see the instance variables assigned (@x = ...) in the controller’s file or its parents’ files, found by spin run gen; re-run it after adding one (cybertrain server does).
  • A block callback takes the controller as its argument (before_action { |c| ... }), so it can call public methods only.
  • render and redirect_to do not end the action, and a second one replaces the first; return after them when more code follows.
  • Every controller also has the route helpers (article_path(...), see Gen::UrlHelpers).

Implementation: the router calls process(:show) { |c| c.show }, so the action name and the action body both arrive as literals (no send).

Callback and rescue declarations are runtime data keyed by class name; symbol callbacks are dispatched through run_callback(name), which gen/controllers.rb overrides in each controller with a case table.

Constant Summary collapse

STATUS_SYMBOLS =

The Symbols status: accepts in #render, #head and #redirect_to. Any other status must be given as an Integer.

{
  ok: 200, created: 201, no_content: 204,
  moved_permanently: 301, found: 302, see_other: 303,
  bad_request: 400, forbidden: 403, not_found: 404,
  unprocessable_entity: 422, internal_server_error: 500
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#action_name ⇒ String (readonly)

The action being run, by its route name: "show", and "new" for new_action.

Returns:

  • (String)


269
270
271
# File 'cybertrain/controller.rb', line 269

def action_name
  @action_name
end

#params ⇒ Params (readonly)

The request parameters: the query string, a urlencoded form body and the route’s :id-style segments (in that order, later ones winning).

Examples:

params[:id]
params.require(:article).permit(:title, :body)

Returns:



263
264
265
# File 'cybertrain/controller.rb', line 263

def params
  @params
end

#request ⇒ Request (readonly)

The request: method, path, headers, raw body.

Returns:



246
247
248
# File 'cybertrain/controller.rb', line 246

def request
  @request
end

#rescued_exception ⇒ StandardError? (readonly)

The exception a rescue_from with: method is handling.

Returns:

  • (StandardError, nil)


274
275
276
# File 'cybertrain/controller.rb', line 274

def rescued_exception
  @rescued_exception
end

#response ⇒ Response (readonly)

The response being built. #render, #redirect_to and #head fill it; set headers and cookies on it directly.

Examples:

response.set_header("Cache-Control", "no-store")

Returns:



254
255
256
# File 'cybertrain/controller.rb', line 254

def response
  @response
end

Class Method Details

.after_action(name = nil, only: [], except: []) {|controller| ... } ⇒ nil

Runs a method or a block after the action and its implicit render, in the same order as before_action (not reversed). Skipped when a before callback halted or anything raised.

Examples:

after_action { |c| c.response.set_header("Cache-Control", "no-store") }

Parameters:

  • name (Symbol, nil) (defaults to: nil)
  • only (Array<Symbol>) (defaults to: [])
  • except (Array<Symbol>) (defaults to: [])

Yield Parameters:

Returns:

  • (nil)


140
141
142
# File 'cybertrain/controller.rb', line 140

def self.after_action(name = nil, only: [], except: [], &block)
  add_callback(Callback.new(:after, name, block, only, except))
end

.before_action(name = nil, only: [], except: []) {|controller| ... } ⇒ nil

Runs a method or a block before the action. The chain runs the parent classes’ callbacks first, then each class’s in declaration order, and stops at the first callback that renders, redirects or calls #head: the action, the implicit render and the after callbacks are then skipped.

One method name per call. The method is called without a receiver, so it may be private; it must be named on the same line as before_action for spin run gen to find it.

Examples:

before_action :set_article, only: [:show, :edit, :update, :destroy]
before_action(except: [:index, :show]) { |c| c.head :forbidden unless c.session[:user_id] }

Parameters:

  • name (Symbol, nil) (defaults to: nil) —

    a method of this controller, or nil with a block

  • only (Array<Symbol>) (defaults to: []) —

    run for these actions only (:new for new_action)

  • except (Array<Symbol>) (defaults to: []) —

    run for every action but these

Yield Parameters:

  • controller (Controller) —

    the controller (no implicit self)

Returns:

  • (nil)


125
126
127
# File 'cybertrain/controller.rb', line 125

def self.before_action(name = nil, only: [], except: [], &block)
  add_callback(Callback.new(:before, name, block, only, except))
end

.rescue_from(klass, with: nil) {|controller, exception| ... } ⇒ nil

Handles an exception raised by a callback, the action or its render: rescue_from NotFound, with: :not_found or rescue_from(NotFound) { |c, e| c.head :not_found }.

Unlike Rails, the class must match exactly (a subclass does not), and the first handler found wins, looking in this class before its parents. A with: method reads the exception from #rescued_exception; keep with: on the same line as rescue_from. A handler that renders nothing leaves an empty 200.

Unhandled, a Params::ParameterMissing or malformed query answers 400 and anything else 500 (a RecordNotFound too: the generated ApplicationController rescues it to a 404).

Examples:

rescue_from Cybertrain::RecordNotFound, with: :record_not_found

private

def record_not_found
  render plain: "Not Found", status: :not_found
end

Parameters:

  • klass (Class) —

    an exception class

  • with (Symbol, nil) (defaults to: nil) —

    a method of this controller, or nil with a block

Yield Parameters:

  • controller (Controller)
  • exception (StandardError)

Returns:

  • (nil)


176
177
178
179
# File 'cybertrain/controller.rb', line 176

def self.rescue_from(klass, with: nil, &block)
  (RESCUES[self.name] ||= []) << RescueHandler.new(klass.name, with, block)
  nil
end

Instance Method Details

#controller_path ⇒ String

“posts” for PostsController: the directory under app/views/ its templates come from.

Returns:

  • (String)


366
367
368
369
370
371
372
373
374
375
376
# File 'cybertrain/controller.rb', line 366

def controller_path
  known = @controller_path
  return known unless known.empty?

  # Asked for at least three times in a render (view_env, render_template,
  # the layout), each an underscore of the class name: remembered.
  name = Inflector.underscore(self.class.name)
  path = name.end_with?("_controller") ? name[0, name.length - 11] : name
  @controller_path = path
  path
end

#flash ⇒ Flash

Messages for the next request (flash[:notice] = "Saved." before a redirect) or, through flash.now, for this one.

Returns:



290
291
292
# File 'cybertrain/controller.rb', line 290

def flash
  @ctx.flash
end

#head(status = DEFAULT_STATUS) ⇒ nil

Answers with a status and an empty body.

status defaults to :ok only so that Spinel boxes the parameter (see DEFAULT_STATUS); callers are expected to pass one.

Examples:

head :forbidden
head 401

Parameters:

  • status (Integer, Symbol) (defaults to: DEFAULT_STATUS) —

Returns:

  • (nil)


493
494
495
496
497
498
# File 'cybertrain/controller.rb', line 493

def head(status = DEFAULT_STATUS)
  @response.status = Controller.status_code(status)
  @response.body = ""
  @response.performed!
  nil
end

#performed? ⇒ Boolean

Returns true once this request has been rendered, redirected or answered with #head.

Returns:

  • (Boolean) —

    true once this request has been rendered, redirected or answered with #head



503
504
505
# File 'cybertrain/controller.rb', line 503

def performed?
  @response.performed?
end

#redirect_to(location, status: DEFAULT_REDIRECT_STATUS) ⇒ nil

Answers with a redirect (302 by default) to a URL String: build it with a route helper. After a form submission use status: :see_other (303), as the scaffold does, so the browser follows with a GET. It does not end the action. To show a message on the next page, set #flash first.

Examples:

flash[:notice] = "Article was successfully created."
redirect_to article_path(@article), status: :see_other

Parameters:

  • location (String) —

    a path or URL (a record is not accepted)

  • status (Integer, Symbol) (defaults to: DEFAULT_REDIRECT_STATUS) —

Returns:

  • (nil)

Raises:

  • (ArgumentError) —

    when the location holds a CR or LF



478
479
480
481
# File 'cybertrain/controller.rb', line 478

def redirect_to(location, status: DEFAULT_REDIRECT_STATUS)
  @response.redirect(location, Controller.status_code(status))
  nil
end

#render(template = nil, plain: nil, html: nil, json: nil, status: DEFAULT_STATUS, content_type: nil, partial: nil, locals: {}, layout: true) ⇒ nil

Renders the response from a template, text, HTML or JSON: exactly one of template, plain:, html:, json: or partial:.

An action that renders nothing renders its own template (app/views/<controller_path>/<action>.html.erb) in the layout.

  • template: app/views/<controller_path>/<template>.html.erb, always this controller’s directory, inside the layout (app/views/layouts/application.html.erb, when it exists) unless layout: false.
  • plain: the String as text/plain.
  • html: as text/html; a plain String is escaped, a SafeString is sent as it is.
  • json: as application/json; a String is sent as it is (taken to be JSON already), anything else goes through JSON.generate. For a record, pass Model#as_json or Model#to_json.
  • partial: _<name>.html.erb ("form" in this controller’s directory, "comments/comment" in another), never in a layout.

It does not end the action: a later render or redirect_to replaces it.

Examples:

render :new, status: :unprocessable_entity
render plain: "Not Found", status: :not_found
render json: { ok: true }, status: :created
render json: @article.to_json

Parameters:

  • template (Symbol, String, nil) (defaults to: nil) —

    an action’s template, :new

  • plain (String, nil) (defaults to: nil)
  • html (String, SafeString, nil) (defaults to: nil)
  • json (Object, nil) (defaults to: nil)
  • partial (String, nil) (defaults to: nil)
  • status (Integer, Symbol) (defaults to: DEFAULT_STATUS) —

    see STATUS_SYMBOLS; 200 by default

  • content_type (String, nil) (defaults to: nil) —

    replaces the content type

  • locals (Hash{Symbol => Object}) (defaults to: {}) —

    extra template variables

  • layout (Boolean) (defaults to: true) —

    false renders a template without the layout

Returns:

  • (nil)

Raises:

  • (ArgumentError) —

    when not exactly one of template, plain:, html:, json:, partial: is given, or the status is unknown

  • (Template::MissingTemplate) —

    when the template does not exist



435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# File 'cybertrain/controller.rb', line 435

def render(template = nil, plain: nil, html: nil, json: nil, status: DEFAULT_STATUS, content_type: nil,
           partial: nil, locals: {}, layout: true)
  given = 0
  given += 1 unless template.nil?
  given += 1 unless plain.nil?
  given += 1 unless html.nil?
  given += 1 unless json.nil?
  given += 1 unless partial.nil?
  raise ArgumentError, "render needs exactly one of template, plain:, html:, json:, partial:" if given != 1

  @response.status = Controller.status_code(status)
  if !plain.nil?
    @response.body = plain
    @response.content_type = "text/plain; charset=utf-8"
  elsif !html.nil?
    @response.body = Html.out(html)
    @response.content_type = "text/html; charset=utf-8"
  elsif !json.nil?
    @response.body = json_body(json)
    @response.content_type = "application/json; charset=utf-8"
  elsif !partial.nil?
    render_partial(partial.to_s, locals)
  else
    render_template(template.to_s, locals, layout: layout)
  end
  @response.content_type = content_type unless content_type.nil?
  @response.performed!
  nil
end

#session ⇒ Session

This visitor’s session, kept in a signed cookie.

The session and flash installed by SessionStore (nil when the middleware is not in the stack, e.g. in bare unit tests).

Returns:



282
283
284
# File 'cybertrain/controller.rb', line 282

def session
  @ctx.session
end