# #59 Hotwire Turbo Streams CRUD

- URL: https://superails.com/posts/ruby-on-rails-59-hotwire-turbo-streams-crud
- Published: 2021-11-12
- Duration: 40:55
- Access: free
- Playlists: Just enough Hotwire for Rails developers

Just Enough Hotwire for Rails Developers with Yaroslav Shmarov | Open Source Every Day

TEXT VERSION: https://blog.corsego.com/turbo-hotwire-CRUD
GIT REPO: https://github.com/corsego/59-turbo-streams-crud/

1. CREATE APP: https://github.com/corsego/59-turbo-streams-crud/commit/38e51d378518313d02fa0f691363aec11037dc5d
2. SCAFFOLD "MESSAGES": https://github.com/corsego/59-turbo-streams-crud/commit/70d81e9399f8e204b8a6498d9c69135b88caddad
3. Turbo #1 Re-render form when creating a message: https://github.com/corsego/59-turbo-streams-crud/commit/4516d1777daa963dbc65ba49ff4e9f7c4d3285ef
4. Turbo #2 Add new messages to the list: https://github.com/corsego/59-turbo-streams-crud/commit/9c238c62e1d3be270c674ca7fb82b9ebab52df36
5. Turbo #3 Remove messages: https://github.com/corsego/59-turbo-streams-crud/commit/b03bb338ef36a140338a0d4723bafa719021a808
6. Turbo #4 Edit messages: https://github.com/corsego/59-turbo-streams-crud/commit/f38797b79e0daa82dff2c81047672f6574abca54
7. Turbo #5 Update messages counter: https://github.com/corsego/59-turbo-streams-crud/commit/9468767a9171464b0d50c03dcb9860602196ffbe
8. Turbo #6 Basic flash: https://github.com/corsego/59-turbo-streams-crud/commit/f103be90a5574418dbde4bcc155ef987d29f9b72

## Transcript

So they're going to build a basic application using hot fire and more notably using Turbo and more notably using Turbo Streams. So I think streams are actually the coolest and most powerful feature in uh, uh, turbo. Now, uh, let's uh, start with creating a new rail seven application. Yes, as you see, I'm running Rail seven Alpha and DR three. So I will say Rails and u Let's uh, say, uh, app and database will be post graph SQLK. Let's create the application. And you see it's from input maps install nice and also turbo install stimulus install. So I don't have to bother about the installation of turbo and the stimulus. That's also good. And you see it's also generated at dummy stimulus controller. Anyway, let's navigate with application. As I'm running on cloud nine, I will have an error if I don't go to development RB and say, config hosts equals Neil. Okay, now I can say rails B Great, and start the application rails us. I'll go to preview. Okay, so the application is running loop school. Now I will say get the add. Oh. So let's say get initialized GI papers, GI at all gi commit main create app. Okay, now let's, uh, actually create the, uh, scaffold. I will say, uh, rails generate scaffold, uh, message. And a message will have a body, for example. So body text, okay? And, uh, let's draw this command. Now I will go to our roots file and say that we'll have a root path to messages index. Okay, let's start the server. Okay, run the pending migrations. And we are on messages index. And it is also the root part. So I can go to create a new message, create it. Now, as you see, I can create it with an empty body. So I will add some basic validations. I will go to app models, message validates, uh, body presence. True. Now let's try on scan, going to edit. Okay, so it has to be present looks good. And let's, uh, save our changes. So give status, give, add, all, give, comment, Main messages. Okay, looks good. Now you see the application is looking well, not really beautiful because it doesn't have any kind of, uh, CSS style by default. So to fix this, I will just add a simple CSS uh, CSS framework. So let me run, add this command to our layouts going to views, layouts, application, HTML, adding it, refreshing, and yeah, looks a bit better. So, okay, now we are looking at our messages index page. Now this is a bit misleading. Let's go to the messages page, messages index. Okay, it is message, yes, I don't know why the scaffold generated, didn't pick it up, but it is messages. And we have, uh, well, one message displayed. And first of all, we are going to make it so that we can add messages without page refresh. So how can we do this? Well, we are going to add a form, first of all. So we will display the form to add a message on the index page. Let's do this. I will say equals render partial shell, uh, messages form and locales will be message, message and closing the braces. And let's see if it is done. Okay, so we have a field for body. I can press create message. If it doesn't fail, I'm redirected to the, uh, edit page with the, uh, notice that the body can get blank. If I, uh, add a message successfully, I'm redirected to the message message. Now, I will override this to behaviors with, uh, our turbo. So first of all, what do I want to do? If a message is, uh, successfully created, then I will, uh, re-render this form here. And if I have, uh, an error. So if, uh, I tried to create a message without any text, for example, this error is going to also be rendered here inside the messages index page. And I'm going to easily do it with the turbo. So I'm going to go to our controllers messages controller, and you see, uh, we can respond with the a format named Turbo Stream. So here we have format html, format J by default in a scaffold, but we can also say format dot turbo stream. Now, I'll put it in a do block. So if you go to the Turbo Handbook, uh, we will have examples of, uh, responding form Turbo Stream, and they're going to render something inside. Anyway, let's try to do it on our own. I will say form Turbo Stream, I will say render Turbo Stream. And uh, next I will say, uh, turbo Stream dot update. And we need to update something. Well, the point of turbo stream is to update, uh, some kind of element, some kind of, uh, H TM L element with an ID on a page. So I will say element ID with some kind of HTML or with some kind of partial locales. So they'll have an element here. Let's just have it element ID for now. Then we'll pass in the, the partial shell that they're going to send. So the partial shell is going to be, uh, messages and what are the locals going to be? So locales are going to be, uh, if they save the message, then we want to, to send a new message form. It'll be message equals message new. Okay? And now this element id, well let's, uh, give an ID to this, uh, partial right here. They're rendering a post shell messages form and they want it to be replaced with a turbo stream. So turbo stream is going to send the HTML of this partial with this locales into our HTML view for index. And it's going to replace this. Now, it must replace some kind of element id. So let's give it an id. I will say diviv id. Uh, new message. I will wrap this all into ad. Let me just, uh, refresh the page. I am going to messages. I will go to inspect element. And um, there is it, you see, uh, I've wrapped it into this ID new message. And now I'll go to our messages controller. And here in this Turbo Stream update, I'm going to say update the HTML, uh, element on the page that, uh, is it sent to with a partial messages form and these locales. So let's see this in action. Now I am, uh, well, I can refresh or I don't actually have to. I'm going to create, uh, a message and you see it has disappeared. Now, what has happened, if I go to the logs, I will see that and message has actually been created. So if I refresh, the message will be added to the list. But uh, this form basically gets rerun. So, uh, only this part of the HTML page gets, uh, r rendered. I might be able to see it, uh, somewhere in the network, I guess. Let me once again add something. And you see I get this messages and I get this stream action update. Target new message and template is going to be the form. So the messages form with the correct, uh, locales. So you see, uh, turbo Stream sends, uh, this, uh, HTML with a turbo stream action update right into this place. And what we've done, we've, uh, rended the form, we've rended the form with the, the locals message message new if we have saved the message. So if it was saved correctly, but what if I press create message and there is no content, I press create message, and you see, I just get, body can be blank. So what do I get? I get this render new status, unprocessable entry. And what I want to get, I want to actually have this form re rendered with the locales message is going to be the message that we are working on at the moment. And they're not going to have any page refresh. So see, we will redirect the new action. And I don't want to be redirected. So I'm going to just copy this formative stream into the L statement and I'm going to say, so turbo stream update, new message, partial messages, form and locales message is going to be then message that, uh, was, uh, rendered invalid. So it is going be this message at message, okay, let me actually also beautify this a bit so that it is also more readable, Okay? Just like it. And now I will once again try to create a message. So I press create message. And you see there was no full page refresh. I sent a request to the server. The server went to the create action, it evaluated the message, it understood that it cannot save the message, and it sent it to robust stream with the, the messages form to the target new message. And with the locales, with this current message that we have working with. And once again, I will refresh, I will go to inspect element and go to network. I press create message, I go to messages. And here you see I get the, this HTML, the form, and I have this errors rendered so body can be blank. So if I go to the form messages form, you see this HTML was run with the correct uh, uh, error message. So this is the basic principle of uh, how, uh, of how turbo transfer. Now let's actually save, uh, our changes we've done already. Advanced small theme, we can, uh, create a message with the, the form being re rendered so GI at all. Now, what did it do in application HTMO? Okay, I just added the style sheets. So GI main, uh, ring render Form, event grading message. Okay, and now let's go back to our application. So, uh, it is far from being complete. You see, I will try to add a new message like hello world. And you see it is not added to the list. So why is it is it not added to the list? Because I need to refresh the page, but it's not the approach that we want to have. We are going to have turbo actually send messages directly into this page. It's going to inject these messages on top on the bottom of the list. So first of all, let's actually just add some ordering to our messages. I will go to our messages controller and go to the index action. Let's say messages dot order, rate it at the send. So the newest ones are going to be on top, okay? And now going to the index page, we have an ID messages already generate by a scaffold and here have a rendering arm messages, okay? And everything that they're going to do now is when a message is created, they're going to have another tool stream that will, uh, add the new message, uh, to this, uh, id. So how is it going to work? I'm just going to cope with this. I'll put a coma so that the syntax works. And you see here I had update. So update is kind of replacing one element with another, but keeping the same id. And uh, here I'm going to update it with the, the a partial messages that, uh, messages message. So it's going to be this partial, the actual message content that, uh, we have sent partial messages, message, and locals is going to be this message. So I will say at message, locals at message, so what am I doing here? Oh, actually not update, but they're going to say prepend. Now if we go to the Turbo handbook, uh, it'll be said that there are a lot of different actions. So there is append, there is prepend, there is replace update and so on. So we'll come to the other ones later. Anyway, this say turbo stream prepend meaning add on top of the list, the list is going to be messages that I have these messages from. I have it from index ID messages. Okay, so add on top of, uh, this id, it'll be added somewhere here. And they're going to send this special message with locales of this message that has just been created. Now again, I can uh, try and create the and new message, press create. And you see actual Turbo did two things. So it uh, updated this form and it added a message on top of the messages list. So works fantastic, doesn't it? Now, what if we had to stream append, let's say to stream append as we have here, action at end. So, uh, append will basically add an element to the bottom of the list. Let's just see how it works. I will say I am appending great. And you see it was added to the bottom of the list and you decide what you want to use based on your particular application needs. So, uh, I usually use, uh, uh, prepend more often anyway. They are running two turbo actions at the same time from the same, uh, great action. So again, I can uh, go to the network, going to inspect element, let's see network, I say hello world, great message. And if I look here, you see the turbo stream action is update. And also somewhere there must be that we are, uh, sending the second turbo stream to append or to Prepend. Yeah, here it is. So Here is the second turbo stream action prepend. And here is the message partial. You see in this, uh, from this controller, I, we send, uh, two different, uh, things that we want to replace. We replace two different, uh, DS with ideas. So kind of to ideas. Here's one and here's the second. And looks cool again, if I just press create message to get the error without refreshing the page. So I think it looks really great. Now let's, uh, save our changes. So get status, get add all GI comment, name, add messages to the list. Okay, looks great. And what if you want to delete a message from the list about how to delete go? Let's see, I would do maybe want to add a delete button. So let's go and see if we already have delete button inside the template. So the show template, we have this button to destroy message. Let's scope it and add it to our message right here. So we have have a link to show and we'll also have a link to destroy. Now I will update the local. So message po message method, delete. Looks good. Okay, now what will happen if I just press delete, if I press delete? You see we have a page refresh. So I press destroy once again and well, these are all page refreshes. Now if, uh, you want to be certain I can just add add timer to our application, h ml dot rb. So I would say something like, uh, click files, time do zone now. Okay, so I press Destroy and you see the time updates. So we are actually having a page of fresh at the moment. And uh, to do it without a page of fresh, we would want to do it with turbo. So turbo Stream would have an action, uh, of uh, delete, I guess of remove. And it would find an element that an ID and remove it from the H TM L that we have rendered from. So from this H TM L index page. Okay, now let's add a few messages and we will go to our messages controller to the destroy action and have a going to also respond to format turbo stream. So format, turbo stream and we can say render Turbo Stream and Dobo Stream Remove. And we will say at message Now by saying at message here, it's uh, where do we know that this is going to be this message? Well, if we look at the HTML, I go to inspect element. You see, uh, each message has a unique ID here in the HTML by this, each message have a unique id because in the default scaffold generator, you see we have this DOM ID message and the what this DOM ID team does, it creates an ID like message and the ID of this message. So each message in our list has a unique id and this is really great and out of the box. And if I go to our messages controller, we have this remove message. And how does, uh, rails know that we're removing the message with this id? Why don't we say something like, uh, uh, message and embraces it would have message id. Well, both things, uh, would work, but uh, turbo just knows that we would want to use this message, uh, Dom id, so we can just have add message. But if you added message, message id, then it would also work. Let's, uh, yeah, we can actually check both just in case. So, um, I will have at message here and let's see. So when I press the destroy button, I expect no page refresh because we will respond to the format of Turbo Stream and I expect the turbo stream to remove this message from the message list. So I will refresh, let's open network just to see if there are any changes. Let's see if, uh, we have a page refresh. So pay attention at the time and I press destroy. And you see there is no page refresh, but elements are being removed from the list. If I refresh the page, you see the elements will total deleted. So it works. Elements, display works. Now you see out a page refresh, they can add messages, they can destroy messages, they can render errors, then creating the messages. So it works so well fantastically. And we then tried a single additional line of JavaScript. Now let's just try this other option that this, uh, id. So just gen generating, well, the ID in the this way just in case. So I will press destroy and you see it works the same way. So you can use both, but uh, this one is more explicit and this one is better looking. So anyway, it, uh, is working well. And let us save our changes, get status, get, uh, add all. Now, what did the change inside the application? Okay, we added the time. Now get go main, uh, remove messages and valve works perfectly. Okay, now what if you want to go a step further and do something more complex? What if you want to actually edit messages without a pager crush? Now the perfect candidate for the job would be to use turbo frames, but now we are going to accomplish this thing with turbo streams. So we would do want to have an added button. First of all, we want to be able to edit each message. So I'm going to go to the message partial and going to add a link to edit. I'm going to go it from here. So edit this message. Okay, let's refresh. I will update the locale. Okay, so we have the button to edit, and if I press edit, they're directed to a new page. Now, uh, how can we make it not re direct us to a new page? How can we, well run the turbo stream. Let's, uh, just go back to the list Of the rabbi now. So going back to messages, okay, uh, we are going to respond with the format of, uh, turbo stream on the added action. So going to the messages controller, added action, and let's say respond to to format, format, Tubo stream. And, and, and inside this format, Tobo Stream, we'll say, uh, render Tubo stream Tub Stream dot update. And we are going to update this message that the p show, uh, messages message and the locales, uh, are going to be message will be the message that they already have. So message now, uh, updating the syntex, okay? So we are responding with turbo stream. Now, I don't like writing this long lines, I don't, but I say do so that it doesn't look that long and put an end statement just like Okay? So we want to respond with the format of turbo stream and replace the message with the edit form. Oh yeah, not with the message, but with the form. So partial messages form locales message. So they want to be able to render the form with the content of this message. Now, if I press edit this message, what do I got? I get action controller unknown format because by the whole design of turbo streams and turbo frames. Uh, so turbo streams can respond to any request except a GA request. So streams can respond to patch, post, destroy, whatever, and the tub frames can respond only to GA requests. So streams to anything except to GA and frames only to ga. And now they're going to kind of play, uh, a work around. So we want to accomplish this particular thing using tobo streams. So, and Tobo streams cannot respond to a GA request. And uh, this edit is a GA request. So if I go to Roots, uh, edit message path, you see it is, uh, a get request. So I'm going to say that it should also respond to, for example, uh, post, yeah, for example, post Let's, uh, go to our roots and have have resources messages, I will say do, then I will say member do, 'cause they're going to have an ID and they'll say, uh, post edit. So it's just going to also give us a post request to edit. Now if I refresh, uh, edit message path, and you see it is now a post request. Now again, I'm saying this is a bit hacky and unusual, but I'm just showing how you can, uh, overcome some natural barriers and two things through box strings. So they're going to respond with post. Now, going back, I will refresh, I'll address edit message, okay? And it still says unknown format. Now, uh, what am I doing wrong? It says unknown format because possibly I am going with a link to, I should actually say method post in this link. So in edit message, I will, uh, add method, uh, post. Now going back refreshing, press an edit message. It still doesn't work. It is because, uh, we are not using rails UGS anymore. And Rails UGS is what was given our links to possibility to use something like method post or method delete. So we are going to have to use a button to, I'm going to add a button to edit this message. Now I will, uh, refresh. So you have button two, edit message. Let's press, uh, edit. Okay, this is still not working, but it's not working because I was added in the wrong template. So you see I'm in the show view and I wanna do it in the partial, okay? So anyway, going uh, to the partial, actually we will not need to show you. I will remove it just for us not to bother about it again, okay? And here in this uh, link to edit message will be replaced with this bottom two edit message. And now this method post convert, I will refresh. Okay, I will say message. And here we have a button to destroy and to edit. Now I'll press edit. And you see, uh, it was re rendered. So we have rendered the form. Now I can go to the other message. And you see it also renders the form for the other message. So seems to be kind of welcome, but if we update something, so I will say hello, update message. What happened? There was a page refresh. So you see we have this notification, the time has changed and the, the other form was removed and we are now going to do it without page refresh. So I will go to our messages controller, I will uh, go with this and I will go to our update action. And we are going to also respond with format to stream in the the successful case and note in the successful case. So first of all, I will say to stream update the message. Partial is going to be message, message. The locals are going to be message and if, uh, it doesn't manage to update. So if there are errors we should render not the partial there should render that for. So I will do the same but render form. Okay? So let's see if it works. Now here's the time I go and press edit message. Hello world. I will also open editing the second message. Hello Yaro. I'll press update. You see it was updated. I press update on the other and there was no page refresh. Now I can refresh the page and you see, well all the changes are persisted. So wow, it works. We managed to edit our messages also using toolbox streams without a single page refresh. Really amazing in my opinion. So actually we did quite all the work and did one thing that can seem a bit hacky. So we had this, uh, get method to edit a message, but we replaced it with a post. So it works. Now let's save our changes, get status, get add all, get com main. And what we do, we can edit messages. Okay, looks good. And going even further to the next level, what if we want to see an updated quantity of messages? So we will see how many messages we have on the page. Well, usually I would just go to our index page. I would have messages, I would have, uh, equals message account. For example, I refresh and you see I'm adding new messages, but the count doesn't increase until I refresh the page and we can do it also with turbo. So in what case it says can we, does the message count change if we destroy a message or if we add a new message? Well, they're going to add a few more turbo actions. So I'm going to go to our messages controller and to the create action. And in case of, uh, a successful message save, I'm going to also, uh, say that we want to replace this particular, uh, count with and new counter. So I will say, uh, to stream the update, then we'll need some kind of id I will say message counter. And we don't need a partial, we don't need locales. We will just stream some HTML. So I will say HTML. And inside I will say something for example like, uh, uh, inbox.com. And now I will close the braces and put a coma here and it might work. Then I add this, uh, id. So I'm going to index page and here I will put this into a diviv id. I will say div ID equals message counter. Just like this. I refresh. Now you see this on a new line. So I will use spam instead of div refresh. Okay, messages five. Now I add some message and I get an error by, do I get an error? Okay, because I used the word in inbox, doesn't matter. Press messages, count again, and you see the calendar is updated. And I didn't have to create any HTML template for this. So I don't have to run the and HTML template. I can just run some HTML. Let's actually try doing the same. But without these uh, braces, let's just say HTML message count. Will it work or not? I don't know. I try and yeah, it works even simpler. Even better. Fantastic will work without the verb. HTML. Let's bend the line. So, and it's very amazing. Okay, looks good. And now let's do add the same, uh, action in case we delete a message. So you see now when we increment the account, it increases, but when we destroy, it doesn't increase only when I reload the page. So let's go to our destroy action. Here we have format to stream and uh, I will change the syntax, I will say dual And then run the to stream. I will add these prices so that we can run the multiple streams to stream, remove message, and to stream, update the message counter. And I'll put a coma between and it should kind of work. So let's try. I'm adding a message. I press destroy and it works. I try to edit the message. Uh, hello and it's working. Amazing. So now you see we also update the message counter books like clockwork. Fantastic. Get status, get add, all you commit main, uh, update messages counter. Fantastic. Now is there anything else that, uh, we can do right here? Actually see when the add message or something, we don't have any kind of flash notification that, uh, they've done it. So, uh, what if you want to have some kind of notifications on the same page without a page refresh that some action has uh, happened. Now this also an interesting case. Um, There's actually a quite easy way we can solve it. Easy and elegant. So, uh, let's, uh, just go to this page and somewhere on this page, yeah, we already actually have by fold this Id notice. Okay? And let's try to see how it works. So we have this, ID notice it is in a big bag. Okay, so we want to update this notice with the something else. Now, by default, we have nothing in this. Notice it is empty. And uh, yeah, they're going to replace this notice with something else. Let's, uh, go to our messages controller and say, uh, then we create a message. We'll, uh, also update the notice, sorry, this notice with the something let's say, uh, message created. We'll put a coma and I now refreshing. Great. And you see we have this message created. We can also add an ad or something. So message, message ID created. Okay, now I'm refreshing. And you see we have the message Id also visible and we can add this kind of, uh, uh, to stream to all other actions. So going to the update action, we will also have, uh, run to stream. They're going to render two streams. So update this partial and they will also update the notice. And the notice will be updated with message updated. Let's see how it works. I will, uh, press create message. I will edit this message. And you see we have this updated. I go and edit another message. And you see it was updated once again. And we can do the same for our destroy action. So going to our destroy action, we will toolbox string update notice with message deleted. Let's see, I delete a message and you see we have this message deleted notification. Okay, so it's broken. Fantastic. Let's again save our changes IT status gi add all GI, main and basic slash Okay, looks cool. And now what have we done in this, uh, small basic application? We've made it so that the, we can using just one page, so it is a single page application. SPA, we can create messages. You see there is no page refresh. We can create messages. The county has been updated. The, uh, flash is been updated. We can edit messages, we can edit multiple messages at the same time. We can destroy messages. And this all happening without any JavaScript and without any page refreshes. And for this, we're just leveraging the one simple functionality of turbo, the turbo streams. So, well, that's it for today. Thanks for being with me and have fun coding.
