Ruby rails: controllers

From wikinotes
Revision as of 21:50, 19 September 2021 by Will (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Controllers manipulate the model/views. They should be short/specific. This is how users interact with the site.
In code, they are represented as ActionController::Base and generally subclasses of your application's ApplicationController.
Handles sessions, logins/authorization, filters, redirection, and errors.


Documentation

official overview https://guides.rubyonrails.org/v2.3.11/action_controller_overview.html

Locations

{project}/app/controllers/application_controller application superclass for all controllers
{project}/app/controllers/{controller}_controller.rb controllers

Basics

Controllers interpret requests

  • Controllers should be skinny, Models should contain most of the logic.
  • Controller-Name should be in plural. ex: UsersController
  • methods are referred to as Actions
  • Controller routes generally refers to urls the same name. UsersController --> https://example.com/users (defined in routes.rb)
  • Request object accessible via request method.

Introspection

Module::ThingController.action_methods   # list all actions on controller
Module::ThingController.controller_path  #

Methods

Some methods are resolved automatically

TODO:

expand on this, document properly. Possibly reference to url helpers

GET domain.com/path     # index
GET domain.com/path/$ID # show

Query Params

Query params ex: https://example.com?status=unread&before=2020-01-01 are processed in the index method.

Simple Params


# ex:  https://example.com/users?status=unread&before=2020-01-01

class UsersController < ActionController::Base
  def index
    @status = params[:status]  # nil if not assigned
    @before = params[:before]  # nil if not assigned
  end
end

Collection Params


lists

# each `{variable}[]=` indicates an entry within the list
# 
# https://example.com/users?ids[]=1&ids[]=2&ids[]=3

class UsersController < ActionController::Base
  def index
    params['ids] == ['1', '2', '3']
  end
end


  • hashes are also possible, but confusing

JSON Params


Params can be json-encoded, if in your HTML header your Content-Type: application/json

# https://example.com/users?{"foo": "bar"}

class UsersController < ActionController::Base
  def index
    params["foo"] == "bar"
  end
end


Request Object

  • the attr ActionController.request contains the instance of ActionDispatch::Request

See https://guides.rubyonrails.org/action_controller_overview.html#the-request-object

# curl -X POST -d '{"json": true}' https://example.com/foo

class MyController < ActionController::Base
  def foo
    payload = request.body.read()  # '{"json": true}'
    request.post?                  # determine HTTP method
  end
end

Response Object

Generally the response object is not used directly, but instead returned by ??.
You can set an http status (error code) by ??.

render plain: "404 Not Found", status: 404   #
render status: 500                           # HTTP-500
render status: :forbidden                    # rails symbol for HTTP-500
flash[:notice] = "bar"
flash[:error] = "bar"
redirect_to photos_url                                       # url method for route /photos
redirect_to post_url(@post), flash: { error: "error msg" }   # 
redirect_to({ action: 'atom' }, alert: "error msg")          #
redirect_back(fallback_location: photos_url)                 #


Forms

  • HTML form data is made accessible on the params() method,
    which returns a dict-like ActionController::Parameters
  • If the param is not assigned, no error is raised, instead the field will be empty
<!-- sample html form -->
<html>
  <body>
    <form action="/create.html" method="post">
      <label for="title">Title:</label>
      <input id="title" type="text">

      <label for="description">Description:</label>
      <input id="description" type="text">

      <input type="submit">
    </form>
  </body>
</html>
# sample controller, receives form data
class BlogPost < ActionController::Base
  def create
    @title = params[:title]
    @description = params[:description]
  end
end

Output Formats

A single endpoint may request different returned formats.

The requested format is determined by:

Within rails routes these format-able routes will show like this:

users GET /users(.:format)  users#index
  • You handle request types with format.${TYPE}
  • You can change return types with render(${TYPE}: ...)
def update
  # .. work ..
  respond_to do |format|
    format.html { redirect_to @url }             # request to ../update.html, redirect
    format.json { render(json: @foo.errors) }    # request to ../update.json, return JSON payload
    format.js                                    # request to ../update.js, evaluate ../views/../update.js.erb
  end
end

Hooks

Like models, controllers have several hooks that you may use to alter controller behaviours.

before_action :set_user, only: [:show, :edit, :update, :destroy]  # call set_user() before invocations of show/edit/update/destroy

Overriding Layout

Instead of using the default (app/views/layouts/application.html.erb or app/views/layouts/{controller}.html.erb), you can override it. This is useful if you want to use a particular layout for a group of views.

Determined by a "string"

class WelcomeController < ApplicationController
  layout "forest_layout"  # {project}/app/views/layouts/forest_layout.html.erb
end

Determined by a :method

class WelcomeController < ApplicationController
  layout :choose_layout

  def choose_layout
    if ENV["USE_OCEAN_LAYOUT"] == "true"
      return "ocean_layout"
    end
    return "forest_layout"
  end
end

Conditional based on Request Format (ex: RSS, HTML, XML, ...)

class WelcomeController < ApplicationController
  # conditionally set layout
  layout "forest_layout", except: :rss
  layout "forest_layout", except: [:rss, :xml]

  # restrict layout type
  layout "forest_layout", only: :html
end

Render different view

render/render_to_string will render a view (and format as a response).

def index
  render :user            # render view for action 'user'
  render "products/user"  # render a different controller's action
end