Rails Controllers

Rails Controllers

·

10 min read

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.