00:00:21.480
here I want to take you on a journey to a place that some of you may know a
00:00:29.039
country is so magical a country that is called alagasia
00:00:34.620
and in this country a villain rules spreading fear across the subjects
00:00:39.780
a dark King a dragon rider who's named gabatorex in this Dragon Shri Khan
00:00:46.440
but then luckily another Dragon hatched and chose her Rider The Majestic Dragon Saphira and her
00:00:53.219
Rider Aragon that the resistances spark of hope again Our Hope
00:01:00.360
and now as the two armies face each other our hero Aragon steps forward with
00:01:06.479
a desire to save all the man on both sides he demands gabatarics to a duel where
00:01:13.260
The Winner Takes Over command of the other side and we all know that dark Kings have
00:01:18.979
incredibly big egos so he could not reject but he accepted
00:01:24.720
the challenge there's one issue though galbatorex is not even on the field
00:01:31.259
he barricaded himself in his castle and this is across the country and not even the Dragon Riders magic can
00:01:38.340
travel this far but then we step forward as we are one
00:01:44.220
of the few knowledgeables of a magic that is even Beyond The Magic of the Dragon Riders
00:01:49.920
a magic so old and Powerful that most believe it is forgotten
00:01:55.799
imagine that goes by many names which people only can whisper rubion rails
00:02:02.780
action cable two of the streams and we all know this magic is so old
00:02:09.179
that no one tries to learn it anymore and we also all know that it's just intro
00:02:16.860
so we take on this enormous responsibility and help Aragon zafira to fight
00:02:23.340
gabatorex in an epic battle of tick attack toe
00:02:30.239
so welcome everyone to my talk exploring the power of tube streams and action cable
00:02:36.000
but first Who Am I who is this Storyteller across the campfire the one
00:02:41.099
that got all your attention which probably is because the campfire stage and so you kind of must watch me
00:02:47.819
um so my name is Kevin lipos I'm a business administration business
00:02:53.040
administration Bachelor turned rubyist by visiting a boot camp so thank you
00:02:59.220
um and I'm from Germany so now I work at a software engineer at
00:03:04.379
portagon which is a fintech in Frankfurt Germany and I've been there since roughly three years now
00:03:10.739
so what we do there is we provide simple reliable and independent access to the
00:03:16.260
private Capital Market and yes we got a beautiful rails monolith for that
00:03:23.159
also I'm one of the co-organizers of the Ruby Meetup in Frankfurt
00:03:28.680
and this is also my very first conference talk ever so please bear with me I'm quite nervous
00:03:40.200
thanks so before we dive back into our story I just want to ask you one question just raise your hand does
00:03:46.920
anyone here even know the inheritance cycle by Christopher paulinho which I based My Story upon
00:03:53.519
all right more than I expected amazing so um for you it makes probably a little
00:03:58.799
bit more sense for the others I still hope it's going to be fun so if you like fantasy and haven't read
00:04:06.720
those books I definitely recommend it so just to tell you why I am telling the
00:04:11.760
story it's because I'm currently rereading those books as I never finish them in my teenage years
00:04:17.760
and I don't know how's that with you but when I read something I connect everything with it so they let me have
00:04:24.240
no choice but to use the story and the talk so let's go back in
00:04:29.580
to cast our spell we need to be exploring the power of two streams and action cable which cocidentally is the
00:04:36.000
name of this talk and by that I want to prevent you from something that happens to those who are
00:04:42.419
unknowing what also happened to me when we did a project at work where we needed some customization for tuber streams and
00:04:49.080
action cable um which is like we just couldn't find the advanced examples that we needed so
00:04:56.040
we Dove on to a long journey of trial and error and researching
00:05:01.800
so the goal for this talk is to provide you such an example and to spark your imagination for your
00:05:10.620
own spells for your own use cases of the old magic cotubo
00:05:16.500
and as anyone who is knowledgeable about magic knows we need to be careful in how
00:05:22.139
we conduct it right otherwise the results could be devastating so first
00:05:28.620
we're gonna explore the ingredients of our spell and explore theory behind it a
00:05:34.500
bit so we're going to dive into web sockets we're going to see how action
00:05:40.259
cable adds some spice to it and how it turbo streams in the end garnishes Our Spell
00:05:45.660
and by that just when you're one of the few candidates that want to be included
00:05:50.820
into the magical cycle of Turbo just don't have no fear we'll cover the basics so that everyone now can follow
00:05:57.660
along then we will be constructing our spells so
00:06:03.180
we're going to build the game of tic-tac-toe we're going to start with a basic game skeleton and from there we're
00:06:10.259
going to add three constraints to it each discovering another part of action cable and turbo streams
00:06:17.520
and last but not least we will let our magic work and we will cast our spell and by that we're going to watch the
00:06:24.240
epic game between Aragon zafira and galbotorix and going over everything we learned in the talk
00:06:31.560
so now let's start off with our three ingredients and explore the theory behind it a bit
00:06:38.039
and first here let's get everyone on board what websockets are so web sockets are about real time or at
00:06:47.160
least close to real time so for me it's always easy easiest to
00:06:52.620
understand that when I again remind myself how HTTP works I mean we all know that but still let's go over that a bit
00:07:00.000
so let's take a chat as an example um when we when we want to have the new
00:07:06.360
messages then we first need to request it from the server and then the server responds by sending
00:07:14.220
us those new messages back the thing is only when coincidentally
00:07:19.560
our Scout on the field on the lookout for our enemies sends the message before
00:07:24.960
our request to the server only then we can receive it
00:07:30.780
with web sockets that's a little bit different with websockets we have an open connection to the server all the
00:07:37.440
time and this means the server can send us anything anytime they want without us
00:07:42.840
needing to request it and by that as soon as the Scout sends
00:07:48.120
the message to the server they can directly forward it to us
00:07:53.340
and by that we still keep the advantage that we directly get the message and not just like maybe an hour later
00:08:00.660
all right let's go to our second ingredient which is action cable
00:08:06.720
so coding all the basics of web sockets might be quite repetitive and might
00:08:12.900
require some protocol level level knowledge so that perfectly fits into a framework
00:08:18.360
and of course rates covers Us here so action cable is the rails magic for
00:08:24.419
websockets but let's dive a little bit more into that by Consulting our amazing rails
00:08:31.139
guides so basically action cable integrates websockets seamlessly into every rails
00:08:39.479
application and by that it allows us to write real-time features in our beloved Ruby
00:08:45.000
language it offers us a gives us a full stack
00:08:50.339
offering and by that stays true to the idea of rails that one magician AKA one developer can do it all
00:08:58.080
for that it provides us a client-side JavaScript framework and a server-side ruby framework but let's keep it like
00:09:05.519
that for now more than when we construct Our Spell so let's go to our third and last
00:09:11.640
ingredient and explore the magical world of hotwire a little bit and talk about
00:09:16.980
two of the streams Broadcasting so we're smart magicians right so at
00:09:23.100
least we think we are so that's why we weave a little helper into our spell as
00:09:28.980
Tupa streams just allows us to use action cable way easier
00:09:34.320
so it kind of is an abstraction of action cable which by itself is an abstraction of websockets
00:09:42.420
but what this exactly does turbo stream broadcasting providers basically it's a simple concern that
00:09:49.260
comes with the tuber rates jam and which is now by default included in the in raid 7.
00:09:54.959
so it gives us Little Helper spells so methods that then do the magic in the
00:10:01.440
back we can use them as the gem also adds that as an initializer
00:10:08.399
the two active record so we can trigger the broadcasting right from there so we
00:10:14.760
sent websocket messages then right from the model so
00:10:20.700
all in all tube stream broadcasting provides us a plug-and-play solution so just install the gem at around three
00:10:27.540
lines of code and that's it it just works by that it's really easy to use even for
00:10:33.480
our smallest magicians but the cool thing is that's not even it it also gives us automatic Dom updates
00:10:42.720
and that's important because normally without trouble you would receive the
00:10:48.060
websocket messages and you need some client-side JavaScript to do something with it right
00:10:54.720
but with turbo it sends us HTML over the wire so in all know Hotwire and then the
00:11:02.459
gem automatically updates the Dome with it so that's nice
00:11:07.860
all right now we finished the list of ingredients already
00:11:14.339
take a short breath and then we receive a foggy picture in
00:11:20.279
our head gabatarix and Drake and started to become impatient so let's quickly move on with
00:11:26.700
constructing our spell and build the game of tic-tac-toe so first things first
00:11:33.660
let's get everyone on board what tic-tac-toe is I know most of you maybe know that some may may not so
00:11:41.220
tic-tac-toe is a game for two players where you start off with nine empty fields
00:11:46.440
then the players take turn and Mark those fields so first player one
00:11:51.779
then player two and as soon as one field is marked you cannot unmark it and you cannot take it
00:11:58.079
away from your opponent as soon as then one player reaches three in a row
00:12:03.720
the vertical horizontal or diagonal then this player wins so
00:12:10.620
basically that's the game that we want to build the big challenge that some of you might
00:12:16.800
already see are instant notifications that one player gets notified when the other player makes a move
00:12:23.040
but the cool thing is with our tool magic that will be no problem
00:12:28.860
so let's go on and build our skeleton game
00:12:34.140
so what do we need basically we need two models so the player and the game let's start with the
00:12:40.920
player a player belongs to a game it has a name and a character assigned like a
00:12:47.279
dragon or a sword and we're gonna assign that character in a callback
00:12:52.680
then let's look at the game which is even simpler as it just has many players and stores the values so the characters
00:12:59.600
of each of its nine fields and now before gabataric's entry can
00:13:05.160
become even more impatient let's make our game playable and at the first constraint
00:13:10.920
which should look a little bit like this so what you see here is we have two
00:13:16.019
clients where localhost and have the epic game for oligasia so first things first we need to add
00:13:22.800
players to it and of course the good ones Aragon zafira are their first
00:13:27.899
so here they are we see they got Schwartz assigned they still need to wait for the opponent
00:13:34.100
and it looks like they cannot actually start to play yet
00:13:39.300
so let's add our notorious latecomer galbotorix the bad King to it
00:13:47.639
so here he is we see that he plays Aragon zafira he
00:13:52.980
got the dragon assigned and it looks like both parties can now start playing
00:13:58.019
so let's do that
00:14:03.240
so first Aragon and zafira make a move then gabatorex does
00:14:10.260
so that looks like it's working let's just sum it up what we just saw
00:14:15.959
so we saw that there are two clients involved otherwise you cannot display
00:14:22.380
and we saw that as soon as Erica and zafira made a move and galbatore is directly saw the effect
00:14:29.160
of that action which comes with some UI differences
00:14:34.740
um but that's not only the move but also like the name of the player is different and the name of the opponent
00:14:41.880
so we come to our first constraint which is actually like two things first that we
00:14:47.339
need two players to start the game the second is that one player directly needs to see the move of the other player
00:14:54.240
so let's do that and start with the two players so on the left side you see that
00:15:00.300
when there's only one player they cannot start on the right side you see as soon as the second player joins
00:15:06.779
the board changes and both can start playing for that we need to connect the Stream
00:15:14.399
so we need to connect the client to the websocket so they can receive any kind
00:15:19.560
of notification and then we need to notify the opponent about that so first things first let's just imagine
00:15:27.899
that everything happens on the games show page here we just add a one liner to it and
00:15:35.579
that's it we're connected to the websocket or stream which I try to call it from now on
00:15:41.579
um let's look at this in Parts though so first we have the tube stream from helper which is provided by the gem and
00:15:48.899
this connects us to a stream to which stream then is defined by the
00:15:54.060
next part as this is kind of the stream name so we could here also use just board and
00:16:01.320
get rid of at player which would be technically perfectly fine though the add player makes it kind of
00:16:08.459
like private is we can later Define the exact single client to which we want to
00:16:14.699
broadcast to and that's because when we broadcast something we always broadcast it to all
00:16:21.120
the clients that are connected to the stream with the same name and the add player makes that kind of unique or at
00:16:27.660
least dynamic so we're connected and you can verify
00:16:32.880
that by looking at our Network tab so here we see that indeed we're
00:16:38.339
connected we're subscribed to a channel where we're welcomed and we receive those amazing things and those pings are
00:16:46.019
kind of important as they're like the heartbeat that the connection is and stays open
00:16:55.259
it's all right we're connected now let's notify our opponent when someone joins and this we do a after create callback
00:17:02.759
in the player model so let's see how that looks like
00:17:08.339
so first we need the broadcast update 2 method that also comes with the gem
00:17:13.559
and then with the and then we Define the name of the stream to which we want to
00:17:19.799
broadcast so the opponent's board um stream and when we look at the front
00:17:25.079
end that quite mirrors what we have there so that's good
00:17:31.620
next we need a Target a Target is the ID of the Dom element that we are going to update and by
00:17:38.760
update here I mean that we're gonna replace the inner HTML of that element
00:17:44.340
and of course there are also other math methods that come with the gem like
00:17:49.500
replacing or stuff but here we Define the update too and just so you see again what it is
00:17:57.179
what we want to update it's basically that in the front end so next we need to Define what we want
00:18:04.080
to update that with which is the gamesport partial
00:18:09.120
and I don't know how you feel but for me this kind of looks somehow similar to a
00:18:14.400
simple render method in the controller so I think that's not too hard to understand
00:18:19.799
but still to make it complete we can also of course pass locals to the
00:18:25.080
partial let's pause a second take a breath and
00:18:30.720
think about what we just did so we just connected a client to the websocket we send a message through that
00:18:39.539
connection to the client and then we are also updated the Dome with it
00:18:45.120
and that with almost no code it's not freaking amazing
00:18:55.860
well all right we now updated the board but it would also be nice to know
00:19:02.039
um against who we are actually playing right so let's also do that quickly
00:19:07.679
and simply add another broadcast update to to the method that we already have there
00:19:13.620
um so this time only we've gone to Target so we're going to update the opponent name Dom element with the games
00:19:20.400
opponent named partial and that's it so you can send two broadcasts from the same method in this
00:19:27.419
case and updating two different things of the Dom with it so all right
00:19:32.700
we have two players connected now and now next thing is we don't want that
00:19:38.520
arrogant zafira like play blindly we need them to know what gabatorics does
00:19:44.100
so we need to send them the action
00:19:49.679
thing is we're already connected to our stream we are the tube stream from helper so we don't need to do that
00:19:56.160
anymore however let's now Define what happens when someone actually makes a move
00:20:03.059
and for that let's look at one single field of our board
00:20:08.760
basically that's just an if else statement so when the field already has a character
00:20:15.120
assigned we simply display the character if not though we have a formula there
00:20:20.160
which is also like a really simple and short form as it just has some hidden attributes values that we then will need
00:20:27.179
later in the controller we're just going to add one new magic to
00:20:33.299
it to the stream so we're going to wrap this form into a turbo to a frame sorry
00:20:39.299
and by that make use of another part of the turbo magic which goes kind of like
00:20:45.900
this so by default a navigation that happens inside a frame it's just scoped to that
00:20:52.620
very frame and when the response of the navigation like here the form submit contains a
00:21:01.380
frame that has the same ID then the new frame simply replaces the old one
00:21:08.580
so we're going to knit that in a second so we submit the form and then we're
00:21:13.799
going to reach the game controller against controller update method and here we just update the correct field on
00:21:20.220
the game quite easy for now and then the game of course updates
00:21:27.120
so first thing is now we want to change our own UI and we do that by simply
00:21:33.059
rendering that's the right one I have assembly
00:21:38.159
rendering this something back to where the click so the form submit originated from
00:21:44.159
and we simply render a field partial back this is the same partial that we already have there this time only it
00:21:51.720
already has a character assigned so we're going to display that one and as this again is wrapped inside the
00:21:58.919
turbo frame that has the same ID as the field that's already there the new field with the character
00:22:06.320
replaces the old field in the front end with the form and that is the magic of
00:22:11.940
tour frames alright then so we updated our own UI
00:22:18.360
but we also need to update our opponent's UI so we do that
00:22:25.140
here in our K model so we're gonna add an after update callback here and this
00:22:32.100
just triggers another broadcasting so here we broadcast a replays to the opponent's board stream and by replace
00:22:39.840
this time I mean we're going to replace the entire Dom element that has the same
00:22:45.299
ID as the field and not only the inner HTML of it
00:22:51.860
and we're going to replace it with the games opponent tick partial which is the
00:22:58.260
same as as a field in the end it just adds a little visual stimulus so the short red background so that the
00:23:04.559
opponent sees that something just happened and that's it we updated our opponent's
00:23:10.620
UI so just to sum it up again with the
00:23:15.900
broadcasting we replace the field of our opponent and with a simple render method
00:23:22.740
with the help of tuber frame we replaced our own UI and change that
00:23:28.140
so our basic game already works now
00:23:37.260
but we're smart magicians right so we think ahead
00:23:42.299
and come to the conclusion that we cannot trust gabatorics to play fair at all
00:23:48.000
and our first thought is that he might try to add a henchmen to the game who manipulates the game in his favor and we
00:23:54.900
cannot let that happen so therefore our second constraint
00:24:00.600
is that we restrict the game to only two players which should look like this
00:24:07.860
so you see that we have three clients Aragon zafira are already there
00:24:15.179
and now we need to wait forget the Torx to join the game
00:24:22.559
so here he is and then next we try to add a henchman to the game
00:24:28.080
of course whose name is henchman because dark bad kings don't care about people's actual
00:24:34.679
names right so sad here we are and now it looks like the
00:24:41.940
henchman is in the game let's try to play though
00:24:49.980
so what we can see here is that the third client the henchman actually doesn't get any updates
00:24:57.539
um and for that to happen we need to reject the connection request of the
00:25:02.580
third client and for that let's start with our customization and talk about action cable connection
00:25:09.900
so action cable connection is kind of like the bottleneck of a websocket connection so
00:25:16.799
there's one connection instance per websocket or per stream and the action cable server can handle multiple
00:25:23.340
instances of course with different clients but a client can only have one
00:25:29.340
connection so just think of that like maybe a tunnel that is the connection between
00:25:35.220
the client and the server and the server when they want to send something to the client they always need
00:25:42.120
to send it through this tunnel and this is the thing that we can like destroy or
00:25:48.600
disconnect for a potential third client but first we need to pass something from
00:25:56.340
the client through the connection to the server something that helps us to
00:26:01.679
identify who is it that tries to connect the good thing again is the old roots of
00:26:08.340
our magic help us here is it passes cookies from the client through the connection
00:26:14.340
to the server so we simply add the player ID to the cookies
00:26:19.980
and that's it and of course probably we should be more sophisticated here and use an encrypted
00:26:25.919
cookie like a session cookie which just works as well but just for the sake of Simplicity we don't want to decrypt
00:26:33.360
something then later so let's do that for now and then as soon as we reach our action
00:26:39.900
cable connections so the application cable we can use this cookie to find the
00:26:45.059
player and can then decide if we want to allow the player or not
00:26:50.820
in which case we're going to reject the connection so let's again look at our Network tab
00:26:58.200
first a reminder so that's a successful connection we see we're connected
00:27:03.299
subscribe to a channel we're welcomed and we receive our pinks
00:27:08.400
when it's not so successful though we directly see that we get disconnected as unauthorized without any reconnection
00:27:15.900
attempt so we're not welcomed and we do not receive our beautiful pinks
00:27:22.980
so that's a win we just successfully hindered gabatorics to add the henchmen
00:27:28.980
to the game and help him manipulate it so that's cool but still we deeply believe that
00:27:35.700
galbotorix is decideful and we can very well imagine a scenario where it just
00:27:41.520
leaves the game and lands a surprise attack on the resistance but this ain't happening with us I say
00:27:48.059
so therefore our third constraint is that as soon as one player leaves we
00:27:53.640
need to end the game and notify the opponent about that so this should look like that
00:28:02.460
so again Aragon zafira are there already so let's now add cabatorics to it
00:28:10.260
here we are and we start playing
00:28:16.260
and now galvatore's just leaves the game and what we can see is that Aragon and Saphira get notified about that and the
00:28:22.860
game ended right but there's also another way of leaving the game
00:28:28.620
let's see how that works
00:28:41.760
so we stop playing again this time we just close the tab
00:28:47.279
and we also see that ericon and zafira got notified about that so cool how we
00:28:52.860
gonna do this now um we're going to talk about action cable channel for that so just imagining
00:29:00.240
the channel is like a chat room or room and when you're inside your room you can
00:29:05.279
hear everything so you could potentially receive all the messages that are sent into that room
00:29:12.840
so even though a client has still a single connection to the server a client could be subscribed to multiple channels
00:29:20.399
so that's one thing another cool thing about channels is we can Define behavior for what happens when a client
00:29:27.539
subscribes to it or unsubscribes from it and that's the thing that we're gonna need
00:29:33.179
so rails has an application cable channel a default one
00:29:38.399
turbo also has a default Channel which is the tuba streams Channel and that's the one that we connect to without
00:29:45.120
further ado and the difference is that it kind of
00:29:50.159
has some turbo magic added to it and it also has a subscribed callback
00:29:55.679
which is triggered when of course the client subscribes um in this callback we can
00:30:03.059
check if someone tampered with the stream so with the stream name especially in which case we can reject
00:30:09.600
it but for the sake of learning we'll not do that we want our very own game channel
00:30:17.340
just so that it's going to be easier in the future to add more channels to our Magic
00:30:23.279
and that for now looks just like the default turbo streams Channel
00:30:30.419
all right let's now Define the flow for what happens when a client connects and
00:30:35.940
subscribes so first a client connects to the action cable
00:30:41.220
connection instance where we can reject the henchmen or allow the player
00:30:46.520
then they subscribe to the game channel which triggers the subscribed callback
00:30:52.740
and then one day they decide to leave which means they unsubscribe from our
00:30:59.700
Channel and that triggers an unsubscribed callback from where we want to trigger the
00:31:06.659
broadcasting to notify the opponent so that the opponents UI updates
00:31:14.520
so let's now just keep like that with all the pictures and let's go through it step by step in our magical Ruby
00:31:22.200
language so first we need to adapt the view a little bit as we now don't want to
00:31:28.919
connect to the default Channel but want to subscribe to our own game channel so we just add a simple hash argument
00:31:35.760
here next we need to adapt our connection a
00:31:41.580
little bit because we also want to access the player from our channel so we need to pass it through
00:31:48.260
and that works with a identifier so an identifier is like the identifier
00:31:55.380
for the connection you can have multiple ones if you like we only need one and all identifiers are passed through to
00:32:02.399
the channel then and to assign a value to that identifier we just change player equals to self
00:32:09.539
play equals and now we can access it from the channel so here we are and now we need an
00:32:16.440
unsubscribe callback which looks like that I know it was incredibly hard
00:32:21.899
so but they're already the highest rank of our magical cycle and can do this so well in the end
00:32:30.000
it's just really simple and we're going to broadcast again from the model
00:32:35.640
maybe some of you don't like that because it's like some view things within the model you can also if you
00:32:41.700
like broadcast right from here just need to add a little bit more code or from a service or something like that but the
00:32:49.140
basic idea of tuber is to do it from the model so we're doing that as well now
00:32:54.899
and here it is the last thing that we need to do is we broadcast our update to
00:33:00.659
the opponent's board stream we want to update again the board
00:33:05.820
element of the Dom and but this time only we don't even need a partial we just need a single HTML string
00:33:14.399
so that's cool and to already feel exhausted now like
00:33:19.740
we checked our three ingredients and carefully constructed Our Spell
00:33:25.500
so Now's the Time to let our magic work and cast Our Spell so let's do that
00:33:37.799
and our game begins to make it all work we delve into our three ingredients we saw what websockets
00:33:44.580
are we added some abstraction to it with rails action cable and even added some
00:33:51.059
more abstraction to it with trouble stream just to get some real-time Dom updates
00:33:58.260
and now let's start so Aragon and Saphira ask the good ones they're always first there and they join
00:34:05.519
the game waiting for gabatorex the notorious bad guy latecomer
00:34:17.099
so here he is we can style finally start to play and
00:34:24.599
to make that work we added some basic broadcasting abilities to our tic-tac-toe skeleton
00:34:31.440
and we added a turbo stream from Tech caliber to the front end to connect to the websocket and added some callbacks
00:34:37.679
to notify the opponent as soon as the other player joins as well as of their actions
00:34:43.619
so let's go on playing
00:34:49.260
but now watch out gabatory tries to add a henchment to the game this time a bad one
00:34:55.020
and here they go this though doesn't really work as we
00:35:00.060
adapted our action cable connection to reject the connector request of the
00:35:05.099
henchmen and then in the end
00:35:12.420
the need to finish the game as we adapted our own action cable game
00:35:18.599
channel um to broadcast as soon as someone tries to
00:35:25.320
leave and now let's see how that decides the faith of oligasia
00:35:34.400
it's a victory all his subjects refuse to obey him anymore and we can finally install democracy in alagasia
00:35:46.740
so before finishing up I'd like to thank everyone who helped me putting my talk and see if P together because they were
00:35:53.220
like kind of a lot of people so I were colleagues mentors coaches and I also like to thank again my
00:36:00.480
employer portagon as they kind of sent me here from Germany so they made it possible for me to come here and also
00:36:08.099
a big big thank you to the organizers here I have no idea if someone is in the room but thanks for putting such an
00:36:15.000
amazing first railscon experience for me together and giving me the opportunity
00:36:20.579
to speak and I know there's still one and a half days ahead and as I now can
00:36:26.520
really enjoy them even better so thanks for that if you want to connect
00:36:32.760
with me I have various social media here and also if you want to check that out down there is the link to the Repository
00:36:40.440
if you saw the little buck with the opponent's name there's where you can
00:36:45.660
open a PR and fix that so if you want to talk to me just talk to me in the hallway try me for lunch or
00:36:52.320
if you stay here longer are probably gonna do a road trip to New Orleans on Sunday so feel free to join as well
00:36:58.560
so um finally thanks to all of you for taking the time deciding to listen to me
00:37:05.400
for I think 40 minutes straight the end