How to build a Pinterest Clone in Rails 5
- Devise
- Paperclip
- Masonry
- Twitter Bootstrap 4
- Font Awesome
- Acts as Votable
Create a new Rails 5 project and pin resource.
rails g scaffold pin title description:text
Migrate the database.
rails db:migrate
Start the server.
rails s
Create some records using the UI.
Add:
gem 'devise', github: 'plataformatec/devise'
Run bundle. Generate the devise related files.
rails generate devise:install
Define the home page in routes.rb.
root to: "pins#index"
Create the user model.
rails generate devise user
Add the user_id
foreign_key to pins table.
rails g migration add_user_id_to_pins user_id:integer
Migrate the database.
rails db:migrate
Add the navigation links:
<% if user_signed_in? %>
<li><%= link_to "New Pin", new_pin_path %></li>
<li><%= link_to "Account", edit_user_registration_path %></li>
<li><%= link_to "Sign Out", destroy_user_session_path, method: :delete %></li>
<% else %>
<li><%= link_to "Sign Up", new_user_registration_path %></li>
<li><%= link_to "Sign In", new_user_session_path %></li>
<% end %>
to layout. You will now be able to signup, signin and logout.
gem "paperclip", "~> 5.0.0.beta1"
Run bundle. Add:
has_attached_file :image, :styles => { :medium => "300x300>" }
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
to pin.rb. Generate the fields required to upload image by running the paperclip generator.
rails g paperclip pin image
Modify the pins table by running the migration.
rails db:migrate
Add:
html: { multipart: true }
to pin form partial. Add file upload field to pin form partial.
<div class='field'>
<%= f.label :image %>
<%= f.file_field :image %>
</div>
You will get the error:
User must exist
if you try to create a pin without a user associated. Change the pins controller new action:
def new
@pin = current_user.pins.build
end
Image will not be uploaded. To fix the problem, add image field to the permitted fields in pin_params
pins controller method:
def pin_params
params.require(:pin).permit(:title, :description, :image)
end
You can now upload and view the image.
Add masonry-rails gem to Gemfile.
gem 'masonry-rails'
Run bundle. In application.scss:
*= require 'masonry/transitions'
It will look like this:
*= require 'masonry/transitions'
*= require_tree .
*= require_self
*/
Style the pin index page to enable transition effect provided by masonry-rails gem.
<div class="transitions-enabled" id="pins">
<% @pins.each do |pin| %>
<div class="box panel panel-default">
<%= link_to (image_tag pin.image.url), pin %>
<h2>
<%= link_to pin.title, pin %>
</h2>
<p class="user">
Submitted by
<%= pin.user.email %>
</p>
</div>
<% end %>
</div>
<%= link_to 'New Pin', new_pin_path %>
Add CSS to application.scss:
body {
background: #E9E9E9;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 100;
}
nav {
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.22);
.navbar-brand {
a {
color: #BD1E23;
font-weight: bold;
&:hover {
text-decoration: none;
}
}
}
}
#pins {
margin: 0 auto;
width: 100%;
.box {
margin: 10px;
width: 350px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.22);
border-radius: 7px;
text-align: center;
img {
max-width: 100%;
height: auto;
}
h2 {
font-size: 22px;
margin: 0;
padding: 25px 10px;
a {
color: #474747;
}
}
.user {
font-size: 12px;
border-top: 1px solid #EAEAEA;
padding: 15px;
margin: 0;
}
}
}
#edit_page {
.current_image {
img {
display: block;
margin: 20px 0;
}
}
}
#pin_show {
.panel-heading {
padding: 0;
}
.pin_image {
img {
max-width: 100%;
width: 100%;
display: block;
margin: 0 auto;
}
}
.panel-body {
padding: 35px;
h1 {
margin: 0 0 10px 0;
}
.description {
color: #868686;
line-height: 1.75;
margin: 0;
}
}
.panel-footer {
padding: 20px 35px;
p {
margin: 0;
}
.user {
padding-top: 8px;
}
}
}
textarea {
min-height: 250px;
}
Reload the page. You will now see styled grids for the pins.
Add bootstrap gem to Gemfile.
gem 'bootstrap', '~> 4.0.0.alpha3'
Run bundle. Require bootstrap in application.scss:
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require 'masonry/transitions'
*/
@import "bootstrap";
Require bootstrap in application.js:
//= require bootstrap
It will now look like this:
//= require jquery
//= require bootstrap
//= require jquery_ujs
//= require turbolinks
//= require_tree .
Add signup, register, logout, account and new pin links in layouts/_header.html.erb
:
<nav class="navbar navbar-dark bg-inverse navbar-fixed-top">
<%= link_to "Puppy Reviews", root_path, class: "navbar-brand" %>
<ul class="nav navbar-nav">
<% if user_signed_in? %>
<li class="nav-item">
<%= link_to "New Pin", new_pin_path, class: 'nav-link' %>
</li>
<li class="nav-item">
<%= link_to "Account", edit_user_registration_path, class: 'nav-link' %>
</li>
<li class="nav-item">
<%= link_to "Sign Out", destroy_user_session_path, method: :delete, class: 'nav-link' %>
</li>
<% else %>
<li class="nav-item">
<%= link_to "Sign Up", new_user_registration_path, class: 'nav-link' %>
</li>
<li class="nav-item">
<%= link_to "Sign In", new_user_session_path, class: 'nav-link' %>
</li>
<% end %>
</ul>
</nav>
Add:
<%= render 'layouts/header' %>
to layout file. In application.js:
//= require masonry/jquery.masonry
It will look like this:
//= require jquery
//= require bootstrap
//= require jquery_ujs
//= require masonry/jquery.masonry
//= require turbolinks
//= require_tree .
In pins.coffee:
$ ->
$('#pins').imagesLoaded ->
$('#pins').masonry
itemSelector: '.box'
isFitWidth: true
Now you will see the transition effect when the browser window is resized.
Add acts_as_votable
gem to Gemfile.
gem 'acts_as_votable', '~> 0.10.0'
Run bundle. Generate and create the tables required for voting functionality.
rails generate acts_as_votable:migration
rails db:migrate
Add:
acts_as_votable
in pin model. Define the route to vote in routes.rb.
resources :pins do
put 'like', to: 'pins#upvote'
end
If you do rails routes
, you can see the route to vote:
pin_like PUT /pins/:pin_id/like(.:format) pins#upvote
In pin show page, display the like icon and the number of votes:
<%= link_to like_pin_path(@pin), method: :put, class: "btn btn-secondary" do %>
<span class="glyphicon glyphicon-heart"></span>
<%= @pin.get_upvotes.size %>
<% end %>
Display the like button only if a user is signed in.
<% if user_signed_in? %>
<%= link_to like_pin_path(@pin), method: :put, class: "btn btn-secondary" do %>
<span class="glyphicon glyphicon-heart"></span>
<%= @pin.get_upvotes.size %>
<% end %>
<%= link_to "Edit", edit_pin_path, class: "btn btn-default" %>
<%= link_to "Delete", pin_path, method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-secondary" %>
<% end %>
If you go to the show pin page, you will get the error:
undefined method `like_pin_path'
The output of rails routes
shows:
pin_like PUT /pins/:pin_id/like(.:format) pins#upvote
Link the like icon to this path in the pin show page.
<%= link_to pin_like_path(@pin), method: :put, class: "btn btn-secondary" do %>
...
Bootstrap 4 does not come with glyphicons support anymore. Let's use fontawesome instead. Add font-awesome-rails gem to Gemfile.
gem "font-awesome-rails"
Run bundle. Require it in application.scss:
@import "font-awesome";
In the pin show view, display the heart icon for the like link:
<i class="fa fa-heart"></i>
Click on the vote. You will get:
undefined method `upvote_by' for nil:NilClass
Initialize the @pin variable by adding the upvote action to before_action
in pins controller.
before_action :set_pin, only: [:show, :edit, :update, :destroy, :upvote]
You will now get the error:
Couldn't find Pin with 'id'=
Currently the route for like is:
rails routes | grep like
pin_like PUT /pins/:pin_id/like(.:format) pins#upvote
Let's enclose the like route within the member block.
resources :pins do
member do
put 'like', to: 'pins#upvote'
end
end
You will now get the error:
undefined method `pin_like_path'
Let's look at the route for like link:
rails routes | grep like
The output shows:
like_pin PUT /pins/:id/like(.:format) pins#upvote
Change the like link in pin show.
like_pin_path(@pin)
We need to protect all actions except index and show from users who are not logged in. Add the before_action
in pins controller that uses devise method to protect those actions.
before_action :authenticate_user!, except: [:index, :show]
Now you must be logged in to like any puppy by clicking the heart icon. You can download the source code for this article from ``.
In this article, you learned how to develop a Pinterest clone using masonry-rails, acts_as_votable, bootstrap 4, devise in Rails 5.