How to Build a Pinterest Clone in Ruby on Rails
Oh hey. Didn’t see you there.

In this tutorial you will get a Ruby on Rails Pinterest Clone up and running on your local server, and will push your work into Github.
A few helpful links if you’re just getting started with Ruby on Rails:
I’m on a Mac, so everything is going to be easier for a Mac user to follow along with.
Keep this in mind: it seems to me that your NUMBER 1 job as a developer is to learn how to learn; read, listen, watch… just figure out how the hell to build something from nothing, using any resources you can get your hands on. So, I’m hoping if you get stuck in this tutorial, you can reinforce yourself with the mantra: “Oh wait, I can just google that sh!t”.
House-keeping almost done. Getting closer to lift-off… I’m going to run through this tutorial as I would do them. It’s not necessarily the way to do it, just how I do it, and — loosely — how Mackenzie Child does it in his tutorial.
Let’s dive in!

Table of Contents
- What are we building?
- Navigating the Command line, Creating the app
- First Git Commit
- Opening the App in Sublime, Installing Gems
- Generate a Pin Model (video)
- Generate a Pin Controller, and Setting up an Index Action and View
- Inside the routes.rb file
- Setting the Homepage
- CRUD, Form Building (video)
- Show Action and View
- List All Pins in Index Page (video)
- Edit, Update and Destroy a Pin (video)
- Devise Gem, Adding Users (video)
- Adding Associations (video)
- Customizing User Views (video)
- Adding Style (video)
- Paperclick Gem, Add Ability to Upload Images (video)
- Masonry (video)
- Acts as Votable (video)
- Authentication
- More Style (video)
p.s. just an idea: if you get some value out of this article, and you’d like to support the author, purchase your next Amazon shipment through my affiliate link here. Cheers!
Step 1: What are we building?
We are building a pinterest-like application! It’ll look something like this:

Features: Pins (image-uploading, title and description for each pin, users can edit and delete) user log-in and authentication, voting, masonry (so that pins scale to the size of the view-port).
Step 2: Navigating the Command line, Creating the app
You may already know how to move about the command line interface, but stick with me here and let’s make sure this content is accessible to those who aren’t familiar yet.
Jumping in… here is an explanation of the commands you’ll see me use in the command line terminal
cd = "change directory"ls = "list files inside directory"rails new pin_board = "rails, please make me a new application called pin_board"rails s = "rails, please start up the local server!"
I’m going to save my new app inside Documents/Projects, so I first need to navigate there in the command line:
cd Documents
then…
cd Projects
then…
rails new pin_board
then…
ls
look at the new files! yay.
then…
rails s
start up the server! Navigate to http://localhost:3000/

Great. A nice little vid of this whole process:
Hold down control and hit “c” to kill the server…
Step 3: First Git Commit
Ok, now let’s make a repository to store our work, and make our first git commit…
FYI my github repo for this project is here. After you make a fresh repo, you’ll see:

Now to commit and push for the first time…
Here is the order of my commands:
git init
git status
git add .
git status
git commit -m "first commit"
git remote add origin https://github.com/deallen7/pinterest-clone.git
git push -u origin master
See me in action:
Step 4: Opening the App in Sublime, Installing Gems
If you’re using Sublime, here’s a neat little trick that let’s you open the text editor from the command line.
Otherwise, just get Sublime open and find your app. I’m looking at:

First thing we’ll do is add some gems…
gem 'haml', '~> 5.0', '>= 5.0.4'
gem 'bootstrap-sass', '~> 3.3', '>= 3.3.7'
gem 'simple_form', '~> 4.0', '>= 4.0.1'
I imagine we’ll be adding devise at some point as well, and maybe a couple other gems, but we’re living in Mackenzie’s world right now so let’s just follow him along!

Add these to your gemfile, save your gemfile, and run an install in your terminal:
bundle install
Step 5: Generate a Pin Model (video)
In the command line:
rails g model Pin title:string description:text

This command creates a model and a migration. You can find both of the files in your Ruby app, and I suggest you go hunt them down.


Now to run the migration:
rake db:migrate

Step 6: Generate a Pin Controller, and Setting up an Index Action and View
Moving forward.
We have a model (think: database) for our Pins, now let’s generate a Pin Controller.
NOTE: You should understand the basic MVC framework of a Ruby on Rails web app. Also applies to other frameworks. M = Model, V = View, C = Controller. Here’s a great article on the subject that I recommend you read.
Ok, the controller:
rails g controller Pins

and then let’s go find that controller file we just made…

Inside the controller… MC: “let’s go ahead and set up some pages…”
Let’s create an index action:
class PinsController < ApplicationControllerdef index
end
end
How easy was that?
Now for the Index view!
First, create a file called “index.html.haml” underneath app/views/pins

Next, I’m going to give the page a header… Just so we know it’s the Index page:

Cool. Now, don’t worry, I’m going to tie this all together, but first I want to introduce you to the routes.rb file:
Step 7: Inside the routes.rb file
Before we make any changes to this routes.rb file, try running this command:
rake routes

No routes defined!
Now, let’s make a change to our routes file:
Rails.application.routes.draw do
resources :pins
end

Now, re-run that rake routes command:
rake routes

Neat! Routes! You might think of routes as links. Together with the controller, the routes.rb file can help you set up links through the controller to your views (think: webpage).
The views may display data that you are calling from your model. Remember the MVC structure. The C helps connect the M to the V.

Ok, moving forward.
Step 8: Setting the Homepage
We’ve created an Index action in the Pins controller, and we’ve created an Index view. Now, let’s set the homepage of our app to the Index page!
We’ll do this in the routes.rb file:
Rails.application.routes.draw do
resources :pins
root "pins#index"
end
translation:
root "pins#index" = "rails, please make the homepage the index action inside of the pins controller"
The index action is blank though, WTF is going on?
It’s pretty simple. The index action doesn’t have much to do, it’s just going to show the index view, with no data-fetching tasks being passed on to the Model.
Let’s fire up the rails server and see this in action:

Cool. We’re flying here. Moving forward.
Step 9: CRUD, Form Building (video)
CRUD = Create, Read, Update, Destroy
These are actions. You might even remember from our routes.rb experiment that these actions also have routes associated with them.
Imagine clicking a “delete” button, or an “edit” button in the browser. Those buttons are connected to routes that are connected to these CRUD actions in the Pins controller.
So, back inside the pins_controller.rb file…
class PinsController < ApplicationControllerdef index
enddef new
@pin = Pin.new
enddef create
@pin = Pin.new(pin_params)
endprivatedef pin_params
params.require(:pin).permit(:title, :description)
end
end
Translations (Forgive me for any mistaken interpretations here. I’m learning, too!):
- We’ve created an action for creating a new pin. It’s a two step process. Inside that action, we’re telling the Controller how to talk to the Model: “please understand that when I say “@pin” I’m trying to create a new entry in the Pin.rb database!”
- When I create a new pin, please consider only the information I’m trying to pass inside this instance variable “@pin” and only permit a title and description. I don’t need any other info from you, browser!
That was a sketchy translation at best. Read up on this here.
Next, we’re going to create a view for the New action…
app/views/pins/new.html.haml

Now we’re going to create a way for the user to submit the data we’re interested in... A Title & Description for a Pin.
%h1 New form= render 'form'= link_to "Back", root_path
Notice the second line there? We’re asking the browser to render a “partial” called form. It’s sorta like rendering a webpage inside another webpage.
Let’s create that partial:
app/views/pins/_form.html.haml

Cool. Now to build the form…
We still need to tinker with setting up simple_form before we can use it…
MC is referencing this document for his simple_form install…
rails g simple_form:install --bootstrap

Cool. Back to the form partial… We’re here in the video if you’d like to follow along…
= simple_form_for @pin, html: { multipart: true } do |f|
- if @pin.errors.any?
#errors
%h2
= pluralize(@pin.errors.count, "error")
prevented this Pin from saving
%ul
- @pin.errors.full_messages.each do |msg|
%li = msg.form-group
= f.input :title, input_html: { class: 'form-control' }.form-group
= f.input :description, input_html: { class: 'form-control' }= f.button :submit, class: "btn btn-primary"
Annnnnnnnnd start that server up and navigate to http://localhost:3000/pins/new

Neat-o.
Back inside the Pin Controller, let’s add some logic to our Create action:
def create
@pin = Pin.new(pin_params)if @pin.save
redirect_to @pin, notice: "Successfully created new Pin"
else
render 'new'
endend
If we can save the new pin, redirect to the pin show page and display the notice “Successfully created new Pin”
Else, render the new page again.
You should note that we don’t yet have a show page. We’ll create one shortly…
But first, let’s make sure this notice can show up in our view!
But first, first, let’s rename our application.html.erb file to application.html.haml…
Then we need to re-write the file in HAML…
Why are we doing this? Ask Mackenzie!
If you are using sublime, you can change the syntax highlighting by doing this (sorry, I can’t actually make this gif any bigger):

Updated application.html.haml file:
!!! 5
%html
%head
%title Pin Board
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
- flash.each do |name, msg|
= content_tag :div, msg, class: "alert alert-info"
= yield
Now, let’s try creating a new pin:

Oops! You’re page might look like this if you forget to rename the application file from .erb to .haml…
Ok, creating a pin:

No show action! (And no show view!)
Step 10: Show Action and View
Ok, so let’s create a show action inside the Pin Controller:
def show
end
And then we’re going to create a private method that will be used for the show, edit, update, destroy actions.
We could write this like so:
def show
@pin = Pin.find(params[:id])
end
But instead, because we’re going to use this middle part for several actions…
@pin = Pin.find(params[:id])
…we’re going to move this piece to a private method, and then create a before_action statement that runs this query before processing the show, edit, update or destroy actions. Get it?
The whole controller file looks like so:
class PinsController < ApplicationController
before_action :find_pin, only: [:show, :edit, :update, :destroy]def index
enddef show
enddef new
@pin = Pin.new
enddef create
@pin = Pin.new(pin_params)if @pin.save
redirect_to @pin, notice: "Successfully created new Pin"
else
render 'new'
end
endprivatedef pin_params
params.require(:pin).permit(:title, :description)
enddef find_pin
@pin = Pin.find(params[:id])
end
end
Ok, now we need that show page, don’t we? We are here in the video.
Inside the app/views/pins/show.html.haml file:
%h1= @pin.title
%p= @pin.description
Fire up the server…

Cool.
Now to add a back link…
%h1= @pin.title
%p= @pin.description=link_to "Back", root_path
Nice.
Neeeext… we’ll list out all of the pins in the Index page…
Step 11: List All Pins in Index Page (video)
Inside the Index View file…

- @pins.each do |pin|
%h2= link_to pin.title, pin
Refresh that index page aaaaaaaand:

No method error! That’s because our Index action looks like:
def index
end
Let’s do something about that…
def index
@pins = Pin.all.order("created_at DESC")
end
Refresh the index page…

Success.
now to add the ability to Edit and Destroy a Pin…
Step 12: Edit, Update and Destroy a Pin (video)
Before we jump in and create these new actions, remember that we already have written the first part of each of these methods…
before_action :find_pin, only: [:show, :edit, :update, :destroy]
That find_pin method looks like:
def find_pin
@pin = Pin.find(params[:id])
end
So basically, each of these actions already looks like:
def edit
@pin = Pin.find(params[:id])
enddef update
@pin = Pin.find(params[:id])
enddef destroy
@pin = Pin.find(params[:id])
end
Moving forward…
def update
if @pin.update(pin_params)
redirect_to @pin, notice: "Pin was Successfully updated!"
else
render 'edit'
end
end
Guess what? Yep, you got it! We need to create an edit view…
And we need to create an edit link on the show page…
Inside the edit.html.haml file we just created:
%h1 Edit Pin= render 'form'= link_to "Cancel", pin_path
And inside the show page:
%h1= @pin.title
%p= @pin.description=link_to "Back", root_path
=link_to "Edit", edit_pin_path
Cool. Try it out!

It works. Great. GRAND. WONDERFUL.
Now, the ability to destroy (or delete).
def destroy
@pin.destroy
redirect_to root_path
end
And then in our show page, we need a delete link…
%h1= @pin.title
%p= @pin.description=link_to "Back", root_path
=link_to "Edit", edit_pin_path
=link_to "Delete", pin_path, method: :delete, data: { confirm: "Are you sure?" }
Refresh our page…

Great! Working…
Now, how about we add a link to add a new pin in our index page?
= link_to "New Pin", new_pin_path
- @pins.each do |pin|
%h2= link_to pin.title, pin
It works. Don’t believe me? Check your index page.
OMG I FORGOT WE NEED TO BE KEEPING OUR GIT REPO UP TO DATE
Opps. We’ve added like 20,012 features and we’ve only initialized our repo.
Let’s go ahead and do a commit now…
git status
oh my, look at that red
git add .
ok…
git status
oh my, look at that green
git commit -m "CRUD action and views"
okkk
git push
cool.
For your viewing pleasure:

Next, let’s add USERS!
Step 13: Devise Gem, Adding Users (video)
Find the latest and greatest devise gem version at rubygems.org…
gem 'devise', '~> 4.4', '>= 4.4.3'
Add to your gemfile…
So now all of the gems we’ve added are looking like dis:
gem 'devise', '~> 4.4', '>= 4.4.3'
gem 'haml', '~> 5.0', '>= 5.0.4'
gem 'bootstrap-sass', '~> 3.3', '>= 3.3.7'
gem 'simple_form', '~> 4.0', '>= 4.0.1'
Great. Save. And Bundle install!
bundle install
Now, visit the devise docs on github…
Next, let’s do the install!
rails g devise:install
then…
Follow the directions…
Step 1: add this line to the development.rb file under Config/environments…
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
And then we’ve already done step 2…
Step 3, we’ve done. Check the application.html.haml file, lines 9 and 10…

Step 4, skip!
Step 5, let’s do it!!!
rails g devise:views
Ok, moving on.
Let’s generate a devise model.
rails g devise User
and then, we need to run:
rake db:migrate

Ok, we’re flying.
Let’s make sure it’s working!
Fire up the server, and navigate to http://localhost:3000/users/sign_up
check it:

Now to create a new user!

Success.
Now to add associations between our pin.rb model and our user.rb model…
Step 14: Adding Associations (video)
Inside the user.rb file:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :pins
end
And inside the pin.rb file:
class Pin < ApplicationRecord
belongs_to :user
end
And now, we need to generate a migration so that our pins have a user_id column!
rails g migration add_user_id_to_pins user_id:integer:index
And then…
rake db:migrate
Great. Check it out:

Onward.
Let’s jump into the rails console!
rails c
and then
@pin = Pin.first
and then, we’re going to assign any saved pins to the first user…
@user = User.first
then…
@pin.user = @user
And then…
@pin.save
Cool. All together now:

Step 15: Customizing User Views (video)
Now, let’s give each of our pins an author:
Inside the show.html.haml file:
%h1= @pin.title
%p= @pin.description
%p= @pin.user.email=link_to "Back", root_path
=link_to "Edit", edit_pin_path
=link_to "Delete", pin_path, method: :delete, data: { confirm: "Are you sure?" }
And then let’s check the view:

Cool. Please, no spam.
Maybe we even do something like:
%h1= @pin.title
%p= @pin.description
%p
Submitted by
= @pin.user.email%br/
=link_to "Back", root_path
=link_to "Edit", edit_pin_path
=link_to "Delete", pin_path, method: :delete, data: { confirm: "Are you sure?" }

Neat-o.
Now we need to make sure that all new pins are associated with a user…
Back inside the controller…
We need to update both our new and create actions…
def new
@pin = current_user.pins.build
enddef create
@pin = current_user.pins.build(pin_params)if @pin.save
redirect_to @pin, notice: "Successfully created new Pin"
else
render 'new'
end
end
Now, we should be able to create a new pin, and it’ll be associated with the user…

Yesssss.
Ok, time for a commit:
git status
git add .
git commit -m "added devise gem and user / pin association"
git push
Cool. Moving onwards. Time to style!
Step 16: Adding Style (video)
We already have the bootstrap sass gem installed, but we still need to make some modifications before we can get going.
If you’re interested, check out the docs here.
First, we need to rename our application.css file to application.css.scss
Next, copy / paste in:
@import "bootstrap-sprockets";
@import "bootstrap";
Save that file.
Go ahead and get the server going and see what bootstrap does right out the gate:

At this point, it really matters what version of rails you are using…
I’m using 5.1.6, so I need to follow these directions:

You can check your rails version with this command:
rails -v

So I need to install jquery and then bundle install before adding the extra bits to application.js…
All of my added gems:
gem 'jquery-rails'
gem 'devise', '~> 4.4', '>= 4.4.3'
gem 'haml', '~> 5.0', '>= 5.0.4'
gem 'bootstrap-sass', '~> 3.3', '>= 3.3.7'
gem 'simple_form', '~> 4.0', '>= 4.0.1'
Run the bundle install…
bundle install
Then copy / paste into the application.js file:
//= require jquery
//= require bootstrap-sprockets
My whole application.js file looks like:
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require bootstrap-sprockets
//= require rails-ujs
//= require turbolinks
//= require_tree .
Ok, now to really make things pop…
We’re here in MC’s video…
Inside the application.html.haml file:
!!! 5
%html
%head
%title Pin Board
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
%nav.navbar.navbar-default
.container
.navbar-brand= link_to "Pin Board", root_path- if user_signed_in?
%ul.nav.navbar-nav.navbar-right
%li= link_to "New Pin", new_pin_path
%li= link_to "Account", edit_user_registration_path
%li= link_to "Sign Out", destroy_user_session_path, method: :delete
- else
%ul.nav.navbar-nav.navbar-right
%li= link_to "Sign Up", new_user_registration_path
%li= link_to "Sign In", new_user_session_path- flash.each do |name, msg|
= content_tag :div, msg, class: "alert alert-info"
= yield
Which gives us a nice navbar for a user that is logged in:

And a nice navbar for an unregistered person or un-signed in user:

Forward!
Next, let’s get rid of that new pin link in the index.html.haml page…
File should look like:
- @pins.each do |pin|
%h2= link_to pin.title, pin
Then, add a container for the flash messages and the “= yield” to the application.html.haml page:
!!! 5
%html
%head
%title Pin Board
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
%nav.navbar.navbar-default
.container
.navbar-brand= link_to "Pin Board", root_path- if user_signed_in?
%ul.nav.navbar-nav.navbar-right
%li= link_to "New Pin", new_pin_path
%li= link_to "Account", edit_user_registration_path
%li= link_to "Sign Out", destroy_user_session_path, method: :delete
- else
%ul.nav.navbar-nav.navbar-right
%li= link_to "Sign Up", new_user_registration_path
%li= link_to "Sign In", new_user_session_path
.container
- flash.each do |name, msg|
= content_tag :div, msg, class: "alert alert-info"
= yield
Makes the home page look like:

Now let’s do a little bit of work on the form…
We saved ourselves a little bit of trouble by adding the -bootstrap flag when we first installed simple_form… The form already looks pretty good:

But we’re still going to tweak the layout a bit…
Inside the new.html.haml file:
.col-md-6.col-md.offset-3
%h1 New form = render 'form' = link_to "Back", root_path
HAML is really funky about indentation, so make sure you are tabbing your junk over.
My file looks like:

Ok. Onward.
We’ll do the same exact thing to the edit.html.haml page…
.col-md-6.col-md.offset-3
%h1 Edit Pin = render 'form' = link_to "Cancel", pin_path
Great. Onward.
Git commit time.
git add .
git commit -m "basic structure and styles"
git push
Next, let’s add the ability to upload images…
Step 17: Paperclick Gem, Add Ability to Upload Images (video)
Add to the gemfile:
gem 'paperclip', '~> 6.0'
And then bundle install. You know how that cookie is crumbled by now…
Now, on to the github docs for paperclip…
Ugh, it’s deprecated…
Let’s see if we can’t use it anyways :)
Inside the pin.rb model file:
class Pin < ApplicationRecord
belongs_to :userhas_attached_file :image, styles: { medium: "300x300>" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
end
and then let’s run a migration:
rails g paperclip pin image
Next…
Don’t forget to run a migraiton!
rake db:migrate
Great. Now to add a new form option to our form partial…
Whole file looks like:
= simple_form_for @pin, html: { multipart: true } do |f|
- if @pin.errors.any?
#errors
%h2
= pluralize(@pin.errors.count, "error")
prevented this Pin from saving
%ul
- @pin.errors.full_messages.each do |msg|
%li = msg.form-group
= f.input :image, input_html: { class: 'form-control' }.form-group
= f.input :title, input_html: { class: 'form-control' }.form-group
= f.input :description, input_html: { class: 'form-control' }= f.button :submit, class: "btn btn-primary"
And then we need to add an additional allowed parameter to the pins params…
Inside the controller… add :image…
def pin_params
params.require(:pin).permit(:title, :description, :image)
end
And then inside the show.html.haml file… That first line of code is fresh, new, ya know…
= image_tag @pin.image.url(:medium)
%h1= @pin.title
%p= @pin.description
%p
Submitted by
= @pin.user.email%br/
=link_to "Back", root_path
=link_to "Edit", edit_pin_path
=link_to "Delete", pin_path, method: :delete, data: { confirm: "Are you sure?" }
Ok, let’s see this in action!
Spin up the server and refresh the page! Try adding a new pin with an image.

And add an image…

Curse you!!!!!!!
Ok, let’s try googling “Could not run the `identify` command. Please install ImageMagick.”
I found this resource. Let’s try it out.
First, running:
brew update
And then running:
brew install imagemagick
Okkkkk. Inside the development.rb file:
# Paperclip config:
Paperclip.options[:image_magick_path] = "/opt/ImageMagick/bin"
Paperclip.options[:command_path] = "/opt/ImageMagick/bin"

Save!
Restart the server.
Refresh.
Create a new pin.
Annnnnnnnnnnnnnd…..
YES!

Ok, moving forward.
Inside the index.html.haml file:
- @pins.each do |pin|
= link_to (image_tag pin.image.url(:medium)), pin
%h2= link_to pin.title, pin
Save, and let’s check out the index file…
Ok, I need to add some images to the first 2 posts…
Ok, great! Look good:

Now, for a change to the edit.html.haml form so that if there is already an image uploaded, and we want to change the image, we’ll be able to see the uploaded image…
Code:
.col-md-6.col-md.offset-3
%h1 Edit Pin
= image_tag @pin.image.url(:medium)
= render 'form'= link_to "Cancel", pin_path
Before:

After:

Cool. Forward!
Git commit!
git add .
git commit -m "add image uploading with paperclip"
git push
Cool.
Now for Masonry and jQuery!
Step 18: Masonry (video)
Find the latest gemfile at rubygems.org…
gem 'masonry-rails', '~> 0.2.4'
Add to your gemfile…
All of the gems I’ve added:
gem 'masonry-rails', '~> 0.2.4'
gem 'paperclip', '~> 6.0'
gem 'jquery-rails'
gem 'devise', '~> 4.4', '>= 4.4.3'
gem 'haml', '~> 5.0', '>= 5.0.4'
gem 'bootstrap-sass', '~> 3.3', '>= 3.3.7'
gem 'simple_form', '~> 4.0', '>= 4.0.1'
Neat. Now check out the documentation on github…
Inside application.js:
//= require masonry/jquery.masonry
My whole file looks like:
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require masonry/jquery.masonry
//= require bootstrap-sprockets
//= require rails-ujs
//= require turbolinks
//= require_tree .
Now, we need to add some junky stuff to our pins.coffee file:
$ ->
$('#pins').imagesLoaded ->
$('#pins').masonry
itemSelector: '.box'
isFitWidth: true
Ok, and now let’s start styling a bit.
Inside the index.html.haml file:
#pins.transitions-enabled
- @pins.each do |pin|
.box.panel.panel-default
= link_to (image_tag pin.image.url(:medium)), pin
.panel-body
%h2= link_to pin.title, pin
Cool. Let’s make sure it works in the browser:

Yep. Looks like MC’s. Onward.
Now, to copy / paste some styles from MC. His stylesheet is here.
My stylesheet:
/*
* 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, 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'
*= require_tree .
*= require_self
*/@import "bootstrap-sprockets";
@import "bootstrap";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;
}
Ok. Forward. Looking a little better but the images are looking a little funky.

MC to the rescue! It’s because we didn’t tab our index.html.haml file properly.
Should look like this:

Save. Refresh. Annnnnd:

Bingo.
Hmmm. The images aren’t full width. MC has the answer!
Just update the 4th line in the index.html.haml file to:
= link_to (image_tag pin.image.url), pin
There. At least 2 of the images are working… Not sure why Dwight is struggling…

Hmmm. I’ll try updating the image…

Ok. Working. Onwards and forwards!
Now to style the show.html.haml page:
#pin_show.row
.col-md-8.col-md-offset-2
.panel.panel-default
.panel-heading.pin_image
= image_tag @pin.image.url(:medium)
.panel-body
%h1= @pin.title
%p.description= @pin.description
.panel-footer
.row
.col-md-6
%p.user
Submitted by
= @pin.user.email
.col-md-6
.btn-group.pull-right
=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-default"
Make sure to be detailed about your indentations!

Looking sharp!

Let’s also add a user under the title on the index page:
#pins.transitions-enabled
- @pins.each do |pin|
.box.panel.panel-default
= link_to (image_tag pin.image.url), pin
.panel-body
%h2= link_to pin.title, pin
%p.user
Submitted by
= pin.user.email
Cool! Sharp. Ouchy.

Moving on…
Git time.
git add .
git commit -m "add masonry and styles"
git push
Next, for the voting feature!
Step 19: Acts as Votable (video)
You know the drill. Rubygems.org for the latest and greatest.
gem 'acts_as_votable', '~> 0.11.1'
Save in the gemfile and run the bundle install…
And then check out the documentation…
Docs say we need to run a migration. Of course we do! We need a place to save the votes.
rails generate acts_as_votable:migration
then run the migration through…
rake db:migrate
Looks like:

Big ol table. Neat.
Now inside the pin.rb file:
class Pin < ApplicationRecord
acts_as_votable
belongs_to :userhas_attached_file :image, styles: { medium: "300x300>" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
end
Save.
Now for some routes!
Inside the routes.rb file…
Rails.application.routes.draw do
devise_for :users
resources :pins do
member do
put "like", to: "pins#upvote"
end
end
root "pins#index"
end
Cool. New paths! Notice we aren’t doing any down-voting (we could, we just aren’t)

Next, inside the pin controller…
We’re going to create a new action called “upvote”
And we’re going to add the action to the before_action…
before_action :find_pin, only: [:show, :edit, :update, :destroy, :upvote]
And inside the upvote action:
def upvote
@pin.upvote_by current_user
redirect_to :back
end
Then, inside the show page…
We need a link to vote!
#pin_show.row
.col-md-8.col-md-offset-2
.panel.panel-default
.panel-heading.pin_image
= image_tag @pin.image.url(:medium)
.panel-body
%h1= @pin.title
%p.description= @pin.description
.panel-footer
.row
.col-md-6
%p.user
Submitted by
= @pin.user.email
.col-md-6
.btn-group.pull-right
=link_to like_pin_path(@pin), method: :put, class: "btn btn-default" do
%span.glyphicon.glyphicon-heart
= @pin.get_upvotes.size
=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-default"
Again, be careful about those indentations or you might have some errors…

Looking good:

Ok, now to test the button!

Ew. Don’t be scared though. Someone has already made this mistake. Let’s google “undefined method ‘back_url”
Sure enough, here’s the answer.

I’ll update my upvote method to:
def upvote
@pin.upvote_by current_user
redirect_back fallback_location: root_path
end
SAve. And refresh…
Yay.

Ok. Onwards.
Git time.
git add .
git commit -m "add and configure acts as votable"
git push
Neato.
Step 20: Authentication
Oh bollocks. Time to work backwards for a minute.
Need to add…
- if user_signed_in?
…above the edit and delete buttons to prevent those buttons from showing up to anyone but the user who made the pin.

Next, we need to add an authenticated before action to the pins controller…
before_action :authenticate_user!, except: [:index, :show]
Great. Now non-users can only view pins, but they can’t CUD (create, update, or delete).
Ok. More styling…
Step 21: More Style (video)
Inside the new.html.haml file:
.col-md-6.col-md.offset-3
.row
.panel.panel-default
.panel-heading
%h1 New form
.panel-body
= render 'form'
Cool:

Ok, inside views/devise/registrations/edit.html.erb…
Just copy / paste this mess from MC’s repo…
<div class="col-md-8 col-md-offset-2">
<div class="row">
<div class="panel panel-default">
<div class="panel-heading">
<h2>Edit Your Account</h2>
</div><div class="panel-body">
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= devise_error_messages! %><div class="form-group">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, class: "form-control" %>
</div><% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
<% end %><div class="form-group">
<%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password, autocomplete: "off", class: "form-control" %>
</div><div class="form-group">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %>
</div><div class="form-group">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password, autocomplete: "off", class: "form-control" %>
</div><div class="form-group">
<%= f.submit "Update", class: "btn btn-primary" %>
</div>
<% end %>
</div><div class="panel-footer">
<h3>Cancel my account</h3><p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, class: "btn btn-default" %></p>
<br>
<%= link_to "Back", :back, class: "btn btn-default" %>
</div>
</div>
</div>
</div>
And then inside the new.html.erb file:
<div class="col-md-8 col-md-offset-2">
<div class="row">
<div class="panel panel-default">
<div class="panel-heading">
<h2>Sign up</h2>
</div>
<div class="panel-body">
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %><div class="form-group">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, class: "form-control" %>
</div><div class="form-group">
<%= f.label :password %> <% if @validatable %><i>(<%= @minimum_password_length %> characters minimum)</i><% end %><br />
<%= f.password_field :password, autocomplete: "off", class: "form-control" %>
</div><div class="form-group">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %>
</div><div class="form-group">
<%= f.submit "Sign up", class: "btn btn-primary" %>
</div>
<% end %><%= render "devise/shared/links" %>
</div>
</div>
</div>
</div>
And now let’s tackle the pin edit.html.haml page:
#edit_page.col-md-8.col-md-offset-2
.row
.panel.panel-default
.panel-heading
%h1 Edit Your Pin
.panel-body
.current_image
%strong.center Current Image
= image_tag @pin.image.url(:medium)
= render 'form'
Ok. Looking good:

Ok, now to update the devise/sessions/new.html.erb page…
<div class="col-md-8 col-md-offset-2">
<div class="row">
<div class="panel panel-default">
<div class="panel-heading">
<h2>Sign In</h2>
</div><div class="panel-body">
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="form-group">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, class: "form-control" %>
</div><div class="form-group">
<%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "off", class: "form-control" %>
</div><% if devise_mapping.rememberable? -%>
<div class="form-group">
<%= f.check_box :remember_me %> <%= f.label :remember_me %>
</div>
<% end -%><div class="form-group">
<%= f.submit "Log in", class: "btn btn-primary" %>
</div>
<% end %><%= render "devise/shared/links" %>
</div>
</div>
</div>
</div>
Okkkk…. Are we done yet MC?
I think we’re done. We’re done. I’m done.
Git time!
git add .
git commit -m "finish updating style"
git push
Wow. Damn. Done.
That was a SPRINT.
Hope you learned something! Thanks for following along. My github is here in case you’re looking for it.

Bye now.
Thanks for reading! I hope this article was helpful.
If you want to support the author, the best way is to purchase your next Amazon shipment through my affiliate link here. Or, consider buying me a coffee through venmo (David-Allen-13), cash app (deallen7), or paypal (davideallen).
Your donations, comments, and claps keep me motivated to create more material. I appreciate you! :D