Build Instagram by Ruby on Rails (Part 1)

I’ll guide you step by step learning Ruby on Rails through building the Instagram application.

What’ll you learn after complete this project?

  • How to start a new Rails application?
  • Design System from Craft
  • Understanding MVC (Model — View — Controller) architecture
  • Model: Active Record migration, validation, callback, association, and query interface
  • View: Layout, Partial and Form helpers
  • Controller: Actions, Strong Parameters
  • Rails Routing
  • Active Storage to upload files
  • Using Bootstrap, Devise, Kaminari gem in Rails application

Table of Contents:

  • Tech Stacks
  • Understanding about MVC in Ruby on Rails.
  • Create New Rails Application
  • Using PostgreSQL database and Bootstrap for our Application
  • Sign Up — Sign In — Sign Out for Users by using Devise gem
  • Create User Profile Page
  • Edit/Update User Profile

Tech Stacks

Back-end:

  • Ruby 2.4
  • Rails 5.2.x
  • Database: Postgres 9.6

Front-end:

  • HTML, CSS, Javascript, jQuery
  • Bootstrap (3.x or 4.x)

MVC (Model — View — Controller) in Ruby on Rails

MVC is an architectural pattern of a software application. This architecture is popular for designing web applications. It separates an application into the following components:

  • Models (Active Record): handle data and business logic.
  • Views (ActionView): handle user interface objects and presentation.
  • Controllers (ActionController): between the Model and View, receiving user input and deciding what to do with it.

Request-Response Cycle in Rails

Source: https://www.codecademy.com
  1. User opens his browser, types in a URL, and presses Enter. When a user presses Enter, the browser makes a request for that URL.
  2. The request hits the Rails router (config/routes.rb).
  3. The router maps the URL to the correct controller and action to handle the request.
  4. The action receives the request, and asks the model to fetch data from the database.
  5. The model returns a list of data to the controller action.
  6. The controller action passes the data on to the view.
  7. The view renders the page as HTML.
  8. The controller sends the HTML back to the browser. The page loads and the user sees it.

Create New Rails Application

Install Rails:

To install Rails, we use the gem install command provided by RubyGems:

gem install rails -v 5.2.1

Check Rails version after installing Rails:

rails --version
=> Rails 5.2.1

If it return something like ‘Rails 5.2.1’, you can continue creating new Rails application.

Install PostgreSQL:

On Mac OSX: You can install PostgreSQL server and client from Homebrew:

brew install postgresql

Start Postgresql service:

brew services start postgresql

Create a new Rails application

rails new instagram --version=5.2.1

After creating instagram application, switch to its folder

cd instagram

Install Gems for our application:

bundle install

Starting up the Web Server:

rails server

To see application, open your browser and navigate to http://localhost:3000/

You’ll see the Rails default page:

And to stop the web server, hit Ctrl+C in the terminal.


Create Homepage

  • Create a new Controller with a Action
  • Add Route

Create Home controller with index action:

rails g controller Home index

Rails will generate some files and a route for you

create  app/controllers/home_controller.rb
route get 'home/index'
invoke erb
create app/views/home
create app/views/home/index.html.erb
invoke test_unit
create test/controllers/home_controller_test.rb
invoke helper
create app/helpers/home_helper.rb
invoke test_unit
invoke assets
invoke coffee
create app/assets/javascripts/home.coffee
invoke scss
create app/assets/stylesheets/home.scss

Open the app/views/home/index.html.erb file and replace existing code with the following code:

<h1>This is my home page</h1>

Restart web server (Ctrl+C to stop server and rails server to start server) and navigate to http://localhost:3000/home/index in your browser. You’ll see the “This is my home page” message you put into views/home/index.html.erb

Open the file config/routes.rb

Rails.application.routes.draw do
get 'home/index'
  # For details on the DSL available within this file, see   
http://guides.rubyonrails.org/routing.html
end

and add the line of code root 'home#index'. It should look something like:

Rails.application.routes.draw do
get 'home/index'
  root to: ‘home#index’
end

get 'home/index' tells Rails to map requests to http://localhost:3000/home/index to the home controller's index action.

root 'home#index' tells Rails to map requests to the root of the application to the home controller's index action.

Restart web server and navigate to http://localhost:3000/ , you will see the “This is my home page” message.

You can view all current routes of application by:

rails routes

Using PostgreSQL in Rails application

To using PostgreSQL for Rails application, we add gem pg’ to Gemfile

gem ‘pg’

Run bundle install to install pg gem.

Configure database (config/database.yml)

default: &default
adapter: postgresql
pool: <%= ENV.fetch(“RAILS_MAX_THREADS”) { 5 } %>
timeout: 5000
development:
<<: *default
database: development_instagram
test:
<<: *default
database: test_instagram
production:
<<: *default
database: production_instagram

More details: http://guides.rubyonrails.org/configuring.html#configuring-a-database

Create database

Using migration command to create database for application.

rails db:create
>> Created database ‘development_instagram’
>> Created database ‘test_instagram’

Installing Bootstrap for Rails application

Bootstrap is an open source toolkit for developing with HTML, CSS, and JS. It help we quickly prototype your ideas or build your entire app with our Sass variables and mixins, responsive grid system, extensive prebuilt components, and powerful plugins built on jQuery.

To integrate bootstrap with Rails application we use gem bootstrap-rubygem (Bootstrap 4 Ruby Gem for Rails)

Add bootstrap to your Gemfile:

gem ‘bootstrap’, ‘~> 4.1.1’

Run bundle install to install bootstrap gem and restart your server to make the files available through the pipeline.

Configure on application.css (app/assets/stylesheets/application.css)

  • Rename application.css to application.scss
  • Then, remove all the *= require and *= require_tree statements from the Sass file. Instead, use @import to import Sass files.
  • Import Bootstrap styles in application.scss:
@import "bootstrap";

Configure on application.js

Bootstrap JavaScript depends on jQuery. Because we’re using Rails 5.1+, add the jquery-rails gem to your Gemfile:

gem 'jquery-rails'

Add Bootstrap dependencies and Bootstrap to your application.js

//= require jquery3
//= require popper
//= require bootstrap

Layout application

I’m going to structure our layout application to 3 main parts:

  • Navigation bar
  • Main content
  • Footer

like as below image:

Rails use default layout file:app/views/layouts/application.html.erb

Add HTML code in layout (/layouts/application.html.erb):

Using Awesome Icon

We use some icons for our application from Font Awesome Icon. To use these icon easily we should be able to install font-awesome-rails gem.

Add this to your Gemfile:

gem 'font-awesome-rails'

and run bundle install.

Import font-awesome to application.scss file:

@import "font-awesome";

CSS code for layout (application.scss)

app/assets/stylesheets/application.scss

Users: Sign Up — Sign In — Sign Out

In this section, we’re going to use devise gem to create sign up, sign in and sign out function for Users.

Devise is a flexible authentication solution for Rails.

It’s composed of 10 modules:

  • Database Authenticatable: hashes and stores a password in the database to validate the authenticity of a user while signing in. The authentication can be done both through POST requests or HTTP Basic Authentication.
  • Omniauthable: adds OmniAuth support.
  • Confirmable: sends emails with confirmation instructions and verifies whether an account is already confirmed during sign in.
  • Recoverable: resets the user password and sends reset instructions.
  • Registerable: handles signing up users through a registration process, also allowing them to edit and destroy their account.
  • Rememberable: manages generating and clearing a token for remembering the user from a saved cookie.
  • Trackable: tracks sign in count, timestamps and IP address.
  • Timeoutable: expires sessions that have not been active in a specified period of time.
  • Validatable: provides validations of email and password. It’s optional and can be customized, so you’re able to define your own validations.
  • Lockable: locks an account after a specified number of failed sign-in attempts. Can unlock via email or after a specified time period.

Installing devise gem

Add this to your Gemfile:

gem 'devise'

then run bundle install.

Next, you need to run the generator:

rails generate devise:install

It’ll auto generate 2 file:

  • config/initializers/devise.rb
  • config/locales/devise.en.yml

Set up the default URL options for the Devise mailer

In development environment: (config/environments/development.rb)

config.action_mailer.default_url_options = { host: ‘localhost’, port: 3000 }

In production environment: :host should be set to the actual host of your application.

Generate User model

rails generate devise User
invoke active_record
create db/migrate/20180722043305_devise_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
insert app/models/user.rb
route devise_for :users

After running command, it generate a file migration to create user, a file in app/models is user.rb, add routes for user and test file.

Open user model file (app/models/user.rb), you can see default devise modules include in User model.

devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable

Then run rails db:migrate

You should restart your application after changing Devise’s configuration options (this includes stopping spring). Otherwise, you will run into strange errors, for example, users being unable to login and route helpers being undefined.

Add sign in / sign out links

In the right of navigation bar, add sign_in and sign_out links as below:

Replace HTML code in layout (application.html.erb):

<a><i class=”fa fa-user”></i></a>

by

  • user_signed_in? is a devise helper which to verify a user is signed in.
  • destroy_user_session_path: sign out path, method: :delete is default HTTP method used to sign out a resource (User)
  • new_user_session_path: sign in path.

When you click to icon to sign in, you’ll see the view like that:

Sign In view

But before you can sign in, you have to create a new account by click to Sign uplink below Log in button.

The sign up page view:

Sign Up view

You can sign up by filling your email and password, and then go back to sign in to application.

When you sign in successful, icon-sign-in will be replace by icon-user in navigation bar. And if you click to icon-user you would be logged out.


Create User Profile Page

Add columns to User

User table has columns: username, name, email, password, website, bio, phone, gender. User have already email and password columns, now we’ll add others fields by using migration:

rails g migration AddMoreFieldsToUsers username:string name:string website:string bio:text phone:integer gender:string

Run migration:

rails db:migrate
== 20180813140820 AddMoreFieldsToUsers: migrating =============================
— add_column(:users, :username, :string)
-> 0.0333s
— add_column(:users, :name, :string)
-> 0.0006s
== 20180813140820 AddMoreFieldsToUsers: migrated (0.0363s) ====================

Create User Controller

To create user profile page, first step we have to create a controller called UsersController by this command:

rails generate controller Users

Open app/controllers/users_controller.rb file in editor and see code like that

class UsersController < ApplicationController
end

Next step, add a show action to UsersController

class UsersController < ApplicationController
def show
end
end

Then, create a corresponding view is app/views/users/show.html.erb

Finally, add show action to routes (config/routes.rb)

Rails.application.routes.draw do
  ...
  resources :users, only: [:show]
end

resources :users, only: [:show] tells Rails to map requests to http://localhost:3000/users/:id to the user controller's show action, with :id is ID of current user. If ID of current user is 1, path point to user profile page is http://localhost:3000/users/1

Update User profile link in Navigation bar

Navigation bar

In the right of navigation bar, replace logout link by user profile link. That mean when user click to user icon, it go to the user profile page. After updating, HTML code like that:

<!-- app/views/layouts/application.html.erb -->
<% if user_signed_in? %>
<a href=”<%= user_path(current_user)%>”>
<i class=”fa fa-user”></i>
</a>

<% else %>
<a href=”<%= new_user_session_path%>”>
<i class=”fa fa-sign-in”></i>
</a>
<% end %>

Create UI for User profile page

I layout user profile page to 2 sections: Basic information and Posts.

  • Basic information: Contains avatar, username, name, posts, followers, following.
  • Posts: Contains images of User.

Section 1: Basic information of User

Mockup for Basic information section

This section is divided to 2 columns:

  • The left column is avatar of User.
  • The right column contain others information of User.

I’m using rows and columns to layout, HTML code look like:

# app/views/users/show.html.erb
<div class="profile row">
<div class="col-md-4 avatar">
<!-- LEFT: AVATAR HERE -->
</div>
  <div class="col-md-8 basic-info">
<!-- RIGHT: USER INFORMATION HERE -->
</div>
</div>

Because we have no data for some user information as name, number of posts, followers, following and images, so I temporary use fake data to build UI first and will update them later.

After add other components in this section, the HTML code look like:

views/users/show.html.erb

CSS for this view: I create assets/stylesheets/users.scss file which will contain styles for this page and import it to application.scss file.

@import "users";

Add CSS code below to users.scss

The view look like:

Section 1: Basic information of User

Section 2: Posts of User

Add 4 tabs: POSTS, IGTV, SAVED, TAGGED like as below image:

<div class="user-tabs">
<a class="tab active" href="">
<i class="fa fa-th"></i>
POSTS
</a>
<a class="tab" href="">
<i class="fa fa-tv"></i>
IGTV
</a>
<a class="tab" href="">
<i class="fa fa-bookmark"></i>
SAVED
</a>
<a class="tab" href="">
<i class="fa fa-tag"></i>
TAGGED
</a>
</div>

Add more style code to users.scss:

.user-tabs{
border-top: 1px solid #efefef;
display: flex;
justify-content: center;
a.tab{
height: 35px;
margin-right: 50px;
line-height: 45px;
color: #999;
font-size: 12px;
font-weight: 500;
text-align: center;
i{
padding-right: 1px;
}
&:hover{
text-decoration: none;
}
}
a.active{
border-top: 1px solid #262626;
color: #262626;
}
}

In images section: Each row of this section will present 3 images as below:

HTML code:

<div class="user-images">
<div class="wrapper">
<img src="your_image_url">
</div>
<div class="wrapper">
<img src="your_image_url">
</div>
<div class="wrapper">
<img src="your_image_url">
</div>
</div>

I use CSS Flexbox technique to layout these images, you can see how to use in CSS code:

.user-images{
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin: 0 20px;
.wrapper{
width: 280px;
height: 280px;
margin: 15px;
img{
width: 100%;
border-radius: 4%;
}
}
}

Finally, user profile page look like:

User Profile page

Yeah, Look awesome! We have created user profile page, in next step, we’ll add edit/update profile page for user.


Edit/Update User

Flow to create Edit profile page includes 4 main steps:

  • Step 1: Add Edit profile page
  • Step 2: Layout Edit profile page
  • Step 3: Add nav-links to the left of the page
  • Step 4: Add Form edit profile to the right of the page.

UI of Edit profile page:

Edit profile page

Step 1: Add Edit profile page

In the first step, we add an action with a name is edit in UsersController

class UsersController < ApplicationController
...
def edit
end
end

Then, create a corresponding view is app/views/users/edit.html.erb

Next, add edit action to routes:

resources :users, only: [:show, :edit]

You can see the path of the edit profile page by type command:

rake routes 
...
...
edit_user GET /users/:id/edit(.:format) users#edit

resources :users, only: [..., :edit] tells Rails to map requests to http://localhost:3000/users/:id/edit to the user controller's edit action.

Add link edit profile to Edit Profile button in user profile page:

<a class="edit-profile" href="<%= edit_user_path(current_user) %>">
<button>Edit Profile</button>
</a>

current_user is a helper of devise, it’s current user signed in.

Step 2: Layout Edit profile page

We layout Edit profile page with 2 columns: The left column is actions and the right column is corresponding details.

The layout of Edit profile page.

HTML code:

<div class="user-edit-page">
<div class="actions">
<!-- Left column -->
</div>

<div class="details">
<!-- Right column -->
</div>
</div>

CSS code: I’m using flex technique to layout

.user-edit-page{
display: flex;
margin-top: 60px;
min-height: 500px;

.actions{
width: 220px;
border: 1px solid #dbdbdb;
}
.details{
flex: 1;
border: 1px solid #dbdbdb;
border-left:none;
}
}

Step 3: Add navigation to the left of the page

We use navs components of Bootstrap 4 with vertical pills for this section. See HTML code in below:

CSS for actions section:

.actions{
width: 220px;
border: 1px solid #dbdbdb;
.nav-link{
font-size: 14px;
background-color: white;
color: black;
border-radius: 0;
padding: 12px 0 12px 30px;
&:hover{
cursor: pointer;
}
}
.nav-pills .nav-link.active{
border-left: 2px solid;
font-weight: 600;
}
}

Now UI look like:

The Left of Edit profile page

Step 4: Add Form edit user to right of the page

Add a line code to find current user to edit action in users_controller.rb

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

In the right column, add a form to update the current user. We use form_with helper of Rails to generate a form.

Form edit user as below:

Form Edit User

CSS of Form:

.form-edit-user{
padding: 30px 100px 10px 50px;
.form-group, input{
font-size: 14px;
color: black;
}
input, textarea{
border: 1px solid #efefef;
}
.col-form-label{
text-align: right;
font-weight: 600;
}
.avatar{
height: 38px;
width: 38px;
border-radius: 50%;
}
.username{
font-size: 20px;
line-height: 22px;
margin-bottom: 0;
}
.change-photo{
color: #3897f0;
text-decoration: none;
font-size: 13px;
}
input[type='submit']{
color: white;
}
}

UI of form will look like:

When you fill information into the form and click submit button to process update user, you will see the error no routes match:

Error occur because we aren’t define update route to user yet. Now, we need to add update user routes to routes.rb

resources :users, only: [:show, :edit, :update]

Fill out the form and submit again, you should see a family error:

The action update could not be found, so we need to create update action in UserController like that:

def update
current_user.update(params[:user])
redirect_to current_user
end

In update action, we update the current user based on params which filled from form edit. current_user is current signed-in user.

Try again, oop! you’ll get an error like this:

Forbidden Attributes Error

Rails support some feature that helps we write secure applications. This one is called strong parameters, which require us to define which parameters are allowed into our controller actions. We have to whitelist our parameters to prevent wrongful mass assignment.

So now, to fix ForbiddenAttributesError we have to use strong parameters before updating the user.

To use strong parameters in our case, we use require and permit methods like that:

params.require(:user).permit(:username, :name, :website, :bio, :email, :phone, :gender)

That means we allow username, name, website, bio, email, phone and gender parameters for the valid params. Now, theupdate action looks like:

def update
current_user.update(user_params)
redirect_to current_user
end
private
def user_params
params.require(:user).permit(:username, :name, :website,
:bio, :email, :phone, :gender)
end

Now, go back to form edit and submit update again. It work! After update user successful, it’ll redirect to user profile page.

To easily see the change after updating user info, we should go back to user profile page (users/show.html.erb) to replace some fake data by real data of user.

Update HTML code like that:

<div class="col-md-8 basic-info">
<div class="user-wrapper">
<h2 class="username"><%= current_user.username %></h2>
...
</div>
  ...
  <h2 class="name"><%= current_user.name %></h2>
<%=link_to current_user.website, current_user.website, class: 'website' %>
</div>

Conclusion

In this article, I help you step by step how to create a new Rails application with PostgreSQL and Bootstrap. Understanding about MVC in Rails. Using devise gem to build authentication functions. And create functions to view and update user profile.

In the next article, I’m going to share with you detail about Active Record (CRUD, Validation, Association), Active Storage feature and using Kaminari gem in pagination.

Full Code on Github: https://github.com/thanhluanuit/instuigram

Related posts:

References: