Tutorial

How To Build a Ruby on Rails Application on Ubuntu 22.04

How To Build a Ruby on Rails Application on Ubuntu 22.04
Not using Ubuntu 22.04?Choose a different version or distribution.
Ubuntu 22.04

Introduction

Rails is a web application framework written in Ruby. It takes an opinionated approach to application development, assuming that set conventions best serve developers where there is a common goal. Rails therefore offers conventions for handling routing, stateful data, asset management, and more to provide the baseline functionality that most web applications need.

Rails follows the model-view-controller (MVC) architectural pattern, which separates an application’s logic, located in models, from the routing and presentation of application information. This organizational structure — along with other conventions that allow developers to extract code into helpers and partials — ensures that application code isn’t repeated unnecessarily.

In this tutorial, you will build a Rails application that enables users to post information about sharks and their behavior. This project will serve as a starting point for future application development.

Prerequisites

To follow this tutorial, you will need:

  • A local machine or development server running Ubuntu 22.04. Your development machine should have a non-root user with administrative privileges and a firewall configured with ufw. For instructions on how to set this up, read our Initial Server Setup with Ubuntu 22.04 tutorial.
  • Node.js and npm installed on your local machine or development server. This tutorial uses Node.js version 18.13.0 and npm version 8.19.3. For guidance on installing Node.js and npm on Ubuntu 22.04, follow the instructions in the Option 2 —Installing Using a PPA section of How To Install Node.js on Ubuntu 22.04.
  • Ruby, rbenv, and Rails installed on your local machine or development server, following Steps 1-4 in How To Install Ruby on Rails with rbenv on Ubuntu 22.04. This tutorial uses Ruby 3.2.0, rbenv 1.2.0-52, and Rails 7.0.4.

With Node.js, Ruby, rbenv, and Rails installed, you’re ready to install a database for you application.

Step 1 — Installing SQLite3

Before creating the Rails shark application, you need a database to store user data. Rails is configured to use SQLite by default, and this is often a good choice in development. Since the application data doesn’t require high level programmatic extensibility, SQLite will meet the application’s needs.

First, inside your terminal, update your package index:

  1. sudo apt update

Next, install the sqlite3 and libsqlite3-dev packages:

  1. sudo apt install sqlite3 libsqlite3-dev

This will install both SQLite and its required development files.

You can check the version to confirm that the installation was successful:

  1. sqlite3 --version
Output
3.37.2 2022-01-06 13:25:41 872ba256cbf61d9290b571c0e6d82a20c224ca3ad82971edc46b29818d5dalt1

With SQLite installed, you are ready to begin developing your application.

Step 2 — Creating a New Rails Project

With the database installed, you can create a new Rails project and access some of the default boilerplate code that Rails offers with the rails new command.

Create a project called sharkapp with the following command:

  1. rails new sharkapp

The output indicates all the different things Rails is creating for your new project. The following output highlights some significant files, directories, and commands:

Output
create . . . create Gemfile . . . create app . . . create app/controllers/application_controller.rb . . . create app/models/application_record.rb . . . create app/views/layouts/application.html.erb . . . create config create config/routes.rb create config/application.rb . . . create config/environments create config/environments/development.rb create config/environments/production.rb create config/environments/test.rb . . . create config/database.yml create db create db/seeds.rb . . . run bundle install . . . Bundle complete! 15 Gemfile dependencies, 69 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed. . . .

Here is a brief description of some of the Rails files and folders created:

  • Gemfile: This file lists the gem dependencies for your application. A gem is a Ruby software package, and a Gemfile allows you to manage your project’s software needs.
  • app: The app directory is where your main application code lives. This includes the models, controllers, views, assets, helpers, and mailers that make up the application itself. Rails gives you some application-level boilerplate for the MVC model to start out in files like app/models/application_record.rb, app/controllers/application_controller.rb, and app/views/layouts/application.html.erb.
  • config: This directory contains your application’s configuration settings:
    • config/routes.rb: Your application’s route declarations live in this file.
    • config/application.rb: General settings for your application components are located in this file.
  • config/environments: This directory is where configuration settings for your environments live. Rails includes three environments by default: development, production, and test.
  • config/database.yml: Database configuration settings live in this file, which is broken into four sections: default, development, production, and test. Thanks to the Gemfile that came with the rails new command, which included the sqlite3 gem, the config/database.yml file has its adapter parameter set to sqlite3, specifying that you will use a SQLite database with this application.
  • db: This folder includes a directory for database migrations called migrate, along with the schema.rb and seeds.rb files. schema.db contains information about your database, while seeds.rb is where you can place seed data for the database.

Finally, Rails runs the bundle install command to install the dependencies listed in your Gemfile.

Once everything is set up, navigate to the new sharkapp directory:

  1. cd sharkapp

Inside the sharkapp directory, start the Rails server to ensure that your application is working by using the rails server command. If you are working on your local machine, enter the following code to start your server:

  1. rails server

Rails binds to localhost by default, meaning you can access your application by navigating to locahost:3000 in your browser:

Rails default landing page.

If you are working on a development server, first ensure that connections are allowed on port 3000:

  1. sudo ufw allow 3000

Then start the server with the --binding flag, to bind to your server IP address:

  1. rails server --binding=your_server_ip

Navigate to http://your_server_ip:3000 in your browser, to access the default Rails landing page.

When you’re ready, you can stop the server by pressing CTRL+C in your terminal.

With your application created and in place, you are ready to start building from the Rails boilerplate to create a unique application.

Step 3 — Scaffolding the Application

To create the shark application, you need to create a model to manage your application data, views to enable user interaction with that data, and a controller to manage communication between the model and the views. To build these, use the rails generate scaffold command. This generates a model, a database migration to alter the database schema, a controller, a full set of views to manage Create, Read, Update, and Delete (CRUD) operations for the application, and templates for partials, helpers, and tests.

The generate scaffold command performs many things under the hood. The command includes the name of the model and the fields you want in your database table. Rails uses Active Record to manage relationships between application data, constructed as objects with models, and the application database. These models are a Ruby class, while also inheriting the ActiveRecord::Base class. This means that you can work with your model class in the same way that you would work with a Ruby class. Furthermore, you are also able to pull in methods from Active Record. Active Record ensures that each class is mapped to a table in your database, and each instance of that class, to a row in that table.

Run the following command to generate a Shark model, controller, and associated views:

  1. rails generate scaffold Shark name:string facts:text

The name:string and facts:text options in this command indicate the fields you are creating in your database table and the type of data they should accept. Both give you room to input what you would like. The text option allows for more characters.

After entering this command, the output reveals all the different files that are generated:

Output
invoke active_record create db/migrate/20190804181822_create_sharks.rb create app/models/shark.rb . . . invoke resource_route route resources :sharks invoke scaffold_controller create app/controllers/sharks_controller.rb invoke erb create app/views/sharks create app/views/sharks/index.html.erb create app/views/sharks/edit.html.erb create app/views/sharks/show.html.erb create app/views/sharks/new.html.erb create app/views/sharks/_form.html.erb . . .

Rails created the model at app/models/shark.rb and a database migration to go with it: db/migrate/20190804181822_create_sharks.rb. The timestamp on your migration file will differ from the example output.

It also created a controller, app/controllers/sharks_controller.rb, as well as the views associated with your application’s CRUD operations, collected under app/views/sharks. Among these views is a partial, _form.html.erb, that contains code used across views.

Finally, Rails added a new resourceful route, resources :sharks, to config/routes.rb. This enables the Rails router to match incoming HTTP requests with the sharks controller and its associated views.

Though Rails has done much of the work of building out the application code, it is worth diving into some files to better understand what is happening.

To understand the controller file, enter the following command in your terminal:

  1. cat app/controllers/sharks_controller.rb
Output
class SharksController < ApplicationController before_action :set_shark, only: %i[ show edit update destroy ] # GET /sharks or /sharks.json def index @sharks = Shark.all end # GET /sharks/1 or /sharks/1.json def show end # GET /sharks/new def new @shark = Shark.new end # GET /sharks/1/edit def edit end # POST /sharks or /sharks.json def create @shark = Shark.new(shark_params) respond_to do |format| if @shark.save format.html { redirect_to shark_url(@shark), notice: "Shark was successfully created." } format.json { render :show, status: :created, location: @shark } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @shark.errors, status: :unprocessable_entity } end end end # PATCH/PUT /sharks/1 or /sharks/1.json def update respond_to do |format| if @shark.update(shark_params) format.html { redirect_to shark_url(@shark), notice: "Shark was successfully updated." } format.json { render :show, status: :ok, location: @shark } else format.html { render :edit, status: :unprocessable_entity } format.json { render json: @shark.errors, status: :unprocessable_entity } end end end # DELETE /sharks/1 or /sharks/1.json def destroy @shark.destroy respond_to do |format| format.html { redirect_to sharks_url, notice: "Shark was successfully destroyed." } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_shark @shark = Shark.find(params[:id]) end # Only allow a list of trusted parameters through. def shark_params params.require(:shark).permit(:name, :facts) end end

The controller is responsible for managing how information gets fetched and passed to its associated model, and how it gets associated with particular views. For instance, the sharks controller includes a series of methods that map roughly to standard CRUD operations. There are more methods than CRUD functions, to enable efficiency in the case of errors.

For example, consider the create method:

~/sharkapp/app/controllers/sharks_controller.rb
. . .
  def create
    @shark = Shark.new(shark_params)

    respond_to do |format|
      if @shark.save
        format.html { redirect_to @shark, notice: 'Shark was successfully created.' }
        format.json { render :show, status: :created, location: @shark }
      else
        format.html { render :new }
        format.json { render json: @shark.errors, status: :unprocessable_entity }
      end
    end
  end
. . . 

If a new instance of the Shark class is successfully saved, redirect_to will spawn a new request that is then directed to the controller. This is a GET request, and it will be handled by the show method, which will reveal to the user the input they’ve recently added.

If there is a failure, then Rails will render the app/views/sharks/new.html.erb template again rather than making another request to the router, giving users another chance to submit their data.

In addition to the sharks controller, Rails created a template for an index view, which maps to the index method in your controller. You will use this as the root view for your application.

Run the following command to output the file:

  1. cat app/views/sharks/index.html.erb
Output
<p style="color: green"><%= notice %></p> <h1>Sharks</h1> <div id="sharks"> <% @sharks.each do |shark| %> <%= render shark %> <p> <%= link_to "Show this shark", shark %> </p> <% end %> </div> <%= link_to "New shark", new_shark_path %>

The index view loops through the instances of your Shark class, which maps to the sharks table in your database. Using ERB templating, the view outputs each field from the table that is associated with an individual shark instance: name and facts.

The view then uses the link_to helper to create a hyperlink with the provided string as the text for the link and the provided path as the destination. The paths themselves are made possible through the helpers that became available to you when you defined the sharks resourceful route with the rails generate scaffold command.

The new view uses what are called partials. Run the following to return the app/views/sharks/new.html.erb template:

  1. cat app/views/sharks/new.html.erb
Output
<h1>New shark</h1> <%= render "form", shark: @shark %> <br> <div> <%= link_to "Back to sharks", sharks_path %> </div>

Though this template may appear like it lacks input fields for a new shark entry, the reference to render 'form' indicates that the template is pulling in the _form.html.erb partial, which extracts code that is repeated across views.

Output the _form.html.erb file to get a fuller sense of how a new shark instance gets created:

  1. cat app/views/sharks/_form.html.erb
Output
<%= form_with(model: shark) do |form| %> <% if shark.errors.any? %> <div style="color: red"> <h2><%= pluralize(shark.errors.count, "error") %> prohibited this shark from being saved:</h2> <ul> <% shark.errors.each do |error| %> <li><%= error.full_message %></li> <% end %> </ul> </div> <% end %> <div> <%= form.label :name, style: "display: block" %> <%= form.text_field :name %> </div> <div> <%= form.label :facts, style: "display: block" %> <%= form.text_area :facts %> </div> <div> <%= form.submit %> </div> <% end %>

This template makes use of the form_with form helper. Form helpers are designed to facilitate the creation of new objects from user input using the fields and scope of particular models. In this example, form_with takes model: shark as an argument and the new form builder object that it creates has field inputs that correspond to the fields in the sharks table. This means that users have form fields to enter both a shark name and shark facts.

Submitting this form will create a JSON response with user data that the rest of your application can access by way of the params method. This creates an ActionController::Parameters object with that data.

Now that you know what rails generate scaffold has produced for you, you can move on to setting the root view for your application.

Step 4 — Creating the Application Root View and Testing Functionality

Ideally, you want the landing page of your application to map to the application’s root, so users can immediately get a sense of the application’s purpose.

For example, you can create a Welcome controller and an associated index view, which gives users a generic landing page that could also link out to different parts of the application.

To set this up, you need to modify the routing settings in config/routes.rb to specify the root of the application.

Open config/routes.rb for editing, using nano or your favorite editor:

  1. nano config/routes.rb
~/sharkapp/config/routes.rb
Rails.application.routes.draw do
  resources :sharks
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Defines the root path route ("/")
  # root "articles#index"
end

Without setting something more specific, the default view at http://localhost:3000 or http://your_server_ip:3000 will be the default Rails welcome page.

To map the root view of the application to the index view of the sharks controller, uncomment out the default #root "articles#index line by removing the # and replacing article with shark:

~/sharkapp/config/routes.rb
Rails.application.routes.draw do
  resources :sharks

  root 'sharks#index' 
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

Save the file and exit your editor when you are finished editing. If you used nano to exit the file, press CTRL+X, Y, then ENTER

Now, when users navigate to your application root, they enter the shark landing page as opposed to the default Rails landing page. Furthermore, they now have the opportunity to create a new shark entry, review existing entries, and edit or delete given entries.

Next, run migrations with the following command:

  1. rails db:migrate

This output confirms the migration:

Output
== 20230124215633 CreateSharks: migrating ===================================== -- create_table(:sharks) -> 0.0041s == 20230124215633 CreateSharks: migrated (0.0045s) ============================

Start your Rails server again. If you are working locally, run:

  1. rails s

On a development server, run:

  1. rails s --binding=your_server_ip

Navigate to localhost:3000 if you are working locally, or http://your_server_ip:3000 if you are working on a development server, to access your new landing page:

Application Landing Page

To create a new shark, click on the New Shark link. This link takes you to the sharks/new route:

Create New Shark

You can add some information to test your application. Write in “Great White” into the Name field and “Scary” into the Facts field:

Add Great White Shark

Then, press the Create Shark button to create the shark.

This button directs you to the show route, which, thanks to the before_action filter, is set with the set_shark method, which grabs the id of the shark you created:

~/sharkapp/app/controllers/sharks_controller.rb
class SharksController < ApplicationController
  before_action :set_shark, only: %i[ show edit update destroy ]

  . . . 
  # GET /sharks/1 or /sharks/1.json
  def show
  end

  . . . 

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_shark
      @shark = Shark.find(params[:id])
    end
  . . .

Show Shark

You can test the edit function by pressing Edit this shark on your shark entry. This will take you to the edit route for that shark:

Edit Shark

Update the facts about the Great White to read “Large” instead of “Scary”, then press Update Shark. This will take you back to the show route:

Updated Shark

Finally, pressing Back to sharks takes you to your updated index view:

New Index View

Now that you have tested your application’s basic functionality, you can add some validations and security checks to make everything more secure.

Step 5 — Adding Validations

Your shark application can accept input from users, but imagine a case where a user attempts to create a shark without adding facts to it, or creates an entry for a shark that’s already in the database. You can create mechanisms to check data before it gets entered into the database by adding validations to your models. Since your application’s logic is located in its models, validating data input there is appropriate.

Note that this tutorial does not cover writing validation tests, but you can find out more about testing by consulting the Rails documentation.

If you haven’t stopped the server yet, stop the server now by pressing CTRL+C in your terminal.

Open your shark.rb model file:

  1. nano app/models/shark.rb

Currently, the file tells us that the Shark class inherits from ApplicationRecord, which in turn inherits from ActiveRecord::Base:

~/sharkapp/app/models/shark.rb
class Shark < ApplicationRecord
end

Add some validations to the name field to confirm that the field is filled out and that the entry is unique, preventing duplicate entries:

~/sharkapp/app/models/shark.rb
class Shark < ApplicationRecord
  validates :name, presence: true, uniqueness: true
end

Next, add a validation for the facts field to ensure that it, too, is filled out:

~/sharkapp/app/models/shark.rb
class Shark < ApplicationRecord
  validates :name, presence: true, uniqueness: true
  validates :facts, presence: true
end

The validates: facts, presence: true line of code is not concerned with the uniqueness of the facts. It validates its association with unique shark entries.

Save and close the file when you are finished.

Start up your server once again with either rails s or rails s --binding=your_server_ip, then navigate to your application’s root at http://localhost:3000 or http://your_server_ip:3000.

Press on the New Shark link. In the form, add “Great White” to the Name field and “Big Teeth” to the Facts field, then press Create Shark. A warning message appears in this instance:

Unique Validation Warning

To check the other validation, click Back to sharks to return to the homepage, then press New Shark once again. In the new form, enter “Tiger Shark” in the Name field, and leave Facts empty. When you press Create Shark, it outputs the following warning:

Fact Presence Warning

With these changes, your application has some validations in place to ensure consistency in the data that’s saved to the database. Now you can turn your attention to your application’s users and define who can modify application data.

Step 6 — Adding Authentication

With some validations in place, you have some guarantees about the data that’s being saved to the database. But what about users? If you don’t want any and all users adding to the database, then you should add some authentication measures to ensure that only permitted users can add sharks. To do this, use the http_basic_authenticate_with method, which allows the creation of a username and password combination to authenticate users.

There are a number of ways to authenticate users with Rails, including working with the bcrypt or devise gems. For now, however, add a method to your application controller that will apply to actions across your application. This will be useful if you add more controllers to the application in the future.

Stop your server with CTRL+C.

Open the file that defines your ApplicationController:

  1. nano app/controllers/application_controller.rb

Inside is the definition for the ApplicationController class, which the other controllers in your application inherit from:

~/sharkapp/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
end

To authenticate users, use a hardcoded username and password with the http_basic_authenticate_with method. Add the following code to the file:

~/sharkapp/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  http_basic_authenticate_with name: 'sammy', password: 'shark', except: [:index, :show]
end

In addition to supplying the username and password here, you’ve also restricted authentication by specifying the routes where it should not be required: index and show. Another way of accomplishing this would have been to write only: [:create, :update, :destroy]. This way, all users will be able to access all of the sharks and read facts about particular sharks. When it comes to modifying site content, however, users will need to prove that they have access.

In a more robust setup, you would not want to hardcode values in this way, but this example demonstrates how you can include authentication for your application’s routes. Rails stores session data by default in cookies: once you authenticate on a specified action, you will not be required to authenticate again in the same session.

Save and close app/controllers/application_controller.rb when you are finished editing. You can now test authentication in action.

Start the server with either rails s or rails s --binding=your_server_ip and navigate to your application at either http://localhost:3000 or http://your_server_ip:3000.

On the landing page, press on the New Shark button. This will trigger the following authentication window:

User Authentication

If you enter the username sammy, and password shark, which is the combination you added to app/controllers/application_controller.rb, you will be able to securely create a new shark.

You now have a working shark application, complete with data validations and a basic authentication scheme.

Conclusion

The Rails application you created in this tutorial is a jumping off point that you can use for further development. If you are interested in exploring the Rails ecosystem, the project documentation is a great place to start.

You can also learn more about adding nested resources to your project by reading How To Create Nested Resources for a Ruby on Rails Application, which will show you how to build out your application’s models and routes.

Additionally, you might want to explore how to set up a more robust frontend for your project with a framework such as React. How To Set Up a Ruby on Rails Project with a React Frontend offers guidance on how to do this.

If you would like to explore different database options, you can also check out How To Use PostgreSQL with Your Ruby on Rails Application on Ubuntu 20.04, which walks through how to work with PostgreSQL instead of SQLite. You can also consult our library of PostgreSQL tutorials to learn more about working with this database.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors

Default avatar
Kong Yang

author


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel