Create a controller¶
Paths on this page are relative to your plugin directory, for example plugins/easyproject/easy_plugins/modification_acme/.
Controller¶
Controller files live in app/controllers. A controller inherits from ApplicationController. Its name is the plural of the model name plus Controller: MyModelsController for MyModel. A controller that works with something other than a model is named after that object, for example a dashboard (Easy pages).
Namespaced controllers do not work well with Redmine routes. For a model in a namespace, prefix the controller name instead: MyNamespaceMyModelsController for MyNamespace::MyModel.
Hotwire responses¶
For dynamic server-rendered screens that do not need Vue, use Hotwire/Turbo instead of legacy AJAX endpoints. See Hotwire in Easy8.
Controller structure¶
Start with before_action declarations. Common ones are authorize_global and a custom guard. Limit them to actions with only or except.
Entity retrieval¶
ApplicationController rescues ActiveRecord::RecordNotFound and renders a 404 page, so you do not need to rescue around .find. Prefer a memoised getter over a find_* before-action.
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 memoised getter and call it from the actions:
def show
my_model # raises RecordNotFound, rendered as 404
respond_to { |f| f.html }
end
private
# @return [MyModel]
# @raise [ActiveRecord::RecordNotFound] rendered as 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 the record is derived from an association rather than loaded by find, raise ActiveRecord::RecordNotFound yourself to get the same 404:
# @raise [ActiveRecord::RecordNotFound] when no active quote exists
def active_quote
@active_quote ||= my_model.active_quote || raise(ActiveRecord::RecordNotFound)
end
Next, include helpers. include AttachmentsHelper makes the methods available in the controller; helper :attachments makes them available in views. A controller that uses queries also calls include_query_helpers.
Then define the actions as public methods. End each action with a respond_to block that lists the 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
Private methods come last: before-action guards and request-handling logic.
Views¶
Action views live in app/views/my_models and are named after the action. A GET action renders its view by default; call render or redirect_to to do otherwise.
Routes¶
Routes are defined in your plugin's config/routes.rb. resources defines the default routes; restrict them with only or except. Add custom routes inside resources with member or collection blocks, each with an HTTP method and a name. For an action that accepts several HTTP methods, use match with via: %i[get put].
You can also define a route outside those blocks as method + path + options, where the method is an HTTP method or match. Options:
to- controller#actionas- route namevia- additional REST methodscontroller- controller nameaction- action name
Tests¶
Test controllers with request specs in test/spec/requests. Cover every endpoint of the controller, including rendering.