00:00:21.480
Here, I want to take you on a journey to a place that some of you may know—a magical country called Alagasia. In this country, a villain rules, spreading fear across the land: a dark King, a dragon rider named Galbatorix, and his dragon, Shrikhan.
00:00:34.620
Fortunately, another dragon hatched and chose her rider, the majestic Saphira, and her rider, Eragon. Together, they sparked a resistance—a glimmer of hope in the darkness.
00:01:00.360
Now, as the two armies face each other, our hero Eragon steps forward with a desire to save all the men on both sides. He challenges Galbatorix to a duel, where the winner takes command of the other side.
00:01:06.479
We all know that dark kings have incredibly big egos, so Galbatorix could not reject the challenge and accepted it. However, there's one issue: Galbatorix is not even on the battlefield; he has barricaded himself in his castle, which is far away across the country.
00:01:24.720
Not even the Dragon Riders' magic can travel that far. But then, we step forward as we are among the few knowledgeable about a magic that is even beyond the magic of the Dragon Riders—a magic so old and powerful that most believe it is forgotten. It goes by many names which people can only whisper: Ruby on Rails, Action Cable, Turbo Streams.
00:01:55.799
We all know this magic is so ancient that no one tries to learn it anymore. However, we are going to take on this enormous responsibility and help Eragon and Saphira fight Galbatorix in an epic game of tic-tac-toe.
00:02:16.860
So, welcome everyone to my talk: Exploring the Power of Turbo Streams and Action Cable! But first, who am I? Who is this storyteller across the campfire—the one that has captured your attention?
00:02:30.239
My name is Kevin Liebholz. I am a business administration graduate turned Rubyist after attending a boot camp. Thank you! I'm from Germany, and I currently work as a software engineer at Portagon, a fintech company in Frankfurt, Germany, where I've been for roughly three years.
00:02:59.220
What we do there is provide simple, reliable, and independent access to the private capital market. Yes, we have a beautiful Rails monolith for that! Additionally, I am one of the co-organizers of the Ruby Meetup in Frankfurt. This is also my very first conference talk ever, so please bear with me—I'm quite nervous.
00:03:40.200
Before we dive back into our story, I want to ask you one question. Just raise your hand if you know the 'Inheritance Cycle' by Christopher Paolini, which my story is based upon. Alright, more than I expected—amazing! For those who do know, it may make a little more sense to you.
00:03:58.799
If you like fantasy and haven't read those books, I definitely recommend them. I am telling this story because I am currently rereading these books, as I never finished them in my teenage years. When I read something, I connect everything with it, so I couldn't resist incorporating this story into my talk.
00:04:29.580
Let’s cast our spell as we explore the power of Turbo Streams and Action Cable. By doing so, I want to prevent you from experiencing what has happened to those who are unaware, which also happened to me during a project at work where we needed customization for Turbo Streams and Action Cable.
00:05:10.620
We couldn't find the advanced examples we needed, so we embarked on a long journey of trial and error and research. The goal for this talk is to provide you with such an example and to spark your imagination for your own use cases of this old magic of Turbo Streams.
00:05:28.500
As anyone knowledgeable about magic knows, we must be careful in how we conduct it; otherwise, the results could be devastating. First, we’ll explore the ingredients of our spell and delve into the theory behind it a bit. We are going to dive into WebSockets, see how Action Cable adds some spice to it, and how Turbo Streams in the end garnishes our spell.
00:05:56.100
Just to reassure those few candidates eager to be included in the magical cycle of Turbo, do not fear—we'll cover the basics so that everyone can follow along.
00:06:03.180
Then we will construct our spell, building the game of tic-tac-toe. We will start with a basic game skeleton, and from there, we'll add three constraints, each uncovering another aspect of Action Cable and Turbo Streams.
00:06:24.240
Last but not least, we will let our magic work and cast our spell, watching the epic game between Eragon, Saphira, and Galbatorix, while reviewing everything we've learned in the talk.
00:06:38.040
Now, let’s start with our three ingredients and explore the theory behind them a bit further.
00:06:47.160
First, let's get everyone on board with what WebSockets are. WebSockets are about real-time communication or at least close to it. It's often easiest to understand this by reminding ourselves how HTTP works.
00:07:07.620
Let’s take a chat application as an example. When we want to receive new messages, we first need to request them from the server. The server then responds by sending those new messages back. However, we can only receive the message if by coincidence, at that moment, our scout on the lookout sends a message to the server before our request is made.
00:07:30.780
With WebSockets, the situation is quite different. Here, we maintain an open connection to the server at all times, meaning the server can send us anything anytime without us needing to request it. As soon as the scout sends a message to the server, it can be directly forwarded to us, ensuring we receive it promptly rather than an hour later.
00:08:06.600
Now, let’s move on to our second ingredient: Action Cable. Coding all the basics of WebSockets may be quite repetitive and may require some protocol-level knowledge, which pairs quite well with a framework. And of course, Rails covers us here.
00:08:31.260
Action Cable is the Rails implementation for WebSockets. It seamlessly integrates WebSockets into every Rails application, allowing us to write real-time features in our beloved Ruby language. It offers a full-stack solution, maintaining the idea that one magician, or one developer, can do it all.
00:09:05.640
For that, it provides us a client-side JavaScript framework and a server-side Ruby framework. But let's keep that for later as we construct our spell.
00:09:36.600
Now, let’s explore the magical world of Hotwire and talk about Turbo Streams broadcasting. As smart magicians, we aim to make our lives easier, and Turbo Streams allow us to use Action Cable much more conveniently.
00:09:59.820
It is essentially an abstraction over Action Cable, which itself is an abstraction over WebSockets. Turbo Stream broadcasting provides simple concerns packaged with the Turbo Rails gem, which is included by default in Rails 7.
00:10:22.680
Turbo Streams give us little helper methods that do the magic in the background. It also adds an initializer for Active Record, allowing us to trigger broadcasting directly from there.
00:10:44.640
In summary, Turbo Stream broadcasting provides us a plug-and-play solution: just install the gem and write about three lines of code, and voilà. It just works! This makes it incredibly easy to use, even for the smallest magicians.
00:11:12.480
But that's not all. It also offers automatic DOM updates, which is essential. Normally, without Turbo, you would receive the WebSocket messages, but you'd still need some client-side JavaScript to do something with them. However, with Turbo, you receive HTML over the wire.
00:11:57.180
In effect, Hotwire and the gem automatically update the DOM with this HTML, and that's a great feature.
00:12:05.760
Alright, we've now finished our list of ingredients.
00:12:11.720
Let's take a short breath and recap what we have. Galbatorix, the dragon, is becoming impatient, so we need to quickly progress to constructing our spell and building the game of tic-tac-toe.
00:12:26.160
First things first, let’s establish what tic-tac-toe is. Tic-tac-toe is a game for two players, beginning with nine empty fields. Players take turns marking these fields: first player one, then player two.
00:12:54.000
Once a field is marked, it cannot be unmarked, and players cannot take a field away from their opponent. When a player manages to get three markers in a row—be it vertically, horizontally, or diagonally—that player emerges victorious.
00:13:20.880
This is essentially the game we aim to build. The significant challenge lies in instant notifications: one player must be notified when the other makes a move. But with our Turbo magic, this will pose no problem.
00:13:47.880
Now, let’s embark on building our skeleton game. Essentially, we require two models: the player and the game. Starting with the player, each player belongs to a game, has a name, and is assigned a character, like a dragon or a sword.
00:14:10.440
We're going to assign that character in a callback. The game model is even simpler, as it merely accommodates many players and stores the values of each of its nine fields. Now, before Galbatorix's impatience escalates further, let’s ensure our game is playable.
00:14:38.640
This should appear like this: Two clients are established on localhost, witnessing the epic game in Alagasia. First, we must add players—as expected, Eragon and Saphira are their priority. They are here, marked with their assigned characters, but they must wait for their opponent.
00:15:00.300
Let’s introduce our notorious latecomer, Galbatorix, the evil king. Now he is here, and it looks like both parties can finally begin playing.
00:15:28.740
Let's proceed as Eragon and Saphira make their first move, followed by Galbatorix. The initial gameplay appears to be functioning smoothly.
00:15:54.300
Let’s summarize what we have observed: there are two clients involved—otherwise, the game isn’t displayable. As soon as Eragon and Saphira made their move, Galbatorix was able to see the effects of that action in real-time, complemented by a few UI changes.
00:16:12.660
This brings us to our first constraint: we require two players to start the game. The second requirement is that one player should instantly see the move made by the other. To accomplish this, we must connect the stream.
00:16:39.540
This means we will link the client to the WebSocket so they can receive any notifications. Then, we’ll need to notify the opponent upon joining. To do this, we can add a simple line of code to the game's show page to connect us to the WebSocket.
00:17:08.799
Let's break this down into parts. First, we use the Turbo Stream helper provided by the gem, which connects us to a stream, named by the next part, which defines the stream name itself. We could also use just 'board' without the 'add player' portion, which would be fine technically.
00:17:27.480
However, including 'add player' makes it unique, allowing us to define the specific client to which we want to broadcast. When we broadcast something, it always goes to all clients connected to the same stream, and 'add player' helps maintain its uniqueness.
00:17:48.600
We can verify if we're connected by checking our Network tab. There, we can see that we are in fact connected, subscribed to a channel, welcomed, and receiving vital pings.
00:18:14.160
Those pings serve as a heartbeat to ensure that the connection remains open. Now that we are connected, let’s proceed to notify our opponent when someone joins, which we will handle through an after-create callback in the player model.
00:18:35.520
Firstly, we need to utilize the 'broadcast update to' method that the gem provides. We’ll define the stream name where we wish to broadcast the message—the opponent's board stream. The front end will reflect this accurately.
00:19:04.260
Next, we need a target, which is the ID of the DOM element we intend to update. By 'update', I mean we will replace the inner HTML of that element. There are additional methods available via the gem, but here, we are focusing on the update functionality.
00:19:15.120
We also define the content we want to use to update it, which is the game board partial. It resembles a simple render method in the controller, making it quite easy to understand. To be complete, we can also pass locals to the partial.
00:19:30.540
Let’s pause for a moment and consider what we just did. We connected a client to the WebSocket, sent a message through that connection, and updated the DOM with minimal code. Isn't that incredible?
00:19:59.400
Now that we have updated the board, it would also be nice to know who we are playing against. We will quickly address this by adding another broadcast update to the method we just established.
00:20:10.980
This time, we will target the opponent's name DOM element with the games opponent name partial. It's feasible to send two broadcasts from the same method, updating two different parts of the DOM.
00:20:45.720
Now, with two players connected, we need to ensure that Eragon and Saphira don’t play blindly. They must be made aware of what Galbatorix is doing, so we need to send them notifications about actions.
00:21:10.920
However, since they are already connected to the stream, there is no need to connect again. We now need to define what occurs when someone makes a move.
00:21:23.880
For that, let's examine one individual field of our board. Essentially, this involves an if-else statement. When the field already has a character assigned, we display that character.
00:21:35.280
If not, we use a simple form that holds hidden attributes and values—these will be necessary later in the controller. We wrap this form in a Turbo Frame, employing another aspect of Turbo magic, as navigations within a frame are scoped to that specific frame.
00:22:03.420
When navigating within the frame, if the response contains a frame with the same ID, the new frame simply replaces the old one. We submit the form, leading us to reach the games controller update method, where we will update the correct field in the game.
00:22:36.180
Once the game updates, we initially want to change our own UI. We achieve that by rendering a field partial back to the place where the submit originated, ensuring that the field now displays the character it has.
00:22:54.360
Because this is wrapped inside a Turbo Frame with the same ID as the existing field, we effectively replace the previous field on the front end with this new character.
00:23:16.440
However, we also need to update our opponent’s UI. Within our game model, we can add an after-update callback that triggers another broadcast.
00:23:40.560
This time, we broadcast a replace to the opponent's board stream, indicating that we will replace the entire DOM element with the same ID as the field—not just its inner HTML but the entire element.
00:23:59.880
This ensures a more pronounced visual cue for the opponent, as we can apply a short red background to indicate that something has just occurred. This sums up how we updated our opponent's UI.
00:24:16.080
Moreover, this broadcasting effectively replaces the field of our opponent. By employing a simple render method, we modified our own UI with the assistance of Turbo Frames. At this point, our basic game is fully operational.
00:24:40.920
However, as clever magicians, we are aware we cannot trust Galbatorix to play fair. Our concern is he may attempt to add a henchman to the game, trying to manipulate it in his favor, so our second constraint is to restrict gameplay to only two players.
00:25:00.480
Now, as we see, we have three clients: Eragon and Saphira, along with Galbatorix. We need to wait for Galbatorix to join the game. Here he is, and let’s attempt to add a henchman to the game, an absurdly named character because dark kings do not care about actual names.
00:25:27.180
As we observed, the third client—the henchman—is not receiving any updates. For that to happen, we need to reject the connection request of this third client, which we will go about by customizing Action Cable's connection.
00:25:49.920
The Action Cable connection acts as a bottleneck for the WebSocket connection. Each connection corresponds to one WebSocket or stream; the Action Cable server can manage multiple client instances, but a single client can only maintain one connection.
00:26:06.480
Think of it as a tunnel—the connection between the client and server. The server sends messages through this tunnel. Thus, we can either disconnect or destroy a connection for a potential third client.
00:26:28.920
For this, we need to transmit pertinent data from the client through the connection to the server. In doing so, we can identify who is attempting to connect. The good news is the old roots of our magic assist us here, as cookies are passed through the connection.
00:26:53.940
Thus, we simply append the player ID to the cookies. Although in a more sophisticated setup, we should utilize an encrypted session cookie to provide better security, we'll keep things simple and bypass any decryption processes for clarity's sake.
00:27:21.180
Once we reach our Action Cable connections, we can utilize this cookie to locate the player and decide whether to permit the connection or reject it.
00:27:38.280
If we opt to reject the connection, we'll witness a swift disconnection without any reconnection attempts, thus prohibiting the connection seamlessly.
00:28:01.920
This is a win, as we effectively prevent Galbatorix from introducing a henchman into the game! But we still suspect Galbatorix might attempt to stage a surprise attack on the resistance by leaving the game.
00:28:24.780
Therefore, our third constraint is that if one player exits the game, we need to conclude the game and inform the opponent.
00:28:47.880
Thus, when both Eragon and Saphira are present, we can add Galbatorix into the mix. As the game starts, Galbatorix can exit, leading to Eragon and Saphira receiving notifications that the game has ended.
00:29:07.200
However, there is another way a player can leave. For example, if they close the tab, we still ensure Eragon and Saphira are notified about the departure.
00:29:36.660
To manage this, we discuss Action Cable channels. Imagine a channel as a chat room; when you’re in a room, you can receive messages sent to it. Although clients have a singular connection to the server, they can subscribe to multiple channels.
00:30:03.060
Channels enable us to define behavior for subscribing and unsubscribing events. Rails has a default application cable channel, and Turbo has a default Turbo Stream channel that we will connect to.
00:30:18.840
These channels have Turbo magic added, including a subscribe callback triggered when a client subscribes. In this callback, we can verify if someone attempted to tamper with the stream's name, allowing us to reject such attempts.
00:30:41.640
However, for educational purposes, we won't explore that angle; we'll create our own game channel to simplify future extensions of our magic.
00:31:05.280
First, we need to adapt the view to connect to our game channel instead of the default channel. Hence, we’ll incorporate a simple hash argument here.
00:31:27.840
Next, we must revise our connection slightly, as we desire to access the player from our channel, so we need to pass it using an identifier. This identifier serves as a unique key for the connection—multiple identifiers can exist, but we will utilize just one.
00:31:50.040
To assign this value to the identifier, we set ‘player’ equal to ‘self’. We now have access to the player from the channel. Now, we need to define our unsubscribe callback, which is a straightforward method to manage when a player disconnects.
00:32:24.120
It indicates the need to broadcast an update to the opponent's board stream informing them of the exit. In the end, we ultimately replace the board element in the DOM.
00:32:48.840
Now that we have checked our three key ingredients and carefully constructed our spell, it is time to unleash our magic and cast our spell.
00:33:12.720
The game commences! We delve into our three components: understanding WebSockets, integrating some abstraction with Rails' Action Cable, and even more abstraction with Turbo Streams for real-time DOM updates.
00:33:44.580
Let’s kick things off! Eragon and Saphira, the heroes, make their entrance and join the game, anticipating the notorious latecomer, Galbatorix.
00:34:15.960
Finally, we can start playing, and we introduce basic broadcasting functionalities into our tic-tac-toe skeleton. We connect Turbo Streams for Action Cable to the front end and incorporate callbacks to notify the opponent upon joining and during actions.
00:34:39.660
But watch out! Galbatorix tries to add a henchman to the game, but that doesn't quite work, as we adapted our Action Cable connection to reject the connection request.
00:34:59.820
In the end, we successfully conclude the game as we customized our Action Cable game channel to broadcast when someone tries to leave.
00:35:21.600
This ultimately leads us to the fate of Alagasia, where victory is attained, and all subjects refuse to obey Galbatorix any longer. We can finally initiate democracy in Alagasia!
00:35:39.780
Before wrapping up, I want to give my heartfelt thanks to everyone who assisted me in putting this talk and presentation together; it truly took a community of people.
00:36:02.640
Special thanks once again to my employer, Portagon, for making this journey from Germany possible, and to the organizers here for crafting such an amazing RailsConf experience for me.
00:36:26.520
If you'd like to connect with me, I have various social media handles listed. If you're interested, there's also a link to the repository below. If you spotted the little bug with the opponent's name, that's where you can open a pull request and help fix that.
00:36:53.880
Feel free to catch me in the hallway, join me for lunch, or if you're staying longer, I might even be doing a road trip to New Orleans this Sunday, so you're welcome to join.
00:37:05.400
Thank you all for taking the time to listen to me for what I think was about 40 minutes. The end!