Class: Cybertrain::Controller
- Inherits:
-
Object
- Object
- Cybertrain::Controller
- 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
newaction isdef new_action(its view is stillnew.html.erb, its action name"new"). - Templates see the instance variables assigned (
@x = ...) in the controller’s file or its parents’ files, found byspin run gen; re-run it after adding one (cybertrain serverdoes). - A block callback takes the controller as its argument
(
before_action { |c| ... }), so it can call public methods only. renderandredirect_todo not end the action, and a second one replaces the first;returnafter 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
-
#action_name ⇒ String
readonly
The action being run, by its route name:
"show", and"new"fornew_action. -
#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). -
#request ⇒ Request
readonly
The request: method, path, headers, raw body.
-
#rescued_exception ⇒ StandardError?
readonly
The exception a Controller.rescue_from
with:method is handling. -
#response ⇒ Response
readonly
The response being built.
Class Method Summary collapse
-
.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 Controller.before_action (not reversed).
-
.before_action(name = nil, only: [], except: []) {|controller| ... } ⇒ nil
Runs a method or a block before the action.
-
.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_foundorrescue_from(NotFound) { |c, e| c.head :not_found }.
Instance Method Summary collapse
-
#controller_path ⇒ String
“posts” for PostsController: the directory under
app/views/its templates come from. -
#flash ⇒ Flash
Messages for the next request (
flash[:notice] = "Saved."before a redirect) or, throughflash.now, for this one. -
#head(status = DEFAULT_STATUS) ⇒ nil
Answers with a status and an empty body.
-
#performed? ⇒ Boolean
True once this request has been rendered, redirected or answered with #head.
-
#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.
-
#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:.
-
#session ⇒ Session
This visitor’s session, kept in a signed cookie.
Instance Attribute Details
#action_name ⇒ String (readonly)
The action being run, by its route name: "show", and "new" for
new_action.
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).
263 264 265 |
# File 'cybertrain/controller.rb', line 263 def params @params end |
#request ⇒ Request (readonly)
The request: method, path, headers, raw body.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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) unlesslayout: false.plain:the String astext/plain.html:astext/html; a plain String is escaped, a SafeString is sent as it is.json:asapplication/json; a String is sent as it is (taken to be JSON already), anything else goes throughJSON.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.
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).
282 283 284 |
# File 'cybertrain/controller.rb', line 282 def session @ctx.session end |