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
- User opens his browser, types in a URL, and presses Enter. When a user presses Enter, the browser makes a request for that URL.
- The request hits the Rails router (config/routes.rb).
- The router maps the URL to the correct controller and action to handle the request.
- The action receives the request, and asks the model to fetch data from the database.
- The model returns a list of data to the controller action.
- The controller action passes the data on to the view.
- The view renders the page as HTML.
- 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: 5000development:
<<: *default
database: development_instagramtest:
<<: *default
database: test_instagramproduction:
<<: *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)
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:
But before you can sign in, you have to create a new account by click to Sign up
link below Log in
button.
The sign up page 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
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
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:
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 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:
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:
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 editend
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.
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:
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:
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:
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
endprivatedef 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:
- Part 2: medium.com/luanotes/build-instagram-by-ruby-on-rails-part-2
- Part 3: medium.com/luanotes/build-instagram-by-ruby-on-rails-part-3