Author: saqibkhan

  • Rendering

    In Rails, the controller is responsible for processing the incoming request and creating the response to be sent back to the client.

    The controller uses mainly the following three ways to create the response

    • render
    • render_to
    • head

    The render Method

    By default, controllers in Rails automatically render views with names that correspond to valid routes. The render method is often called implicitly.

    If you have a BooksController class with index action as defined below:

    classBooksController<ApplicationControllerdefindex@books=Book.all
      endend

    You also have the routes defined as:

    resources :books

    And a view file index.html.erb is also in place.

    There is no explicit call to render at the end of the index action, as Rails follows the principle “convention over configuration”, according to which you do not explicitly render something at the end of a controller action, Rails automatically looks for the action_name.html.erb template in the controller’s view path and render it.

    Here, Rails automatically looks for a view template in app/views/books/index.html.erb and renders it.

    However, if you want to override the default behaviour and render a different template, partial, or even JSON, you can explicitly call render.

    classBooksController<ApplicationControllerdefindex@book=Book.all
    
    render 'list'endend</pre>

    This will render app/views/books/list.html.erb instead of index.html.erb.

    Incidentally, there’s also a render_to_string method. It takes exactly the same options as render, but it returns a string instead of sending a response back to the browser.

    You can also send an HTML string back to the browser by using the :html option to render method. This is especially useful when you're rendering a small snippet of HTML code instead of the entire template.

    render html:"<h1>Welcome to the Books Section</h1>".html_safe
    

    The .html_safe clause is required to prevent Rails from escaping HTML.

    Rails has built-in support for converting objects to JSON and rendering that JSON back to the browser:

    render json:@book

    Likewise, Rails also has built-in support for converting objects to XML and rendering that XML back to the caller:

    render xml:@book

    In both the cases, you don't need to call to_json or to_xml on the object that you want to render. If you use the :xml option, render will automatically call to_xml for you. Similarly, if you use the :json option, render will automatically call to_json for you.

    Rendering JavaScript is also possible with the render js method.

    render js:"alert('Hello World');"

    The :plain option lets you can send plain text - with no markup at all - back to the browser.

    render plain:"OK"

    Note: The text is rendered without using the current layout. To put the text into the current layout, you need to add the layout: true option and use the .text.erb extension for the layout file.

    The redirect_to Method

    The redirect_to method does something completely different: it tells the browser to send a new request for a different URL. This method needs to be explicitly called.

    The key difference between render vs. redirect_to methods is that – "while the render method returns a response directly (HTML, JSON, etc.), the redirect_to method sends an HTTP redirect to another URL."

    classBooksController<ApplicationControllerdefshow@book=Book.find(params[:id])
    
    redirect_to books_path
    endend

    This changes the URL to /books and performs a new request.

    You can use redirect_back to return the user to the page they just came from. Note that these methods do not halt and return immediately from method execution, but simply set HTTP responses.

    Rails uses returns the status code 302, which indicates a temporary redirect, when you call redirect_to. To use a different status code, such as 301, (it stands for a permanent redirect) you can use the :status option:

    redirect_to books_path, status:301

    The head Method

    The head method can be used to send responses with only headers to the browser. The head method optionally accepts a number representing an HTTP status code. For example, you can return only an error header:

    head :bad_request

    Or you can use other HTTP headers to convey other information:

    head :created, location: book_path(@book)

    The options argument is interpreted as a hash of header names and values.

  • Views

    Rails View is an ERB program that shares data with controllers through mutually accessible variables. Rails uses ERB (Embedded Ruby) as its default template engine to interpret and process .html.erb files.

    If you look in the app/views directory of any Rails application, you will see one subdirectory for each of the controllers. Each of these subdirectories was created automatically when the same-named controller was created with the generate script.

    Rails lets you know that you need to create the view file for each new method. Each method you define in the controller needs to have a corresponding erb file, with the same name as the action (method), to display the data that the method is collecting.

    Assume that a controller is created with the following command:

    rails generate controller Book

    This will create the controller (app/controllers/books_controller.rb). If you define an action inside this controller:

    defindexend

    You need to create the view file as app/views/books/index.html.erb.

    ERB (Embedded Ruby) allows you to write Ruby code inside HTML using special tags:

    ERB SyntaxPurpose
    <% … %>Executes Ruby code (no output)
    <%= … %>Executes and outputs the result
    <%# … %>Comment (ignored in output)

    So, let’s create view files for all the methods we have defined in the books_controller.rb. While executing these views, simultaneously check these actions are applicable into the database or not.

    Creating View File for create Action

    Modify the books_controller.rb file to add following action methods:

    defnew@book=Book.newend

    Here, @book is an instance variable. The new action initializes a new Book and used in app/views/books/new.html.erb to build the form and enter Book details. (We shall see how to create the form).

    Also define create action to handle the form submission. The form data is submitted to this action which calls save method of the book instance and pushes a flash message indicating the success.

    defcreate@book=Book.new(book_params)[email protected]
    
      flash[:notice]="Book was successfully created."
      redirect_to @book# Redirects to show action (book details)else
      render :new, status::unprocessable_entityendend</pre>

    Rails form Helpers

    Rails recommends using its helpers instead of using the normal HTML elements to build the content tags and form elements. The following code in new.html.erb uses form helpers like form_with, form.label, form.text_field etc. to render a blank Book form.

    <%= form_with(model: @book, local: true, html: { class: 'book-form' }) do |form| %>
    
      <div class="field"><%= form.label :title, class: 'form-label' %>
    
    &lt;%= f.text_field :title,class:"form-control", placeholder:"Enter book title"%&gt;
    </div><div class="field"><%= form.label :author, class: 'form-label' %>
    &lt;%= f.text_field :author,class:"form-control", placeholder:"Enter author's name"%&gt;
    </div><div class="field"><%= form.label :price, class: 'form-label' %>
    &lt;%= f.number_field :price,class:"form-control", step:"0.01", placeholder:"Enter book price"%&gt;
    </div><div class="actions"><%= form.submit CreateBook', class: 'form-submit' %>

    Some of the Rails view helpers are as follows:

    • form_with − Generates a form based on a Model
    • form_tag − Creates a form without a model

    Following helpers are used inside the form helper form_with, for example −

    <%= form_with(model: @book, local: true) do |f| %>
      <%= f.label :title%>
      <%= f.text_field :title %><%= f.submit "Save"%>
    <% end %>

    Form element helpers

    • label : renders a HTML label
    • text_field : renders a text input element, equivalent to <input type= "text">
    • text_area : generates a textarea element
    • radio_button : Rails helper for <input type= "radio">
    • check_box : used to render a check box, as done by <input type="checkbox">
    • submit : outputs an <input type= "submit"> button that submits the form.

    URL & Link Helpers

    • link_to : creates a hyperlink, equivalent to href
    • button_to : generates a form-based button
    • image_tag : Inserts an image, similar to img src

    Make sure that the config/routes.rb file is present with following resources statement.

    resources :books

    Start the Rails server

    rails server
    

    Add New Book

    Visit http://localhost:3000/books/new URL to display the form.

    Add New Book

    Enter data for a new book and click the Create Book button.

    Creating View File for list of books

    The index action in the BooksController stores all the objects in the Book model in @books variable.

    defindex@books=Book.all
    end

    This will need the index.html.erb file. The book data is displayed in a HTML table. In addition to the columns title, author and price, we use link_to helper to hyperlink buttons for show, edit and delete actions are provided in each row.

    We also use the flash[:notice] helper to extract the flash messages pushed by any of the earlier actions.

    <div class="container"><h2 class="text-center">All Books</h2><!-- Flash Messages -->
      <% if flash[:notice] %>
    
    &lt;p class="notice"&gt;&lt;%= flash[:notice] %&gt;&lt;/p&gt;
    <% end %> <!-- Books Table --><table class="table table-bordered table-striped"><thead class="table-dark"><tr><th>ID</th><th>Title</th><th>Author</th><th>Price</th><th colspan=3>Actions</th></tr></thead><tbody>
      &lt;% @books.each do |book| %&gt;
        &lt;tr&gt;&lt;td&gt;&lt;%= book.id %&gt;&lt;/td&gt;&lt;td&gt;&lt;%= book.title %&gt;&lt;/td&gt;&lt;td&gt;&lt;%= book.author %&gt;&lt;/td&gt;&lt;td&gt;₹&lt;%= book.price %&gt;&lt;/td&gt;&lt;td&gt;
            &lt;%= link_to "Show", book_path(book), class: "btn btn-info btn-sm me-2" %&gt;
            &lt;/td&gt;&lt;td&gt;
            &lt;%= link_to "Edit", edit_book_path(book), class: "btn btn-warning btn-sm me-2" %&gt;
            &lt;/td&gt;&lt;td&gt;
            &lt;%= link_to "Delete", book_path(book), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-danger btn-sm" %&gt;
          &lt;/td&gt;&lt;/tr&gt;
      &lt;% end %&gt;
    &lt;/tbody&gt;&lt;/table&gt;&lt;!-- Button to Add New Book --&gt;
    <%= link_to "Add New Book", new_book_path, class: "btn btn-primary mt-3" %> </div>

    The index view displays the list of books as follows −

    Creating View File for list of books

    Creating View File for show Method

    The show action in booksController fetches a book of the ID passed to it as the parameter

    defshow@book=Book.find(params[:id])# Fetch book by IDend

    The corresponding show.html.erb view template is as follows:

    <div class="container"><h2 class="text-center">Book Details</h2><div class="card"><div class="card-body"><h4 class="card-title"><%= @book.title %></h4><p><strong>Author:</strong> <%= @book.author %></p><p><strong>Price:</strong> ₹<%= @book.price %></p></div></div>
    
      <%= link_to "Back to Books", books_path, class: "btn btn-secondary mt-3" %>
    </div>

    Click on the Show button of inside the desired row to display the detailed view of a book.

    Creating View File for show Method

    Creating View File for edit Method

    Add the edit action in the BooksController to fetch the specified book object.

    defedit@book=Book.find(params[:id])end

    This will render the edit.html.erb view, which we shall write later.

    Also, add the update action to process the data submitted by the edit template.

    defupdate@book=Book.find(params[:id])[email protected](book_params)
    
      redirect_to @book, notice:"Book was successfully updated."else
      render :edit, status::unprocessable_entityendend</pre>

    The edit.html.erb code is similar to new.html.erb, except that the fields are populated with the data of the book to be updated.

    <div class="container"><h2 class="text-center">Edit Book</h2>
    
      <%= form_with model: @book, local: true do |form| %>
    
    &lt;div class="form-group"&gt;
      &lt;%= form.label :title %&gt;
      &lt;%= form.text_field :title, class: "form-control" %&gt;
    &lt;/div&gt;&lt;div class="form-group"&gt;
      &lt;%= form.label :author %&gt;
      &lt;%= form.text_field :author, class: "form-control" %&gt;
    &lt;/div&gt;&lt;div class="form-group"&gt;
      &lt;%= form.label :price %&gt;
      &lt;%= form.number_field :price, class: "form-control" %&gt;
    &lt;/div&gt;
    &lt;%= form.submit "Update Book", class: "btn btn-success mt-3" %&gt;
    <% end %> <%= link_to "Cancel", book_path(@book), class: "btn btn-secondary mt-3" %> </div>

    While on the index page that shows the list of books, click on the edit button to update the details. Change the desired data and click on Update book button. The browser returns the list of books with a flash message “Book was successfully Updated”

    Creating View File for edit Method

    Finally, the delete action in the BooksController calls the destroy() method on the selected Book object and removes from the book model.

    defdelete@book=Book.find(params[:id])@book.destroy
    
    redirect_to books_path, notice:"Book was successfully deleted."end</pre>

    What is Next?

    Hope now you are feeling comfortable with all the operations of Rails.

    The next chapter explains how to use Layouts to put your data in a better way. We will show you how to use CSS in your Rails applications.

  • Routes

    The routing module provides URL rewriting in native Ruby. It’s a way to redirect incoming requests to controllers and actions. It replaces the mod_rewrite rules. Best of all, Rails’ Routing works with any web server. Routes are defined in app/config/routes.rb.

    A route is the part of the URL that determines how an incoming HTTP request is directed to the appropriate controller and action for processing. It tells Rails which controller and action should respond to a request.

    Think of creating routes as drawing a map for your requests. The map tells them where to go based on some predefined pattern −

    Rails.application.routes.draw doPattern1 tells some request to go to one place
       Pattern2 tell them to go to another
       ...end

    When Rails sees a request that matches any of the patterns, it will send the request to the corresponding controller and the appropriate action inside of that controller.

    There are 4 common actions you will generally need for a resource: CreateReadUpdateDelete (CRUD). This translates to 7 typical routes:

    ActionHTTP VerbPathPurpose
    indexGET/booksList all records
    showGET/books/:idShow a single record
    newGET/books/newDisplay a form for a new record
    createPOST/booksSave a new record to the database
    editGET/books/:id/editDisplay a form to edit a record
    updatePATCH/PUT/books/:idUpdate an existing record
    destroyDELETE/books/:idDelete a record

    Typing out these routes every time is redundant, so Rails provides a shortcut for defining them. To create all of the same CRUD routes, replace the above routes with this single line.

    Example

    Let us consider our library management application contains a controller called BookController. We have to define the routes for those actions which are defined as methods in the BookController class.

    Open routes.rb file in library/config/ directory and edit it with the following content.

    Rails.application.routes.draw do
      resources :booksend

    If you decide to define only a certain routes instead of all the default routes, you can put the required routes in a list.

    Rails.application.routes.draw do
      resources :books, only:%i[index show create]end

    You may also define the routes explicitly by mentioning the respective HTTP verbs

    Rails.application.routes.draw do
      get 'books/'
      get 'books/show'
      post 'books/create'end

    This works because Rails automatically maps get ‘books/index’ to BooksController#index.

    If the path does not directly match the controller#action, you must specify to: explicitly:

    get 'all-books', to:'books#index'# Custom URL: /all-books
    get 'book-view/:id', to:'books#show'# Custom URL: /book-view/:id

    If your application has multiple models (e.g., Book and Employee), you should have separate resources statements for each model in config/routes.rb.

    Rails.application.routes.draw do
      resources :books
      resources :employeesend

    The routes.rb file defines the actions available in the applications and the type of action such as get, post, and patch.

    Use the following command to list all your defined routes, which are useful for tracking down routing problems in your application, or giving you a good overview of the URLs in an application you’re trying to get familiar with.

    rails routes
    

    In the older version of Ruby on Rails (before version 5), you would use:

    rake routes
    

    What is Next?

    Next, we will create the code to generate screens to display data and to take input from the user.

  • Authentication

    The library application used in this tutorial has the CRUD actions to add, edit, and delete book objects. However, these actions are accessible to anyone, which isn’t safe. Let us add a security layer to the application, so that only authenticated users get the access.

    Authentication Generator

    Starting with Rails version 8.0, a default authentication generator is included to streamline the process of securing your application by allowing access only to verified users.

    From the command terminal (windows) or Linux terminal, run the following command:

    rails generate authentication
    

    It generates the following models −

    app/models/session.rb
    app/models/user.rb
    app/models/current.rb
    

    Rails also generates the following controllers −

    app/controllers/sessions_controller.rb
    app/controllers/passwords_controller.rb
    

    And these views −

    app/views/passwords/new.html.erb
    app/views/passwords/edit.html.erb
    app/views/sessions/new.html.erb
    

    Next, you need to run the database migration to create the respective tables necessary for the authentication system:

    rails db:migrate

    Now, if you try to visit any route, Rails asks for the user to login with email and password.

    How the Authentication Works?

    Let us create a user object first. Open the Rails console and run the following command:

    User.create! email_address:"[email protected]", password:"a1b2c3", password_confirmation:"a1b2c3"

    Assuming that you have already created the Bookscontroller class, defined an index action in it and provided the corresponding index.html.erb file.

    Run the Rails server and visit http://localhost:3000/ upon which the browser displays this login page:

    How the Authentication Works?

    Rails authenticates the entered email address and password registered with it and automatically redirects the browser to the index view.

    By default, the Rails authentication generator will restrict all pages to authenticated users only. However, if you want some of the pages to be accessible without authentication, such as guest visitors, allow_unauthenticated_access property can be used for the purpose.

    How to Allow Guest Users

    To allow guests to view products, we can allow unauthenticated access in our controller.

    classBooksController<ApplicationController
      allow_unauthenticated_access only:%i[ index show ]# ...end

    Rails also provides the before_action that runs before executing a specific controller action. It’s mainly used for tasks like authentication, setting up variables, or checking permissions.

    classApplicationController<ActionController::Base
      before_action :set_messagedefindexend
    • The before_action callback helps avoid redundant code (e.g., authentication checks in every action). Use only: and except: inside the controller to control execution.
    • There is also an after_action callback, intended to run after the action runs, and the round_action callback that wraps before & after the logic.

    This built-in authentication system works well for small applications. You can use Devise Authentication by using Devise (a popular authentication gem). To generate authentication with Devise:

    rails generate devise:install
    rails generate devise User# Generates User model with authentication
    rails db:migrate

    You can also use jwt for token-based authentication.

  • Cookies and Session

    The Ruby on Rails framework follows the MVC pattern that implements REST architecture. One of the important constraints of REST is statelessness. It implies that each request sent by the client must have enough information for the server to handle and satisfy it, and that the server doesn’t hold on to the request data once it forms the response and disconnects from the client.

    What are Cookies?

    In some situations web applications need to maintain state between multiple requests. When a user successfully logs in, they shouldn’t be asked to log in again when accessing another resource. Rails (in general the applications implementing REST) uses cookies to store data between multiple requests.

    Cookies are small pieces of data stored in the user’s browser. Rails allows you to set, read, and delete cookies. The cookies method in Rails acts as a hash.

    To set a cookie −

    cookies[:username]={ value:"Test User", expires:1.hour.from_now }

    To return the cookie value −

    cookies[:username]

    in this case Test User.

    To delete a cookie, use the following expression:

    cookies.delete(:username)

    What are Sessions?

    Sessions are a wrapper over cookies. Rails encrypts and signs to store more sensitive user-related data securely. Rails stores session data in a cookie by default using ActionDispatch::Session::CookieStore.

    Setting session data −

    session[:user_id][email protected]
    

    Reading session data −

    User.find(session[:user_id])if session[:user_id]

    Deleting session data −

    session.delete(:user_id)

    Flash Messages

    Flash messages in Rails are built on top of the session object. They are used to pass temporary one-time messages (like success or error alerts) from one request to the next.

    Rails provides a flash hash that’s stored in the session. Flash uses the session behind the scenes −

    • It stores the flash hash in session[‘flash’].
    • On the next request, Rails clears the flash data automatically (unless told otherwise).

    You can render the following types of flash messages −

    Flash KeyPurposeDisplay Color
    :noticeFor general success messagesGreen or Blue
    :alertFor warnings or errorsRed
    :errorCustom — often for errorsRed
    :successCustom — for clear successGreen

    Examples −

    flash[:notice]="Successfully logged in"
    flash[:alert]="Invalid username or password"
    flash[:error]="Something went wrong!"
    flash[:success]="Your profile has been updated"

    Example: Cookies, Sessions, and Flash Messages

    Let us put the concept of cookies, session and flash in the following example.

    Start by creating a new Rails application −

    rails new sessiondemo 
    cd sessiondemo
    

    Add a User model and corresponding controller and views by generating the scaffold.

    rails generate scaffold User name:string email:string password:string
    rails db:migrate

    You need to create a Sessions Controller for handling Login/Logout operations

    rails generate controller Sessionsnew create destroy
    

    Add the login and logout routes by editing the routes.rb file in the app/config folder.

    resources :users
    get 'login', to:'sessions#new'
    post 'login', to:'sessions#create'
    delete 'logout', to:'sessions#destroy'
    root "users#index"

    The SessionController class defines the methods for performing Session activities. The new action sets up a cookie as the email of logged user.

    When a user logs in, a new session is created. It also flashes a message letting the user know that the login is successful. The destroy action removes the session variable.

    In app/controllers/sessions_controller.rb −

    classSessionsController<ApplicationControllerdefnew@remembered_email= cookies[:remembered_email]enddefcreate
    		user =User.find_by(email: params[:email])if user && user.password == params[:password]
    			session[:user_id]= user.id
    			flash[:notice]="Welcome, #{user.name}!"if params[:remember_me]=="1"
    
    			cookies[:remembered_email]={ value: user.email, expires:1.week.from_now }else
    			cookies.delete(:remembered_email)end
    redirect_to user_path(user)else flash.now[:alert]="Invalid email or password" render :newendenddefdestroy session.delete(:user_id) flash[:notice]="You have been logged out." redirect_to login_path endend

    Add Helper Methods to ApplicationController −

    classApplicationController<ActionController::Base
    	helper_method :current_user,:logged_in?defcurrent_user@current_user||=User.find_by(id: session[:user_id])enddeflogged_in?
    		current_user.present?endend

    Create Login Form View

    Rails automatically creates the view for the new action.

    Edit the app/views/sessions/new.html.erb as follows −

    <h1>Login</h1><%= form_with url: login_path, method: :post do %>
       <div>
    
      &lt;%= label_tag :email%&gt;&lt;br&gt;&lt;%= text_field_tag :email, @remembered_email %&gt;
    </div> <div>
      &lt;%= label_tag :password%&gt;&lt;br&gt;&lt;%= password_field_tag :password %&gt;
    </div> <div>
      &lt;%= check_box_tag :remember_me,"1"%&gt;
      &lt;%= label_tag :remember_me, "Remember me" %&gt;&lt;/div&gt;&lt;%= submit_tag "Login"%&gt;
    <% end %>

    The Flash Messages are added to the Application Layout.

    Open the app/views/layouts/application.html.erb file to add a logout button when a user is logged in.

    <body><%if flash[:notice]%>
    
      &lt;p style="color: green;"&gt;&lt;%= flash[:notice] %&gt;&lt;/p&gt;
    <% end %> <% if flash[:alert] %>
      &lt;p style="color: red;"&gt;&lt;%= flash[:alert] %&gt;&lt;/p&gt;
    <% end %> <% if session[:user_id] %>
      &lt;p&gt;Logged in as &lt;%=User.find(session[:user_id]).name %&gt; |
      &lt;%= button_to "Logout", logout_path, method: :delete, data: 
        { confirm: "Are you sure you want to log out?" } %&gt;&lt;/p&gt;&lt;%else%&gt;
    <%= link_to "Login", login_path %><%end%> <%= yield %></body>

    You can now test the functionality by starting the Rails server and visiting the /users route to create a new user.

    Rails Cookies and Session1

    Go to /login and log in.

    Rails Cookies and Session2

    Check the “Remember me” box and reload the page later to see your email remembered.

    Rails Cookies and Session3

    Log out to test session and flash behavior.

    Rails Cookies and Session4
  • Controller

    The Rails controller is the logical centre of your application. It coordinates the interaction between the user, the views, and the model. The controller is also a home to a number of important ancillary services.

    • It is responsible for routing external requests to internal actions. It handles people-friendly URLs extremely well.
    • It manages caching, which can give applications orders-of-magnitude performance boosts.
    • It manages helper modules, which extend the capabilities of the view templates without bulking up their code.
    • It manages sessions, giving users the impression of an ongoing interaction with our applications.

    The following diagram explains how the controller interacts with the model and the view layer −

    Ruby on Rails Controller

    The process for creating a controller is very easy, and it’s similar to the process we’ve already used for creating a model.

    Make sure that you have already created the book model and performed migrations

    rails generate model Book title:string author:string price:integer
    rails db:migrate

    We will create the BooksController here −

    rails generate controller Books
    
      create  app/controllers/books_controller.rb
      invoke  erb
      create    app/views/books
      invoke  test_unit
      create    test/controllers/books_controller_test.rb
      invoke  helper
      create    app/helpers/books_helper.rb
      invoke    test_unit

    Notice that you are capitalizing Book and using the singular form. This is a Rails paradigm that you should follow each time you create a controller.

    This command accomplishes several tasks, of which the following are relevant here −

    It creates a file called app/controllers/book_controller.rb. If you look at book_controller.rb, you will find it as follows −

    classBooksController<ApplicationControllerend
    • Controller classes inherit from ApplicationController, which is the other file in the controllers folder: application.rb.
    • The ApplicationController contains code that can be run in all your controllers and it inherits from Rails ActionController::Base class.

    In the MVC architecture, a route maps a request to a controller action. A controller action performs the necessary work to handle the request, and prepares any data for the view. A view displays data in a desired format.

    We need to define actions in the BooksController class to handle the requests. Actions are instance methods in the class. Note that it is upto you what name you want to give to these action methods, but better to give relevant names.

    By default, the rails generate controller command creates the controller file (books_controller.rb) but does not include any actions unless explicitly specified. Let us define index(), show() and create() actions in the BooksController class.

    classBooksController<ApplicationControllerdefindexenddefshowenddefcreateendend

    Note that you can generate a controller with specific actions (e.g., index, show, create, etc.), if you include them in the command:

    rails generate controller Books index show create
    

    Implementing the create Method

    The create action is expected to be invoked by the router in response to POST /books request. It creates a new book object and returns the JSON response of newly created object.

    defcreate@book=Book.new(book_params)[email protected]
    
      render json:@book, status::createdelse
      render json:@book.errors, status::unprocessable_entityendend</pre>

    The first line creates a new instance variable called @book that holds a Book object built from the data, the user submitted. The book_params method is used to collect all the fields from object :books. The data was passed from the new method to create using the params object.

    The next line is a conditional statement that renders the book data in JSON if the object saves correctly to the database. If it doesn't save, the user is sent back error message.

    Implementing the show Method

    The show action displays the details of a book object of a certain ID passed by GET /books/:id request.

    defshow@book=Book.find(params[:id])
    
    render json:@bookend</pre>

    The show method's @book = Book.find(params[:id]) line tells Rails to find only the book that has the id defined in params[:id].

    The params object is a container that enables you to pass values between method calls. For example, when you're on the page called by the list method, you can click a link for a specific book, and it passes the id of that book via the params object so that show can find the specific book.

    Implementing the index Method

    The index method gives you a list of all the books in the database. This functionality will be achieved by the following lines of code. Edit the following lines in book_controller.rb file.

    defindex@books=Book.all
    
    render json:@booksend</pre>

    The books_controller.rb file has the code as follows −

    classBooksController<ApplicationControllerdefindex@books=Book.all
    
    render json:@booksenddefshow@book=Book.find(params[:id])
    render json:@bookenddefcreate@book=Book.new(book_params)[email protected]
      render json:@book, status::createdelse
      render json:@book.errors, status::unprocessable_entityendendprivatedefbook_params
    params.require(:book).permit(:title,:author,:price)endend</pre>

    Note that the book_params method defined above is a private method in the controller that ensures only the allowed attributes of a Book record are accepted from user input. It prevents mass assignment vulnerabilities, which could allow a user to update fields they shouldn’t have access to.

    Rails also generates config/routes.rb file. Modify it to define RESTful routes:

    Rails.application.routes.draw do
      resources :booksend

    You will learn more about routes in Rails in one of the subsequent chapters.

    Test the Controller

    To verify if the actions defined in BooksController work as desired, start your Rails server:

    rails server
    

    Use the Postman tool to test endpoints of your application:

    POST http://localhost:3000/books → to create a new book
    

    Set the Content-Type header application/json and enter the following JSON expression as body:

    {"book":{"title":"FUNDAMENTALS OF COMPUTERS","author":"Rajaraman","price":475}}

    Execute the POST action. This adds a new record in the books table:

    GET/books/:1 → Fetch a specific book with id=1

    Postman's response pane will display the details of specified book:

    GET/books → list all the books in the model 
    

    The responses pane displays the JSON representation of all the books available.

    You can also implement the UPDATE and DELETE operations in the BooksController by adding these action methods.

    defupdate@book=Book.find(params[:id])[email protected](book_params)
    
      render json:@bookelse
      render json:@book.errors, status::unprocessable_entityendenddefdestroy@book=Book.find(params[:id])@book.destroy
    head :no_contentend</pre>

    What is Next?

    You have created almost all the methods, which will work on backend. Next we will define routes (URLs) for actions.

  • Active Model

    In the earlier chapters of this tutorial, we explained the Active Record ORM in Ruby on Rails framework. Active Record is an ORM (Object Relational Mapper) that connects objects whose data requires persistent storage to a relational database. One of the functionality of Active Record is to form Active Model. In this chapter, we will explore the features of Rails Active Model.

    Relation between Active Model and Active Record

    The relation between Active Model and Active Record can be stated as follows −

    • Active Record includes Active Model.
    • Every Active Record object has all the capabilities of Active Model plus persistence.

    Hence, you will use the Active Record when you need to persist data in a database, whereas you need to use the Active Model when you need form-like models (e.g., ContactForm, SearchForm, Payment) that don’t need to be stored in the database.

    Rails Active Model Features

    Active Model is a library containing various modules that presents a set of interfaces and minimal functionalities for building model-like classes without having to use a database.

    Features of Active Model include −

    • Validations (validates)
    • Callbacks (before_validation, etc.)
    • Naming and conversion (e.g., model_name)
    • Serialization (e.g., to_json, to_xml)
    • Error handling (errors.add, errors.full_messages)
    • Dirty tracking (attribute_changed?)

    Note − Active Model allows non-persistent Ruby objects (i.e., not backed by a DB) to behave like Active Record objects in terms of form handling, validation, and serialization.

    Here are some more features of Rails Active Model −

    • ActiveModel::API to interact with Action Pack and Action View by default, and is the recommended approach to implement model-like Ruby classes.
    • The Active Model class has Attributes with which you can define data types, set default values. To use Attributes, include the module in your model class and define your attributes.
    • Inside the Active Model class, you may have one or more callback functions to hook into model lifecycle events, such as before_update and after_create.
    • ActiveModel::Serialization provides basic serialization for your object. An attribute hash (must be strings, not symbols) hash should contain the attributes you want to serialize.
    • Another feature is ActiveModel::Validations that adds the ability to validate objects and it is important for ensuring data integrity and consistency within your application.

    Example: Active Model in Rails Application

    Let us use Active Model in the following Rails application. To start with, create an application and declare a Book scaffold as below −

    rails new myapp
    cd myapp
    rails generate scaffold Book title:string author:string
    rails db:migrate

    Create the Active Model

    Create a forms folder under the app folder in your project directory and declare the BookForm class acting as the Active Model −

    # app/forms/book_form.rbclassBookFormincludeActiveModel::ModelincludeActiveModel::Attributes
    
    	attribute :title,:string
    	attribute :author,:string
    
    	validates :title,:author, presence:truedefsavereturnfalseunless valid?Book.create(title: title, author: author)endend

    Update the Controller

    Update the controller to use BookForm. Open the BooksController class and modify the create action −

    # app/controllers/books_controller.rbdefnew@book_form=BookForm.newenddefcreate@book_form=BookForm.new(book_form_params)if@book_form.save
    		redirect_to books_path, notice:"Book was successfully created."else
    		render :new, status::unprocessable_entityendend

    Update the Form View

    Rails has already created a view as new.html.erb file. Open it and edit the same to include the following code −

    <%= form_with model: @book_form, url: books_path, local: true do |form| %>
       <% if @book_form.errors.any? %>
    
      &lt;div id="error_explanation"&gt;&lt;h2&gt;&lt;%= pluralize(@book_form.errors.count, "error") %&gt; 
         prohibited this book from being saved:&lt;/h2&gt;
         &lt;ul&gt;
            &lt;% @book_form.errors.full_messages.each do |msg| %&gt;
            &lt;li&gt;&lt;%= msg %&gt;&lt;/li&gt;&lt;%end%&gt;
         &lt;/ul&gt;&lt;/div&gt;&lt;%end%&gt;
    <div class="field"><%= form.label :title %>
      &lt;%= form.text_field :title%&gt;
    </div><div class="field"><%= form.label :author %>
      &lt;%= form.text_field :author%&gt;
    </div><div class="actions"><%= form.submit "Create Book"%> </div><%end%>

    That’s it. Save the changes and run the Rails server. Visit http://localhost:3000/books/new to display a New Book form.

    Rails Active Model1

    Try clicking the Create Book button without entering any data −

    Rails Active Model2

    The error messages will be flashed as below −

    Rails Active Model3

    This time around, enter the title and author fields and submit the data. Rails will display a confirmation as below −

    Rails Active Model4

    In this example, we used the Book model (Active Record) which is mapped to the books table in the database. On the other hand, BookForm is the Active Model object. The attributes of the Active Model are not stored persistently in the database. It is used for validation and abstraction.

  • Migrations

    Rails Migration allows you to use Ruby to define changes to your database schema, making it possible to use a version control system to keep things synchronized with the actual code. Instead of writing schema modifications in pure SQL, you can use a Ruby Domain Specific Language (DSL) to make required changes to your table structure.

    This has many uses, including –

    • Teams of Developers − If one person makes a schema change, the other developers just need to update, and run “rake migrate”.
    • Production Servers − Run “rake migrate” when you roll out a new release to bring the database up to date as well.
    • Multiple Machines − If you develop on both a desktop and a laptop, or in more than one location, migrations can help you keep them all synchronized.

    Migration acts as a new ‘version’ of the database. When you generate a model, a schema has nothing in it. Each migration modifies it to add or remove tables, columns, or indexes.

    What Can Rails Migration Do?

    • create_table(name, options)
    • drop_table(name)
    • rename_table(old_name, new_name)
    • add_column(table_name, column_name, type, options)
    • rename_column(table_name, column_name, new_column_name)
    • change_column(table_name, column_name, type, options)
    • remove_column(table_name, column_name)
    • add_index(table_name, column_name, index_type)
    • remove_index(table_name, column_name)

    Migrations support all the basic data types − The following is the list of data types that migration supports −

    • string − for small data types such as a title.
    • text − for longer pieces of textual data, such as the description.
    • integer − for whole numbers.
    • float − for decimals.
    • datetime and timestamp − store the date and time into a column.
    • date and time − store either the date only or time only.
    • binary − for storing data such as images, audio, or movies.
    • Boolean − for storing true or false values.

    Valid column options are − The following is the list of valid column options.

    • comment − Adds a comment for the column.
    • collation − Specifies the collation for a string or text column.
    • default − Allows to set a default value on the column. Use nil for NULL. (:default => “blah”)
    • limit − Sets the maximum number of characters for a string column and the maximum number of bytes for text/binary/integer columns. (:limit => “50”)
    • null − Allows or disallows NULL values in the column. (:null => false)
    • precision − Specifies the precision for decimal/numeric/datetime/time columns.
    • scale − specifies the scale for the decimal and numeric columns.

    Create the Migrations

    In the previous chapter, you have created Book and Subject models with the following commands −

    malhar@ubuntu:~/library$ rails generate model Book title:string price:float subject:references
    malhar@ubuntu:~/library$ rails generate model Subject name:string

    Note that using subject:references in the Book model automatically creates a subject_id foreign key column in the books table.

    Migration files are created inside the db/migrate folder, one for each migration class. The name of the file is of the form YYYYMMDDHHMMSS_create_books.rb, which has the following code −

    classCreateBooks<ActiveRecord::Migration[8.0]defchange
    
    create_table :booksdo|t|
      t.string :title
      t.float :price
      t.references :subject, null:false, foreign_key:true
      t.timestamps
    endendend</pre>

    Similarly, the create_subjects.rb file in db\migrate folder has the following code −

    classCreateSubjects<ActiveRecord::Migration[8.0]defchange
    
    create_table :subjectsdo|t|
      t.string :name
      t.timestamps
    endendend</pre>

    Make sure that you have created associations between Book and Subject model. The Book class in book.rb file has belongs_to relation with subjects

    classBook<ApplicationRecord
      belongs_to :subjectend

    Similarly, there is a has_many relationship between Book and subjects (subject.rb)

    classSubject<ApplicationRecord
      has_many :booksend

    NOTE − Before running the migration generator, it is recommended to clean the existing migrations generated by model generators.

    The db:migrate command

    Now that you have created all the required migration files. It is time to execute them against the database. To do this, go to a command prompt and go to the library directory in which the application is located, and then run the rails migrate command as follows −

    malhar@ubuntu:~/library$ rails db:migrate==20250305181938CreateBooks: migrating ======================================-- create_table(:books)->0.0188s
    ==20250305181938CreateBooks: migrated (0.0054s)=================================20250305182034CreateSubjects: migrating ===================================-- create_table(:subjects)->0.0069s
    ==20250305182034CreateSubjects: migrated (0.0084s)==========================

    Rails creates db\schema.rb file to define the structure of the tables in the database.

    ActiveRecord::Schema[8.0].define(version:2025_03_05_182034)do
      create_table "books", force::cascadedo|t|
    
    t.string "title"
    t.float "price"
    t.integer "subject_id", null:false
    t.datetime "created_at", null:false
    t.datetime "updated_at", null:false
    t.index ["subject_id"], name:"index_books_on_subject_id"end
    create_table "subjects", force::cascadedo|t|
    t.string "name"
    t.datetime "created_at", null:false
    t.datetime "updated_at", null:falseend
    add_foreign_key "books","subjects"end

    The migration step creates a db/seeds.rb file is used to populate the database with initial or default data when setting up a Rails application.

    Edit the file and add the following statement in it −

    Subject.create([{ name:"Physics"},{ name:"Mathematics"},{ name:"Chemistry"},{ name:"Psychology"},{ name:"Geology"}])

    and then run −

    malhar@ubuntu:~/library$ rails db:seed

    This inserts the records in the Subject table.

    generate migration

    This command is used to modify the structure of an existing table, such as adding or removing a column.

    Adding Columns

    When you want to add a new column to an existing table in your database, you can use a migration with the format "AddColumnToTable" followed by a list of column names and types.

    To add a new column named author in the book model, use the following command −

    malhar@ubuntu:~/library$ rails generate migration AddAuthorToBooks author:string

    This creates a migration file −

    classAddAuthorToBooks<ActiveRecord::Migration[8.0]defchange
    
    add_column :books,:author,:stringendend</pre>

    You then apply it using the migrate command again:

    malhar@ubuntu:~/library$ rails db:migrate

    Removing Columns

    Similarly, if the migration name is of the form "RemoveColumnFromTable" and is followed by a list of column names

    malhar@ubuntu:~/library$ rails generate migration RemoveAuthorFromBooks author:string

    The change() Method

    Whenever a model structure is altered, the migration command defines the change() method to perform corresponding action (add_column, remove_column etc). It supports the following actions −

    • add_column
    • add_foreign_key
    • add_index
    • add_reference
    • create_join_table
    • create_table
    • drop_table
    • remove_column
    • remove_foreign_key
    • remove_index
    • remove_reference
    • remove_timestamps
    • rename_column
    • rename_index
    • rename_table

    You can also use the up and down methods instead of the change method (change method was introduced in Rails 5.0, and is the standard way to modify the migrations, although up/down methods are still supported but not recommended).

    The method up is used when migrating to a new version, down is used to roll back any changes if needed.

    Other Migration Commands

    In addition to the rails db:migrate command, the following migration commands are useful.

    Rolling Back

    If you have made a mistake in the model definition, rather than tracking down the version number associated with the previous migration you can run:

    malhar@ubuntu:~/library$ rails db:rollback

    This will roll back the ldatabase to the latest migration, either by reverting the change method or by running the down method. If you need to undo several migrations you can provide a STEP parameter:

    malhar@ubuntu:~/library$ rails db:rollbackSTEP=2

    Setting Up the Database

    The rails db:setup command will create the database, load the schema, and initialize it with the seed data.

    Resetting the Database

    The rails db:reset command will drop the database and set it up again. This is equivalent to rails db:drop db:setup command.

  • Active Record Query

    Read this chapter to learn the different ways to retrieve data from the database using Active Record.

    You generally need to execute the SELECT query in raw SQL to retrieve data from the tables in a database of any type (such as MySQL, PostgreSQL, Oracle SQLite etc.). In most cases, the Rails ORM implemented by Active Record insulates you from the need to use raw SQL.

    An Active Record helps you perform query operation in database-agnostic manner. So that, you don’t need to change the Rails code even if the database type is changed.

    For this chapter, we shall use the employees table populated with a sample data.

    First of all, generate the Employee model and migrate the same.

    rails generate model Employee emp_id: string email: string first_name: 
    string last_name: string ph_no: string  salary: integer
    rails db: migrate
    

    Populate the Table

    Open the console corresponding to the database in use (for example SQLite) and populate the employees table with the following data:

    INSERTINTO employees (emp_id, email, first_name, last_name, ph_no, salary)VALUES(1,'[email protected]','John','Doe','+11536198741',14320),(2,'[email protected]','Jane','Doe','+11536219842',12050),(3,'[email protected]','Michael','Smith','+11536298753',11340),(4,'[email protected]','Emily','Jones','+11536376482',9800),(5,'[email protected]','David','Lee','+11536492837',15430)

    While in the Rails application directory, open the Rails Console and verify that the Employee model has the above data by calling the all method. Observer that it internally executes the SELECT query to fetch all records.

    library(dev)>Employee.all
    SELECT"employees".*FROM"employees"

    Methods to Retrieve Objects

    Rails Active Record query interface provides different methods to retrieve objects. Let us learn about the most frequently used methods.

    The find() Method

    With the find() method, you can retrieve the object corresponding to the specified primary key that matches any supplied options. For example, the following statement fetched the employee with id = 2

    library(dev)>Employee.find(2)SELECT"employees".*FROM"employees"WHERE"employees"."id"=2LIMIT1

    The console also echoes the corresponding SELECT query.

    If no matching record is found, then the find method will raise an ActiveRecord::RecordNotFound exception.

    To fetch multiple objects, call the find method and pass in an array of primary keys.

    library(dev)>Employee.find([1,3])

    The SQL equivalent query is as follows:

    SELECT"employees".*FROM"employees"WHERE"employees"."id"IN(1,3)

    The take() Method

    Rails will not apply any implicit ordering with the take method that retrieves a given record. Without any arguments, the very first record in the table is retrieved, as can be observed from the SQL query.

    library(dev)>Employee.take
    SELECT"employees".*FROM"employees"LIMIT1

    You can specify the number of records to be retrieved as an integer argument. For example, the following command returns first four records.

    library(dev)>Employee.take(4)SELECT"employees".*FROM"employees"LIMIT4

    The first() Method

    The first method finds the first record ordered by primary key. Accordingly, it adds the ORDER BY clause in the SELECT query internally executed by Rails.

    library(dev)>Employee.first
    SELECT"employees".*FROM"employees"ORDERBY"employees"."id"ASCLIMIT1

    The last() Method

    On the other hand, the last method retrieves the last record ordered by primary key.

    library(dev)>Employee.last
    SELECT"employees".*FROM"employees"ORDERBY"employees"."id"DESCLIMIT1

    The last method returns nil if no matching record is found and no exception will be raised.

    The find_by Method

    The find_by method finds the first record matching some conditions. For example, use the following command to find the record with first name as Jane.

    library(dev)>Employee.find_by first_name:"Jane"SELECT"employees".*FROM"employees"WHERE"employees"."first_name"='Jane'LIMIT1

    As you can see, the SELECT query adds the WHERE clause.

    You can also achieve the same effect by the where() method, passing the condition as argument.

    library(dev)>Employee.where(first_name:"Jane").take
      EmployeeLoad(0.2ms)SELECT"employees".*FROM"employees"WHERE"employees"."first_name"='Jane'LIMIT1

    The find_each() Method

    You can also retrieve multiple objects. The find_each() method is a memory-friendly technique to retrieve a batch of records and then yields each record to the block individually as a model.

    You can use the rails runner command to echo the records on the command terminal.

    rails runner "Employee.find_each { |e| puts e.first_name }"JohnJaneMichaelEmilyDavid

    You may also do the same inside the rails console. Have a look at the equivalent SQL query.

    library(dev)>Employee.find_each {|e| puts e.last_name }SELECT"employees".*FROM"employees"ORDERBY"employees"."id"ASCLIMIT1000

    Conditional Retrieval

    You can use the where() method for conditional retrieval. You can execute a parameterized SELECT query by using the ? symbol as a place holder.

    library(dev)>Employee.where("last_name = ?","Smith")SELECT"employees".*FROM"employees"WHERE(last_name ='Smith')

    The params Keyword

    You can use array conditions in the Employee model while incorporating the params keyword, and pass params as argument to where() method.

    Here, the last_name is defined as a parameter. Rails retrieves objects matching with the condition. Use of params helps in avoiding SQL injection.

    library(dev)> params ={ last_name:"Smith"}=>{:last_name=>"Smith"}
    library(dev)>Employee.where(last_name: params[:last_name])SELECT"employees".*FROM"employees"WHERE"employees"."last_name"='Smith'

    Here is another example. All the objects with last_name column having the given characters are returned. As in SQL, using LIKE keyword is defined in Rails Active Record class.

    library(dev)> params ={ last_name:"Do"}=>{:last_name=>"Do"}
    library(dev)>Employee.where("last_name LIKE ?", params[:last_name]+"%")EmployeeLoad(0.3ms)SELECT"employees".*FROM"employees"WHERE(last_name LIKE'Do%')

    The order() Method

    You know that the SELECT query has the ORDER BY clause. Active Record interface implements the same with its order() method. You need to pass the expression on which the query result should be ordered.

    The following statement retrieves the employee objects on the ascending order of salary:

    library(dev)>Employee.order(:salary)SELECT"employees".*FROM"employees"ORDERBY"employees"."salary"ASCLIMIT11

    Add the desc keyword to generate the SELECT query with ORDER BY DESC clause:

    library(dev)>Employee.order(first_name::desc)SELECT"employees".*FROM"employees"ORDERBY"employees"."first_name"DESCLIMIT11

    The select() Method

    The Model.find method selects all the fields, which equivalent to SELECT * from table. To select only a subset of fields from the result set, you can specify the subset via the select method.

    library(dev)>Employee.select(:first_name,:last_name,:salary)SELECT"employees"."first_name","employees"."last_name","employees"."salary"FROM"employees"LIMIT11

    Alternatively, you can pass a string with desired column names separated by a single space.

    library(dev)>Employee.select("first_name last_name salary")

    To translate the SELECT query with the DISTINCT clause, append the distinct keyword to the select() call.

    library(dev)>Employee.select(:last_name).distinct
    SELECTDISTINCT"employees"."last_name"FROM"employees"

  • Active Record Associations

    Relational databases have tables related to each other, and their relationship is established with primary and foreign keys. In Rails, the Active Record associations allow you to define relationships between models. Associations indicate how your models relate to each other. When you set up an association between models, Rails migration defines the Primary Key and Foreign Key relationships, ensuring the consistency and integrity of the database.

    Rails supports six types of associations.

    • belongs_to
    • has_one
    • has_many
    • has_many :through
    • has_one :through
    • has_and_belongs_to_many

    belongs_to Association

    Consider a case of Book and Author models, where each book can be assigned to exactly one author. A belongs_to association reflects the relationship between the two.

    Generate the models with the following commands −

    rails generate model Author name:string
    
    rails generate model Book title:string author:references

    Rails generates the Book model with a foreign key column (author_id) and sets up the belongs_to association.

    classBook<ApplicationRecord
      belongs_to :authorend

    The database server creates the tables books and authors with the relationship expressed by primary and foreign keys.

    Belongs to Association

    The belongs_to association means that this model’s table contains a column which represents a reference to another table. Use belongs_to in combination with a has_one or has_many to set up one-directional or bi-directional relationship.

    has_one Association

    has_one association is useful when one other model has a reference to this model. That model can be fetched through this association. For example, if each supplier in your application has only one account.

    Open the command prompt and use the following commands to generate the models −

    rails generate model Supplier name:string
    
    rails generate model Account account_number:string supplier:references

    Edit the supplier.rb script and define the haas_one association with account model.

    classSupplier<ApplicationRecord
      has_one :accountend

    In the underlying database, the relationship between suppliers and accounts tables is reflected by the following figure −

    Has one Association

    The has_one association creates a one-to-one match with another model. says that the other class contains the foreign key. This relation can be bi-directional when used in combination with belongs_to on the other model.

    has_many Association

    has_many association is similar to has_one, but indicates a one-to-many relationship with another model. This association indicates that each instance of the model has zero or more instances of another model.

    This is how the has_many association is established between Author and multiple instances of Book model.

    classAuthor<ApplicationRecord
      has_many :booksend

    The equivalent relationship diagram is as follows −

    Has Many Association

    The has_many establishes a one-to-many relationship between models, allowing each instance of the declaring model (Author) to have multiple instances of the associated model (Book).

    Unlike a has_one and belongs_to association, the name of the other model is pluralized when declaring a has_many association.

    has_many :through Association

    has_many :through association is often used to set up a many-to-many relationship with another model through an intermediate model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding through a third model.

    Consider a situation where a Doctor can have multiple Patients and a Patient can have multiple Doctors through Appointments, you can use a has_many :through association.

    Generate the following models with these commands −

    rails generate model Doctor name:string specialization:string
    rails generate model Patient name:string age:integer
    rails generate model Appointment doctor:references patient:references appointment_date:datetime

    Now, create the associations. Edit the doctor.db script in app/models directory.

    classDoctor<ApplicationRecord
      has_many :appointments
      has_many :patients, through::appointmentsend

    The patient model also should have the has_many association with appointments and doctors (note the use of plurals) models

    classPatient<ApplicationRecord
      has_many :appointments
      has_many :doctors, through::appointmentsend

    Lastly associate the Appointment model with Doctor and Patient models with belongs_to association.

    classAppointment<ApplicationRecord
      belongs_to :doctor
      belongs_to :patientend

    has_one :through Association

    has_one :through association sets up a one-to-one relationship with another model through an intermediary model. This association indicates that the declaring model can be matched with one instance of another model by proceeding through a third model.

    • Employee, Office, and Company
    • An Employee works in an Office.
    • Each Office belongs to a Company.
    • An Employee belongs to a Company through their Office.

    If the models are set up as follows −

    rails generate model Company name:string
    rails generate model Office location:string company:references
    rails generate model Employee name:string office:references

    The Employee model is associated with Company with has_one:though type, and there’s a belongs_to association between Employee and Office models.

    classEmployee<ApplicationRecord
      belongs_to :office
      has_one :company, through::officeend

    has_and_belongs_to_many Association

    has_and_belongs_to_many association creates a direct many-to-many relationship with another model, with no intervening model. This association indicates that each instance of the declaring model refers to zero or more instances of another model.

    For example, consider an application with Assembly and Part models, where each assembly can contain many parts, and each part can be used in many assemblies.

    Generate the models −

    rails generate model Assembly name:string
    rails generate model Part name:string

    Establish the associations −

    classAssembly<ApplicationRecord
      has_and_belongs_to_many :partsendclassPart<ApplicationRecord
      has_and_belongs_to_many :assembliesend

    The has_and_belongs_to_many association creates a direct many-to-many association. Even though a has_and_belongs_to_many does not require an intervening model, it does require a separate table to establish the many-to-many relationship between the two models involved.