- Read Tutorial
- Watch Guide Video
Now that we have our comment ready and we know that we have our relationship set up properly between comments, user and blogpost. We can cross some things off the list. We integrated the Redis
database by installing the gem. We talked a little bit about the ActionCable
overview, we're going to be talking about ActionCable
this entire section. So, I don't really feel comfortable crossing this task off the list. We did build out the model, so I'll cross that off. We also integrated users and associated them with comments along with the blog. So that is good.
Now that we have that. We might have a comment model, but that doesn't really let us do what we need to do from a data flow perspective. We need to be able to create comments outside of the rail console obviously. So, we need to come and implement a controller. If you go to the app/controllers/comments_controller.rb
You can see that the resource generator simply gives us a comments_controller
that inherits from application_controller
and that we're in charge of building it. Which is perfectly fine. We're not going to have any views related to comments in regards to say going to a comment page. We're simply going to create comments so we only need the create statement. So I'm going to say
class CommentsController < ApplicationController def create @comment = current_user.comments.build(comment_params) end private def comment_params params.require(:comment).permit(:content) end end
Now, this should be all that we need. If you're wondering about this call right here (current_user.comments.build(comment_params)
What we're essentially able to do because of the way that current_user this method works. Is this is going to grab the current user and it's going to because of our relationship between users and comments. It is going to build this comment for us and it's going to automatically associate the user with the comment.
So with all this in place, we can move on to the next controller we're going to have to work with. Which is the blogs_controller.rb
Now with this one what we need to do is we need to update our show page. Because if I open up the rails server and we start this up. We're going to see and we can plan out exactly how we want our comments shown. So lets go to Google Chrome and I go to
localhost:3000/blogs
This is going to give us our blog index page. From there we going to be able to check out the blog show page, and then we can see exactly how we want that to show up. So if I click on my blog post. Right here we can see that nothing is there. In fact, I'm also going to log in because we only want the form shown to users that are logged in. So if I click on this (blog post) You can see that we obviously have no comment form here. Because we haven't built it, but this is where I want the form and the comments to be. Just so you have an idea, visually of what we need. So, that's the first part. The next thing we need to do. Now that we know where it goes, is we need to go and update this show action. We already have access to blog through the :set_blog
right here. Remember that :set_blog comes down to the set_blog method and it calls our blog, but we're going to actually override this action now. And the reason why is for some performance reasons that I will show you here shortly. So our show action right now if I kept the code like this, would act exactly the same. If I hit save and hit refresh, nothing is going to change because all I'm essentially doing is I'm overriding the blog instance variable with the identical query for blog and so that does nothing. What I want to do is I now want to include comments. Now, this is something that comes in very handy from a performance standpoint. If you remember back to when we walked through our deep-dive on SQL
and we talked about what includes does for us. This is something that can be very handy because it allows us to now include not just a blog but also the comments for that blog. So that we're not going to have to hit the database multiple times. So this is going to help out tremendously from a performance point of view if you have a blog that could have a hundred comments or a couple hundred comments then you definitely don't want to be hitting the database for every single one of those. So, I'm going to say
@blog = Blog.includes(:comments).friendly.find(params[:id])
everthing else here is the same. Now that's the first part, the next thing that we have to do is because we're going to have a form we need to have a new instance of comment. So just like we had a new instance of blog for our new blog because we're going to have a form now we need to kind of mimic that and say
@comment = Comment.new
This is going to give us an empty class and an instantiation of comment and now we can set up a form that is going to do this for us. So this is exactly what we need to do in our controller. Actually, this is all we're going to have to do in our blogs_controller. Which is pretty cool. We're going to be able to close this off and we can also close out the comments_controller. Now lets come down into our view and start looking at what we need to implement here. I'm going to have two files we need to build. One is going to be a comments partial, it is not going to be shown on anything comment related in terms of the comment page. It's only going to be called from our blog show page. So, if I open up blogs and then go to blogs/show.html.erb
right now we just have this content right here...
So, we're going to make a few sections. We're going to make one section and I can put some comments here just for the sake of having them. This is going to be the start of comment form and this is going to be the end of it. Then right below that I can say this is the start of comments and the end of the comments. That's all we're going to have to do right here. So this is going to be, just like we talked about here, we're going to build a form and then I want to render the comments right down below it. So this is going to give us the initial thing that we need.
Now let's build out the form first. The first thing we talked about is we do not want this form shown to a user that is not an actual logged in user. We want to protect against one of our guest users accessing it. It is one of the rare times I will use a Ruby
'unless statement'. So I'm going to say
<% unless current_user.is_a? GuestUser %> <% end %>
All I'm saying is don't show this form if the user is a guest user. If the user is logged in, then we're good to go with showing this form element. Let's build the form. (this will be a good exercise if we're going to have to build a form_for completely from scratch) So I'm going to say
<% unless current_user. is_a? GuestUser %> <%= form_for @comment, url: '#' do |f| %> <div class ="form-group"> <%= f.label :content %> <%= f.text_area :content, class: 'form-control' %> </div> <% end %> <% end %>
Remember the reason we have access to comment is because we created comment_new inside of the blog_show action. This is going to give us all of our cool routes because this is what form_for does for us. Then I'm going to say url:
and pass in a '#' This may seem a little bit weird to you, because aren't we supposed to pass in the create action in the route? Well not exactly and this is the reason why because if we did that. Then the rails process would kick in and it would try to load another page. What we want is something a little bit more modern. We want something with a javascript kind of flare where users can type in a comment and it simply adds it automatically and we don't have to take the user to a different page. So that's the reason why we're going to do this and eventually, in our javascript code, we're going to write. We're going override the default behavior. The default behavior when you click a submit button is that it would go to another page, but that's not what we want. If you want another example of it, imagine being on Twitter or Facebook and posting an update, you just expect to be able to type it in, hit submit and just see it appear right there. Well, that is not default behavior, by default what should happen is you should hit submit and it should take you to another page or it should reload the page and reload everything. Javascript allows us to get away from that default behavior and by passing in this little '#' for the URL this lets us do it. So from here, I'm going to create the start of a ruby block and we have a block variable of 'f' which stands for form. Let's also end this just to make sure that we don't have any crazy end tags hanging around. Inside of this, I'll create a div just using bootstrap styles. We're going to say 'form-group' and inside of this, let's create a basic form element. so I'm going to say 'f.label' and remember our attribute is called content. That's all we need to do, and now I want a textarea. The format for that inside of rails is(this is our rails helper for the form) it's 'text_area' and this is for content and I want a class of 'form-control' and that's all we need there. And let's give one other item, this is going to be our 'submit' button. I'm going to say f.submit 'Post Comment', class: 'btn btn-primary'. Feel free to put any other styles that you want in there. Okay, let's hit save and hit refresh on the page. And see what that gets us. There we go, we have a content form for our comments. That looks really good. Obviously, this is not functional yet. We haven't wired it up with anything. So, it's more just for looks. It has form_for, but if you were to come in here and type something you would see no matches. And it's good to do that because what is happening is here? It's saying we have a routing error. And it says No route matches [POST] "/blogs/my-blog-post-0"
Because this is in the new action, it automatically created a post request but because we gave the '#', the route it was looking for is 'blogs/my-blog-post-0" it tried to assume that we meant the current page we're on because we added the '#' and that just is not going to work. Which is totally fine, we wouldn't want it to work. We have much cooler plans in store for it. That is the first part.
Now we need to be able to render the form itself. So I'm going to get rid of our comments here now that we know where our form goes. And right here what I want to do is start to actually iterate over it. So I'm going to create a bunch of divs
<%= render @blog.comments %> <div class="comment-card"> <div class="card"> <div class="card-block"> <div class="row"> <div class="col-md-1"> </div> <div class="col-md-11"> <%= comment.content %> </div> </div> </div> </div> </div> </div>
This first one that I'm going to create is going to be a wrapper class and I'm going to just call it comment-card. And if you have followed along up to this point then you probably have a good idea that the reason why I'm doing this is because I want to wrap up the card class and be able to give specific styles just to how comment cards look as opposed to the basic bootstrap class throughout the rest of the application and following the same pattern I've used before. I'm now going to call the card class and let's put another one because we have access to another set of styles called the card-block class so now I can say class and card-block end that div. This is going to be one of the larger nested divs that we've done in this course so far but it's going to be worth it because it has I think a pretty cool look and feel. Now that we have all that. Let's now go and create a row. So we're going to have a row of items one is going to be for our traditional picture. So we're going to have a picture of ourself or whoever's making the comment and then the other is going to be for the content itself. So first I'm going to say div with a class of col-md-1. Let's end this out and now let's duplicate this. And the other one if you remember we always add up to 12 with the bootstrap grid class so it's going to be 1 and 11 and that is going to give the side to side action we're looking for. Now we're not going to worry about this part yet because the adding the pictures is going to be one of the refinements we do at the very end. I'm just going to leave it blank. And then here this is what we're going to be a little bit more concerned with. This is what we're going to use in order to you take in the comments and have them rendered. This is going to be I think a pretty cool little thing. Let's say that we have right here say we have access to a comment and then content. And you may wonder what am I doing here. I'm calling comment. We don't have access to comment and that is because right here this is going to be our partial. You didn't really think I was going to leave this here did you? This is our comment. I just want to flesh kind of everything out here. And so that's the reason why. But what I want to have is our form followed by our content. And this is simply going to be a partial call. So if you want to follow along on this we're going to say render. And then we can say render comments and the cool thing with how Rails works is, this is our cool shortcut where it's going to go look through views and it's going to look for a partial called '_comment' and then it is going to call everything that we have right in here. So now that we have all of this scoped out I can get rid of everything here...
and we'll worry about creating a partial for our form later but for right now I just want to follow Rail's conventions. So if you go to views and we go into comments new file save and comment each melancholy. Paste it all in. Fix the indentation. And this is all we need to do for right now.
Eventually, we're going to style it we're going to put some dates around it all that kind of good stuff but that would be taking way too many steps at one time. Remember one of the biggest secrets on being a great developer is to take very small easy to manage chunks so that is following in line with this pattern. Now before we end this let's come and let's verify that we don't have any bugs. So if I hit refresh Let's see what we have it says nil is not an active model compatible object
And this gives us a little bit of a clue on what it is. Notice how it says nil
right there. That means that it's looking for some data that does not exist. And the reason because I made a rookie mistake comments is not instantiated. So we have no concept of comments I just made a mistake. It should be at blog.comments remember how we were using the rails console to test it out. We have access to a blog. So what we can do is actually just call @blog.comments and this is going to bring all the comments for that specific blog. So if I come back and now hit refresh. It all works. It doesn't work you know in terms of you can't see comments because no comments exist there. For this blog, let's come and add some just to verify that this is working. If you start up the rails console we can say
b = Blog.find_by_slug('my-blog-post-0')
And so he can pass in that URL and hit enter and that's going to give us our blog. And so now we can say
u = User.last
User.last store that in a user variable and now a comment.create and say
Comment.create!(content: "Something", user_id: u.id, blog_id: b.id)
Now if I refresh it appears to have worked. So startup the rails server again. And now if everything is working then it should show us that. So let's let the rails server get started and then. Wait till its going there go. And now if I hit refresh Let's see if that's working. And there you go. You can see we have a comment and it says the content that we had. So that is working perfectly. Now don't worry about the styles just like normal. We want to focus on functionality first and we'll take care of the styles We'll have the picture all that kind of stuff afterward.
So let me say
git status
see everything that we changed
git add .
git commit -m "built out initial form and partial call for comments along with controller actions"
and now I'll say
git push origin actioncable
and we are good to go. I will see you in the next guide as we continue building out the feature.