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.
To follow this tutorial, you will need:
ufw
. For instructions on how to set this up, read our Initial Server Setup with Ubuntu 22.04 tutorial.With Node.js, Ruby, rbenv, and Rails installed, you’re ready to install a database for you application.
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:
- sudo apt update
Next, install the sqlite3
and libsqlite3-dev
packages:
- 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:
- sqlite3 --version
Output3.37.2 2022-01-06 13:25:41 872ba256cbf61d9290b571c0e6d82a20c224ca3ad82971edc46b29818d5dalt1
With SQLite installed, you are ready to begin developing your application.
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:
- 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:
- 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:
- rails server
Rails binds to localhost
by default, meaning you can access your application by navigating to locahost:3000
in your browser:
If you are working on a development server, first ensure that connections are allowed on port 3000
:
- sudo ufw allow 3000
Then start the server with the --binding
flag, to bind to your server IP address:
- 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.
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:
- 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:
- cat app/controllers/sharks_controller.rb
Outputclass 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:
. . .
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:
- 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:
- 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:
- 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.
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:
- nano 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
:
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:
- 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:
- rails s
On a development server, run:
- 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:
To create a new shark, click on the New Shark link. This link takes you to the sharks/new
route:
You can add some information to test your application. Write in “Great White” into the Name field and “Scary” into the Facts field:
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:
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
. . .
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:
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:
Finally, pressing Back to sharks takes you to your updated 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.
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:
- nano app/models/shark.rb
Currently, the file tells us that the Shark
class inherits from ApplicationRecord
, which in turn inherits from ActiveRecord::Base
:
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:
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:
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:
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:
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.
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
:
- nano app/controllers/application_controller.rb
Inside is the definition for the ApplicationController
class, which the other controllers in your application inherit from:
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:
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:
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.
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.
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!