Let's Build: With Ruby on Rails - A Blog with Comments
Tags: rails, ruby, tutorial
Published: 2019-01-24 05:41:32

---
title: Let's Build: With Ruby on Rails - A Blog with Comments
published: true
description: A beginner tutorial for those looking to learn more about Ruby on Rails
tags: rubyonrails, rails, ruby, tutorial
cover_image: https://thepracticaldev.s3.amazonaws.com/i/8ato2uhhpfxpz65gixzx.jpg
---


Building a blog with comments using Ruby on Rails is a foundational exercise I went through to learn more about the framework. Working together, both Ruby and Rails lend us a hand to generate a fairly simple MVC pattern built on top of a CRUD approach when working with dynamic data. 

### Watch the entire build
{% youtube XEkJ3vZb6-Q %}


### Kicking things off with a blog

To easily demonstrate the principles of working with Ruby on Rails I chose to build a basic blog. Each blog post will be able to be created, read, edited, and deleted. There will also be comments associated with each individual blog post. Comments will be able to be created and deleted.

With Ruby on Rails, the possibilities are pretty endless in terms of what you can build. I'm sure new features and improvements to our blog are easy to spot as I guide you through the process of building it. My goal was to ease newcomers into the conventions, methods, and code patterns that helped me best when I first dove in. I hope you too can benefit.

Ultimately, the point of this tutorial and video is to help anyone new to the framework understand how it operates as well as the necessary conventions required to create a blog using Ruby on Rails. I touch on things such as routing, controllers, views, models, migrations, relations, and more. For this project, we make use of a few `gems` of which help make my life a bit easier as I build out applications. You can find out more about those below.

### Gems used in the project

- [Better Errors](https://rubygems.org/gems/better_errors) - Easier on the eyes when it comes to errors.

- [Bulma](https://github.com/joshuajansen/bulma-rails) - Most of the time I would roll my own CSS or just use bits of a framework. I'm a big fan of Bulma so we will be using it a lot throughout this series.

- [Guard](https://github.com/guard/guard) - This gem is useful for live reloading our `scss`, `js`, `css`, and `erb` files, although it's capable of much more! *Guard is required for the Guard LiveReload gem to work*

  Add the following within the development space in the `Gemfile`. Make sure to run `bundle` and restart your server (covered in the video).

<div class="highlight">
  <pre class="highlight ruby"><code>
  group :development do
    # Guard is a command line tool to easily handle events on file system modifications.
    gem 'guard', '~&gt; 2.14', '&gt;= 2.14.1'
  end
 </code></pre>
</div>

- [Guard LiveReload](https://github.com/guard/guard-livereload) - This gem depends on the Guard gem. I use this to automatically reload the browser when Guard senses changes within the code base.

  1. Download the [livereload browser extension](http://livereload.com/extensions/) for your browser. 
  2. Add the following within the development space of the `Gemfile`. Make sure to run `bundle` and restart your server.

<div class="highlight">
  <pre class="highlight ruby"><code>
  group :development do
    # reload the browser after changes to assets/helpers/tests 
    gem 'guard-livereload', '~&gt; 2.5', '&gt;= 2.5.2', require: false
  end
  </code></pre>
</div>

  1. Run `guard init livereload`

  2. Be sure to comment out the following block in the `Guardfile if it gets generated for your project.

     <pre class="language-ruby"><code>
     # comment this whole block out as we won't be making use if minitest
     # guard :minitest do
     # ....
     # end
     </code></pre>

  3. **Restart your server** once more for good measure. Run:

     <pre class="language-bash"><code>bundle exec guard</code></pre>

      to start the "watching" process within your project directory. We use `bundle exec` as a prefix here so guard has access to all of our dependences in the project. ​

  4. Make sure your browser extension is active when navigating to your app. If your console reads back something similar to the following, then you are in good shape.

    <pre class="language-bash"><code>
     00:00:00 - INFO - LiveReload is waiting for a browser to connect.
     00:00:00 - INFO - Guard is now watching at '/path/to/your/project/'
     [1] guard(main)&gt; 00:00:00 - INFO - Browser connected.
    </code></pre>

- [Simple Form](https://github.com/plataformatec/simple_form) - For simpler forms!

  Our final post form partial is as follows. Here I add Bulma specific classes to get Simple Form to play nice with the CSS framework. If you use Simple Form with [Bootstrap](https://getbootstrap.com) or [Foundation](https://foundation.zurb.com/sites/download.html/) you likely need even less markup than this. 

<div class="highlight">
<pre class="highlight html"><code>
  &lt;div class="section"&gt;
  &lt;%= simple_form_for @post do |f| %&gt;
    &lt;div class="field"&gt;
      &lt;div class="control"&gt;
        &lt;%= f.input :title, input_html: { class: 'input' }, wrapper: false, label_html: { class: 'label' } %&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="field"&gt;
      &lt;div class="control"&gt;
        &lt;%= f.input :content, input_html: { class: 'textarea' }, wrapper: false, label_html: { class: 'label' }  %&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;%= f.button :submit, 'Create new post', class: "button is-primary" %&gt;
  &lt;% end %&gt;
  &lt;/div&gt;
</code></pre>
</div>
  ​

### The Post Controller

The Post controller helps handle our actions when we create, edit, show, update and delete posts on the blog. 

**Creating a Post Controller (see video)**

1. Run `rails g controller posts`

2. Update routes file in `app/config/routes.rb` to use `resources :posts` and also add `root: "posts#index"`

3. Create `index` action on your  `posts_controller.rb` file and `index.html.erb` view inside `app/views/posts/`

4. Repeat all CRUD actions with a final controller as follows (follow along on the video for a complete explanation)

<pre><code>
class PostsController < ApplicationController

       def index
           @posts = Post.all.order("created_at DESC")
       end

       def new
           @post = Post.new
       end

       def create
           @post = Post.new(post_params)

           if @post.save
               redirect_to @post
           else
               render 'new'
           end
       end

       def show
           @post = Post.find(params[:id])
       end

       def update
           @post = Post.find(params[:id])

           if @post.update(post_params)
               redirect_to @post
           else
               render 'edit'
           end
       end

       def edit
           @post = Post.find(params[:id])
       end

       def destroy
           @post = Post.find(params[:id])
           @post.destroy

           redirect_to posts_path

       end

       private

       def post_params
           params.require(:post).permit(:title, :content)
       end

   end
</code></pre>

### The Post Model

1. Our app depends on a `Post` model of which we can create by running 

<pre class="language-bash"><code>rails g model Post title:string content:text</code></pre>

The model is responsible for the data of our post and interacting with the database. I've declared two new record types which we will make use of. `title` is of the type `string` and `content` is of the type `text`. You can name these whatever you wish but it makes sense to describe what they are for the specific data model in mention. You can add more data types at any time with `migrations`. It's best to start with the basics and later add new `migrations` as you go.

1. After generating the `Post` modal you will need to run `rails db:migrate` to migrate those new records into the database. After doing so you should now be able to interact with those specific records.



### The Comments Controller

Similar to our posts controller we need to generate one for comments.

<pre class="language-bash"><code>rails g controller comments</code></pre>

We only want the ability to create and delete comments associated with a post. To do this we need our controller to interact with both the Post model and Comment model (still need to generate this).

<pre><code>
class CommentsController < ApplicationController

    def create
        @post = Post.find(params[:post_id])
         @comment = @post.comments.create(params[:comment].permit(:name, :comment))
        redirect_to post_path(@post)    
    end

    def destroy
        @post = Post.find(params[:post_id])
        @comment = @post.comments.find(params[:id])
        @comment.destroy
        redirect_to post_path(@post)
    end
  
end
</code></pre>


### The Comment Model

To generate a comment model we can run:

<pre class="language-bash"><code>rails g model Comment name:string comment:text</code></pre>

This creates a new Comment table with a named row as a `string` datatype and a comment row as a `text ` datatype. 

After generating the model you will need to run `rails db:migrate` to migrate the new rows into the database table.

## Data Relations

Our blog posts need a way to talk to our comments as comments should only belong to the blog post they were created on. To do this we need to set up some relations between both the `Post` model and the `Comment` model.

Modify the `Post` model to include the following

<pre><code>
# found in app/models/post.rb

class Post < ApplicationRecord
    has_many :comments, dependent: :destroy 
      # dependent: :destroy means the comments related 
      # to the specific post in mention get deleted if the post does.
end
</code></pre>

Modify the `Comment` model to include the following:

<pre><code>
# found in app/models/comment.rb

class Comment < ApplicationRecord
    belongs_to :post
end
</code></pre>

With this relationship in place, our application is set up to communicate between the post and comment models. 

### Associating an id

For the comments to work, we need an id added to the database table that comes from the post. To do this we need to run a migration that added another row of which will tie the relationship together. 

Run the following migration:

<pre class="language-bash"><code>rails g migration AddPostIdToComments</code></pre>

Then navigate to `app/db/migrate/XXXXXXXXXXXX_add_post_id_to_comments.rb` and add the following code:

<pre><code>
class AddPostIdToComments < ActiveRecord::Migration[5.1]
  def change
      add_column :comments, :post_id, :integer
  end
end
</code></pre>

Here we are creating a new row on the comments column called `post_id` of the type `integer`.  Run `rails db:migrate` to migrate that new migration.

Now at this point, we can create comments of where the  `post_id`  on the `Comment` model matches the `id` of the `Post` model.



## Views

The views are pretty straightforward in the sense of a blog. We have a basic layout file that acts as our "master" file of sorts. You can find the `application.htm.erb` in `app/views/layouts/`. Here I've added some markup that comes from the Bulma framework as well as some navigational links to help get around the blog easier.

The final `application.html.erb` file is below:

<pre class="language-markup"><code>
&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;DemoBlog&lt;/title&gt;
    &lt;%= csrf_meta_tags %&gt;

    &lt;%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %&gt;
    &lt;%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %&gt;
  &lt;/head&gt;

  &lt;body&gt;
      &lt;section class="hero is-primary is-medium"&gt;
      &lt;!-- Hero head: will stick at the top --&gt;
      &lt;div class="hero-head"&gt;
        &lt;nav class="navbar"&gt;
          &lt;div class="container"&gt;
            &lt;div class="navbar-brand"&gt;
              &lt;%= link_to 'Demo Blog', root_path, class: "navbar-item" %&gt;
              &lt;span class="navbar-burger burger" data-target="navbarMenuHeroA"&gt;
                &lt;span&gt;&lt;/span&gt;
                &lt;span&gt;&lt;/span&gt;
                &lt;span&gt;&lt;/span&gt;
              &lt;/span&gt;
            &lt;/div&gt;
            &lt;div id="navbarMenuHeroA" class="navbar-menu"&gt;
              &lt;div class="navbar-end"&gt;
                &lt;%= link_to "Create New Post", new_post_path, class:"navbar-item" %&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/nav&gt;
      &lt;/div&gt;

      &lt;!-- Hero content: will be in the middle --&gt;
      &lt;div class="hero-body"&gt;
        &lt;div class="container has-text-centered"&gt;
          &lt;h1 class="title"&gt;
            &lt;%= yield :page_title %&gt;
          &lt;/h1&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/section&gt;
    &lt;%= yield %&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>



#### Posts Views

Our posts views folder is also straightforward. We have a new file for each route within our application. Kicking things off is the index page:

Find this code in `app/views/posts/index.html.erb`

<pre class="language-markup"><code>
&lt;% content_for :page_title,  "Index" %&gt;

&lt;div class="section"&gt;
    &lt;div class="container"&gt;
        &lt;% @posts.each do |post| %&gt;
            &lt;div class="card"&gt;
          &lt;div class="card-content"&gt;
            &lt;div class="media"&gt;
              &lt;div class="media-content"&gt;
                &lt;p class="title is-4"&gt;&lt;%= link_to post.title, post  %&gt;&lt;/p&gt;
              &lt;/div&gt;
            &lt;/div&gt;
            &lt;div class="content"&gt;
                 &lt;%= post.content %&gt;
            &lt;/div&gt;
            &lt;div class="comment-count"&gt;
                &lt;span class="tag is-rounded"&gt;&lt;%= post.comments.count %&gt; comments&lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;% end %&gt;
    &lt;/div&gt;
&lt;/div&gt;</code></pre>

We render a partial which gets used to but the `edit.html.erb` and `new.html.erb` pages within the `posts` views folder.

The `new.html.erb` page found in `app/views/posts/new.html.erb`. 

<pre class="language-markup"><code>
&lt;% content_for :page_title, "Create a new post" %&gt;
&lt;%= render 'form' %&gt;
</code></pre>

The `edit.html.erb` page found in `app/views/posts/edit.html.erb`

<pre class="language-markup"><code>
&lt;% content_for :page_title, "Edit Post" %&gt;
&lt;%= render 'form' %&gt;
</code></pre>

And of course the `_form.html.erb` partial found in `app/views/posts/_form.html.erb`

<pre class="language-markup"><code>
&lt;div class="section"&gt;
&lt;%= simple_form_for @post do |f| %&gt;
  &lt;div class="field"&gt;
    &lt;div class="control"&gt;
      &lt;%= f.input :title, input_html: { class: 'input' }, wrapper: false, label_html: { class: 'label' } %&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="field"&gt;
    &lt;div class="control"&gt;
      &lt;%= f.input :content, input_html: { class: 'textarea' }, wrapper: false, label_html: { class: 'label' }  %&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;%= f.button :submit, 'Create new post', class: "button is-primary" %&gt;
&lt;% end %&gt;
&lt;/div&gt;
</code></pre>

 The and finally the `show.html.erb` file found in `app/views/posts/show.html.erb`

<pre class="language-markup"><code>
&lt;% content_for :page_title, @post.title %&gt;

&lt;section class="section"&gt;
    &lt;div class="container"&gt;
        &lt;nav class="level"&gt;
          &lt;!-- Left side --&gt;
          &lt;div class="level-left"&gt;
            &lt;p class="level-item"&gt;
                &lt;strong&gt;Actions&lt;/strong&gt;
            &lt;/p&gt;
          &lt;/div&gt;
          &lt;!-- Right side --&gt;
          &lt;div class="level-right"&gt;
              &lt;p class="level-item"&gt;
                &lt;%= link_to "Edit", edit_post_path(@post), class:"button" %&gt;
              &lt;/p&gt;
              &lt;p class="level-item"&gt;
                &lt;%= link_to "Delete", post_path(@post), method: :delete, data: { confirm: "Are you sure?" }, class:"button is-danger" %&gt;
                &lt;/p&gt;
          &lt;/div&gt;
        &lt;/nav&gt;
        &lt;hr/&gt;

        &lt;div class="content"&gt;
            &lt;%= @post.content %&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/section&gt;

&lt;section class="section comments"&gt;
    &lt;div class="container"&gt;
        &lt;h2 class="subtitle is-5"&gt;&lt;strong&gt;&lt;%= @post.comments.count %&gt;&lt;/strong&gt; Comments&lt;/h2&gt;
        &lt;%= render @post.comments %&gt;
        &lt;div class="comment-form"&gt;
            &lt;hr /&gt;
            &lt;h3 class="subtitle is-3"&gt;Leave a reply&lt;/h3&gt;
             &lt;%= render 'comments/form' %&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/section&gt;
</code></pre>



#### Comments Views

Most of our comment views are nested inside the show views. We do create a comment partial as well as a comment form.

The comment partial (`_comment.html.erb`) found in `app/views/comments/` is what each comment contains when authored. Below is the final code of that partial.

<pre class="language-markup"><code>
&lt;div class="box"&gt;
  &lt;article class="media"&gt;
    &lt;div class="media-content"&gt;
      &lt;div class="content"&gt;
        &lt;p&gt;
          &lt;strong&gt;&lt;%= comment.name %&gt;:&lt;/strong&gt;
          &lt;%= comment.comment %&gt;
        &lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
     &lt;%= link_to 'Delete', [comment.post, comment],
                  method: :delete, class: "button is-danger", data: { confirm: 'Are you sure?' } %&gt;
  &lt;/article&gt;
&lt;/div&gt;
</code></pre>

And the comment `_form.html.erb` gets embedded on the show page of our `Posts` show page. 

<pre class="language-markup"><code>
&lt;%= simple_form_for([@post, @post.comments.build]) do |f| %&gt;
&lt;!--
collection.build(attributes = {}, …) Returns one or more new objects of the collection type that have been instantiated with attributes and linked to this object through a foreign key, but have not yet been saved. Note: This only works if an associated object already exists, not if it‘s nil!
--&gt;
&lt;div class="field"&gt;
  &lt;div class="control"&gt;
    &lt;%= f.input :name, input_html: { class: 'input' }, wrapper: false, label_html: { class: 'label' } %&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;div class="field"&gt;
  &lt;div class="control"&gt;
    &lt;%= f.input :comment, input_html: { class: 'textarea' }, wrapper: false, label_html: { class: 'label' }  %&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;%= f.button :submit, 'Leave a reply', class: "button is-primary" %&gt;
&lt;% end %&gt;
</code></pre>


## Rounding out

If you made it through the video you got a good look at what it takes to build a basic blog with comments using Ruby on Rails. I invite you to [download the source code](https://github.com/justalever/demo_blog_rails) to see the final result as shown in the video and to also use as you troubleshoot your way on your own projects. Rails is good about telling you what is wrong using errors when in development. Sometimes the errors are obvious whereas others they are not. Googling the error message often lead me to answers. Being that Ruby on Rails is a big convention-based framework, most people that run into errors find the answers they need without too much fuss. There are definite perks to have an opinionated stack!

I hope you enjoyed this tutorial and video. Up next I plan to build a clone of a popular application which will start to focus more on user roles, authentication, security, and more.  If you enjoyed this please let me know in the comments. I'm also always happy to help troubleshoot any errors you find along the way if you follow along!

#### Download the source code

{% github https://github.com/justalever/demo_blog_rails %} 

#### The Series So Far

+ [01 - Introduction](https://web-crunch.com/lets-build-with-ruby-on-rails-introduction/)
+ [02 - Installation](https://web-crunch.com/lets-build-with-ruby-on-rails-installation/)
+ [03 - Build a blog with comments](https://web-crunch.com/lets-build-with-ruby-on-rails-blog-with-comments/) - You are here
 

## Shameless plugs 

If you liked this post, I have many more builds on [YouTube](https://youtube.com/c/webcrunch) and my [blog](https://web-crunch.com). I plan to start authoring more here as well. Want more content like this in your inbox? [Subscribe to my newsletter](https://web-crunch.com/subcribe) and get it automatically.

<a href="https://hellorails.io" target="_blank">
![](https://thepracticaldev.s3.amazonaws.com/i/wkb5yyst7pxa1s16kr2u.jpg)
</a>
☝ Want to learn Ruby on Rails from the ground up? Check out my upcoming course called [Hello Rails](https://hellorails.io).