How to Build a Forum with Ruby on Rails

Howdy, hello, how are ya? Like the subtitle states… this tutorial is based on Week 10 of Mackenzie Child’s 12 Web Apps in 12 Weeks Series. Link to his video here.
I’ve made sure that this is the BEST tutorial I’ve ever written (yet). I included a bunch of helpful links and videos and screenshots and gifs and memes to help you immerse yourself in the content. Should get you out the gate pretty quick with your Ruby on Rails learning path.
If you are casually browsing and are maybe interested in doing a Ruby on Rails tutorial, stop what you are doing RIGHT NOW and give this podcast a listen! It will give you the fuel you need to continue down the web developer path; it definitely fueled me.
This tutorial assumes you have Ruby on Rails installed and all other pre-requisites, like a github account and a text editor and such, and that you understand how to jump around in the command line / terminal.
If you don’t know how to do any of these things, that’s certainly ok. Everybody starts somewhere. Here are a few helpful links to get started:
P.S. I’m on a Mac, so everything is going to be a bit 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.
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:

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!
Table of Contents
- Getting Started. Creating the App.
- Git init
- That Sublime Trick
- Create a Post Model (video)
- Create a Post Controller (video)
- Routes (video)
- Creating an Index View
- Gems!
- Revisiting our Index View with HAML
- Add New and Create Methods to Post Controller
- Create New Post Form (video)
- Create the Show Action and View (video)
- Add Ability to Edit and Destroy a Post (video)
- Looping Through Posts on the Index Page (video)
- Setting up Devise (video)
- Connect Users and Posts (video)
- Add Navigation and HAML Styling (video)
- Users can sign in and out (video)
- Users can comment (video)
- Style the Comments (video)
Step 1: Getting Started. Creating the App.
Open up terminal and…
cd Documents
then
cd Projects
then
rails new forum
I have just generated a brand new rails app called “forum” inside of my Projects directory. Hurray.

Here, watch the thing:
annnnnd then let’s jump into the new directory and make sure the brick is working properly!
cd forum
then
rails s
then check out local host! http://localhost:3000/

Sweeeeeet. We gold baby.
Then ctrl + c to kill the server!
Looks something like this:
Ok cool. Moving on.

Step 2: Git init
Ok, we’ve done a very satisfying thing here. We now have a Ruby on Rails app brick that’s ready to be turned into a magical brick with powers. We’re like the hogwarts that transforms the brick into a wizard, you know?
But first, let’s practice some proper git hygiene and go ahead and commit our project to a fresh repository.
Soooo, get on over to Github and create a new repo!

and then when you click “create repository”…

And then, I’ll follow these directions above and do…
git init
then check the status with…
git status
you’ll see some red files that are unstaged…

Add ALL those files with a…
git add .
and then run another quick git status to see some green…
git status

Now, go ahead and…
git commit -m "first commit"
then copy / paste that junk from your repo…
git remote add origin https://github.com/deallen7/forum.git
git push -u origin master
Hit “enter” and then you should be golden…
Refresh your git page to see your uploaded files…
Ok, we’re really getting somewhere. Moving forward.
Step 3: That Sublime Trick
Short step here. If you’re using Sublime, you should most definitely check this little trick out so that you can open the files directly from the command line.
Step 4: Create a Post Model (video)
Alright, so. Well create our Post Model next.
rails g model post title:string content:text
then jump into sublime text and look at our migration file…

then, as always, we’ll run a migration…
rake db:migrate
looks like:
Now our post model is g2g…
Step 5: Create a Post Controller (video)
The model stores the post data… The controller helps control the conversation between the backend (database) and the frontend (the view!).
So we need a controller. Controllers are plural. Posts instead of post.
rails g controller posts
Now let’s open that controller up!

And inside the controller…
class PostsController < ApplicationController
def index
end
end
The index controller basically controls the page that will display all of the posts from all of the users. Maybe think of it as the reddit frontpage. And now, we’re going to ask the application to consider the Posts Index page to be the homepage by updating the routes.rb file…
Step 6: Routes (video)
Now inside the routes file…

Rails.application.routes.draw do
resources :posts root 'posts#index'
end
If we start up our server again, we’ll see something new!
AN ERROR PAGE!

Get used to seeing red, people. It’s your job to google that sh!t and figure it out. Welcome to life as a dev.
It’s pretty simple, though. We’re missing a template. We’re missing a view. The data can’t be displayed because there is no associated view for the index controller!
Make sense?
So let’s create a view!
Step 7: Creating an Index View
Create a new file called index.html.erb inside the app/views/post folder…

And then let’s restart the server!
Remember we just have to hit:
rails s
And then navigate to local host… http://localhost:3000/

Step 8: Gems! (video)
Whelp. Looks like I jumped the gun a bit. So let me get re-aligned with Mackenzie. He’s going to use a few gems, including HAML. Let’s follow along.
Nav to rubygems.org…
Search for HAML…
Copy / paste the gem into your Gemfile…
gem 'haml', '~> 5.0', '>= 5.0.4'
Then we’re adding the simple form gem…
gem 'simple_form', '~> 4.0', '>= 4.0.1'
And then the devise gem…
gem 'devise', '~> 4.4', '>= 4.4.3'
Great. Save.
You’ll now how these puppies inside your gemfile…
gem 'devise', '~> 4.4', '>= 4.4.3'
gem 'simple_form', '~> 4.0', '>= 4.0.1'
gem 'haml', '~> 5.0', '>= 5.0.4'
Next, inside the command line:
bundle install
Cool.
Step 9: Revisiting our Index View with HAML
We’re going to use HAML, so we need to re-name our index file from index.html.erb to index.html.haml…
And we’ll be using a slightly different syntax. Learning something new erday…

Start up the server and let’s see what we’re dealing with…
rails s

Cool.
Step 10: Add New and Create Methods to Post Controller (video)
Inside the post controller…
class PostsController < ApplicationController def index
end def new
@post = Post.new
end def create
@post = Post.new(post_params)
endprivate def post_params
params.require(:post).permit(:title, :content)
end
end
Great. SAVE!
Now, let’s start up the server and see what we’ve accomplished…
rails s
Navigate to http://localhost:3000/posts/new
And…

Ouch. Error.
But we know how to deal with this. We’re just missing a View file. See? READ THE ERROR MESSAGE. Do not fear the red.
Kill the server. crtl + c.
Inside the view/posts file…

Now, save. Then restart the server.
rails s
And…

#winning

Step 11: Create New Post Form (video)
This form is going to be a partial. A partial is a cool thing. Google what it is please. “what is a ruby on rails partial?”
There. We learning how to be a dev already. Googling stuff.
Anyways… a partial. We’ll want to create a new file underneath view/posts called:
_form.html.haml

Inside the form partial, using simple form…
= simple_form_for @post do |f|
= f.input :title
= f.input :content
= f.submit
Save.
We’re going to render the partial inside of our new.html.haml file…
= render 'form'

Let’s SAVE and then restart the server…

Ugly but hey at least it works.
Now back inside the post controller…
def create
@post = Post.new(post_params)if @post.save
redirect_to @post
else
render 'new'
end
end
If we’re able to save our new post, redirect to the post show page (we haven’t create yet), else render the form again…
Let’s try it out!
And we get: The action ‘show’ could not be found for PostsController
That was to be expected. We don’t have a show action OR a show view, do we?
Step 12: Create the Show Action and View (video)
Inside the controller…
def show
@post = Post.find(params[:id])
end
And of course we also need the view…
Create a new file called:
show.html.haml
Inside that file…
%h1= @post.title
%p= @post.content

Restart the server and see what we’ve done…

Great! Still #winning.
Step 13: Add Ability to Edit and Destroy a Post (video)
Back inside the post controller…
We’re going to create some new actions / methods / whateveryouwannacallthem…
def edit
enddef update
enddef destroy
end
More meat coming inside these sandwiches but for now let’s do a bit of refactoring with Mackenzie…
Create a new private method called “find_post”…
def find_post
@post = Post.find(params[:id])
end
And now… a before action
before_action :find_post, only: [:show, :edit, :update, :destroy]
Now this private find_post method will be run before all of these actions… before the show, edit, update and destroy action…
We can now update our show action to…
def show
end
Cool. DRY principles in action here. DON’T REPEAT YOURSELF.
Actions looks like:
def edit
enddef update
if @post.update
redirect_to @post
else
render 'edit'
end
enddef destroy
@post.destroy
redirect_to root_path
end
You probably see that we don’t have a ‘edit’ view yet…
And we also don’t have a destroy or delete button in our view…
We’ll add those things…
The links first!
%h1= @post.title
%p= @post.content= link_to "Edit", edit_post_path(@post)
= link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure you want to do this ?" }
= link_to "Home", root_path
Looks like:

Now, for the edit post view…
Create a new file under the view/posts called edit.html.haml

And let’s check it out in our server…
rails s


Cool. Things are working as they should!
Now, let’s try to update a post…

Big ol FAIL.
Back into the post controller…
We just need to add in (post_params) to our update action…
Like so:
def update
if @post.update(post_params)
redirect_to @post
else
render 'edit'
end
end
Ok, try again!

It works!
Great. Now to test the delete function. Go create a new post and then delete it.
Worked for me.
Now, let’s commit our work to Github before moving on…
git status
woah, lots of red.
git add .
and then
git commit -m "added post actions and views; new, create, update, destroy"
then
git push
We done. Moving forward.
Step 14: Looping Through Posts on the Index Page (video)
Inside the post controller, update the Index action:
def index
@posts = Post.all.order("created_at DESC")
end
Then inside the index view file…
%h1 Hi there! This is the Index Page.- @posts.each do |post|
%h2= post.title
%p
Published at
= time_ago_in_words(post.created_at)= link_to "New Post", new_post_path
This should loop through all the Posts in our database…
Let’s try it out!

Great. Design is sh!t but it’s working. Moving forward.
Actually wait. Let’s update to:
%h1 Hi there! This is the Index Page.- @posts.each do |post|
%h2= link_to post.title, post
%p
Published at
= time_ago_in_words(post.created_at)= link_to "New Post", new_post_path
We’ve made the titles links to the full post.
Ok, let’s commit to git.
git status
git add .
git commit -m "added loop to publish all posts to index page"
git push
Cool. Onward.
Step 15: Setting up Devise (video)
Check out the github page for devise. It’ll walk you through getting started. Or just follow along here :)
In our command line:
rails generate devise:install
then follow those directions…
In the config/environments/development.rb file…
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
we already have a root url…
then copy paste those notices into our application.html.erb file…
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
then skip the heroku piece…
skip the views piece…
run the model generator!
rails generate devise user
Cool. Onward. My screen looks like this:

Now, migrate that database…
rake db:migrate

Ok. Now let’s run the server and try to sign up!
rails s
Nav to http://localhost:3000/users/sign_up
And look! A sign up form. Thanks Devise!

Now to try signing myself up…

Great. See the message up top?
Cool. Onward. Git commit time.
git add .
git commit -m "set up devise"
git push
Step 16: Connect Users and Posts (video)
Back inside the controller…
def new
@post = current_user.posts.build
end
and…
def create
@post = current_user.posts.build(post_params)if @post.save
redirect_to @post
else
render 'new'
end
end
save.
Now to create an association!
inside post.rb…
class Post < ApplicationRecord
belongs_to :user
end
And, inside user.rb…
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 :posts
end
Whelp, looks like ol Mackenzie hit a snag here so we need to work backwards and add a user column to our database…. onward.
rails g migration add_user_id_to_posts user_id:integer
then,
rake db:migrate
then back inside the rails console…
rails c
and
@posts = Post.first
Check it:

We have a user:id: nil
Great. Working.
Now let’s try creating a new post and see if the new post is tied to me, the user.
So I’m starting up the server again, I’m creating a new post, and then jumping back inside the console to see if the new post is tied to a user id…

IT IS! Success.

Great. Everything is working correctly.
Now, I’m going to work backwards and assign all the old posts to ME!
Looks something like this:
@post = Post.first
@post.user_id = 1
@post.save
Then, you can do something like this to see all the posts and see which have nil user_ids…
@post = Post.all
Op, there they are…
Select one that is unassigned like so:
@post = Post.find(3)
@post.user_id = 1
@post.save
Cool. Repeat. Save. Onward.
Inside our index page, let’s display the user email alongside the posts…
%h1 Hi there! This is the Index Page.- @posts.each do |post|
%h2= link_to post.title, post
%p
Published at
= time_ago_in_words(post.created_at)
= post.user.email= link_to "New Post", new_post_path
Start up your server and see if this worked. Worked for me.
Onwards. Git commit.
git add .
git commit -m "added association between post and user"
git push
Step 17: Add Navigation (video)
Ok, inside the application.html.erb file…
Converting to HAML!
Rename to application.html.haml
And then update the file to haml syntax…
!!!
%html
%head
%title Forum
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
= javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
= csrf_meta_tags
%body
%p.notice= notice
%p.alert= alert
= yield
try it out in the localhost to see if everything is ok…
Works. Great. Onwards.
Now to create a header. Fun. HAML is annoying to me but hey, let’s just learn something new why don’t we?
Onwards.
!!!
%html
%head
%title Forum
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
= javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
= csrf_meta_tags
%body
%nav
#logo
%p David's Rails Forum
.wrapper
%p.notice= notice
%p.alert= alert
.wrapper
= yield
then inside my stylesheet, following MC here:

Update that file name to applcation.css.scss
And add:
.wrapper {
width: 60%;
max-width: 1140px;
margin: 0 auto;
}
Save. Refresh. See?
Ugh. HAML. Keep at it David. Just keep going.
Ripped the new post link out of the index page and stuck it inside the application.html.haml page…
Index page:
%h1 Hi there! This is the Index Page.- @posts.each do |post|
%h2= link_to post.title, post
%p
Published at
= time_ago_in_words(post.created_at)
= post.user.email
Application page:
!!!
%html
%head
%title David's Rails Forum
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
%header
#logo
%p David's Rails Forum
#buttons
= link_to "New Post", new_post_path.wrapper
%p.notice= notice
%p.alert= alert.wrapper
= yield
Ok geez. That was 37 minutes of following Mackenzie, and 2.5 hours of my time. Time to get up and get outside for a bit. Stretch, get some sun, that sort of thing. Back later this afternoon….
Ok! Back at it.
OK! So I’m here. And I just figured out how darn picky HAML is about your indentations and line breaks…
If your page doesn’t look like this, you might need to get really detailed about your spaces or tabs or indentations.


Little spaces / tabs joke there ^
Moving forward. More HAML. Just slogging through.
Updated fonts:
body {
font-family: 'Lato', sans-serif;
}.wrapper {
width: 60%;
max-width: 1140px;
margin: 0 auto;
}.main_header {
width: 80%;
max-width: 1140px;
margin: 0 auto;
}
And linked to the font resource:
!!!
%html
%head
%title David's Rails Forum
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
%link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css?family=Lato", type: "text/css" }
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
%header.main_header
#logo
%p David's Rails Forum
#buttons
= link_to "New Post", new_post_path.wrapper
%p.notice= notice
%p.alert= alert.wrapper
= yield
(the font came from Google fonts here)
Time to style the logo…
It’s actually pretty useful to see how MC uses Google Chrome to preview style changes. Check out the video here. If you aren’t interested, you’ll find the code below…
Application.css.scss file up to this point:
/*
* 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_tree .
*= require_self
*/body {
font-family: 'Lato', sans-serif;
}
.wrapper {
width: 60%;
max-width: 1140px;
margin: 0 auto;
}.clearfix:before, .clearfix:after {
content: " ";
display: table;
}.clearfix:after {
clear: both;
}.main_header {
width: 80%;
max-width: 1140px;
margin: 0 auto;
#logo {
float: left;
p {
text-transform: uppercase;
font-weight: 700;
letter-spacing: -1px;
font-size: 1.2rem;
}
}
#buttons {
float: right;
}
}
And the application.html.haml file:
!!!
%html
%head
%title David's Rails Forum
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
%link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css?family=Lato", type: "text/css" }
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
%header.main_header.clearfix
#logo
%p David's Rails Forum
#buttons
= link_to "New Post", new_post_path.wrapper
%p.notice= notice
%p.alert= alert.wrapper
= yield
Page looks like:

Now let’s add some colors yay.
Added a background color:
body {
font-family: 'Lato', sans-serif;
background: #EDEFF0;
}
More styling to follow…
Adding colors, updating width of the header, adding ‘normalize’, (again, you can follow along here, I’m not going to be super-detailed about documenting this styling stuff)
application.html.haml:
!!!
%html
%head
%title David's Rails Forum
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
%link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css?family=Lato", type: "text/css" }
%link{ rel: "stylesheet", href: "http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.min.css" }
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
%header.main_header.clearfix
.wrapper
#logo
%p David's Rails Forum
#buttons
= link_to "New Post", new_post_path.wrapper
%p.notice= notice
%p.alert= alert.wrapper
= yield
application.css.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, 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_tree .
*= require_self
*/body {
font-family: 'Lato', sans-serif;
background: #EDEFF0;
}
.wrapper {
width: 60%;
max-width: 1140px;
margin: 0 auto;
}.clearfix:before, .clearfix:after {
content: " ";
display: table;
}.clearfix:after {
clear: both;
}.main_header {
width: 100%;
margin: 0 auto;
background: white;
#logo {
float: left;
p {
text-transform: uppercase;
font-weight: 700;
letter-spacing: -1px;
font-size: 1.2rem;
}
}
#buttons {
float: right;
}
}
Updating the index.html.haml file now…
#posts
- @posts.each do |post|
.post
%h2.title= link_to post.title, post
%p.date
Published at
= time_ago_in_words(post.created_at)
= post.user.email
Now back inside the application.css.scss file, add:
#posts {
background: white;
padding: 2em 5%;
border-radius: .5em;
.post {
margin: 1em 0;
padding: 1em 0;
border-bottom: 1px solid #D1d1d1;
.title {
margin: 0;
a {
color: #397CAC;
text-decoration: none;
font-weight: 100;
font-size: 1.25rem;
}
}
.date {
margin-top: .25rem;
font-size: 0.9rem;
color: #B2BAC2;
}
}
}
Allllright, looking like a snack.

Next, style the New Post button…
#buttons {
float: right;
a {
line-height: 60px;
background: #397CAC;
padding: .5em 1em;
border-radius: 0.2em;
color: white;
text-decoration: none;
font-weight: 100;
}
}

Not the worst!
Moving forward.
Inside the show.html.haml file…
#post_content
%h1= @post.title
%p= @post.content= link_to "Edit", edit_post_path(@post)
= link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure you want to do this ?" }
= link_to "Home", root_path
And boom:

More style to follow.
#post_content {
background: white;
padding: 2em 5%;
border-radius: .5em;
h1 {
font-weight: 100;
font-size: 2em;
color: #397CAC;
margin-top: 0;
}
p {
color: #B2BAC2;
font-size: 0.9em;
line-height: 1.5;
font-size: 100;
}
}
Makes this junk happen:

Now we’re going to make “David’s Rails Forum” link back to the homepage…
!!!
%html
%head
%title David's Rails Forum
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
%link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css?family=Lato", type: "text/css" }
%link{ rel: "stylesheet", href: "http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.min.css" }
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
%header.main_header.clearfix
.wrapper
#logo
%p= link_to "David's Rails Forum", root_path
#buttons
= link_to "New Post", new_post_path.wrapper
%p.notice= notice
%p.alert= alert.wrapper
= yield
Line updated:
%p= link_to "David's Rails Forum", root_path
And we need to style it a bit…
Updated the following:
.main_header {
width: 100%;
margin: 0 auto;
background: white;
#logo {
float: left;
a {
text-transform: uppercase;
font-weight: 700;
letter-spacing: -1px;
font-size: 1.2rem;
text-decoration: none;
color: #397CAC;
}
}
#buttons {
float: right;
a {
line-height: 60px;
background: #397CAC;
padding: .5em 1em;
border-radius: 0.2em;
color: white;
text-decoration: none;
font-weight: 100;
}
}
}
The ENTIRE application.css.scss file looks like this right now:
/*
* 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_tree .
*= require_self
*/body {
font-family: 'Lato', sans-serif;
background: #EDEFF0;
}
.wrapper {
width: 60%;
max-width: 1140px;
margin: 0 auto;
}.clearfix:before, .clearfix:after {
content: " ";
display: table;
}.clearfix:after {
clear: both;
}.main_header {
width: 100%;
margin: 0 auto;
background: white;
#logo {
float: left;
a {
text-transform: uppercase;
font-weight: 700;
letter-spacing: -1px;
font-size: 1.2rem;
text-decoration: none;
color: #397CAC;
}
}
#buttons {
float: right;
a {
line-height: 60px;
background: #397CAC;
padding: .5em 1em;
border-radius: 0.2em;
color: white;
text-decoration: none;
font-weight: 100;
}
}
}#posts {
background: white;
padding: 2em 5%;
border-radius: .5em;
.post {
margin: 1em 0;
padding: 1em 0;
border-bottom: 1px solid #D1d1d1;
.title {
margin: 0;
a {
color: #397CAC;
text-decoration: none;
font-weight: 100;
font-size: 1.25rem;
}
}
.date {
margin-top: .25rem;
font-size: 0.9rem;
color: #B2BAC2;
}
}
}#post_content {
background: white;
padding: 2em 5%;
border-radius: .5em;
h1 {
font-weight: 100;
font-size: 2em;
color: #397CAC;
margin-top: 0;
}
p {
color: #B2BAC2;
font-size: 0.9em;
line-height: 1.5;
font-size: 100;
}
}
And the ENTIRE application.html.haml file looks like this:
!!!
%html
%head
%title David's Rails Forum
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
%link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css?family=Lato", type: "text/css" }
%link{ rel: "stylesheet", href: "http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.min.css" }
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
%header.main_header.clearfix
.wrapper
#logo
%p= link_to "David's Rails Forum", root_path
#buttons
= link_to "New Post", new_post_path.wrapper
%p.notice= notice
%p.alert= alert.wrapper
= yield
Index.html.haml file looks like:
#posts
- @posts.each do |post|
.post
%h2.title= link_to post.title, post
%p.date
Published at
= time_ago_in_words(post.created_at)
= post.user.email
And the show.html.haml file looks like:
#post_content
%h1= @post.title
%p= @post.content= link_to "Edit", edit_post_path(@post)
= link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure you want to do this ?" }
= link_to "Home", root_path
Onward…
Step 18: Users can sign in and out (video)
First, we’re going to add some links to the application template…
And you should know that when adding links, it’s useful to understand what kind of routes you have available in your application.
A link is generally in the formula of:
link_to "name of link", link_path
that “link_path” piece is a route. We can see all of our available routes in the application by running a rake route command in the terminal:
rake routes

So if I need to add a link to sign a user up, or sign a user in, I can run the command to see what the route is for a new user session or the route for a new user!
A link to sign up a new user would look like:
link_to "Sign up", new_user_registration_path
So I’ve added the links with some logic and the whole file looks like:
!!!
%html
%head
%title David's Rails Forum
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
%link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css?family=Lato", type: "text/css" }
%link{ rel: "stylesheet", href: "http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.min.css" }
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
%header.main_header.clearfix
.wrapper
#logo
%p= link_to "David's Rails Forum", root_path
#buttons
- if user_signed_in?
= link_to "New Post", new_post_path
- else
= link_to "Sign Up", new_user_registration_path
= link_to "Sign In", new_user_session_path.wrapper
%p.notice= notice
%p.alert= alert.wrapper
= yield
Testing this out in incognito mode in google chrome and we get:

Now we are going to create another user.


Great. Working. Now for a new post by the new user:

Now, we’re going to add a before action to the controller to authenticate the user…
class PostsController < ApplicationController
before_action :find_post, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
This authenticate_user!
action makes sure non-users can’t create, delete and edit content that they didn’t create, makes sure users can’t delete and edit other user’s content, and is a default feature provided by devise.
Next, we’ll create the ability to add comments!
But first, let’s push our changes to github. Go ahead and walk through the commands yourself. Use the screenshot below as a reference if you need it!

Step 19: Users can comment (video)
First, we are going to build a model for Comments:
rails g model Comment comment:text post:references user:references
Translation: “Rails, please generate a model for me called “Comment”. Each comment will be text, and each comment will belong to a post and to a user.”
Now, let’s check out the migration file:

No changes to make, so let’s:
rake db:migrate

Now we need to update our post.rb model to associate it with Comments:
class Post < ApplicationRecord
belongs_to :user
has_many :comments
end
And user.rb model:
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 :posts
has_many :comments
end
Now to create some routes for our comments!
Inside the routes.rb file:
Rails.application.routes.draw do
devise_for :users
resources :posts do
resources :comments
endroot 'posts#index'
end

Now, let’s create a Comments controller!
rails g controller Comments

And now inside of the new controller file:
class CommentsController < ApplicationController
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment].permit(:comment))if @comment.save
redirect_to post_path(@post)
else
render 'new'
end
end
end
And now let’s create some views for our comments! File names are:
_form.html.haml_comment.html.haml
And they should be saved underneath the view/comments folder…

Inside the _comment.html.haml file:
.comment
%p= comment.comment
Inside the _form.html.haml file:
= simple_form_for([@post, @post.comments.build]) do |f|
= f.input :comment
= f.submit
And now inside the posts/show.html.haml file:
#post_content
%h1= @post.title
%p= @post.content#comments
%h2= @post.comments.count
= render @post.comments%h3 Reply to thread
= render "comments/form"= link_to "Edit", edit_post_path(@post)
= link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure you want to do this ?" }
= link_to "Home", root_path
Saving all these files and now checking out the browser. Fingers crossed…
Great! Style is weak but at least it’s working :)

Ok, now we need to do a little more work before we can add a comment…
Note: Consider checking out MC troubleshooting around minute 1:10:00! It’s a great look into working through a troubling spot. Link here. Watch through minute 1:13:50!
Comments controller should look like:
class CommentsController < ApplicationController
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment].permit(:comment))
@comment.user_id = current_user.id if current_user
@comment.saveif @comment.save
redirect_to post_path(@post)
else
render 'new'
end
end
end
_comments.html.haml should look like:
.comment
%p= comment.comment
%p= comment.user.email
And now let’s test it out:

Great! It’s working. I’m sure we’ll be styling this shortly :)
Next, let’s work on editing and deleting comments…
Inside the comments controller… Add a destroy action
def destroy
@post = Post.find(params[:post_id])
@comment = @post.comments.find(params[:id])
@comment.destroy
redirect_to post_path(@post)
end
Now, inside the comment partial, _comments.html.haml:
.comment
%p= comment.comment
%p= comment.user.email
= link_to "Delete Reply", [comment.post, comment], method: :delete, data: { confirm: "Are you sure?" }
Now, let’s refresh the page and see if this mess is working.

Yep. Great. It works.
Now for editing comments… Inside the _comment.html.haml partial:
.comment
%p= comment.comment
%p= comment.user.email
= link_to "Edit", edit_post_comment_path(comment.post, comment)
= link_to "Delete Reply", [comment.post, comment], method: :delete, data: { confirm: "Are you sure?" }
And inside the comments controller:
def edit
@post = Post.find(params[:post_id])
@comment = @post.comments.find(params[:id])
enddef update
@post = Post.find(params[:post_id])
@comment = @post.comments.find(params[:id])if @comment.update(params[:comment].permit(:comment))
redirect_to post_path(@post)
else
render 'edit'
end
end

We can certainly see an edit option, but it doesn’t work quite yet:

We’re missing a template! Time to create a new view:
Create a new file: views/comments/edit.html.haml, and inside the file:
%h1 Edit Reply= simple_form_for([@post, @comment]) do |f|
= f.input :comment
= f.submit
Annnnnnnd, refresh:

Great! It worked :)
One quick small change inside the Comments partial:
.comment
%p= comment.comment
%p= comment.user.email
= link_to "Edit", edit_post_comment_path(comment.post, comment)
= link_to "Delete", [comment.post, comment], method: :delete, data: { confirm: "Are you sure?" }
updated delete link to “Delete” instead of “Delete reply”
Git time!
Step 20: Style the Comments (video)
Whole style sheet:
/*
* 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_tree .
*= require_self
*/body {
font-family: 'Lato', sans-serif;
background: #EDEFF0;
}
.wrapper {
width: 60%;
max-width: 1140px;
margin: 0 auto;
}.clearfix:before, .clearfix:after {
content: " ";
display: table;
}.clearfix:after {
clear: both;
}.main_header {
width: 100%;
margin: 0 auto;
background: white;
#logo {
float: left;
a {
text-transform: uppercase;
font-weight: 700;
letter-spacing: -1px;
font-size: 1.2rem;
text-decoration: none;
color: #397CAC;
}
}
#buttons {
float: right;
a {
line-height: 60px;
background: #397CAC;
padding: .5em 1em;
border-radius: 0.2em;
color: white;
text-decoration: none;
font-weight: 100;
}
}
}#posts {
background: white;
padding: 2em 5%;
border-radius: .5em;
.post {
margin: 1em 0;
padding: 1em 0;
border-bottom: 1px solid #D1d1d1;
.title {
margin: 0;
a {
color: #397CAC;
text-decoration: none;
font-weight: 100;
font-size: 1.25rem;
}
}
.date {
margin-top: .25rem;
font-size: 0.9rem;
color: #B2BAC2;
}
}
}#post_content {
background: white;
padding: 2em 5%;
border-radius: .5em;
h1 {
font-weight: 100;
font-size: 2em;
color: #397CAC;
margin-top: 0;
}
p {
color: #B2BAC2;
font-size: 0.9em;
line-height: 1.5;
font-size: 100;
}
#comments {
.comment {
border-bottom: 1px solid #d1d1d1;
padding-bottom: 1em;
margin-bottom: 1em;
.comment_content {
margin: 0;
padding: 0;
}
.comment_author {
color: #397CAC;
margin-top: 0.5rem;
font-size: 0.6rem;
font-weight: 700;
}
}
}
}
And now, inside the comment partial:
.comment
%p.comment_content= comment.comment
%p.comment_author= comment.user.email
= link_to "Edit", edit_post_comment_path(comment.post, comment)
= link_to "Delete", [comment.post, comment], method: :delete, data: { confirm: "Are you sure?" }
Looking better:

Now to tweak the buttons!
Tweak, tweak, tweak…

you know…. just sorta kinda make things like a little nicer.
Application.css.scss up to this point:
/*
* 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_tree .
*= require_self
*/body {
font-family: 'Lato', sans-serif;
background: #EDEFF0;
}
.wrapper {
width: 60%;
max-width: 1140px;
margin: 0 auto;
}.clearfix:before, .clearfix:after {
content: " ";
display: table;
}.clearfix:after {
clear: both;
}.main_header {
width: 100%;
margin: 0 auto;
background: white;
#logo {
float: left;
a {
text-transform: uppercase;
font-weight: 700;
letter-spacing: -1px;
font-size: 1.2rem;
text-decoration: none;
color: #397CAC;
}
}
#buttons {
float: right;
a {
line-height: 60px;
background: #397CAC;
padding: .5em 1em;
border-radius: 0.2em;
color: white;
text-decoration: none;
font-weight: 100;
}
}
}#posts {
background: white;
padding: 2em 5%;
border-radius: .5em;
.post {
margin: 1em 0;
padding: 1em 0;
border-bottom: 1px solid #D1d1d1;
.title {
margin: 0;
a {
color: #397CAC;
text-decoration: none;
font-weight: 100;
font-size: 1.25rem;
}
}
.date {
margin-top: .25rem;
font-size: 0.9rem;
color: #B2BAC2;
}
}
}#post_content {
background: white;
padding: 2em 5%;
border-radius: .5em;
h1 {
font-weight: 100;
font-size: 2em;
color: #397CAC;
margin-top: 0;
}
p {
color: #B2BAC2;
font-size: 0.9em;
line-height: 1.5;
font-size: 100;
}
#comments {
.comment {
border-bottom: 1px solid #d1d1d1;
padding-bottom: 1em;
margin-bottom: 1em;
.content {
width: 75%;
float: left;
}
.buttons {
width: 25%;
float: left;
font-size: 0.8em;
text-align: right;
padding-top: 1.5em;
a {
color: #397CAC;
border: 1px solid #397CAC;
padding: .5em 1em;
border-radius: 0.2em;
text-decoration: none;
margin-right: 2%;
}
}
.comment_content {
margin: 0;
padding: 0;
}
.comment_author {
color: #397CAC;
margin-top: 0.5rem;
font-size: 0.6rem;
font-weight: 700;
}
}
}
}
And the _comment.html.haml partial:
.comment.clearfix
.content
%p.comment_content= comment.comment
%p.comment_author= comment.user.email
.buttons
= link_to "Edit", edit_post_comment_path(comment.post, comment)
= link_to "Delete", [comment.post, comment], method: :delete, data: { confirm: "Are you sure?" }
Looking like:

How about that comment form though? We’ll tackle that soon.
Inside the show.html.haml file:
#post_content
%h1= @post.title
%p= @post.content#comments
%h2
= @post.comments.count
Comments
= render @post.comments%h3 Reply to thread
= render "comments/form"= link_to "Edit", edit_post_path(@post)
= link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure you want to do this ?" }
= link_to "Home", root_path
Very small change…
But now instead of “2” we see “2 Comments”

NOW, let’s style the comment form…
Following along with MC, I’ve updated the show.html.haml page to:
#post_content
%h1= @post.title
%p= @post.content#comments
%h2
= @post.comments.count
Comments
= render @post.comments%h3 Reply to thread
= render "comments/form"%br/
%hr/
%br/= link_to "Edit", edit_post_path(@post), class: "button"
= link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure you want to do this ?" }, class: "button"
And I’ve updated the application.css.scss file to:
/*
* 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_tree .
*= require_self
*/body {
font-family: 'Lato', sans-serif;
background: #EDEFF0;
}
.wrapper {
width: 60%;
max-width: 1140px;
margin: 0 auto;
}.clearfix:before, .clearfix:after {
content: " ";
display: table;
}.clearfix:after {
clear: both;
}.main_header {
width: 100%;
margin: 0 auto;
background: white;
#logo {
float: left;
a {
text-transform: uppercase;
font-weight: 700;
letter-spacing: -1px;
font-size: 1.2rem;
text-decoration: none;
color: #397CAC;
}
}
#buttons {
float: right;
a {
line-height: 60px;
background: #397CAC;
padding: .5em 1em;
border-radius: 0.2em;
color: white;
text-decoration: none;
font-weight: 100;
}
}
}#posts {
background: white;
padding: 2em 5%;
border-radius: .5em;
.post {
margin: 1em 0;
padding: 1em 0;
border-bottom: 1px solid #D1d1d1;
.title {
margin: 0;
a {
color: #397CAC;
text-decoration: none;
font-weight: 100;
font-size: 1.25rem;
}
}
.date {
margin-top: .25rem;
font-size: 0.9rem;
color: #B2BAC2;
}
}
}.button {
color: #397CAC;
border: 1px solid #397CAC;
padding: .5em 1em;
border-radius: 0.2em;
text-decoration: none;
margin-right: 2%;
}#post_content {
background: white;
padding: 2em 5%;
border-radius: .5em;
h1 {
font-weight: 100;
font-size: 2em;
color: #397CAC;
margin-top: 0;
}
p {
color: #B2BAC2;
font-size: 0.9em;
line-height: 1.5;
font-size: 100;
}
#comments {
.comment {
border-bottom: 1px solid #d1d1d1;
padding-bottom: 1em;
margin-bottom: 1em;
.content {
width: 75%;
float: left;
}
.buttons {
width: 25%;
float: left;
font-size: 0.8em;
text-align: right;
padding-top: 1.5em;
a {
color: #397CAC;
border: 1px solid #397CAC;
padding: .5em 1em;
border-radius: 0.2em;
text-decoration: none;
margin-right: 2%;
}
}
.comment_content {
margin: 0;
padding: 0;
}
.comment_author {
color: #397CAC;
margin-top: 0.5rem;
font-size: 0.6rem;
font-weight: 700;
}
}
input[type="submit"] {
background: #397CAC;
border: none;
color: white;
font-weight: 100;
padding: .5em 1em;
border-radius: .2em;
}
textarea {
width: 100%;
min-height: 200px;
border: 1px solid #d1d1d1;
border-radius: .2em;
margin: 1em 0;
}
}
}
Great! Looking like:

Cool. I think we’re done here.
Yep, we’re definitely done here.
There’s still a few tings to do on the style side, but we’ve built out a great Rails app and we’re done with the heavy lifting. MC is signing out and so am I.

My github repository for this project is here.
Hope you enjoyed the tutorial!
Peace.
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