Realtime Nested Comments in Rails with Hotwire & Turbo.js - Part 1

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] hey guys this episode we're going to be talking about how to build real-time nested comments with hot wire and turbo js now we've talked about this in the past where we built nested comments and we're going to be building something similar to that but we are building a real-time version of that to take advantage of some of the turbo functionality in hotwire so i've got a rails app with a post scaffold and we've already installed hotwire and setup devise and bootstrap and so we can dive into building comments now so um we are going to start by creating the comment model in our terminal so we'll generate a model called comment and it's going to have a user belongs to on it so we know who created the comment we'll have a commentable belongs to that is polymorphic so we can use these comments for any model that we want to associate with and we'll have our body as rich text and then actually let's add a parent id integer so that we can have comments that know their parent now we could use the commentable as the reference to the parent comments but by doing this we can have them all associated with the post and that will make it easy to delete all the comments whenever we delete a post and we can just use the parent id for rendering out visually on the children so let's run this and generate that model um i've already done that so we're going to run railsdb migrate to create that in our database and then we can hop to our code in the post show action now this is the default show action but i've added a section here for comments and i just said let's go ahead and give you a link whenever you're not signed in and if you are signed in then we can add a form for the comments so let's render out a partial called comments form and we will have some locals here we will have the comment itself which will be a new comment and then we need a comma commentable which will be our post so that's where the um the comment will be posted to the commentable which is posts slash number one slash comments and we'll need a form for that and matching routes so we can hop into our routes and add a nested resource under posts and we'll call that comments and we'll say module posts and that will allow us to define a scoped controller for this so we'll say posts slash comments controller.rb and inside of there we can define the posts comments controller now this controller will be one that we want to be somewhat reusable so if we add another model like an episode or a video and we want comments on those we're going to make a nested controller for those comments as well but most of the logic is actually going to be in a shared either parent controller or module like a concern that implements the actions like create and so on so what these will do is just run it before action to set the commentable and that is going to look up the commentable so in this case we'll have at commentable equals post.find params id and we'll set that and we'll be able to write the rest of the controller logic inside of either a parent controller or a concern and i'm going to add a concern here so we're going to have a commentable.rb concern and this will be module commentable and we will extend active support concern we'll have an included block where it can do anything we want like before action authenticate user the stuff that you put on the class level and then we can define our crate and any other actions we might want to have in here so that's going to take care of this we'll be able to go fill this out next but our comments controller we'll just need to include commentable and it will set up the before actions from commentable and the before action for set commentable okay so let's go into our models and make sure we have our associations we want as many comments as commentable on our post and our user model should have many comments as well but it's going to be the user association so that is all good now we can go into our app views folder and add our comments form.html.erb and inside of comments form we are going to have a form with a model this is going to be our commentable that we passed in as an argument from our view so let me pull that up real fast our post show renders this partial with the comment and the commentable as post so if we say models commentable and po and that being a post then this is gonna generate a form with an action of slash post slash one slash comments and any time commentable changes to say a let's say it's a videos model then i would just change to that and post to a different route so that's good and we can go and create our little form here we can say let's do a form rich text area for the body and then let's have a form submit button and let's make this class a button button primary and we probably want to print out any form object errors so we'll say form.object.errors.any we'll have a div we'll go through those form object errors dot oops full messages each do message and each one of these will just get printed out in a div so we can see the message on its own line so if we hop over to our commentable concern again we can fill out the create action so this is going to be simple we will say um at comment equals at commentable which is already set with the before action say comments dot new comment prams we will define comment params which is the params require comment and permit the body and the parent id and we'll make that private and we will attempt to save the comment if that is successful um we want to redirect to the commentable and if it's not we want to have a way to replace the form now we're using hotwire and turbojs so this form is going to be submitted and we can replace just that form with the errors if something goes wrong so let's go instead of doing this we'll say respond to do format and we'll nest this inside of that block so here we can say format dot turbo stream we'll do one thing and format dot html we can do another thing so if this wasn't submitted by turbo for whatever reason we could have this redirect to the commentable and you could say a notice or something that it was successfully saved but in our case we want to render out on errors we want to render out a replacement for that form so if we render out a new post and take a look at the comment form we will see here if we inspect that our form does not have an id and we need an id for the turbo stream to actually go and find it and replace it so we need to go and update our comment form partial to include some sort of id so we can target it so we can add a new line here and we can say id and give it some string like new comment if we wanted to do something simple but what we're going to do is actually define a helper method and this is going to allow us to target this specific form and that's going to be important later on because once we get into nested comments we'll be able to target an individual form even though we might have this rendered on the page 10 different times so let's do that what we really want is something like dom id comment but we want to be able to also specify different object in there as well and kind of concatenate all of those together so what we'll do is we will build our own dom id for records and you can pass in the commentable and the comment and it's going to figure out how to handle that so let's go create app helpers we'll call it recordhelper.rb and inside here we define our module record helper and a dom id for records and we can take the records and a prefix because that's the option that dom id has we can go through records dot map do record we'll do the dom id for the record with the prefix being prefix and once that's all mapped we can join this together with say an underscore so that's going to be able to combine the dom ideas together for us so that our comment form can have a combined id and so that's going to be really useful for us to target those very specific forms later on when we have multiple on the page because if we had all of them called id is new comment well it'd be redundant ids are supposed to only be on the page a single time and this allows us to solve that problem so now we can use this dom id for records helper inside of here and we can include the action view record identifier and the record helper we just defined and use that to do our turbo stream inside of here so what we'll do is we'll say turbo stream dot replace dom id for records at commentable and we'll have our comment dot or at comment um and that's going to be a new comment so because it's on the validation errors part of the else and then we can just render that same partial so we say partial common slash form locals is comment commentable is at commentable and if that's all done correctly we don't need that last curly brace there what we should be able to do is if we go into our comment model and say validates body presence is true we should see that this controller will fire it will then go into the else it will run the turbo stream and it will replace that form on the page with the errors so let's click create and see if that works now i made a typo and authenticate user so we see that being an error so let's create now and we get post without an id now i wrote this as in our comments controllers pram's id that should be pram's post id because we're in a nested controller this time let's try this one more time and there we go we get user must exist body can't be blank and that first one is something we should do here we should say current comment dot user equals current user to always assign that user the other thing we should do is go into here and give this div class alert alert danger in order to make it look a little prettier there we go so now we have this form actually being targeted by that and it is automatically handling that case of replacing that form with errors so our commentable is well on its way now on success we need a format.html where we redirect same as the one below and we can pretty much do the same thing here except now our comment is saved and so we need to actually um take the comment and use a different dom id for records in order to reference a brand new comment so we want to replace that on the page with a new form and by doing that we can say use a local variable for a new comment and that's going to re-render that form out and so now if we were to submit a successful comment our form is cleared out replaced with a new copy but we don't see our comments and that's for a couple reasons for one we're not rendering our comments so if we go to post show we should actually go down here and render at comments at post.comments and that's going to give us our comments to render that's going to render a comments comment.html.erb and inside of here we can define a turbo frame tag for the comment and then render our body inside of here so comment.body we can put a div up at the top that says um let's do this split so we'll say d flex justify content between and we'll print out the comment.user.name and then on the other side we will include links to edit so edit comment path for the comment and let's do a class of mr2 on the side so that we can separate that from a button to delete which will be to the comment path for the comment this should be not an instance variable this will be method delete and a data confirm of are you sure so this gets us the representation of the comments down here at the bottom turbo frame tag we'll fix that and we don't have our edit comment path yet so let's go ahead and add that up here resources comments and allow us to just render that out now we need of course some way of rendering this out so that it is aligned properly so let me fix this slash div that's missing there and then all of a sudden starts to look a lot better so that's much better we have our name and our links and we can even say this should be class d flex in order to get these to line on the sides um we could even go through and add hr's or something to separate them but we're going to wait on that because we're going to be doing nested comments so this is getting us well on the way but one of the things i would love to be able to do is see that these comments are rendered automatically so let's go ahead and delete these out of the database so we'll say comment dot destroy all to clear those out we'll be able to refresh our page and if we go to our post show we can add something right here above the comments that is a turbo stream from at post comments and now we will have our javascript connect to the post comments channel and anytime we broadcast a comment to that it will go toward javascript and execute that operation whatever we want it to be which means we can go into our models comment.rb and broadcast to the post so let's go and do this in a little bit different of a manner than you might be used to if we say after create commit we can specify a lambda to run and that's going to be a broadcast append to or append later to if you want to do that in a background job which i would recommend then we can broadcast this to commentable comments which is the same thing as our turbo stream from that we added to the page and so if you look at your page and you inspect down here by the comments you'll see the turbo stream from that is a signed id so it can't be tampered with but that will match the same thing that this will broadcast to so then from here we want to set a target and we're not going to use the default target and that's why we're adding this we're adding the dom id of the commentable and in order to use that in our model we need to include action view record identifier so we can call dom id from here you can also call action view record identifier dot dom id but it's a little long and then we're gonna add underscore comments to match the name of our stream and this is the dom id that we're gonna have on the page where all of our comments will live okay so this is now going to match up with post show and around our comments we want to render a tag with the id of the dom id at post and we want comments as well so we're going to make these match underscore comments and we'll wrap that in the div tag so now if we refresh our page and inspect just beneath this button we'll see post one underscore comments that is going to match the target here and then our stream from post comments is what we match here when we broadcast so now if we open up this page in a couple of our browser windows we can type in here create our comment and we'll see it gets broadcast and appended to all of our open browsers that are looking at this page so that is a great step in the right direction and the next piece we need to do is do editing and deletions in line and then we'll be able to go in further and broadcast those update steps we can do the editing form in just the single browser and get on to our nested comments so thanks for watching part one of the real time nested comments series i will be back in the next one and i'll talk to you then peace
Info
Channel: GoRails
Views: 4,643
Rating: undefined out of 5
Keywords: Ruby, Ruby on Rails, Rails, Javascript, HTML, CSS, Servers, Databases, Screencast, Tutorial, Rails 6, Action Text, Active Support, Action Mailbox, Webpacker, Active Storage, Active Record, rails testing, ruby on rails testing, ruby testing, devise, rails devise
Id: b7dx1Yt3FzU
Channel Id: undefined
Length: 20min 34sec (1234 seconds)
Published: Tue Feb 02 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.