Currently in the ApplicationController
, we first find a post from the database, then we need to get a collection of comments that are associated with the post. We're getting that from directly executing SQL in the controller action, which is not the ideal solution.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
def show_post
post = Post.find(params['id'])
comments = connection.execute('SELECT * FROM comments WHERE comments.post_id = ?', params['id'])
render 'application/show_post', locals: { post: post, comments: comments }
end
# ...
end
Since the SQL command needs a post id to find the comments, we'll move the command to the Post
model.
app/models/post.rb
class Post
# ...
def comments
comment_hashes = connection.execute 'SELECT * FROM comments WHERE comments.post_id = ?', id
comment_hashes.map do |comment_hash|
Comment.new(comment_hash)
end
end
# ...
end
Now we can change the code in the controller:
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
def show_post
post = Post.find(params['id'])
render 'application/show_post', locals: { post: post }
end
# ...
end
And make adjustments on the view:
app/views/application/show_post.html.erb
<html>
<body>
<div class="post">
<!-- display post -->
<br /> <br />
<div class="comments">
<h3>Comments:</h3>
<% post.comments.each_with_index do |comment, index| %>
<p class="comment">
<small class="comment_meta">
<span class="comment_author">#<%= index %> by <%= comment.author %> -</span>
<em class="comment_created_at"><%= comment.created_at %></em>
</small>
<p class="comment_body"><%= comment.body %></p>
</p>
<hr />
<% end %>
</div>
</div>
<!-- ... -->
</body>
</html>
Moving on, we'll provide users with a way of adding a comment to a post.
And because it's a CRUD action, creating a comment will very much resemble creating a post. Below you'll find a <form>
to create a new comment placed in show_post
, the route it submits to, and the create_comment
action that route points to:
<!-- app/views/application/show_post.html.erb -->
<html>
<body>
<div class="post">
<div class="comments">
<hr />
<% comments.each_with_index do |comment| %>
<!-- display each comment -->
<% end %>
<!-- new comment form -->
<form method="post" action="/create_comment_for_post/<%= post.id %>">
<label for="body">Comment</label>
<textarea id="body" name="body"></textarea>
<br /> <br />
<label for="author">Name</label>
<input id="author" name="author" type="text" />
<br /> <br />
<input type="submit" value="Add Comment" />
</form>
<hr />
</div>
</div>
<br />
<a href="/list_posts">Back to Posts</a>
</body>
</html>
### config/routes.rb ###
Rails.application.routes.draw do
# ...
post '/create_comment_for_post/:post_id' => 'application#create_comment'
end
### app/controllers/application_controller.rb ###
class ApplicationController < ActionController::Base
# ...
def create_comment
insert_comment_query = <<-SQL
INSERT INTO comments (body, author, post_id, created_at)
VALUES (?, ?, ?, ?)
SQL
connection.execute insert_comment_query,
params['body'],
params['author'],
params['post_id'],
Date.current.to_s
redirect_to "/show_post/#{params['post_id']}"
end
# ...
end
The primary difference in creating a comment, as opposed to creating a post, is our concern for post_id
. When creating a comment, we'll need to be sure to associate it with the right post. This is essentially done in three steps.
First, we point the <form>
action
to a path that includes post.id
:
<form method="post" action="/create_comment_for_post/<%= post.id %>">
Then we capture this post_id
in the route:
post '/create_comment_for_post/:post_id' => 'application#create_comment'
And finally, we make sure to set the post_id
for the row in the comments
table:
connection.execute insert_comment_query,
params['body'],
params['author'],
params['post_id'],
Date.current.to_s
This way, when a comment is created through this process, it is always associated with the post identified by the ID in the URL. At the end of create_comment
, we just redirect back to show_post
, which will end up rendering the post again, now with a new comment.
Similar to how we moved the SQL command to retrieve comments associated to a post to the Post
model, we'll do the same to the SQL command to create a comment associated to a post.
### app/controllers/application_controller.rb ###
class ApplicationController < ActionController::Base
# ...
def create_comment
post = Post.find(params['post_id'])
post.create_comment('body' => params['body'], 'author' => params['author'])
redirect_to "/show_post/#{params['post_id']}"
end
# ...
end
Now the Post
model will look like:
class Post
# ...
def create_comment(attributes)
comment = Comment.new(attributes.merge!('post_id' => id))
comment.save
end
# ...
end
Then the Comment
model:
app/models/comment.rb
class Comment
# ...
def initialize(attributes={})
@id = attributes['id'] if new_record?
@body = attributes['body']
@author = attributes['author']
@post_id = attributes['post_id']
@created_at ||= attributes['created_at']
end
def save
if new_record?
insert
else
# update # ...not yet defined
end
end
def insert
insert_comment_query = <<-SQL
INSERT INTO comments (body, author, post_id, created_at)
VALUES (?, ?, ?, ?)
SQL
connection.execute insert_comment_query,
@body,
@author,
@post_id,
Date.current.to_s
end
def self.connection
db_connection = SQLite3::Database.new 'db/development.sqlite3'
db_connection.results_as_hash = true
db_connection
end
def connection
self.class.connection
end
end
Notice that we have nearly identical database connections in Comment as we do in Post. We'll be extracting these commonalities later on.