Roda Controller

Adds controller functionality to Roda

Configuration

Configure your Roda application to use this plugin:

plugin :controller

You can already register your controllers:

plugin :controller, controllers: { users: UsersController }

# Infer controller key to 'users'
plugin :controller, controllers: [UsersController]

# Infer controller key to 'users_handler'
plugin :controller, controllers: [UsersHandler]

You may also register controllers later, inside your application class declaration:

register_controller UsersController
register_controller MoviesController

# Passing a list of controllers
register_controller [UsersController, MoviesController]

# Explicitly declaring the controller key
register_controller :users, UsersHandler
register_controller :movies, MoviesHandler

Usage

Controller Naming

The controller class is not required to follow any naming convention, specially if you specify explicitly the controller key when registered.

However if it ends with Controller this will be stripped from the controller key if it is inferred. I.E. UsersController => 'users' and UsersHandler => 'users_handler'

Controller Definition

You just need to implement the methods with the action names you are going to use:

class UsersController
  def index
    "User Index"
  end

  def show(user_id)
    "User Show ##{user_id}"
  end
end

Dispatching to controllers

Call #dispatch whenever you want to receive the result of the controller action, this is a textbook RESTful routing definition:

r.on("users") do
  r.is do
    r.get do
      dispatch('users#index')
    end

    r.post do
      dispatch('users#create', args: r.params)
    end
  end

  r.on(:id) do |user_id|
    r.is do
      r.get do
        dispatch('users#show', args: user_id)
      end

      r.patch do # requires all_verbs plugin to be loaded
        dispatch('users#update', args: [user_id, r.params])
      end

      r.delete do # requires all_verbs plugin to be loaded
        dispatch('users#destroy', args: user_id)
      end
    end
  end
end

Receiving dispatched responses

You may expose the controller responses using the @responds_with variable:

# users_controller.rb
class UsersController
  def show(user_id)
    user = user: User.find(user_id)

    @responds_with = { user: user, best_movie: Movie.best_for(user) }
  end
end

# app.rb
r.get do
  dispatch('users#show', args: user_id)

  # Do stuff with @user and @best_movie, or access @responds_with directly
  # if the instance variable cannot be overwritten
end

Rendering Views

If you are using the render plugin you can use the #controller method instead of #dispatch. It receives the same parameters but will call the #view method with the following convention:

controller('users#show', args: user_id) # will call view('users/show')

Initializing Controllers

You may specify parameters for controller initialization, to provide context to your actions without polluting the method arguments:

# users_controller.rb
class UsersController
  attr_reader :request, :response

  def initialize(request, response)
    @request = request
    @response = response
  end

  # be able to access the request and response inside actions

# app.rb
## inside routes
dispatch('users#update', args: [user_id, r.params], inject: [request, response])
## registering controller
register_controller(:users, -> { |req, res| UsersController.new(req, res) }

Advanced Configuration

You can setup default arguments for the #dispatch and #controller methods:

# Will always initialize controllers with request and response args
plugin :controller, inject: ->{ [request, response] }

DRYing up

By following conventions you may dry everything up.

This:

  • Creates a RodaController class to be inherited on your controllers
  • Exposes request and response variables inside actions
  • Adds helper methods to your actions
  • Automatically loads all controllers inside controllers folder
# roda_controller.rb
class RodaController
  attr_reader :request, :response

  # Access request and response methods
  def initialize(request, response)
    @request = request
    @response = response
  end

  # Enables respond_with exposed_variable: local_variable
  def respond_with(opts)
    @responds_with ||= {}
    @responds_with.merge!(opts)
  end

  # Get list of all controllers
  def self.descendants
    ObjectSpace.each_object(Class).select { |klass| klass < self }
  end
end

# app.rb
## configuring the plugin
plugin :controller, inject: ->{ [request, response] }

## automatically registering all controllers inside controllers folder 
Dir["controllers/*.rb"].each { |file| require_relative file }

RodaController.descendants.each do |controller|
  controller_key = Roda::RodaPlugins::Controller.underscore(controller.name)
  controller_proc = -> (req, res) { controller.new(req, res) }

  register_controller(controller_key, controller_proc)
end

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/badosu/roda-controller

License

The gem is available as open source under the terms of the MIT License.