Tim Riley

Keynote: Quest of the Rubyist by Tim Riley

Official Website:http://2023.rubyconf.tw

Quest of the Rubyist

What do curiosity, cooperation and cognisance mean for the budding Rubyist? And can these virtues help us follow the path of programmer happiness?

As we enter a land where both peril and opportunity abound, how can we plot a course towards towards the Ruby of our future? Let’s find out together, as we embark on the adventure of our time!

RubyConf Taiwan 2023

00:00:01.560 All right! Hi everyone, I'm Tim. I'm really excited to be here. This has been a wonderful conference—my first time at RubyConf Taiwan and my first time in Taiwan. I truly hope I can come back again. Until then, it is my great pleasure to bring this event to a close with some final thoughts about Ruby.
00:00:13.799 First, let me introduce myself. I work at Bill Kite, where we make fast, flexible, and secure continuous deployment solutions. We accomplish all of that using Ruby, which is super cool! I also work on a range of Ruby open-source projects, and since we're covering these today, I would just like to introduce them to you now one by one.
00:00:30.599 First, we have Hanami. Hanami is an application framework for Ruby that we can use to build web APIs or even full-blown web applications. I was delighted to learn about the city support for this event and the beautiful connections formed between Hanami and this gathering. This resonated with me deeply because the project I've worked on for many years now, Hanami, also features blossoms as a way of presenting ourselves to the world. Here, you'll see these represented on our website, invoking the Japanese meaning behind our project name, literally translating to 'flower viewing'—a reference to the tradition of enjoying the transient beauty of flowers.
00:01:08.520 Now, I love talking about Hanami, and I was honored when Muan asked me to share with you all today. However, I truly didn’t expect him to create an entire airline brand just to welcome me! So, that’s Hanami. We also have Dry, which provides a range of standalone focused Ruby gems to help solve the common tasks we encounter when building applications with Ruby. These gems have been implemented using several of Dry's gems. Lastly, we have ROM, a powerful and flexible standalone data access toolkit for Ruby that works with SQLite.
00:01:39.640 With all this talk during the event about milestones—30 years of Ruby, 10 years of this event—and accompanying Ted’s wonderful tour of the web's history this afternoon, it got me thinking about my own journey. I’ve been working on these projects since 2015, and believe me, this work didn’t come out of the blue. It took a long time coming, and it really emerged from a place where I've been working with Ruby for quite a while.
00:02:07.520 My time with Ruby actually dates back to 2001! Time really does fly; 2001 feels like ages ago, and during such a long time, some things change while others remain the same. For instance, here’s a picture of Matt from back in 2001 delivering a talk. And here’s how he looked yesterday when he opened the keynote. I don’t know how this is possible; maybe I need to become a programming language designer! As for me, I went back in time and dug up a picture from around 2001, and yes, I've aged somewhat since then.
00:02:34.360 After 22 years on this journey, I turned 39 this year, which means I've been using Ruby for well over half my life now—a considerable amount of time! Continuing down memory lane, I found the code for my very first Ruby project. Mind you, this was before Rails and even before RubyGems. My first project wasn’t even about the web at all. It was an IRC bot!
00:03:03.800 Back then, I had a group of friends whose hobby was making these bots in completely inappropriate languages. Yes, I was young, but I already knew that bash wasn't for me. So, I went looking around and found this thing called Ruby, and it looked nice, so I gave it a try and made my own bot. Now, looking through that code today feels like examining a strange relic from another age. From the little things, like the way I chose to camelCase my file names, to more egregious decisions like building a plugin system out of global variables.
00:03:43.200 While I learned yesterday that global variables were how Rails got started, perhaps I can give myself a break on this one! There were also some truly bizarre design choices, like the complete absence of empty lines in that project—yes, every class looked like this, with no white space whatsoever! I think I've come a long way since that little 11-file application, that's for sure, but the amazing thing is that, 22 years later, I'm still using Ruby day in and day out and I'm still loving it.
00:04:16.320 I truly believe there is no better language to accompany someone in their journey as a programmer over the years. That's what I want to share with you today: some thoughts from a life lived with Ruby, from past to present. To do this, we're going to weave together a bunch of threads here, and I think I've found just the right way to do it. So, give me a minute to set this up.
00:04:53.760 Are you all ready for this journey? It is a golden age in the land of Rinia! After enduring years of unrest, the compatibility crisis of the 1.9 epoch, and the roiling dramas that played out across many impassioned community threads, at last the citizens of Rinia have arrived at a time of peace and prosperity. This golden age, thanks to the dedicated oversight of Lord Matz and the Knights of Ruby, has led the villages to thrive and the harvests to be well managed.
00:05:26.600 Cycling new facilities are being built and spread across all the lands for everyone to enjoy. Most of the sources of trouble have been eliminated, with the only remaining hazards to sojourners being the occasional stray bug. Most of the lands have been mapped, although some unexplored regions still exist.
00:05:57.840 Now, I believe the perfect avenue to explore this golden age of Ruby is through a big open-world computer role-playing game from its own golden age—in this case, 1985. Together, today, we will be playing a game set in a golden age. We don't have a big evil arch-enemy to defeat; instead, our quest as Rubyists is to exemplify the virtues of our craft and in doing so, help create a better future for everyone.
00:06:34.080 As we go through this game today, we will do two things: firstly, we'll figure out what steps we have to take inside this game, and then we’ll see how Rubyists in our world can follow these same steps to make a better future for ourselves. Just checking—can you still hear me? All right, great! So some of you might realize, if you’re as old as me, that this game world is based on Alal 4, the classic role-playing game by Richard Garriott.
00:07:06.720 If you aren't familiar with this style or this era of gaming, that’s okay! We’ll be discussing Ruby at length throughout this journey. Now, I hope you can sit back, relax, and enjoy this adventure! And between you and me, I think we can find our way. So, let’s get going! The first thing we need to do is explore the world! This means understanding the landscapes and the people while engaging in dialogue to discover all the experiences, which is how we learn about the roles we play in this ecosystem.
00:07:49.920 Now, what does this mean for us? Well, this active observation requires us to read texts, follow newsletters, and stay engaged with knowledge and updates in our community.
00:08:01.520 Additionally, it involves connecting with others through conversations, much like how my connections in Ruby started. I remember increasingly feeling the pulse of the community while observing practices, theories, and principles that led me to discover the value behind open-source collaboration.
00:08:10.640 As I engaged with others, it became essential to close the loop—to reach out and engage with the people I wanted to learn from or work with. Because these small, constructive steps make us part of the community. For me, this meant sending a single email saying, 'Hey, I like what you're doing. Can we talk about it?' This eventually led to a long and fruitful collaboration.
00:08:40.360 At the root of it, programming is a collaborative, social activity. For our work to really make a difference, it has to happen in teams, communities, and society. That’s why we’re leading with these points today, as the skills to work fruitfully in society are the foundation of a happy programming career.
00:09:16.680 Back in Rinia, we’re starting to get a clearer vision of how this society operates, and as we continue our explorations, we find ourselves confronting one of those nasty bugs! But that’s okay! Because as Rubyists, it's our job to find and squash these bugs.
00:09:47.560 To do this successfully, however, we need to gear up with the right understanding of our tools. This brings us to the first technical exercise for Rubyists: understanding the purpose and danger of one of Ruby’s most essential pieces of equipment—state.
00:10:16.080 When we think about building things with Ruby, we think about objects. Each object is an instance of a class. In this case for a new entry, we can create a new instance of the log class and assign some attributes—a body and a date. Once we save the thing, we’ve made the first steps toward building a new app and everything seems to be looking pretty good. But here’s where the catch lies—perhaps it’s easy to gloss over because we take it for granted as a feature of Ruby: the way we work with this entry's state.
00:10:51.920 When we modify these variables after an object has been created, it means we can change the state of any one of these objects over time, which is known as mutable state. While I know we’re covering some basics here, the basics are crucial, just as important in Ruby as they are for our quest in Rinia.
00:11:28.640 In a simple scenario, this would play out fine. But we all know that nothing stays simple for long. What I found is that too often, the unwanted complexity or sources of bugs in my applications come from objects that both 'be' and 'do'. An entry object combines attributes such as its body and its date, but it also has methods that enact change by mutating its own state.
00:11:56.640 Now, while some argue that this role combination is a fundamental aspect of object-oriented programming—combining data with behavior, we must recognize that even with the best intentions, and encapsulation, this approach still merges our object's behavior with the notion of time.
00:12:22.760 To truly know a system, we need to know every possible combination of objects and all their possible state permutations leading up to any given moment. Anyone who’s dealt with a large Ruby application has likely encountered this challenge.
00:12:54.320 With that said, what if we took those two roles and separated them? We could start modeling our player log entries as objects that 'be'. We could keep them as plain data structures. For the actions and behaviors, we create a new dedicated class to manage those. This separates concerns; it enhances simplicity because now each piece has just one responsibility.
00:13:29.680 This arrangement is now much simpler. There’s no more mutation, which means we’ll likely have fewer bugs in the app. We’ve also satisfied one of the most helpful object-oriented design principles: single responsibility, meaning create_entry now has only one reason to change.
00:14:02.400 This section of code is a blend of object-oriented and functional programming styles. Ruby is a flexible language that allows us to mix and match paradigms. With this newfound perspective in hand, we’re now better equipped to tackle those bugs.
00:14:33.640 Having handled our first encounter, we can now continue to roam the lands of Rinia. What we need to do now is form a party. As we visit towns and talk to people, we’ll be on the lookout for those who can complement our skills and help us become even better Rubyists.
00:15:05.920 What could a party look like for a Rubyist? I'd like to share some people who have been part of mine over the years as I worked my way towards Ruby open-source contributions. First, I'd like you to meet Max and Michael; they were my business partners for ten years. While building Ruby apps for clients, I realized I wasn't satisfied with the quality of what we were building.
00:15:39.440 I sought a new approach, and both Max and Michael trusted and supported me during this journey. This eventually led me to Peter and Nikita—Peter, who gave that inspirational talk earlier, and Nikita, who has been a fantastic teammate.
00:16:04.640 We also have Luka, the original author of Hanami. When I joined him five years ago to work on version 2, he welcomed me and embraced my ideas, and since then, we’ve created something incredible together.
00:16:31.760 Another key figure is Phil, one of our early power users, and Mark, who is quickly becoming our first ongoing contributor from outside our small team.
00:16:38.760 As Rubyists, we need a party similar to the one I’ve had on my journey. I invite you to ponder who is in your party, who you would like to work with, and who you might learn from. The party is essential because we were lucky to squash that bug earlier—it was just one creature! More often than not, we can face many at once.
00:17:04.560 What we need is for our party to support one another. This leads us to our next exercise. So far, we’ve built things on a very small scale. But imagine if we wanted to express more complex behavior, involving several different participants working together—much like how our party must work together strategically.
00:17:28.760 Let’s reflect on the code we called single-responsibility. While it has one reason to change, there’s an issue. We ignored the method that creates an entry in the database. We need something external to provide this behavior so that the create_entry can retain its focus. This is the concept of dependency, where we will employ state as a constructive element.
00:18:01.680 We will add an initializer to our class that accepts a single argument for an entry repository. By capturing this within the object's state, we maintain our focus. We can then have the create_entry method create an entry in the database using that repository’s create method.
00:18:32.480 With this approach, we're preserving focus; we never mutate our state after initial creation. This means we can call it multiple times and expect consistent outcomes every time. Here, we see composition at work. Instead of adding behaviors directly into existing classes, we leverage objects together to ensure behavior is accessible where needed.
00:19:07.760 We accomplish this through dependency injection, a classic object-oriented technique that meshes well with functional-style objects. Thanks to composition and this injected dependency, our class is fully functional while preserving its focus and single responsibility.
00:19:25.520 With our newly acquired compositional advantages, our party can effectively work together to conquer even the most daunting challenges. Now we can progress, continuing to push the frontiers of our exploration. Along the way, we may find treasure boxes. Naturally, we want to see what's inside, but we also hear tell of poisonous traps lurking within, so we must be prepared.
00:19:55.600 To handle this, we’ll need to cast spells! In Rinia, spells can be prepared by combining special ingredients we can buy from apothecaries in certain towns. Here, we go to the counter, acquire what we need, and—the right combination of reagents will let us prepare whatever spells we desire.
00:20:29.760 And you know, thinking about it, this is similar to what we do as Rubyists every day. We visit marketplaces, acquire components, and combine them for great effect! Yes, you guessed it: we’re talking about Ruby gems! Thank you to Samuel and all the team for maintaining this Apothecary for us.
00:21:01.120 One of the things that will empower us as Rubyists is becoming acquainted with the kind of focused, flexible, interoperable gems that express the same Ruby philosophy we’ve discussed. Several gems fall under this organization we call Dry.
00:21:36.120 Now, let's take a tour of dry-validation. Say we want to create player log entries verified only if all the attributes are correct. This is where Dry Validation excels, allowing us to define standalone validation contracts and specify our own validation rules.
00:22:03.839 For our entries, we’ll ensure both the body and the date are red and correct. Once this validation is done, we can return to the create_entry class and update its call method to run the entry through the validation contract first before saving the entry.
00:22:27.680 Now, thinking about how a valid entry should look, I wonder if there’s a better way to model these objects as we pass them around. That’s where Dry Struct and Dry Types come into play. With Dry Struct, we can create classes for player log entries with strictly typed attributes.
00:22:58.440 For instance, the body must be a string and the date must be a valid date object. These classes provide a foundation to add custom methods, extending the behavior of our objects. We can be confident these methods will work, as the entries can only initialize with valid attributes.
00:23:26.680 Once initialized, these entries are immutable and won’t change, offering a robust way to enhance our values. This means we can pass them across our application without worrying about mutations or side effects.
00:23:53.440 Now that we've sorted this out, let's return to our create_entry class. Since it can conditionally succeed based on validations, we need to properly express the opposite result when it fails. For this, we’ll use Dry Monads.
00:24:22.600 If you saw H's talk earlier, we now have a ready-to-go approach for error handling. By using Dry Monads, we can cover both success and failure as two sides of what we call a 'Result.' This creates a wrapper around our return values, ensuring the caller considers both success and failure cases.
00:24:47.160 This promotes a more consistent approach to error handling, treating failures as first-class citizens just as we handle success cases. Now, we can take advantage of Ruby’s built-in pattern matching with our results.
00:25:14.200 So, as we achieve these results, we create the possibility of modeling pipelines that conditionally execute methods. That’s where Dry Operations come in. We're building a gem to make this all easy and it’ll be ready in the coming months.
00:25:39.840 If you're at Masa Fumi's talk this afternoon, you can see how method hooks work in the wild. Our goal with Dry Operations is to provide a battery-included approach to creating service or command classes, like the one we’ve been constructing.
00:26:01.760 Let’s look at another class; this one takes our player log and converts it into a printed journal, maybe in scroll form. Using Dry Operations, we start by inheriting from the class. Since this operation is quite involved, we’ll break it into steps to call one after the other: finding entries from our player log, printing them onto pages, and finally binding them into a book.
00:26:26.000 This way, we build a pipeline from the individual steps in the operation. If every step is successful, the operation completes; however, if a failure arises, the pipeline short-circuits and returns that failure immediately, skipping subsequent steps, providing safe, expressive Ruby.
00:26:56.240 While this behavior may seem like magic, it’s the good kind of magic. By combining small, flexible gems, we achieve something greater than the sum of its parts. That’s the ethos behind DRY B.
00:27:20.360 If this all sounds intriguing, we boast nearly 30 gems you can explore. They’re designed to work with Ruby apps of all kinds. So, if you work with Rails, a world of opportunities awaits.
00:27:40.440 We can enhance your app’s configuration with types, improve controller parameters using schema validation, and build service objects with standalone validations. This highlights the power of combining flexible, composable design philosophy into our Ruby applications.
00:28:06.760 Creating gems provides a great opportunity to practice building systems from reusable parts. The Ruby ecosystem may feel established after 30 years, but there’s room for new and interesting approaches.
00:28:35.960 Every one of us has a unique perspective worth sharing. We’ve seen examples arise from this conference, like the view component shared yesterday that barely existed a few months ago. Fresh takes on the ageless concerns of views and databases.
00:29:04.640 This proof suggests Rubyists can continually find new methods to construct our applications, allowing our ecosystem to evolve and remain relevant. After this enlightening journey, we discover there's a dungeon that will only permit our entry once we've mastered the virtues of the Rubyist.
00:29:35.440 This dungeon, we understand, contains the Codex of Ruby Wisdom—a text that will reveal the future of Ruby. I believe we're ready to seek it out! We've demonstrated curiosity by paying attention to the happenings around us and finding opportunities to engage with our community.
00:30:03.800 We’ve shown cooperation through forming a party and working together. Finally, we've exhibited cognizance through our development of foundational knowledge in Ruby as well as establishing a robust collection of gems to better structure our code.
00:30:36.240 Now it's time to explore this dungeon and confront whatever dangers lie ahead. After sailing long and far, we found this secluded island featuring an entrance. Let’s go in; we’re almost there!
00:31:04.080 Upon entering, we’re met with an unexpected challenge; it is pitch dark! Who can cast a light spell? That should do. That's better! Wow, what a new perspective—this dungeon presents new challenges, revealing anonymous passageways we'll need to navigate to reach the depths to find the Codex.
00:31:35.540 Along the way, we must utilize all skills and tools we've developed. These dungeons present a heightened level of challenge! You're likely wondering how we’ll know what combination of tools and techniques to deploy during these trials.
00:32:09.520 Fortunately, some work has already been done to solve this problem: Hanami, the modern, flexible application framework for Ruby. Even better, a major new version has emerged after these groups teamed up to build something wonderful.
00:32:51.160 Hanami 2 is exactly what we need in this dungeon as it brings together the tools and concepts we've learned and curates them into an easy-to-use framework experience. To employ Hanami effectively, however, we must learn how to use it, and this is the last technical exercise to prepare us.
00:33:26.040 We’ll begin by installing the gem and running a command to create a new app. In keeping with themes we've explored, we’ll name our app 'Player Log'. At this point, we can hop into our terminal and initiate our Hanami development server to see a friendly welcome screen.
00:33:56.040 The welcome screen will guide us through everything we might need to know about Hanami. Luckily, we’re all here together, so let’s investigate the app class itself. This is straightforward, and we won’t spend long on it today, but there’s one key point: all code in Hanami resides within a single namespace corresponding with the app's name.
00:34:24.720 Inside this module, we define our routes. The first step in making our app functional is to establish a route. Using Hanami’s generator, we can easily create an action now for our entry index.
00:34:56.760 Returning to our routes, we can see the generator has set up a route for us—a GET request to 'entries' directs to our index action. Each action class handles our app's HTTP interactions, and it’s the handle method that controls how it behaves.
00:35:38.880 In this method, we can inspect request parameters or adjust the response parameters. Speaking of parameters, action supports flexible validation schemas to ensure that they meet our expectations regarding structure and type. For instance, our index action can have an optional parameter for page number that must be an integer greater than zero.
00:36:07.440 This beautiful syntax is familiar because it builds upon Dry Validation—a gem already in our toolkit. We’ll see here the range of features it offers for handling HTTP layers, including automatic view rendering.
00:36:38.280 Next, let’s explore the view. Like actions, each view in Hanami has its class. We can prepare the values we intend to pass to its template here, forming the body as the entry for each list item.
00:37:10.280 That means we need to fetch the entries' logic! The job of fetching is where we encounter one of Hanami’s important features: the app acts as a container! The container serves as a central hub to load and offer access to all components in our app.
00:37:41.920 Here we have our view, fetching it by utilizing a key that matches its name. What we retrieve back is an instance of that view prepared for us to engage with. The same is true for our action, which we access through its own key in our routes.
00:38:06.640 The principle extends to any class placed within the app. In this example, let’s create an entry repository that can return a list of entries reminding us of our app structure.
00:38:29.840 Now, we’ll assemble a method to identify and load the latest entries from our database. This function relies on the robust query API from ROM, which has matured through years of real-world use.
00:38:53.440 Thus, we’ve constructed two components to play. How do we unite them? We leverage the Hanami depths mixin, including it within any class in Hanami. This mixin allows us to list components we want, rendering them as accessible instance methods where needed.
00:39:21.760 Utilizing depths promotes creating high-level behaviors through uniting various focused components. Furthermore, this experience is lightweight and low-friction. Thus, we can now pass real entries from our repository directly into our view.
00:39:51.920 We can return to our terminal while the development server is operational, make a request, and behold our entries displayed as HTML! We've realized our first functional feature with Hanami; and everyone, we’ve successfully reached the lower levels of the dungeon.
00:40:24.680 But this dungeon is relentless; it seems there’s a final round of challenges awaiting us. To proceed, we’ll need to master the knowledge of Hanami deeper. Thus far, we've only skimmed the surface.
00:40:51.360 Now that we’ve constructed a view, how might we test it? Like every part of Hanami, views are designed for direct testing. We can call new directly on our view class, mini-testing by leveraging test doubles for that entry repository dependency.
00:41:31.360 This will allow us to control its behavior for this test. This low-level isolation is a key aspect of testing, yet we also have integrated end-to-end testing like request specs or feature specs through Cybar.
00:42:04.040 Each Hanami object encourages clarity in testing situations. We can interact with our code via console commands! Opening up the Hanami console is straightforward, allowing us to refer to our components as instances ready for action.
00:42:22.440 The console starts quickly, no matter how extensive the application grows. That's possible due to its lightweight boot mode, which accelerates across all facets of Hanami development—from running tests to the development servers.
00:42:49.600 On boot, we load our settings. These values help our app operate correctly in each environment, including API keys for third-party services. We can add a setting for its API key, defining its type.
00:43:14.280 Using this type syntax, we confirm each setting and ensure our app operates consistently without risk of unpredictable states.
00:43:34.280 After setting, Hanami loads these configurations from matching environment variables; locally, they can even come from environment files. Following this, we can access the loaded settings as new components within our app. This includes methods for every defined setting, ensuring accessible value retrieval.
00:44:01.480 Focusing on providers, we can create one for our email service. Having defined our email service API key, we now require the gem for our service and grab the API key from the settings to configure an instance of the client.
00:44:29.440 We then register that instance within our app using the email service key, meaning we can now utilize email service as a dependency in any class with a pre-configured instance.
00:44:52.200 We’ve covered a lot about Hanami, but now let’s delve into an essential feature: 'slices.' Slices help us architect our app into separate domains—or technical concerns—providing modularity and clear internal boundaries as our applications expand.
00:45:20.560 When we create a new Hanami app, we won’t see these slices right away because we want to keep the initial experience as simple as possible. But they're built in and come into play once we begin placing code in the slices directory.
00:45:46.720 Using our handy generator, let’s proceed by creating an admin slice. This will prepare the admin slice directory, and we can now include a 'products create' entry action within it, ensuring every slice aligns with its own Ruby namespace.
00:46:11.760 This slice works similarly to our app, providing access to components, but it only loads those components from its own directory.
00:46:35.120 Both the app and slices act as containers for our components, and thanks to our depths mix-in, we can clearly recognize the dependencies between these components. This process allows us to view our app as a directed graph.
00:46:52.480 From this groundwork, slices become capable of importing components from other slices, enabling us to envision an even clearer graph highlighting our app’s significant concerns.
00:47:13.120 This represents a powerful trend in our framework toward robust organization of our code, promoting understanding and maintainability at every level. Not only that, slices allow operational flexibility; helping us intelligently choose specific slices to load during app deployments.
00:47:38.760 Now let’s delve into the gem file of a new Hanami app. Initially, we’re seeing a web app because it includes necessary view, router, and controller gems. However, these dependencies aren’t fixed; they reside in the gem file.
00:48:00.600 Say we’d like to construct something other than a web application? All we need to do is remove the additional lines, and with just one remaining Hanami gem, we keep the quality-of-life features—containers, components, depths, settings, providers, the console, and even slices.
00:48:25.280 This means we retain everything essential while eliminating what is superfluous, offering versatility for any application we develop. In this regard, Hanami 2 becomes more than just a web framework—it’s an everything framework!
00:48:51.960 Should we decide to build a CLI, serverless function, a stream consumer, or even a new take on an IRC bot, we still enjoy all conveniences from a complete framework, empowering us to embrace good design principles.
00:49:15.520 In my experience with Ruby, I don't think I've ever seen anything quite like this! I'm incredibly excited to witness where others will take it. With everything we need to clear this dungeon with Hanami 2, we've demonstrated Rubyist virtues.
00:49:39.840 Demonstrating curiosity by examining the entire Ruby framework story, displaying cooperation by recognizing shared ground between open-source contributors, and proclaiming cognizance through our mastery of good Ruby principles.
00:50:05.960 Finally, we find ourselves in a long, straight corridor. We tread wearily yet filled with hope, making our way to the end—and it appears the dungeon reveals something to us. An altar appears before us—should we check it?
00:50:30.560 Upon the altar lies the Codex of Ruby wisdom. As we open it, we read, 'The most expressive programming language in the world is the one we hope to write someday.' The next expressiveness comes from whatever we can turn Ruby into in the meantime.
00:50:52.440 These words originate from my friend Nathan Lad—a longtime Rubyist—who beautifully expresses the wonder and potential of our language. As we wander the vast, open spaces within Rinia, for the individual who first encountered an open-world role-playing game in '85, the possibilities must have seemed endless.
00:51:17.760 And indeed, that parallels the world of Ruby today; the possibilities are endless. Ruby flourished because it embraces possibility, and if we reciprocate that embrace, those possibilities are ours to create.
00:51:39.520 As individuals, we can take these steps at whatever point we feel ready. For me, it took 15 years of daily engagement with the language before I found myself aligned with the right people at the right moment to discover how I wanted to craft Ruby's future.
00:52:02.240 Ruby's been waiting patiently for all this time, and now, as it reaches its Golden Age, we can confidently express it will be waiting for a long time to come. The journey is long, and we should embrace it and enjoy the ride.
00:52:29.280 Before I finish, I want to share my thanks and outline potential next steps. Firstly, I acknowledge the people who contributed to the projects I shared today—Luka, Peter, Nikita, my teammates, and all those who contributed to Hanami 2 development.
00:53:06.880 A huge thank you to the organizers of Ruby Taiwan for hosting this incredible event and inviting me to contribute. I also express my gratitude to Ancient B.S. for granting me permission to use their music to set the scene for our journey today.
00:53:27.760 It's an honor to speak with you today and to close this event, especially while sharing three of my greatest loves: Ruby, retro games, and epic operatic metal. For Ruby, I'm more excited about the future than ever before due to the ongoing work in these projects.
00:53:46.480 If you’re interested in learning more about any of them, I encourage you to check out their websites; we have fantastic documentation. Hanami also provides getting started guides to help you create your very first app.
00:54:07.360 There's much to explore as Hanami has become our primary focus lately. I’m thrilled with how things are shaping up; Hanami 2 has been out for a year and is gaining good traction, with support for version 21 already in the release candidate stage.
00:54:28.920 So, perfect timing to check it all out! As for me, I’ve had a fantastic time meeting many of you at the conference and would love to hear your feedback on your journeys with Ruby. Thank you very much!
00:54:49.920 Tim Riley.