How to create new Controller
This section provide basic information what needs to be implemented for new working controller.
Controller
Controllers source codes are located in directory app/controllers. Controller must be inherited from ApplicationController.
Usual controller name is pluralized name of model, which is used in controller, with suffix Controller.
For example MyModelsController for model MyModel. There are some special occasions, when controller doesn't work with model,
but some other object instead. In this case, controller name is name of object with suffix Controller. For example EasyPages.
Namespacing of controllers does not work well with routes in redmine. If controller works with model in namespace, prefix should be used.
For example MyNamespaceMyModelsController for model MyNamespace::MyModel.
Controller structure
First properties to define within a new controller are before actions. Most common before actions for easy controllers are authorize_global and a custom action guard.
Definition of before_action contains name of method to be executed and options to restrict execution to specific actions. Possible options are only and except.
Entity retrieval — prefer memoized getters over find_* before-actions
ApplicationController in Easy8 already registers a global rescue_from for ActiveRecord::RecordNotFound
that renders a 404 page. This means you do not need to rescue locally around .find calls.
Instead of a find_* before-action with a local rescue:
# Avoid this pattern
before_action :find_my_model
def find_my_model
@my_model = MyModel.visible.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
Use a private memoized getter and call it directly from actions:
def show
my_model # raises RecordNotFound → global handler → 404
respond_to { |f| f.html }
end
private
# @return [MyModel]
# @raise [ActiveRecord::RecordNotFound] handled globally → 404
def my_model
@my_model ||= MyModel.visible.find(params[:id])
end
If the record is optional (not finding it is not an error), use .find_by and handle nil explicitly:
If a record has no dedicated AR table (e.g. active_quote derived from an association), raise
ActiveRecord::RecordNotFound manually so the global handler still produces a 404:
# @raise [ActiveRecord::RecordNotFound] when no active quote exists
def active_quote
@active_quote ||= my_model.active_quote || raise(ActiveRecord::RecordNotFound)
end
After that, there are helpers to be included. There are two ways to include helper.
First is to include helper module (include AttachmentsHelper) directly into controller.
Second is to use helper method. This allow to use helper methods within views.
If controller uses queries, is good to include query helper.
Next to define are controller actions. Each action is represented by public controller method.
Each method should be ended by block respond_to, which defines various response formats.
def my_action
# request process implementation
respond_to do |format|
format.html do
flash[:notice] = l(:notice)
redirect_to path
end
format.js { render_api_ok }
end
end
Last to define are private methods. These methods usually serves as before_actions or define logic necessary to request handling.
Views
Action views are defined in app/views/my_models directory. Each action view name must correspond to action name.
Each GET action use action view by default. To achieve otherwise, render or redirect_to methods must be used within action.
Routes
One way how to define endpoints for actions are routes. Routes are defined in config/routes.rb file.
Default actions routes for controller are defined by resources method, which should correspond with name of object/model_plural within controller name.
To restrict default routes, only or except options can be used. There are several ways how to define custom routes.
Within resources block is possible to use member or collection block. Each custom route must have defined REST method and name.
If action is using multiple REST methods, match method in combination with option via: %i[get put] can be used.
It is also possible to define custom routes outside mentioned blocks. This kind of definition follows template method + path + options.
Method is a REST method or match. Options can be:
to- controller#actionas- route namevia- additional REST methodscontroller- controller nameaction- action name
Tests
Our best-practice is to describe controller tests in request specs. These are defined in spec/requests directory.
Within request spec should be tested each endpoint, handled by controller under test including "render".