Category: Ruby on Rails

  • Deployment

    So far, we have learned about various features of the Rails framework by building the applications on the locally running Puma server. In this chapter, we will learn how to deploy a Rails application on a publicly accessible server.

    Deploy with Rails Kamal

    Rails comes with a deployment tool called Kamal that we can use to deploy our application directly to a server. Kamal uses Docker containers to run your application and deploy with zero downtime. It is a simple, SSH-based deployment tool for Dockerized applications across any host or cloud provider. It is a lightweight, straightforward alternative to heavier tools like Kubernetes, or cloud-specific services like Heroku or AWS ECS.

    To deploy with Kamal, we need −

    • A server running Ubuntu LTS with 1GB RAM or more.
    • A Docker Hub account and access token. Docker Hub stores the image of the application so it can be downloaded and run on the server.

    On Docker Hub, create a Repository for your application image. Use “library” as the name for the repository.

    When you create a new Rails application with Rails new command, the dockerfile gem is automatically added.

    # Deploy this application anywhere as a Docker container [https://kamal-deploy.org]
    gem "kamal",require:false

    Note that Docker must be installed on your local machine and target servers, and have SSH access to your remote VPS.

    Kamal Configuration File

    Rails creates the config/deploy.yml file. This is the Kamal configuration file.

    It defines the following −

    • Service name
    • Docker image name
    • List of remote servers
    • Registry and environment settings

    For example, your deploy.yml might look like −

    # Name of your application. Used to uniquely configure containers.
    service: library
    
    # Name of the container image.
    image: your-user/library
    
    # Deploy to these servers.
    	servers:
    		web:-192.168.0.1
    
    	proxy:
    		ssl:true
    		host: app.example.com
    
    	# Credentials for your image host.
    	registry:
    		username: your-user
    		password:-KAMAL_REGISTRY_PASSWORD
    
    	env:
    		secret:-RAILS_MASTER_KEY
    		clear:# Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.# When you start using multiple servers, you should split out job processing to a dedicated machine.SOLID_QUEUE_IN_PUMA:true
    
    	aliases:
    		console: app exec --interactive --reuse "bin/rails console"
    		shell: app exec --interactive --reuse "bash"
    		logs: app logs -f
    		dbc: app exec --interactive --reuse "bin/rails dbconsole"
    
    	volumes:-"library_storage:/rails/storage"
    
    
    	asset_path:/rails/public/assets
    
    	# Configure the image builder.
    	builder:
    		arch: amd64
    

    Note that you should replace 192.168.0.1 with your server’s IP address and your-user with your Docker Hub username.

    DockerFile

    You will also find DockerFile in the project structure, ready to containerize your Rails app. It includes steps to install system dependencies, bundle gems, precompile assets, and launch the app. You can customize it to add JS tools (e.g., Node, esbuild, Tailwind).

    The DockerFile includes −

    • Ruby image base (e.g., ruby:3.2)
    • System package installs (nodejs, postgresql-client, etc.)
    • bundle install
    • Asset precompilation
    • Puma server boot command

    Here’s a sample Dockerfile −

    FROM ruby:3.2# Install dependenciesRUN apt-get update -qq && apt-get install -y nodejs postgresql-client
    
    # Set working directoryWORKDIR/app
    
    # Install gemsCOPYGemfile*./RUN bundle install
    
    # Copy the rest of the appCOPY..# Precompile assets and prepare productionRUN bundle exec rake assets:precompile# Start serverCMD["bash","-c","bundle exec rails db:migrate && bundle exec puma -C config/puma.rb"]

    A file named as .dockerignore is also created. It works like .gitignore, but for Docker builds. Its purpose is to prevent unnecessary files (like log/, tmp/, node_modules/, .git/) from being added to the Docker image.

    Kamal Commands

    Listed below are the important commands in Kamal −

    • kamal build − Builds the Docker image
    • kamal push − Pushes the image to the container registry
    • kamal deploy − Deploys the image on the server
    • kamal env push − Syncs secret environment variables
    • kamal rollback − Rolls back to the previous deployment
    • kamal ssh app − SSH into the running container

    To set up your server and deploy your application for the first time, run the following command −

    bin/kamal deploy
    

  • Error Handling

    In Rails, error handling mainly works through a combination of Ruby exception handling (begin-rescue-end blocks) and Rails’ built-in mechanisms for managing errors in web requests, models, and controllers.

    Difference between Error and Exception

    In programming, you come across the terms exception and error, which have different meanings.

    An exception represents an error condition in a program. Exceptions provide a mechanism for stopping the execution of a program. They work somewhat like break statements in that they cause the program flow to jump to another location. However, unlike break, the location may be another layer of the program stack. Unhandled exception causes the program to stop.

    An error, on the other hand, is an unwanted or unexpected event, which occurs during the execution of a program, i.e., at runtime, that disrupts the normal flow of the program’s instructions.

    Ruby has a hierarchy of exception classes, highest level of exception handling StandardError class.

    Exception Handling Keywords

    The following keywords are important for exception handling −

    • begin – end − A set of instructions where probable exception appears is put inside the begin – end block.
    • raise − The raise statement allows the program to explicitly raise an exception.
    • ensure − The ensure statement allows the program to ensure that a block of code is executed, whether an exception is raised or not
    • rescue − A block where an exception is captured.

    The standard Ruby exception handling syntax is represented as −

    begin# risky operationrescueSomeError=> e
    	# handle errorend

    The rescue_from Method

    In a Rails application, you usually don’t write explicit begin-rescue blocks for each action of a controller. Instead, Rails provides structured ways to catch errors.

    Starting from the release 2.0, Rails provides a clean way to rescue exceptions in a controller, mapping specific error classes to corresponding handlers. Instead of rescue, Rails has the rescue_from method.

    rescue_from is a class-level method specific to Ruby on Rails, typically used in controllers or ActiveSupport-based classes. This enables you to handle exceptions for the entire class, without repeating the same error handling code in every action or method.

    You can also specify the type of exception you want to handle and provides a block or method reference that will be called when that exception is raised.

    rescue_from is useful for handling application-level exceptions and keeping your code DRY (Don’t Repeat Yourself).

    classBooksController<ApplicationController
       rescue_from ActiveRecord::RecordNotFound, with::record_not_founddefshow@book=Book.find(params[:id])endprivatedefrecord_not_found
    
      render plain:"Book not found", status::not_foundendend</pre>

    In this code snippet, if Book.find(params[:id]) raises ActiveRecord::RecordNotFound, the record_not_found method is called.

    rescue_from can be used at the controller level or in ApplicationController to handle errors app-wide.

    Model-Level Error Handling

    When saving models (save, update, etc.), Rails provides a validation errors system.

    @user=User.new(email:"")[email protected]
    	# successelse# @user.error full_messages will have validation error messagesend

    Since failed saves don't raise exceptions by default, you inspect @model.errors to know what went wrong. If you want to force exceptions on validation failures, use save! or update! −

    [email protected]!rescueActiveRecord::RecordInvalid=> e
    	puts e.message
    end

    Rails Error Reporter

    The Rails framework also has the Active Support Error Reporter feature that is a common interface for error reporting services. It provides a standard way to collect errors in your application and report them to your preferred service (monitoring service such as Sentry).

    • Error Handling refers to how your code reacts to exceptions (rescue, fallback actions, custom error pages, etc.).
    • Error Reporting, on the other hand, is how your application logs, notifies, or tracks errors (e.g., showing logs, sending error notifications, etc.).

    To rescue and report any unhandled error, you can use the handle method −

    Rails.error.handle do
    	do_something!end

    If an error is raised, it will be reported and swallowed.

    Alternatively, if you want to report the error but not swallow it, you can use record −

    Rails.error.record do
    	do_something!end

    Ways to Report Methods

    Rails error reporter has four methods that allow you to report methods in different ways −

    Rails.error.handle

    This method will report any error raised within the block. It will then swallow the error, and the rest of your code outside the block will continue as normal.

    Rails.error.record

    This method will report errors to all registered subscribers and then reraise the error, meaning that the rest of your code won't execute.

    Rails.error.report

    You can also manually report errors by calling this method −

    begin# coderescueStandardError=> e
    	Rails.error.report(e)end

    Rails.error.unexpected

    You can report any unexpected error by calling this method.

  •  Rack

    Rack provides a minimal, modular, and adaptable interface for building web applications. It wraps HTTP requests and responses in the simplest way possible, and unifies and distills the API for web servers, web frameworks, and middleware into a single method call.

    The standardized Interface of Rack is common for all web applications and web servers. This interface expects a Ruby object (usually a class or proc) that responds to a single method call −

    call(env)

    where env is a hash containing the HTTP request data. The call() method must return an array with three elements −

    [status_code, headers_hash, body_array]

    Pure Rack Application

    To begin with, let us build a pure Rack Application without Rails.

    mkdir rackapp
    cd rackapp
    

    Save the following code in the config.ru file.

    # config.ruclassAppdefcall(env)
    
      headers ={'Content-Type'=&gt;'text/html'}
      response =['&lt;h1&gt;Greetings from Rack!!&lt;/h1&gt;'][200, headers, response]endend
    run App.new

    This is a basic web application that returns a simple response. We shall use Puma the web server that Ruby on Rails ships with. Rack also works with servers such as Thin and Unicorn.

    To run the application with Puma, use the following command −

    puma config.ru
    

    The server starts at port 9292.

    ***SIGUSR2not implemented, signal based restart unavailable!***SIGUSR1not implemented, signal based restart unavailable!***SIGHUPnot implemented, signal based logs reopening unavailable!Puma starting in single mode...*Puma version:6.6.0("Return to Forever")*Ruby version: ruby 3.3.7(2025-01-15 revision be31f993d7)[x64-mingw-ucrt]*Min threads:0*Max threads:5*Environment: development
    *PID:12756*Listening on http://0.0.0.0:9292

    Use Ctrl-C to stop

    Open a browser and visit http://localhost:9292 to get the following output −

    Rails on Rack1

    Any Rack-compliant web server works well here, as it will always invoke a call method on an object (the Rack application) and serve the result of that method.

    Rack allows application frameworks and web servers to communicate with each other, and replace each without changing the other. The advantage of Rack is that it provides a common protocol (or interface, or specification) that different web servers can use to talk to different web applications, without worrying about the internals of each.

    Rails is built on top of Rack, so that every Rails application is also a Rack application. Rails uses a Rack-compatible interface to handle HTTP requests and responses.

    You can add or customize Rack middleware in a Rails app through the config/application.rb or environment-specific config files.

    Rack App in Rails

    Let us now create a standalone Rack app and mount it inside Rails routes.

    Assume that you have already created a Rails app.

    rails new myapp
    

    Enter the lib folder of the project file structure and open the following code as lib/hello_rack_app.rb –

    classHelloRackAppdefcall(env)[200,{"Content-Type"=>"text/html"},["<h1>Greetings from Rack!!</h1>"]]endend

    This is a simple Rack app as before. To mount it along with other Rails routes, join its path with other routes. Edit the config/routes.rb file of the project with the following code −

    requireRails.root.join("lib/hello_rack_app")Rails.application.routes.draw do# Other Rails routes...
       root "index#index"
       mount HelloRackApp.new=>"/hello_rack"end

    That’s it. Save the changes and run the Rails server. You now have mounted the Rack app on a Rails application. Visit http://localhost:3000/hello_rack to get the following output −

    Rails on Rack2

    Rack Middleware

    Rack allows middleware components (e.g., logging, session handling, CORS handling, etc.) to be inserted into the request/response cycle. Middleware sits between the server and the application and can modify the request or response.

    To define your Rack middleware, create a new class in lib/middleware folder that implements the call(env) method −

    # lib/middleware/logger_middleware.rbclassLoggerMiddlewaredefinitialize(app)@app= app
       enddefcall(env)Rails.logger.info ">> Request received at: #{Time.now}"
    	  status, headers, response [email protected](env)Rails.logger.info "<< Response status: #{status}"[status, headers, response]endend

    Next, add it to the middleware stack −

    # config/application.rb
    config.autoload_paths <<Rails.root.join('lib/middleware')
    config.middleware.use LoggerMiddleware

    You can also use insert_before or insert_after to control its position relative to other middleware.

  • Send Emails

    Action Mailer is the Rails component that enables applications to send and receive emails. The other email related component in the Rails framework is Action Mailbox, which deals with receiving emails.

    Action Mailer

    Action Mailer uses mailers classes and views to create and configure the email to be sent. Mailer class inherits from ActionMailer::Base. It is similar to controller classes, as it also has:

    • Instance variables
    • It can use layouts and partials
    • It has access to a params hash
    • It also defines actions and associated views in app/views

    Let’s start creating a new project using the following command –

    rails new mailtest
    

    This will create the required framework to proceed. Now, we will start with configuring the ActionMailer.

    Action Mailer – Configuration

    Follow the steps given below to complete your configuration before proceeding with the actual work –

    Go to the config\environments folder of your project and open development.rb file and add the following line inside the Rails.application.configure do block.

    config.action_mailer.delivery_method =:smtp

    It tells ActionMailer that you want to use the SMTP server. You can also set it to be :sendmail if you are using a Unix-based operating system such as Mac OS X or Linux.

    This configuration file has many configuration settings. Keep them unchanged, configure smtp setting as follows to use Gmail for sending email:

    Rails.application.configure do# Other existing settings...# Configure mailer settings for Gmail SMTP
      config.action_mailer.delivery_method =:smtp
      config.action_mailer.smtp_settings ={
    
    address:"smtp.gmail.com",
    port:587,
    domain:"gmail.com",
    authentication:"plain",
    enable_starttls_auto:true,
    user_name:"GMAIL_USERNAME",
    password:"GMAIL_PASSWORD"}
    config.action_mailer.perform_deliveries =true# Ensure emails are actually sent config.action_mailer.raise_delivery_errors =true# Show errors for debuggingend

    Replace each hash value with proper settings for your Simple Mail Transfer Protocol (SMTP) server. You can take this information from your Internet Service Provider if you already don’t know. You don’t need to change port number 25 and authentication type if you are using a standard SMTP server.

    Note that Gmail has strict security policies. To send emails from your app, you can enable Less Secure Apps access in your Google account. However this feature not available for accounts with 2 Factor Authentication. In such case, the preferred option is to use an App Password instead of your real password. You can generate it in your Google Account Security settings.

    You may also change the default email message format. If you prefer to send email in HTML instead of plain text format, add the following line to the development.rb as well −

    ActionMailer::Base.default_content_type ="text/html"

    ActionMailer::Base.default_content_type could be set to “text/plain”, “text/html”, and “text/enriched”. The default value is “text/plain”.

    The next step will be to generate a mailer.

    Generate a Mailer

    Use the following command to generate a mailer as follows –

    rails generate mailer UserMailer

    This will create a file user_mailer.rb in the app\mailer directory. Check the content of this file as follows −

    classEmailer<ActionMailer::Baseend

    Let’s add the welcome_email method as follows −

    classUserMailer<ApplicationMailer
      default from:" GMAIL_USERNAME"defwelcome_email(user)@user= user
    
    mail(to:@user.email, subject:"Welcome to My Awesome Site!")endend</pre>
    • default Hash − This is a hash of default values for any email you send from this mailer. In this case we are setting the :from header to a value for all messages in this class. This can be overridden on a per-email basis
    • mail − The actual email message, we are passing the :to and :subject headers in.

    Create a file called welcome_email.html.erb in app/views/user_mailer/. This will be the template used for the email, formatted in HTML –

    <h1>Welcome to example.com, <%= @user.name %></h1><p>
    	You have successfully signed up to example.com,your username is: 
    	<%= @user.email %>.<br></p><p>
    	To login to the site, just follow this link: 
    	<%= @url %>.
    </p><p>Thanks for joining and have a great day!</p>

    You can also create Plain text email view in app/views/user_mailer/welcome_email.text.erb:

    Welcome to example.com,<%= @user.name %>
    ===============================================You have successfully signed up to example.com,Your username is:<%= @user.email %>.
    
    To login to the site, just follow this link: <%=@url%>.Thanksfor joining and have a great day!

    Calling the Mailer

    First, let's create a simple User scaffold.

    rails generate scaffold User name:string email:string

    Then, run migrations:

    rails db:migrate

    This will create User model, UsersController class with new, create, edit, update, destroy, index, show actions and the corresponding views such as new.html.erb, edit.html.erb, etc.

    Modify UsersController to Send Email on User Creation

    Edit the Create action in app/controllers/users_controller.rb to send a welcome email after a user is created:

    defcreate@user=User.new(user_params)[email protected]
    
      UserMailer.welcome_email(@user).deliver_now  # Send email after user creation
      redirect_to @user, notice:"User was successfully created. A welcome email has been sent."else
      render :new, status::unprocessable_entityendendprivatedefuser_params
    params.require(:user).permit(:name,:email)end</pre>

    Ensure that the UsersController also has the show action as follows:

    defshow@user=User.find(params[:id])end

    The set_user method stores the current user in the @user instance variable.

    That’s it. Now, to test your application start the server and visit  http://127.0.0.1:3000/users/new. It displays the following screen to add a new user

    Modify UsersController

    This adds a new User object and calls the welcome_email method to generate the welcome email, using the Gmail address set in the configuration.

    While the user can verify that he has received the email, the app redirects the browser to show the confirmation.

    welcome email

    On the console, you get the following log:

    UserMailer#welcome_email: processed outbound mail in 148.6msDelivered mail [email protected] (22000.3ms)Date:Thu,03Apr202512:22:33+0530From:*****@gmail.com
    To:*****@gmail.com
    Message-ID:<[email protected]>Subject:Welcome to MyAwesomeSite!Mime-Version:1.0Content-Type: multipart/alternative;
      boundary="--==_mimepart_67ee3030ed242_73d4638841e";
      charset=UTF-8Content-Transfer-Encoding:7bit
    
    ----==_mimepart_67ee3030ed242_73d4638841e
    Content-Type: text/plain;
      charset=UTF-8Content-Transfer-Encoding:7bit
    

    For more information on how to send emails using Rails, please go through the official documentation on ActionMailer (https://guides.rubyonrails.org/action_mailer_basics.html)

  • File Uploading

    You may have a requirement in which you want your site visitors to upload a file on your server. Rails makes it very easy to handle this requirement. Now we will proceed with a simple and small Rails project.

    As usual, let’s start off with a new Rails application called FileUploadapp. Let’s create the basic structure of the application by using simple rails command.

    rails newFileUploadapp
    cd FileUploadapp

    Before starting application development, we should install gem files. Open up your Gemfile and add the following two gems at the bottom.

    gem 'carrierwave','~> 3.0'
    gem install bootstrap-sass
    

    After adding gems in, we need to run the following command −

    bundle install
    

    The command terminal shows the following log −

    Resolving dependencies...Fetching mini_magick 5.2.0Fetching ssrf_filter 1.2.0Fetching ffi 1.17.1(x64-mingw-ucrt)Installing ssrf_filter 1.2.0Installing mini_magick 5.2.0Installing ffi 1.17.1(x64-mingw-ucrt)Fetching ruby-vips 2.2.3Installing ruby-vips 2.2.3Fetching image_processing 1.14.0Installing image_processing 1.14.0Fetching carrierwave 3.1.1Installing carrierwave 3.1.1Bundle complete!22Gemfile dependencies,125 gems now installed.

    What is CarrierWave?

    CarrierWave is a file uploader library for Rails. It handles file uploads through forms, and stores files locally or in the cloud (e.g., Amazon S3). With the help of libraries like MiniMagick, it allows you to process and resize images, and gives you more control over file paths, naming, and structure.

    You need to configure CarrierWave by adding the following settings in config\initializers\carrierwave.rb

    require"carrierwave"require"carrierwave/orm/activerecord"CarrierWave.configure do|config|
      config.root =Rails.root.join("public")# Ensures uploads go to /public/uploads
      config.cache_dir ="tmp/uploads"end

    Note that by default, CarrierWave stores uploaded files in −

    public/uploads/

    Generate Uploader

    Now we need to create an uploader. An Uploader came from carrierWave gem and it tells to carrierwave how to handle the files. In short, it contained all file processing functionalities. Run the command to create an uploader as shown below.

    rails generate uploader ProfilePicture

    This creates app/uploaders/profile_picture_uploader.rb.

    classProfilePictureUploader<CarrierWave::Uploader::Base# Choose what kind of storage to use for this uploader:
      storage :filedefstore_dir"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"endend

    Generate User Scaffold

    Let us create the User model, the controller and the required views with the scaffold command −

    rails generate scaffold User username:string email:string profile_picture:string
    rails db:migrate

    Mount the Uploader

    Open the User model (app/models/user.rb) and call the uploader as shown below −

    classUser<ApplicationRecord
      mount_uploader :profile_picture,ProfilePictureUploaderend

    Also, make sure that the user input Only allows a list of trusted parameters through.

    defuser_params
      params.require(:user).permit(:username,:email,:profile_picture)end

    Update the Views

    Edit the _form.html.erb file to add the File field to let the user choose profile picture −

    <%= form_with(model: user) do |form| %>
      <% if user.errors.any? %>
    
    &lt;div style="color: red"&gt;&lt;h2&gt;&lt;%= pluralize(user.errors.count, "error") %&gt; prohibited this user from being saved:&lt;/h2&gt;
      &lt;ul&gt;
        &lt;% user.errors.each do |error| %&gt;
          &lt;li&gt;&lt;%= error.full_message %&gt;&lt;/li&gt;&lt;%end%&gt;
      &lt;/ul&gt;&lt;/div&gt;&lt;%end%&gt;
    <div><%= form.label :username, style: "display: block" %>
    &lt;%= form.text_field :username%&gt;
    </div><div><%= form.label :email, style: "display: block" %>
    &lt;%= form.text_field :email%&gt;
    </div><div><%= form.label :profile_picture, style: "display: block" %>
    &lt;%= form.text_field :profile_picture%&gt;
    </div><div class="field"><%= form.label :profile_picture %>
    &lt;%= form.file_field :profile_picture%&gt;
    </div><div><%= form.submit %> </div><%end%>

    Similarly, modify the show.html.erb file.

    <p style="color: green"><%= notice %></p><p><strong>Username:</strong>
      <%= @user.username %>
    </p><p><strong>Email:</strong>
      <%= @user.email %>
    </p>
    
    <% if @user.profile_picture? %>
      <p><strong>Profile Picture:</strong><br>
    
    &lt;%= image_tag @user.profile_picture.url, width: 150, height: 150 %&gt;
    </p> <% else %> <p>No profile picture uploaded.</p> <% end %> <div> <%= link_to "Edit this user", edit_user_path(@user) %> | <%= link_to "Back to users", users_path %> <%= button_to "Destroy this user", @user, method: :delete %> </div>

    That’s it. Now start the server and visit http://localhost:3000/users/new to open up the user registration form.

    Update the Views

    Click on the choose File button, navigate to the desired file and then click Create User button. Rails responds with a confirmation page, displaying the profile picture you uploaded.

    Update the View

    CarrieerWave vs Active Storage

    In Rails 6 and onwards, the Active Storage framework was added which can natively handle file uploads. Here is a comparison of handling uploads with CarrierWave and Active Storage:

    FeatureActive StorageCarrierWave
    Built into RailsYes (since Rails 6)No (requires separate gem)
    Setup complexitySimple, plug-and-playMore configurable, but requires boilerplate
    File storage structureManaged internally (in storage/)You control the structure (e.g., /uploads/users/)
    Image processingOn-demand variants (using MiniMagick/Vips)Immediate, during upload (using MiniMagick)
    Metadata storageIn database (active_storage_blobs)Typically stored in model columns or filenames
    Cloud storage supportYesYes
    Attachment declarationhas_one_attached, has_many_attachedYou mount uploaders in models
    Preview/caching supportBuilt-in previewsNeeds custom logic or plugins

  • Action Cable

    Rails follows the MVC architecture and by default uses the HTTP protocol to handle the request-response cycle in a web application. However, since Rails version 5, the Action Cable feature was included to lend support for the new WebSocket protocol.

    Unlike the HTTP protocol, WebSocket is an asynchronous protocol, thus enabling real-time communication between the client and the server.

    In Rails 8, Action Cable is enabled by default. In earlier versions, it must be included as a gem to be able to use.

    Add this to the Gemfile −

    gem "action_cable"

    and then,

    bundle install
    

    What is Action Cable?

    Action Cable lets you seamlessly integrate the WebSockets in your Rails application. You can write performant and scalable real-time features in Ruby that has the access to the Active Record or your ORM of choice.

    While a regular Rails server accepts only HTTP requests, an ActionCable server accepts WebSocket requests. The ActionCable server subscribes to channels and broadcasts from channels using Redis.

    What is Action Cable?

    Action Cable seamlessly integrates WebSockets with the rest of your Rails application. It allows for real-time features to be written in Ruby in the same style and form as the rest of your Rails application, while still being performant and scalable. It’s a full-stack offering that provides both a client-side JavaScript framework and a server-side Ruby framework. You have access to your entire domain model written with Active Record or your ORM of choice.

    To mount Action Cable, you need to edit the routes.rb file of your application −

    mount ActionCable.server =>'/cable'

    Action Cable Terminology

    Action Cable uses WebSockets instead of the HTTP request-response protocol. One needs to get familiar with the following terms:

    Connections

    Connections are fundamental to the client-server relationship. A single Action Cable server can handle multiple connection instances. It has one connection instance per WebSocket connection.

    A new connection object is instantiated for every WebSocket accepted by the server. The connection object is a parent of all subsequent channel subscriptions, although it deals only with authentication and authorization and not any specific application logic.

    Consumers

    The client of a WebSocket connection is called the connection consumer. An individual user will create one consumer-connection pair per browser tab, window, or device they have open.

    Consumers

    Every client of a WebSocket connection is called the consumer. In Action Cable, the consumer is created by the client-side JavaScript framework.

    Channels

    A channel is similar to what a consumer is in a MVC framework. Each channel encapsulates a logical unit of work. A consumer can subscribe to multiple channels.

    For example, you could have a ChatChannel and an AppearancesChannel, and a consumer could be subscribed to either or both of these channels. A consumer should be subscribed to one channel.

    You can generate new channels where WebSocket features live using the generate channel command.

    rails generate channel Chatroom

    It creates the ChatroomChannel class which inherits class. You can override the following methods from the parent −

    • subscribed − Called when a client successfully subscribes to the channel.
    • unsubscribed − Called when the client disconnects or unsubscribes.
    • receive(data) − Called when the client sends a message via the WebSocket.
    • stream_from(stream_name) − Starts streaming messages from a named broadcasting channel to the client.
    • transmit(data) − Sends data directly to the client without a broadcast.

    Subscribers

    When the consumer is subscribed to a channel, he becomes a subscriber. The connection between the subscriber and the channel is called a subscription.

    A consumer can subscribe to a given channel any number of times. For example, a consumer could subscribe to multiple chat rooms at the same time.

    Publishers

    In Action Cable, your application code (controllers, models, jobs, etc.) acts as the publisher, sending data or events to a specific channel.

    Pub/Sub Pattern

    As publishers send messages to a channel, subscribers (clients) listen for messages on that channel. This is called the publish/subscribe pattern.

    Action Cable uses the (Pub/Sub) pattern which refers to a message queue paradigm in which the senders (publishers) send data to an abstract class of recipients (subscribers), without specifying individual recipients. Action Cable uses this approach to communicate between the server and many clients.

    Broadcastings

    broadcasting is a pub/sub link where anything transmitted by the broadcaster is sent directly to the channel subscribers who are streaming that named broadcasting. Each channel can be streaming zero or more broadcastings.

    With these basics of Action Cable features understood, let us build a simple real-time chat application using Action Cable and WebSocket.

  • WebSockets

    In this chapter, you will learn how to use the Action Cable feature in Rails to build a real-time chat application that employs the WebSocket protocol.

    What are WebSockets?

    The World Wide Web primarily works on the principle of REST, which encapsulates the HTTP protocol. However, one of its drawbacks is that it can be slower for real-time updates since it initiates a fresh connection each time.

    WebSockets are highly efficient for real-time data exchange, providing low-latency communication. Unlike HTTP which provides a half-duplex communication, the WebSocket protocol enables full-duplex interaction between a web browser or any other client, and a web server with low overhead.

    What are WebSockets?

    Most browsers including Chrome, Firefox and Edge, support the WebSocket protocol. The WebSocket protocol introduces ws (WebSocket) and wss (WebSocket Secure) as two new uniform resource identifier (URI) schemes.

    The process of initiating the WebSocket communication starts with a client opening a HTTP connection with the server. The connection request has an upgrade header that specifies the WebSocket protocol. The server responds by including the upgrade header, indicating that it is switching to WebSocket protocol. This process is called as handshake.

    What are WebSocket?

    A Real-time Chat App using WebSocket

    Let us create a Rails application and enable Action Cable feature in it, to build a simple chat application that implements the WebSocket protocol.

    To start with, create a new application –

    rails new chat_app 
    cd chat_app
    

    If you are using Rails version earlier than 8.0, then you will need to include action_cable gem in your GemFile –

    gem "action_cable"

    And then,

    bundle install
    

    You need not perform the above step, if you have the following in your Gemfile,

    gem "rails","~> 8.0.0"

    As Rails will automatically include the correct version of Action Cable.

    You should mount the Action Cable in the application’s routing configuration by modifying config/routes.rb as:

    Rails.application.routes.draw do
      root "chatroom#index"
      mount ActionCable.server =>"/cable"end

    Your chat application provides a channel for the users. Use the generate command to generate Chatroom channel.

    rails generate channel Chatroom

    The messages entered by the users are characterized by the content attribute of the message model.

    rails generate model Message content:text
    rails db:migrate

    You now have a Message table in the SQLite database, and the files app/channels/chatroom_channel.rb as well as a JavaScript code in app/javascript/channels/chatroom_channel.js

    Update chatroom_channel.rb and define subscribed as well as receive methods in the ChatroomChannel class −

    # app/channels/chatroom_channel.rbclassChatroomChannel<ApplicationCable::Channeldefsubscribed
    
    stream_from "chatroom"enddefreceive(data)Message.create!(content: data["content"])ActionCable.server.broadcast("chatroom", data)endend</pre>

    The subscribed Method is called automatically when the client sends a WebSocket message. It tells the server: "Start streaming messages from the chatroom broadcast to this client", while stream_from sets up a listener (stream) on the server side for the "chatroom" broadcast.

    The receive method is called when the client sends a WebSocket message. It gets that data payload (after parsing JSON). It saves the message to the database and immediately re-broadcasts it back to all clients subscribed to "chatroom".

    To create a Controller with an index action –

    rails generate controller Chatroom index
    

    Modify the index action to retrieve all the messages from the Message model –

    classChatroomController<ApplicationControllerdefindex@messages=Message.all
      endend

    As you know, this index action automatically renders the index view.

    <!-- app/views/chatroom/index.html.erb --><h1>Chatroom</h1><div id="chatbox" style="border:1px solid #ccc; padding:10px; height:200px; overflow-y:scroll;"><%@messages.eachdo|msg|%>
    
    &lt;p&gt;&lt;%= msg.content %&gt;&lt;/p&gt;
    <% end %> </div> <input type="text" id="message_input" placeholder="Type a message..."/><button onclick="sendMessage()">Send</button><script> const chatSocket =newWebSocket(ws://${window.location.host}/cable); chatSocket.onopen = function (){
    chatSocket.send(JSON.stringify({
      command:"subscribe",
      identifier:JSON.stringify({ channel:"ChatroomChannel"})}));};
    chatSocket.onmessage = function (event){
    const response =JSON.parse(event.data);if(response.type ==="ping"||!response.message)return;
    const chatbox = document.getElementById("chatbox");
    const message = document.createElement("p");
    message.innerText = response.message.content;
    chatbox.appendChild(message);
    chatbox.scrollTop = chatbox.scrollHeight;};
    function sendMessage(){
    const input = document.getElementById("message_input");
    const content = input.value;
    input.value ="";
    chatSocket.send(JSON.stringify({
      command:"message",
      identifier:JSON.stringify({ channel:"ChatroomChannel"}),
      data:JSON.stringify({ content: content })}));}&lt;/script&gt;</pre>

    How does this view function? Note that Rails mounts the WebSocket server as it starts. The index view starts a WebSocket client and sends a connection request. When the send Button in the browser is clicked, the text in the input box is received by the server and is stored in the Message model. The text area element of the web page is populated by the messages available so far.

    With all the above steps accurately performed, run the Rails server. Open two tabs of your browser and try sending messages as shown in the figure below:

    How does this view function?

  •  AJAX

    Ajax stands for Asynchronous JavaScript and XML. Ajax is not a single technology; it is a suite of several technologies. Ajax incorporates the following −

    • XHTML for the markup of web pages
    • CSS for the styling
    • Dynamic display and interaction using the DOM
    • Data manipulation and interchange using XML
    • Data retrieval using XMLHttpRequest
    • JavaScript as the glue that meshes all this together

    Ajax enables you to retrieve data for a web page without having to refresh the contents of the entire page. In the basic web architecture, the user clicks a link or submits a form. The form is submitted to the server, which then sends back a response. The response is then displayed for the user on a new page.

    When you interact with an Ajax-powered web page, it loads an Ajax engine in the background. The engine is written in JavaScript and its responsibility is to both communicate with the web server and display the results to the user. When you submit data using an Ajax-powered form, the server returns an HTML fragment that contains the server’s response and displays only the data that is new or changed as opposed to refreshing the entire page.

    For a complete detail on AJAX, you can go through our AJAX Tutorial

    How Rails Implements Ajax

    Rails has a simple, consistent model for how it implements Ajax operations. Once the browser has rendered and displayed the initial web page, different user actions cause it to display a new web page (like any traditional web application) or trigger an Ajax operation −

    • Trigger − This trigger could be the user clicking on a button or link, the user making changes to the data on a form or in a field, or just a periodic trigger (based on a timer).
    • Request − A JavaScript method, XMLHttpRequest, sends data associated with the trigger to an action handler on the server. The data might be the ID of a checkbox, the text in an entry field, or a whole form.
    • Processing − The server-side action handler (Rails controller action) does something with the data and returns an HTML fragment to the web client.
    • Response − The client-side JavaScript, which Rails creates automatically, receives the HTML fragment and uses it to update a specified part of the current page’s HTML, often the content of a <div> tag.

    These steps are the simplest way to use Ajax in a Rails application, but with a little extra work, you can have the server return any kind of data in response to an Ajax request, and you can create custom JavaScript in the browser to perform more involved interactions.

    Example of AJAX

    This example works based on scaffold; Destroy concept works based on AJAX.

    In this example, we will provide, list, show and create operations on ponies table. If you did not understand the scaffold technology, then we would suggest you to go through the previous chapters first and then continue with AJAX on Rails.

    Creating An Application

    Let us start with the creation of an application It will be done as follows –

    rails new ponies
    

    The above command creates an application, now we need to call the app directory using with cd command.

    cd ponies
    

    It will enter into an application directory then we need to call a scaffold command. It will be done as follows −

    rails generate scaffold Pony name:string profession:string

    Above command generates the scaffold with name and profession column.

    • Model(app/models/pony.rb)
    • Controller (app/controllers/ponies_controller.rb)
    • Views (app/views/ponies/)
    • Routes

    We need to migrate the data base with the following command −

    rails db:migrate

    Scaffolding adds the required routes in config/routes.db

    Rails.application.routes.draw do
    	resources :poniesend

    Now Run the Rails application as follows command

    rails server
    

    Now open the web browser and call a URL as http://localhost:3000/ponies/new. The output will be as follows −

    Rails And AJAX1

    Click the Create Pony button, it will generate the result as follows –

    Rails And AJAX2

    Creating an Ajax

    Now open app/views/ponies/index.html.erb with a suitable text editor. Update your destroy line with :remote => true, :class => ‘delete_pony’. It will look like as follows −

    <table><thead><tr><th>Name</th><th>profession</th><th>colspan="3"</th></tr></thead><tbody>
    
      &lt;% @ponies.each do |pony| %&gt;
      &lt;tr&gt;&lt;td&gt;&lt;%= pony.name %&gt;&lt;/td&gt;&lt;td&gt;&lt;%= pony.profession %&gt;&lt;/td&gt;&lt;td&gt;&lt;%= link_to "Show", pony %&gt;&lt;/td&gt;&lt;td&gt;&lt;%= link_to "Edit", edit_pony_path(pony) %&gt;&lt;/td&gt;&lt;td&gt;&lt;%= link_to "Destroy", pony, method: :delete, data: { confirm: "Are you sure?" }, 
         :remote =&gt; true, :class =&gt; 'delete_pony' %&gt;&lt;/td&gt;&lt;/tr&gt;
      &lt;% end %&gt;
    </tbody></table> <%= link_to "New pony", new_pony_path %>

    Create a file, destroy.js.erb, put it next to your other .erb files (under app/views/ponies). It should look like this –

    Rails And AJAX3

    Now enter the code as shown below in destroy.js.erb −

    $('.delete_pony').bind('ajax:success', function(){
    	$(this).closest('tr').fadeOut();});

    Now open your controller file which is placed at app/controllers/ponies_controller.rb and add the following code in the destroy method as shown below −

    # DELETE /ponies/1.jsondefdestroy@pony=Pony.find(params[:id])@pony.destroy
    
    	respond_to do|format|
    		format.html { redirect_to ponies_url }
    		format.json { head :no_content}
    		format.js   { render :layout=>false}endend

    It will finally look as follows −

    classPoniesController<ApplicationController
    	before_action :set_pony, only:%i[ show edit update destroy ]# GET /ponies or /ponies.jsondefindex@ponies=Pony.all
    	end# GET /ponies/1 or /ponies/1.jsondefshowend# GET /ponies/newdefnew@pony=Pony.newend# GET /ponies/1/editdefeditend# POST /ponies or /ponies.jsondefcreate@pony=Pony.new(pony_params)
    
    		respond_to do|format|[email protected]
    
    			format.html { redirect_to @pony, notice:"Pony was successfully created."}
    			format.json { render :show, status::created, location:@pony}else
    			format.html { render :new, status::unprocessable_entity}
    			format.json { render json:@pony.errors, status::unprocessable_entity}endendend# PATCH/PUT /ponies/1 or /ponies/1.jsondefupdate
    respond_to do|format|[email protected](pony_params)
    			format.html { redirect_to @pony, notice:"Pony was successfully updated."}
    			format.json { render :show, status::ok, location:@pony}else
    			format.html { render :edit, status::unprocessable_entity}
    			format.json { render json:@pony.errors, status::unprocessable_entity}endendend# DELETE /ponies/1 or /ponies/1.jsondefdestroy@pony=Pony.find(params[:id])@pony.destroy!
    respond_to do|format| format.html { redirect_to ponies_url } format.json { head :no_content} format.js { render layout:false}endendprivate# Use callbacks to share common setup or constraints between actions.defset_pony@pony=Pony.find(params.expect(:id))end# Only allow a list of trusted parameters through.defpony_params params.expect(pony:[:name,:profession])endend

    Create a few ponies as shown below –

    Rails And AJAX4

    Till now, we are working on scaffold, now click the Destroy button, it will call a pop-up that works based on Ajax.

    Rails And AJAX5

    If you click the OK button, it will delete the record from pony. Final output will be as follows −

    Rails And AJAX6
  • ImportMap

    In Rails, the Asset Pipeline library is designed for organizing, caching, and serving static assets, such as JavaScript, CSS, and image files. In this chapter, you will learn about ImportMap that streamlines and optimizes the delivery of JavaScript to enhance the performance and maintainability of the application.

    From Version 7, Rails uses ImportMaps as default, enabling building JavaScript applications without JS bundlers like jsbuild. You can still use the JS builders such as esbuild, rollup or webpack by specifying in the rails new command −

    Railsnew myapp --javascript=webpack
    

    Install ImportMaps

    When the application structure is created without the above syntax, Rails automatically installs the importmaps-rails gem, as it can be found in the project’s Gemfile −

    # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
    gem "importmap-rails"

    You can also install it manually in existing applications −

    bundle add importmap-rails
    rails importmap:install

    Note: ImportMaps are supported natively in all major, modern browsers.

    ImportMap helps you to map and load JavaScript modules directly in browser without installing the npm packages with Node.js.

    With ImportMap installed, Rails uses it to serve JavaScript by adding the following statement inside the <head> section of the layout template (app\views\layouts\application.html.erb) −

    <%= javascript_importmap_tags %>

    A file named as application.js is also stored in app\javascript\controllers\application.js which the modular JS files live. Additionally, the config\importmap.rb file the module pins from where the modules are imported. This file is automatically reloaded in Development upon changes, but note that you must restart the server if you remove pins and need them gone from the rendered ImportMap or list of preloads.

    typical example of importmap.rb file is −

    # Pin npm packages by running ./bin/importmap
    
    pin "application"
    pin "@hotwired/turbo-rails", to:"turbo.min.js"
    pin "@hotwired/stimulus", to:"stimulus.min.js"
    pin "@hotwired/stimulus-loading", to:"stimulus-loading.js"
    pin_all_from "app/javascript/controllers", under:"controllers"

    What is Pin?

    pin tells Importmap how to find a JavaScript module that your code is trying to import. It follows the syntax −

    pin "module_name", to:"path_or_url", preload:true

    where,

    module_nameHow you refer to it in import statements
    toWhich file it points to (relative path or full URL)
    preload(Optional) If true, browser starts loading it early

    You can pin a local file from your application’s assets or from a CDN by specifying its URL. For example −

    pin "axios", to: https://cdn.skypack.dev/axios
    

    The common pins you find in a typical Rails application are −

    Pin commandWhat it means
    pin “application”Your main JavaScript file (application.js)
    pin “@hotwired/turbo-rails”Makes Turbo available for Turbo Drive/Frames
    pin “@hotwired/stimulus”Makes Stimulus JS framework available
    pin_all_from “app/javascript/controllers”, under: “controllers”Automatically pins all Stimulus controllers

    Note that you can use pin_all_from to pick all files in a specific folder, so you don’t have to pin each module individually.

    The pinned modules can be imported in the application.js file, such as −

    import {Application} from "@hotwired/stimulus"

    You can use JavaScript from Rails frameworks like Action Cable, Action Text, and Active Storage by pinning the libraries manually.

    Use Action Cable Channels

    In config/importmap.rb −

    pin "@rails/actioncable", to:"actioncable.esm.js"
    pin_all_from "app/javascript/channels", under:"channels"

    In application.js −

    import "@rails/actionable"

    Use direct uploads for Active Storage

    In config/importmap.rb −

    pin "@rails/activestorage", to:"activestorage.esm.js"

    In application.js −

    import "@rails/activestorage"

    It makes sense to use logical names that match the package names used by npm, such that if you later want to start transpiling or bundling your code, you won’t have to change any module imports.

    To add new npm Packages if your application is supported by importmap-rails, run the bin/importmap pin command from your terminal −

    importmap pin react react-dom
    

    Then, import the package into application.js as usual −

    import React from "react"
    import ReactDOM from "react-dom"

    Choosing between ImportMaps and other JS bundlers depends on the requirements of the application. Import maps are the default option because it reduces complexity, improves developer experience, and delivers performance gains. You may need other JS bundlers like webpack if your code requires a transpilation step, such as JSX or TypeScript.

  •  Propshaft

    Asset Pipeline in Rails is a framework that helps you manage and deliver static assets â€” like JavaScript, CSS, and image files — in an organized, efficient, and performant way.

    Earlier versions of Rails used a gem called Sprockets for the asset pipeline. However, there has been a lot of evolution of Asset Management Techniques within the last few years, leading to significant changes that have influenced how assets are managed in web applications. These include −

    • Browser Support − Modern browsers have improved support for new features and syntax, reducing the need for transpilation.
    • HTTP/2 − The introduction of HTTP/2 has made it easier to serve multiple files in parallel, reducing the need for bundling.
    • ES6+ − Modern JavaScript syntax (ES6+) is supported by most modern browsers, reducing the need for transpilation.

    Therefore, the asset pipeline powered by Propshaft, no longer includes transpilation, bundling, or compression by default.

    The purpose of the Asset Pipeline is to −

    • Organize your frontend assets
    • Preprocess assets (e.g., compile SCSS to CSS, ES6 to JS)
    • Concatenate multiple files into a single file to reduce HTTP requests
    • Minify or compress assets for faster loading.
    • Cache-bust assets using fingerprinting

    Propshaft Features

    Propshaft assumes that your assets are in a browser-ready format—like plain CSS, JavaScript, or preprocessed images (like JPEGs or PNGs). Its job is to organize, version, and serve those assets efficiently. This is done by adopting the following strategies −

    Asset Load Order

    With Propshaft, the loading order of dependent files can be controlled by specifying each file explicitly and organizing them manually or ensuring they are included in the correct sequence within your HTML or layout files.

    Here is an example of how you can specify the exact order for loading CSS and JavaScript files by including each file individually in a specific order −

    <!-- application.html.erb --><head><%= stylesheet_link_tag "reset" %>
       <%= stylesheet_link_tag "base"%>
       <%= stylesheet_link_tag "main" %></head><body><%= javascript_include_tag "utilities" %>
       <%= javascript_include_tag "main"%>
    </body>

    Use Modules in JavaScript (ES6)

    ES6 modules are helpful if you have dependencies within JavaScript files. Use import statements to explicitly control dependencies within JavaScript code. Just make sure your JavaScript files are set up as modules using <script type=”module”> instead of script type=”text/javascript” in your HTML −

    // main.js
    import { initUtilities } from "./utilities.js";
    import { setupFeature } from "./feature.js";

    Combine Files when necessary

    If you have several JavaScript or CSS files that must always load together, you can combine them into a single file. For example, you could create a combined.js file that imports or copies code from other scripts.

    Fingerprinting

    Fingerprinting is a technique that makes the name of a file dependent on its content. In Rails, asset versioning uses fingerprinting to add unique identifiers to asset filenames. Fingerprinting still remains an integral part in spite of the evolution mentioned above.

    Propshaft: The Default Asset Pipeline in Rails 8

    In Rails 8, Propshaft is the default asset pipeline replacing Sprockets for new apps. With Propshaft, the asset management becomes Simple, modern, minimalistic.

    Propshaft uses a direct folder structure to map assets. For example, your assets are placed here −

    app/assets/
    	stylesheets/
    	javascripts/
    	images/
    Rails Propshaft1

    When a Rails application is created with Propshaft as its asset management tool,

    rails new myapp
    

    The project’s Gemfile includes “propshaft” gem in addition to the other important gems such as −

    # For asset pipeline
    gem "propshaft"# For managing JavaScript imports
    gem "importmap-rails"# Turbo and Stimulus (Hotwire)
    gem "turbo-rails"
    gem "stimulus-rails"

    The project’s assets live inside the designated folders as −

    Rails Propshaft2

    As you can see, Assets are directly served based on folder structure. Also, the fingerprinting is automatic. When you run rails assets:precompile, files get digested with a unique hash.

    For example −

    application-0d23fcd3245.css
    logo-3f9d7ab8c3.png
    

    Rails also uses ImportMap as the default for serving JavaScript. Hence, Propshaft uses the ImportMap configuration to pin various JavaScript modules −

    pin "application"
    pin "@hotwired/turbo-rails", to:"turbo.min.js", preload:true
    pin "@hotwired/stimulus", to:"stimulus.min.js", preload:true
    pin "@hotwired/stimulus-loading", to:"stimulus-loading.js", preload:true
    pin_all_from "app/javascript/controllers", under:"controllers"

    The application’s CSS files are loaded via Propshaft and JavaScript via ImportMap. This is reflected in the master layout of the application (app/views/layouts/application.html.erb)

    <head><%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
       <%= javascript_importmap_tags %>
    </head>