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.
Your questions are private and won't be used to evaluate your performance.
Your questions are private and won't be used to evaluate your performance.