Starting with Ruby on Rails [v6]- Part 2
Esteban Campos / November 05, 2020
10 min read • ––– views
As a continuation of the Starting with Ruby on Rails v6, we are going to keep using Learn Enough tutorial.
In this post, we’ll develop a demo application to show off some of the power of Rails. The purpose is to get a high-level overview of Ruby on Rails programming (and web development in general) by rapidly generating an application using scaffold generators, which create a large amount of functionality automatically. The resulting app will allow us to interact with it through its URLs, giving us insight into the structure of a Rails application, including a first example of the REST architecture favored by Rails.
We are going to use scaffolding
, that way we can have a palpable sense of what Ruby on Rails can offer, but later we are going to cover, step by step what is happening behind the hood. At each stage of developing, we will write small, bite-sized pieces of code—simple enough to understand. The cumulative effect will be a deeper, more flexible knowledge of Rails, giving you a good background for writing nearly any type of web application.
Warning: Following the scaffolding approach risks turning you into a virtuoso script generator with little (and brittle) actual knowledge of Rails.
Table of Contents#
- Let's plan the dummy application
- 1.1 Objective
- 1.2 Setting up
- 1.3 Planning the models
- Let's create some resources
- 2.1 User
- 2.1.1 Users routes
- 2.1.2 Users Controller
- 2.1.3 MVC in action
- 2.2 Microposts
- 2.3 A user has_many microposts
- 2.1 User
- Using Rails console
- Conclusion
- Resources
1. Let's plan the dummy application#
1.1 Objective#
As with the forthcoming sample application, the app will consist of users and their associated microposts (thus constituting a minimalist Twitter-style app).
1.2 Setting up#
We are going to create a new app called toy_app
$ rails _6.0.3.4_ new toy_app
$ cd toy_app/
With the next fixed dependencies:
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "2.6.6"
gem "rails", "6.0.3.4"
gem "puma", "4.3.6"
gem "sass-rails", "6.0.0"
gem "webpacker", "4.2.2"
gem "turbolinks", "5.2.1"
gem "jbuilder", "2.10.0"
gem "bootsnap", "1.4.6", require: false
group :development, :test do
gem "sqlite3", "1.4.2"
gem "byebug", "11.1.3", platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
gem "web-console", "4.0.2"
gem "listen", "3.2.1"
gem "spring", "2.1.1"
gem "spring-watcher-listen", "2.0.1"
end
group :test do
gem "capybara", "3.32.2"
gem "selenium-webdriver", "3.142.7"
gem "webdrivers", "4.3.0"
end
group :production do
gem "pg", "1.2.3"
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
# Uncomment the following line if you're running Rails
# on a native Windows system:
# gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
1.3 Planning the models#
We are going to need users. We are going to keep it simple.
Users
Property | Type |
---|---|
id | integer |
name | string |
string |
Posts
The posts has only an id and a content field. Also, each post is associated with a particular user (user_id
).
Property | Type |
---|---|
id | integer |
content | string |
user_id | string |
2. Let's create some resources#
2.1 User#
The syntax to scaffold
our Users
model with some optional parameters for the data model’s attributes:
rails generate scaffold User name:string email:string
By including name:string
and email:string
, we have arranged for the User model to have the desirable structure (Note that there is no need to include a parameter for id; it is created automatically by Rails for use as the primary key in the database.)
To proceed with the application, we first need to migrate the database using rails db:migrate
.
$ rails db:migrate
== CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0027s
== CreateUsers: migrated (0.0036s) =============================
The effect of last command is to update the database with our new users data model.
2.1.1 Users routes#
The scaffolding process create a basic CRUD with the following routes:
URL | Action | Purpose |
---|---|---|
/users | index | page to list all users |
/users/1 | show | page to show user with id 1 |
/users/new | new | page to make a new user |
/users/1/edit | edit | page to edit user with id 1 |
2.1.2 Users Controller#
A controller contains a collection of related actions, and the pages from 2.1.1 correspond to actions in the Users controller. The Users controller in schematic form.
class UsersController < ApplicationController
def index
end
def show
end
def new
end
def edit
end
def create
end
def update
end
def destroy
end
end
2.1.3 MVC in action#
Here is a summary of the steps shown in above figure:
- The browser issues a request for the
/users
URL. - Rails routes
/user
s to the index action in the Users controller. - The index action asks the User model to retrieve all users (User.all).
- The User model pulls all the users from the database.
- The User model returns the list of users to the controller.
- The controller captures the users in the
@users
variable, which is passed to the index view. - The view uses embedded Ruby to render the page as HTML.
- The controller passes the HTML back to the browser
You might notice that there are more actions than there are pages; the index, show, new, and edit actions all correspond to pages from Section 2.1.1, but there are additional create, update, and destroy actions as well. These actions don’t typically render pages (although they can); instead, their main purpose is to modify information about users in the database.
HTTP request | URL | Action | Purpose |
---|---|---|---|
GET | /users | index | page to list all users |
GET | /users/1 | show | page to show user with id 1 |
GET | /users/new | new | page to make a new user |
POST | /users | create | create a new user |
GET | /users/1/edit | edit | page to edit user with id 1 |
PATCH | /users/1 | update | update user with id 1 |
DELETE | /users/1 | destroy | delete user with id 1 |
Just to examine the relationship between the Users Controller and the User Model, let’s focus on the index
action.
class UsersController < ApplicationController
.
.
.
def index
@users = User.all
end
.
.
.
end
This index action has the line @users = User.all
which asks the User model to retrieve a list of all the users from the database and then places them in the variable @users
. Once the @users
variable is defined, the controller calls the view. Variables that start with the @sign
, called instance variables, are automatically available in the views; in this case, the index.html.erb
.
<p id="notice"><%= notice %></p>
<h1>Users</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, method: :delete,
data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New User', new_user_path %>
The view converts its contents to HTML, which is then returned by the controller to the browser for display.
2.2. Microposts#
Any micropost worthy of the name should have some means of enforcing the length of the post. Implementing this constraint in Rails is easy with validations; to accept microposts with at most 140 characters, we use a length validation.
class Micropost < ApplicationRecord
validates :content, length: { maximum: 140 }
end
2.3 A user has_many microposts#
One of the most powerful features of Rails is the ability to form associations between different data models. In the case of our User model, each user potentially has many posts. We can express this in code by updating the User and Post models.
class User < ApplicationRecord
has_many :microposts
end
class Micropost < ApplicationRecord
belongs_to :user
validates :content, length: { maximum: 140 }
end
There is built-in association with belongs_to, the model/controller automatically looks for the <TABLE_NAME>_id
that is defined for that relationship, in our case, that means User
and user_id
.
3. Using Rails console#
We are far from have a fully functional app but we can still create some users and microposts. Also, we can interact directly with our database using rails commands:
$ rails console
>> first_user = User.first
(0.5ms) SELECT sqlite_version(*)
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC
LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "Esteban Campos", email: "campos.esteban@gmail.com",
created_at: "2020-10-08 09:39:14", updated_at: "2020-10-08 09:41:24">
>> first_user.microposts
Micropost Load (3.2ms) SELECT "microposts".* FROM "microposts" WHERE
"microposts"."user_id" = ? LIMIT ? [["user_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Micropost id: 1, content:
"First micropost!", user_id: 1, created_at: "2020-10-08 10:04:13", updated_at:
"2020-10-08 10:04:13">, #<Micropost id: 2, content: "Second micropost",
user_id: 1, created_at: "2020-10-08 09:40:14", updated_at: "2020-10-08
10:04:30">]>
>> micropost = first_user.microposts.first
Micropost Load (0.2ms) SELECT "microposts".* FROM "microposts" WHERE
"microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ?
[["user_id", 1], ["LIMIT", 1]]
=> #<Micropost id: 1, content: "First micropost!", user_id: 1, created_at:
"2020-10-08 10:04:13", updated_at: "2020-10-08 10:04:13">
>> micropost.user
=> #<User id: 1, name: "Esteban Campos", email: "campos.esteban@gmail.com",
created_at: "2020-10-08 09:40:14", updated_at: "2020-10-08 09:41:24"
>> exit
In the above example, we queried for the first user: User.first
and the value for that query was stored in first_user
. Also, we could retrieve all the microposts related to that user, just calling first_user.microposts
. This show the power of rails and how automatically can guess relationships between models. Later, we tried with first_user.microposts.first
that returns the first micropost and we retrieved the user that belongs to that entry calling first_user.micropost.first.user
.
4. Conclusion#
We’ve come now to the end of the high-level overview of a Rails application. The app described here has several strengths and a host of weaknesses.
You might use low code if...
High-level overview of Rails
Introduction to MVC
First taste of the REST architecture
Beginning data modeling
A live, database-backed web application in production
You might not use low code if...
No automatic user/micropost association
No notion of “following” or “followed”
No micropost feed
No meaningful tests
4.1 What we learned until now#
- scaffolding automatically creates code to model data and interact with it through the web.
- Scaffolding is good for getting started quickly but is bad for understanding.
- Rails uses the Model-View-Controller (MVC) pattern for structuring web applications.
- As interpreted by Rails, the REST architecture includes a standard set of URLs and controller actions for interacting with data models.
- Rails supports data validations to place constraints on the values of data model attributes.
- Rails comes with built-in functions for defining associations between different data models.
- We can interact with Rails applications at the command line using the Rails console.