Introduction to Controllers
What are controllers?
In a Rails application, controllers act as a mediator between the user interface and the data models. They receive user input from the interface, make decisions based on that input, and interact with the model layer to perform the requested actions. In the Model-View-Controller (MVC) architecture followed by Rails, the controllers sit between the models and views. They handle the logic of the application and translate the user's request into a suitable response. Essentially, controllers are responsible for executing the functionality needed in a Rails application. They act as the coordinator of all user requests and bridge the gap between the users and the underlying data models. Controllers interact with models in Rails through the use of methods, which allow them to query, create, update, and delete data as needed.
Generating a controller
In Rails, generating a controller is a quick and easy process. You can generate a controller using the 'rails generate controller' command followed by the name of your controller. For example, if you want to generate a controller named 'Users', you would type 'rails generate controller Users' in the terminal. This generates a controller file with the name 'users_controller.rb' and related files in the app/controllers directory.
Alternatively, you can create a controller manually by creating a new file with the .rb extension in the app/controllers directory and naming it according to Rails naming conventions. The controller's name should consist of the plural form of the model's name, followed by the word "Controller". For example, if your model name is "User", the corresponding controller name would be "UsersController". Inside the controller file, you can define actions using methods, which are used to handle requests and perform specific tasks.
A controller is a normal class
In Ruby on Rails, a controller is a normal class that is responsible for processing requests. When you generate a new controller, you get a class that already inherits from the ApplicationController
class, which in turn inherits from the ActionController::Base
class.
class MyController < ApplicationController
def index
# Do something here
end
end
This code snippet shows the definition of a MyController
class that inherits from the ApplicationController
class. MyController
has an index
method that is responsible for handling requests to the /my
path.
The ApplicationController
class is a great place to put common functionality that's shared by all controllers in your application. For example, if you have constants that are used throughout all controllers, you can place them in your ApplicationController
class.
class ApplicationController < ActionController::Base
MY_CONSTANT = "This is a constant"
# Define other constants here...
def my_helper_method
# Define a helper method here...
end
end
In the code snippet above, we see the definition of a MY_CONSTANT
constant in the ApplicationController
class. This constant can be accessed by any controller that inherits from ApplicationController
.
You can also define your own base class for your controllers, rather than inheriting from ApplicationController
directly. This is useful if you have some controllers with completely different functionality from others and you don't want them to share a common ancestor.
class MyCustomBaseController < ActionController::Base
MY_CUSTOM_CONSTANT = "This is another constant"
def my_custom_method
# Do something here
end
end
class MyCustomController < MyCustomBaseController
def index
my_custom_method
# Do something here
end
end
In this code snippet, we define a new base class called MyCustomBaseController
that includes a MY_CUSTOM_CONSTANT
constant and a my_custom_method
method. The MyCustomController
class inherits from MyCustomBaseController
and has access to both the constant and method.
Actions and Parameters
Actions
Basic Actions: index, create, update, destroy (link to rails routing) + custom functions
Scopes (visibility) of methods
The basic actions that are commonly used in controllers are index
, create
, update
, and destroy
. These actions are linked to Rails routing and are used to manipulate data in the database. For example, to create a new record, the create action is used which typically involves creating a new instance of the model and saving it to the database. Custom functions can also be created within controllers to perform additional actions based on specific business logic.
In addition to the basic actions, controllers also use scopes to control the visibility of methods. Scopes are used to control how methods are accessed by other parts of the application. For example, a private method can only be accessed within the controller, while a protected method can be accessed by the controller and its subclasses. Public methods, on the other hand, can be accessed by any part of the application. Scopes are defined using the private, protected, and public keywords in the controller class.
Here is an example of a controller with basic actions and custom functions:
class UsersController < ApplicationController
def index
@users = get_users
end
private
def get_users
User.all
end
end
Parameters
When working with controller parameters in Rails, there are two types: query parameters and post parameters. Query parameters are passed in the URL and are used for GET requests, while post parameters are passed in the request body and are used for POST requests. Both types of paramters can be accessed using the params
tag with the parameter name e.g. params['name']
.
To ensure that the parameters being passed to a controller action are valid and safe, it's important to sanitize them using require
and permit
. require
is used to specify which parameters are required, and if they are not present in the request, an error will be raised. permit
, on the other hand, is used to specify a whitelist of parameters that are allowed to be accessed, and any other parameters will be ignored.
For example, in a user registration form, the parameters for the user's name, email, and password would be required, while any additional parameters such as a user's role would need to be permitted by the controller. Here's an example:
def create
@user = User.new(user_params)
end
private
def user_params
params.require(:user).permit(:name, :email, :password)
end
In this example, the create
action creates a new user and the user_params
method sanitizes the input data. The params.require(:user)
line ensures that the user
parameter is required, and the subsequent .permit(:name, :email, :password)
line specifies that only the name
, email
, and password
parameters are permitted. Any other parameters will be ignored.
For instance, if the request contains the following JSON data:
{
"user": {
"name": "Omar Diaa",
"email": "omardiaa@example.com",
"password": "password123",
"role": "admin"
}
}
The user_params
method will only permit the name
, email
, and password
parameters, and the role
parameter will be ignored. This helps to prevent unexpected or malicious input from affecting the application.
Filters
Rails controller filters such as before_action
and after_action
allow you to execute specific methods before or after controller actions. These filters are useful for modifying the behavior of a controller before or after it runs a specific action. For example, you can use a before_action
filter to check if the user is authorized to perform a certain action, and a after_action
filter to send specific headers like HTTP status codes
or content-type
in the response.
Here's a small example that shows how to use a before_action
filter in a Rails controller:
class ExampleController < ApplicationController
before_action :require_login
def index
# ...
end
private
def require_login
# ...
end
end
In this example, the require_login
method is called before the index
and show
actions are executed. If the user isn't logged in, they are redirected to the login page. This prevents unauthorized access to actions that require authentication.
In addition to before_action
and after_action
, Rails controller filters also allow you to limit the actions that they are run on. You can do this by specifying the only
or except
options.
For example, here's how to use only
to only run the require_login
filter on the index
action:
class ExampleController < ApplicationController
before_action :require_login, only: [:index]
def index
# ...
end
def show
# ...
end
private
def require_login
# ...
end
end
In this example, the require_login
method is only called before the index
action is executed, and not before the show
action.
You can also use the skip_before_action
method to skip filters for specific actions:
class ExampleController < ApplicationController
before_action :require_login
skip_before_action :require_login, only: [:index]
def index
# ...
end
def show
# ...
end
private
def require_login
# ...
end
end
In this example, the skip_before_action
method is used to skip the require_login
filter for the index
action. This allows anonymous users to access the index
page, but not the show
page.
Concerns
Rails Concerns help to avoid code repetition by extracting and reusing common functionality in multiple controllers. For example, you could create a FetchableResource
module that includes a fetch_resource
method for fetching a resource by its ID:
module FetchableResource
def fetch_resource(id)
@resource = Resource.find(id)
# ...
end
end
Then, you can include this module in the controllers where you need its functionality:
class PostsController < ApplicationController
include FetchableResource
before_action :fetch_resource, only: [:show, :edit, :update, :destroy]
# ...
end
class CommentsController < ApplicationController
include FetchableResource
before_action :fetch_resource, only: [:show, :edit, :update, :destroy]
# ...
end
This way, you can reuse the fetch_resource
method in both controllers without repeating the code.
Exception Handling
In Ruby on Rails, controllers are responsible for handling requests and returning responses. When an error occurs during the request processing, such as a record not found, we need to handle it gracefully to avoid crashing the application. The rescue_from
keyword in Rails allows us to catch and handle exceptions globally across the controller. This is useful because it automatically applies error handling to all actions within the controller. We can define our error handling methods and specify the type of exception we want to handle using rescue_from
.
class PostsController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :handle_error
def show
@post = Post.find(params[:id])
end
private
def handle_error
render json: { error: "Record not found" }, status: :not_found
end
end
In the code above, rescue_from
keyword is used to catch ActiveRecord::RecordNotFound
exceptions globally across the PostsController
. If such an exception occurs, the handle_error
method will be invoked. This will render a JSON error response with a message of "Record not found", to indicate to the user that the requested record cannot be found.
Session Managent
a session refers to a way of storing data for a user between requests. A session is used to maintain data that is related to a particular user, such as their login status or shopping cart contents, between requests. In Rails, this data is stored on the server, while a session id is stored as a cookie on the client-side. Rails provides a framework for handling sessions, with the session
method being the primary means of interacting with it. To access and modify session data, we can simply assign values to keys within the session
hash.
Here's an example of how to handle a session in Rails:
class ApplicationController < ActionController::Base
def homepage
session[:user_id] = 23
@user = User.find(session[:user_id])
end
end
In the code above, we're setting a session variable user_id
to the value of 23
. Later on, we're looking up the associated User
object for this user ID and storing it in an instance variable @user
. This session data persists across requests, so we can use it to maintain data relevant to a user as they navigate our application. Note that, to use sessions in Rails, the config/initializers/session_store.rb
configuration file must be specified.
Conclusion
In conclusion, controllers are the backbone of the Rails framework, acting as a mediator between the models and the views, and executing the necessary functionality required in the application. Ruby on Rails provides a simple yet powerful framework for generating controllers, accessing and modifying parameters, and handling exceptions. The filter functionality in Rails controllers allows developers to modify the behavior of a controller before or after executing specific actions. Additionally, concerns functionality can avoid code repetition in various controllers, and session management allows us to maintain data relevant to a user across requests. With the help of this article, developers can understand and use the concepts of controllers in Ruby on Rails to create efficient and functional web applications.