Ruby on Rails model which process comments on articles

Generating a Model

We intend to use the same generator that we used earlier when creating the Article model. This time we will create a Comment model containing a link to the article. Run the following command in a terminal:

$ bin/rails generate model Comment commenter:string body:text article:references

This command generates four files:

File Destination

db/migrate/20140120201010_create_comments.rb Migrate to create the comments table in your database (your filename will include a different timestamp)

app/models/comment.rb Model Comment

test/models/comment_test.rb Framework for testing the comment model

test/fixtures/comments.yml Sample comments for use in testing

First, let’s take a look at app/models/comment.rb:

class Comment < ApplicationRecord

  belongs_to :article

end

This is very similar to the Article model we saw earlier. The difference is in the belongs_to :article line, which establishes an Active Record link. You will learn about connections in the next section of the guide.

The (:references) keyword used in the bash command is a special data type for models. It creates a new column in your database with the name of the rendered model with an _id appended, which can contain numeric values. To better understand, analyze the db/schema.rb file after the migration is done.

In addition to the model, Rails also made a migration to create the corresponding database table:

class CreateComments < ActiveRecord::Migration[5.0]

  def change

    create_table :comments do |t|

      t.string :comment

      t.text :body

      t.references :article, foreign_key: true

      t.timestamps

    end

  end

end

The t.references line creates a numeric column named article_id, an index on it, and a foreign key constraint pointing to the id column of the articles table. Next, we run the migration:

$bin/railsdb:migrate

Rails is smart enough to only run those migrations that haven’t already been run against the current database, in our case you’ll see:

== CreateComments: migrating =========================================== ====

— create_table(:comments)

   -> 0.0115s

== CreateComments: migrated (0.0119s) ======================================

Linking Models

Active Record links allow you to easily declare relationships between two models. In the case of comments and articles, you can describe the relationship as follows:

Each comment belongs to one article.

One article can have many comments.

In fact, it’s very close to the syntax that Rails uses to declare this relationship. You have already seen the line of code in the Comment model (app/models/comment.rb) that makes each comment belong to an article:

class Comment < ApplicationRecord

  belongs_to :article

end

You need to edit app/models/article.rb to add the other side of the link:

class Article < ApplicationRecord

  has_many :comments

  validates :title, presence: true,

                    length: { minimum: 5 }

  […]

end

These two announcements automatically make a large number of features available. For example, if you have an @article instance variable containing an article, you can get all the comments that belong to that article in an array by calling @article.comments.

TIP: For more information about Active Record Links, see the Active Record Links manual.

Adding a route for comments

As with the welcome controller, we need to add a route so that Rails knows which address we want to go to in order to see the comments. Open the config/routes.rb file again and edit it as follows:

resources :articles do

  resources :comments

end

This will create comments as a nested resource in articles. This is the other side of capturing the hierarchical relationship that exists between articles and comments.

TIP: For more information about routing, see the Routing in Rails guide.

Generate a controller

With the model in place, let’s turn our attention to creating the appropriate controller. Again we will use the same generator that we used before:

$ bin/rails generate controller

Five files and an empty directory will be created:

File/Directory Purpose

app/controllers/comments_controller.rb Comments Controller

app/views/comments/ Controller views are stored here

test/controllers/comments_controller_test.rb Controller test

app/helpers/comments_helper.rb Helper for views

app/assets/javascripts/comments.coffee CoffeeScript for controller

app/assets/stylesheets/comments.scss Cascading style sheet for the controller

As with any other blog, our readers will create their comments immediately after reading the article, and after adding a comment, they will be directed back to the article display page and see that their comment has already been reflected. In this regard, our CommentsController serves as a means of creating comments and removing spam, if any.

This is a bit more complicated than what you saw in the article controller. This is a side effect of the attachment you set up. Each request for a comment keeps track of the article to which the comment is attached, so we first resolve the article retrieval issue by calling find on the Article model.

In addition, the code takes advantage of some of the methods available for relationships. We use the create method on @article.comments to create and save a comment. This automatically links the comment so that it belongs to a specific article.

Once we’ve created a new comment, we take the user back to the original article using the article_path(@article) helper. As we’ve seen, it calls the show action on the ArticlesController, which in turn renders the show.html.erb template. This is where we want to display the comments

You can now add articles and comments to your blog and display them in the right places.

Article with comments

Refactoring

Now that we have our articles and comments working, let’s take a look at the app/views/articles/show.html.erb template. It became long and uncomfortable. Let’s use partials to unload it.

Rendering Collections of Partials

First, let’s make a comment partial that shows all the comments for the article. Create a file app/views/comments/_comment.html.erb and put the following in it:

<p>

  <strong>Commenter:</strong>

  <%= comment.commenter %>

</p>

<p>

  <strong>Comment:</strong>

  <%= comment.body %>

</p>

You can then change app/views/articles/show.html.erb

This will now render the app/views/comments/_comment.html.erb partial once for each comment in the @article.comments collection. Since the render method iterates over the @article.comments collection, it assigns each comment to a local variable named like the partial, in our case comment, which is available to us in the partial for rendering.

Partial Shape Rendering

Let’s also move the new comment section to our partial. Again, create a file app/views/comments/_form.html.erb,

The second render just defines the template for the partial we want to render, comments/form. Rails is smart enough to substitute an underscore in this line and understand that you wanted to render the _form.html.erb file in the app/views/comments directory.

The @article object is available in any partials rendered in the view because we have defined it as an instance variable.

Deleting comments

Another important feature of the blog is the ability to remove spam. To do this, we need to insert some link in the view and a destroy action in the CommentsController.

Therefore, we first add a delete link to the app/views/comments/_comment.html.erb partial:

Clicking this new “Destroy Comment” link will trigger a DELETE /articles/:article_id/comments/:id in our CommentsController which will then be used to find the comment we want to delete, so let’s add a destroy action to our controller (app/controllers/ comments_controller.rb):

The destroy action will find the article we’re viewing, find the comment in the @article.comments collection, and then remove it from the database and bring us back to the article view.

Deleting related objects

If you delete an article, the comments associated with it must also be deleted, otherwise they will just take up space in the database. Rails allows you to use the dependent option on the link to achieve this. Edit the Article model, app/models/article.rb, as follows:

class Article < ApplicationRecord

  has_many :comments, dependent: :destroy

  validates :title, presence: true,

                    length: { minimum: 5 }

  […]

end

Leave a Reply

Your email address will not be published.