Fork me on GitHub

Controllers

Controllers in Caboose are inspired by rails and will look very similar.


Controllers are the glue that connect the views with the routing system. They are primarily responsible for dealing with models, collecting and arranging data, backend routing, and view determination. You should aim to have short controllers and embed most of your logic in other functional units like models or helpers and just coordinate and translate in the controller.

Creating a new Controller

Caboose applications start out with an ApplicationController which serves to be the root controller that all others in your application inherit from. This is not necessary, but is a good pattern to follow. The ApplicationController is a place to put all of the filters and helpers that will be common to all controllers within your application.

The ApplicationController inherits from Controller which contains the necessary methods to respond to requests. You can either extend Controller directly or extend another controller.

To create a new controller, you can either use the command line

$ caboose generate controller Posts
    

or just create a new file in the app/controllers directory.

app/controllers/posts_controller.coffee

import 'ApplicationController'

class PostsController extends ApplicationController
  index: ->
    @render()
    

Please note that naming conventions are very important here. If you are creating controllers by hand, be sure to put them in the app/controllers directory and name the file in all lowercase letters as [controller name]_controller.coffee. This should correspond to the controller class, which should always have Controller at the end of it and be camel-case. So PostsController would correspond to posts_controller.coffee, and UniqueUsersController would correspond to unique_users_controller.coffee.

Controller actions

Methods on a controller correspond to a route action. When caboose finds an appropriate route for a request, it will instantiate the controller and call the action named in the route. For instance, if you have a route to the posts controller's index action:

config/routes.coffee

modules.exports = ->
  @route '/posts', 'posts#index'
    

and you make a request to http://your-server/posts, caboose will first create the PostsController and then execute the index method. If the index method looks like above, then it will simply render the page without taking any further actions.

Responding

There are a few ways that you can respond to a request from within a controller. The most common will be @render and @redirect_to.

Available response methods

@render()
@render(view)
@render(options)
@render(view, options)

The main response method. All other methods are just sugar.

@redirect_to(path)
@redirect_to(path, flash)

Responds with a 302 redirection to the path of your choosing.
The optional flash object will be available on the next rendered page.

@redirect_to '/authorized_path', {success: 'Welcome to my website!'}

@not_found()
@not_found(error)

Responds with a 404, optionally taking an error object.

@error(error)

Passes the error to the next middleware in the stack. The default stack will respond with a 500 and print out the error.

@unauthorized()
@unauthorized(error)

Responds with a 401, optionally taking an error object.

Controller variables

Available variable

@request

The request object as it comes back from express.

@response

The response object as it comes back from express.

@headers

An object with the current request's headers.

@cookies

An object with the current request's cookies.

@session

An object containing the current session.

@body

The body of a request when the encoding is multipart.

@params

An object that contains the parameters of the current request. This is populated by the routing system.

@query

An object with the URL query parameters broken out. For example, http://www.caboosejs.com?foo=bar&page=1 will result in a @query object that looks like

{
  foo: 'bar',
  page: '1'
}
            

@flash

Attributes

Available attributes

helper

Make an object's methods available to the view. Check out the helper docs.

before_action

Execute a method before an action is executed.

import 'ApplicationController'

class UsersController extends ApplicationController
  before_action (next) ->
    console.log "Request to #{@request.url}"
    next()
  before_action 'something_in_application_controller'
  before_action 'filter_something', {only: ['index']}
  before_action ((next) ->
    console.log 'This is not the index action'
    next()
  ), {except: ['index']}
  
  filter_something: (next) ->
    return next(new Error()) unless @params.foobar?
    next()
  
  index: -> @render()
            

after_action
Execute a method after an action is executed. Docs and implementation coming soon!

How does this come together?

Here we will create a resources route for our blog posts and another route that will map the root route to the PostsController index route.

config/routes.coffee

module.exports = ->
  @resources 'posts'
  @route '/', 'posts#index'
    

We have an ApplicationController with a filter method that can be shared by all controllers who extend ApplicationController. We're using the caboose-model plugin to query for the post specified by the id parameter, setting the successful response to an instance variable to be used by the actions later.

app/controllers/application_controller.coffee

import 'Post'

class ApplicationController extends Controller
  posts_filter: (next) ->
    Post.where(_id: @params.id).first (err, post) =>
      return next(err) if err?
      return next(new Error("Could not find post with id #{@params.id}")) unless post?
      @post = post
      next()
    

Finally, we create a PostsController to handle the resources actions. We're telling the controller to filter the show, edit, update and destroy methods with the posts_filter from the ApplicationController to find the current post. We've also implemented the index and show actions here. The show action will already have @post set from the posts_filter, so all it does is render the view (which will be located at app/views/posts/show.html.[view-engine]).

The index action is a bit more complex, and implements a rudimentary form of paging. It is pulling the page and page_size from the @query and then using that to skip and limit the post records. Finally, we render the index view (which will be located at app/views/posts/index.html.[view-engine]).

app/controllers/posts_controller.coffee

import 'Post'
import 'ApplicationController'

class PostsController extends ApplicationController
  before_action 'posts_filter', {only: ['show', 'edit', 'update', 'destroy']}

  index: ->
    page_size = @query.page_size || 10
    page = @query.page || 1
    Post.skip(page_size * (page - 1)).limit(page_size).array (err, posts) =>
      return @error(err) if err?
      @posts = posts
      @render()
  
  show: -> @render()
  
  # handle other actions...