00:00:07.160
Right, hello everyone! Can you hear me all right? Okay, so I'm Tim, visiting today from Canberra, Australia. I love living there; it's a great city, but it has been the tail end of winter. I have really enjoyed being here in Bangkok these last few days; the weather and food have both been refreshingly hot. I'm really excited to be here to help kick off this conference, so thanks to all the organizers for making this happen, and thanks to all of you for being here today. I also need to say a big thank you to my family for giving me tons of time to prepare this little talk that we’ll be hearing.
00:00:36.060
Back home in Australia, I work at a company called IceLab, where I write a lot of Ruby. I also contribute to open-source projects, which share a common goal: offering a fresh approach to helping you build more maintainable Ruby applications. We’ll look at some of these in detail today. I want to point out that my work on these projects didn't just come out of nowhere; it has been a long journey. I have been working with Ruby since 2001, which is quite some time, and time really does fly.
00:01:05.370
To reminisce, I went back into my old collections and pulled up a kick from those days just to see what I looked like back then. I couldn't believe how young I looked, considering it has been 18 years since that time. Being 35 now means I’ve been writing Ruby for over half my life. Continuing this little trip down memory lane, I even found the code for my very first Ruby project. This was before Rails and before RubyGems, and it was a little project that wasn't about the web at all; in fact, it was an IRC bot. Back then, I had a group of friends whose hobby was to hang out and write these things in completely inappropriate languages.
00:01:57.660
Even though I was young back in 2001, I already knew that BASIC wasn't for me. I noticed a friend who had started experimenting with Ruby, and it really did look nice, so I gave it a go. That’s what I used to write my little IRC bot. Looking back through the code today, however, it seems like a strange relic from a foreign age. From things like my camel casing in the filenames to some perhaps more egregious beginner mistakes, like building a whole plug-in system based on global variables, it is amusing to see my old code. I even noticed there was a complete absence of empty lines; every single class looked the same, with not a bit of white space to be seen.
00:02:42.390
Yet, looking back on my 11-file application, I can confidently say that I have come a long way from that! The amazing thing is that, even after 18 years, I am still writing Ruby, and I still love it. I couldn’t have found a better programming language to accompany me on this journey as a developer. Today, I want to share my thoughts from a lifetime of living with this language, Ruby, and address what excites me about it today. We will be tying together a whole bunch of threads throughout this talk.
00:03:14.700
So let me take the stage for you. Once upon a time, in the land of Rubinia, after enduring years of unrest, the citizens of Rubinia have at last arrived at a time of peace and prosperity, thanks to the dedicated oversight of Lord Mattes and the Knights of the core team. Villagers are thriving, the harvest is well-managed and bountiful, and with every passing cycle, new facilities are built and spread across the land for all to enjoy. Most of the ancient pockets of trouble have been destroyed, with the only remaining hazards being the occasional stray band of marauding bugs.
00:04:00.060
When you arrive as a Rubyist in Rubinia, your quest is to find and master the virtues of the Rubyist, then wield them to foster a new standard and create a shining vision to lead all of Rubinia further into this new age of light. Your quest will not be simple; it will not be easy, but it will be noble because your destiny is Rubinius, and Rubinia awaits you. So let’s begin the quest of the Rubyist!
00:04:46.260
I believe the perfect way to explore this stage of Ruby is to enter a big open-world computer role-playing game from its very own golden age, in 1985. Since today we are set in this golden age, we have no big evil arch-nemesis to defeat; instead, our quest as Rubyists is to exemplify a good life, learn and master the virtues of the Rubyist, and help create a better future for everyone. Today, we will go through this game together, and as we do so, we will do two things: first, we will find out the steps we need to take as we navigate through this game; second, we’ll see how, as Rubyists in our world, we can take those steps to create a better future for ourselves and those around us.
00:05:54.990
Some of you might have noticed that I’ll be heavily inspired today by "Ultima IV," the 1985 role-playing game by Richard Garriott. But if you're not familiar with this style or era of gaming, that’s okay; we’ll be talking a lot about Ruby. This game is simply going to help us connect the threads, so I hope you can sit back, relax, and enjoy this adventure!
00:06:31.590
The first thing we want to do in Rubinia is explore the world—understand the landscapes and the townships and meet the people, who are all really friendly and very happy to talk with us. In fact, engaging in dialogue is one of the most critical actions we can take in this world. Through dialogue, we begin to learn how this world works and discover things that we might not yet know about ourselves. This is how we learn what role we have to play—how we can be part of this ecosystem. But what does this mean for us as Rubyists in our world today? Well, first, I think it means we need to make an effort to observe what is happening around us in the community.
00:07:40.010
We need to observe the discourse, listen to the thoughts being shared in the community, and engage in conversations in forums and chat groups. These discussions reveal not just what people are thinking on the surface, but they also shed light on the values and priorities underpinning our community. For example, here's a talk I watched in 2015 that completely changed the way I thought about Ruby. I only found it because I chose to follow some Rubyists online whose work I found interesting. We can also observe the actual work being done within our community. That particular talk was given by Tom Nades, and I found his ideas were informed by the work he was doing. From there, it was just one click away to see his ideas played out in concrete code on GitHub, which I could explore myself.
00:09:07.740
I believe we should take the time to actively observe as well. Exploring code can often be some of the most instructive reading we can do as programmers; it helps ground our ideas in different techniques and opens our eyes to different problem spaces. Doing this with Ruby feels like a superpower! With commands like `bundle open`, it’s easy to dive into any piece of code running an application, and even after 18 years of using this language, this is something I still do every day. If this is not already part of your regular programming flow, I highly recommend you make it so.
00:09:57.780
After all that observation and watching, there’s just one thing left for us to do: we need to close the loop. We need to reach out and engage with the people we want to learn from or work with because the smallest constructive steps can make a big difference. For me, that meant sending a simple email to someone, which led to a long-term collaboration. Programming is a collaborative, social activity, and for our work to make the biggest difference, it needs to happen within teams, communities, and society. It’s why we're discussing these points today: the skills to work within any given society are the same skills that lay the foundation for a happy programming career. These are not soft skills; they are core skills, and they’re worth pursuing.
00:11:10.060
In the Ruby community, we already have some guiding principles that can help shape our actions. It’s good to remember these principles every day, especially as we continue our explorations. As we move forward, let’s say we encounter a nasty bug—that’s alright! As Rubyists, it’s our job to find and squash these bugs. To do this successfully, however, we need to understand our equipment. This brings us to our first technical exercise of the day: we’re going to start by understanding the purpose and dangers of Ruby's mutable state.
00:12:05.080
When we build an application with Ruby, we think about working with objects, where every object is an instance of a class. For example, we might create an entry for our player's log, assign attributes, save the entry, and away we go! It seems pretty straightforward, but there is one key aspect to notice here: we just mutated the objects’ state.
00:12:38.580
When we assigned those attributes, we used methods known as attribute writers. Behind the scenes, these methods will assign or modify the object's instance variables, which effectively make up the object’s state. By modifying these values after the object has already been created, it means it now has a mutable state, which means it’s possible to change the state of any of these objects over time. While this works in simple scenarios, we all know things can get complicated quickly, especially as we start building out this application.
00:13:36.570
These features may seem convenient, allowing us to get a long way in a short period of time, but they can create complexity, bugs, and even technical debt. It’s crucial to recognize that some of these problems arise when we have classes whose objects try to play two distinctly different roles. We have objects that hold data and also ones that perform actions. In our case, our log entry object is one of those that holds data due to the attributes it has, while also performing actions because of the methods it offers.
00:14:59.530
You could argue that combining these roles is central to object-oriented programming, but the best intentions in encapsulation can still lead to problems. This approach conflicts the objects’ behavior with the notion of time, meaning we need to know every possible combination of objects and permutations of their states at any given moment. Anyone who's dealt with large Rails apps knows some of these challenges.
00:15:36.520
So how about if we separated these two roles? We could model our player log entries simply as objects that hold data. Then, we could create a dedicated class just for creating those entries, which would define zero internal state—no instance variables at all. This arrangement satisfies the single responsibility principle, where each piece of code has less work to do without mutation to worry about, leading to fewer bugs.
00:16:54.490
We have a much simpler arrangement now; we've achieved the same outcome as before while gaining several benefits. This updated architecture allows our `create_entry` to have only one reason to change if we want to add new behavior dealing with entries. It’s clear that the best way to do this will be by adding other single responsibility classes around the one we already have.
00:17:19.990
This separation of objects that hold data from objects that perform actions leads us to another important realization: objects that hold values, and objects that enact functions can coexist in harmony within our code. We’ve just kept creating those foundational aspects of object-oriented programming while moving toward some fundamental building blocks of functional programming.
00:17:59.640
What we've arrived at is a style of Ruby that blends both object-oriented and functional programming. This approach serves us well in Rubinia and provides us with the right tools to squash bugs. Now that we’ve had our first encounter handled, we can continue our explorations.
00:18:44.180
But now that we know how to handle those dangers, it’s best we don’t travel alone; we would be much stronger as a group. So we need to gather our party! As we visit towns and talk with people, we’ll be on the lookout for those who would be interested in joining us. These are the people who can complement our capabilities and will certainly help us become better Rubyists.
00:19:13.080
What could a party of Rubyists look like? Let me share a few folks who have been part of mine at different points in my Ruby life. First, we have some connectors, like Sue, whom I met in 2007. Back then, there was a website called 'Working with Rails,' where you could register and list yourself as a Rails programmer. It was small enough that anyone interested could scroll through and see everyone registered. Sue reached out when he noticed I had moved to his hometown; he helped me get my first Ruby job! Then, we have Matt, who came to some early meetups and introduced me to the broader Australian community.
00:20:07.430
Moving forward in time, we meet our teachers, like Yo, whose talk I shared earlier, which was so formative for me, and Nikita, both of whom were my teammates on the Dry Bean projects. I wouldn’t hesitate to say they’re some of the smartest developers I know! I learned so much just by working through problems with them and observing their coding techniques. We all need a party a little like this as we explore Rubinia.
00:20:56.280
Now, luckily, we squashed that bug before, as it was just one little creature coming at us alone. We know that, if not, we could encounter many of these bugs all at once, and that’s where having a party is essential. For our party to work effectively, we need to take our previous work to the next level. So far, we’ve tackled things on a very small scale, but what if we needed to express some complex behavior? This is where we have to bring in composition.
00:21:54.580
Previously, I mentioned that we can have a single responsibility class. I did cheat a bit because I ignored the `create_in_db` method. We need some behavior provided externally so that our entry creation retains its focus. This is where we rely on dependencies, capturing them in what we call state. Now, we can create the entry in the database using the `create` method from our entry repository. By instantiating `create_entry` and passing in the repository, we retain the dependencies' focus on their core actions.
00:23:03.290
With this done, we can now use the entry object multiple times with different inputs, and it continues to work as expected. This is the essence of composition: instead of embedding new behavior into existing classes, we provide it through separate objects and compose those objects together to ensure behavior is available where needed. This method also utilizes dependency injection, a classic OOP technique that fits well with our functional style objects.
00:23:44.850
Thanks to this composition approach, we maintain each class’s singular focus while making it easier to add functionality to our application. This flexibility enhances our overall app architecture. Next up, let’s look at Dry Validation. Suppose we want to ensure we only save our entries if all required attributes are present; this is where Dry Validation shines. This tool allows us to define standalone validation contracts for our entries, ensuring both the body and date are provided and correctly typed.
00:24:57.760
After defining our validation contract, we can include it in our `create_entry` class’s list of dependencies. In our core method, we can run the input data through the contract and only save the entry if the validation succeeds. Now that we’re considering valid entries, it would be ideal if we could model them neatly as we pass them around our app, since we’ve only been using a plain hash so far. This is where `dry-struct` and `dry-types` come in to help. We can create classes to represent our player log entries, establishing strictly typed attributes and allowing additional methods related to our data.
00:26:39.230
These classes ensure that attributes can only be initialized if all type expectations are met. This immutability simplifies passing the entries around our application. With the validation now conditional on valid data, it’s time to express both sides of that result. Simply returning nil isn't sufficient for handling the validation scenario, so we can utilize Dry Monads to wrap our `create_entry` results in either a success object or a failure object.
00:27:36.090
Now, when we call `create_entry`, we'll receive either one of those outcomes—success or failure. We can then use pattern matching to manage both cases in our code. This approach improves our application’s robustness by making failure handling a first-class concept, just as important as managing the success path. Additionally, success and failure are both instances of the result monad, allowing us to form pipelines that execute conditionally. By employing this, we can express operations naturally in Ruby.
00:28:14.470
If every step in this pipeline returns success, it executes all the way to the end. The moment a failure occurs, the pipeline short-circuits, returning the failure immediately, skipping subsequent operations while supporting safe, expressive flow in Ruby. This is the kind of magic we should embrace! It’s the consequence of employing small, focused, flexible gems—valuable on their own but even more potent when combined. These gems serve as the reagents enabling us to cast various spells to navigate our future challenges.
00:30:02.660
What about creating our own gems? Every gem starts from somewhere; Dry Herbs began solving a common problem, and some have remained small like Matcha. If you identify a recurring pattern or behavior in your code, I encourage you to extract and publish that. Each gem offers an opportunity for us to level up our skills and enhance our abilities to build reusable systems. The Ruby ecosystem may seem established, but there is always room for new insights and contributions.
00:31:07.370
Having gained this experience, I'd like us to explore one more area on our journey. Rumor has it that there's a dungeon that will permit our entry only once we've mastered the virtues of being Rubyists. I believe we are ready! We’ve demonstrated curiosity by observing and engaging with our community. We’ve shown cooperation by forming a party and working together, and we’ve exhibited cognizance in mastering Ruby’s fundamentals alongside helpful gems.
00:32:27.730
And now, we stand at the dungeon, where our final task awaits us: to find the Codex of Ultimate Ruby Wisdom, which promises to foretell the future of Ruby. The catch is, we must take it upon ourselves to build the future we envision. In our journey thus far, we've learned many valuable concepts, and we can start to piece something together. One of our party members expressed a fair question: how do we know what combination of tools and techniques to use at any given moment?
00:33:29.780
Fortunately, some groundwork has been laid by the Hanami team, which offers a modern, complete web framework for Ruby. Established by Luca Aquarian, we’ve realized how similar our goals are, so the three teams have united to work on delivering the next major release of Hanami. This new version will curate all the concepts we’ve discussed so far and wrap them up into a cohesive, user-friendly framework.
00:34:51.290
In Hanami 2.0, we will focus on immutable functional objects, working together efficiently. We incorporate Dry System at the core, allowing you to build entire applications around these principles. As we break down applications into layers for various functionalities—routing, HTTP, view rendering, and database persistence—Hanami will use ROM to provide powerful repository objects right out of the box.
00:35:35.920
We'll also enable decomposing applications into distinct subsystems, each representing a specific area of concern. Within each subsystem, we can easily use and import others as needed, creating a dependency graph of our applications that is clear and easy to navigate. When a request enters a Hanami app, we can trace it from start to finish without encountering unsafe mutations.
00:36:42.370
This clarity makes it a joy for developers to understand and maintain these applications, both in the small and at scale. As applications mature, we can also adjust our individual components without feeling constrained. Hanami 2 will be a framework of the future, employing modern Ruby tools and respecting our applications' evolution.
00:37:50.340
We have laid down great steps for the future within this dungeon together. Once again, we’ve demonstrated the virtues of Rubyists by being curious about the current frameworks and the challenges developers encounter. We’ve shown cooperation by forming alliances between Ruby open-source groups. And through our practice of principles, we’ve embedded our best Ruby ideas into the framework.
00:39:06.370
Returning to the altar, we see the Codex lying before us. Upon opening it, we read the last page: "You never know what’s going to happen when you set out on an adventure." Today, we’ve spent our time exploring the vast landscapes of Rubinia, just as those adventurers in 1985 must have felt the infinite possibilities before them. Now, with Ruby, those possibilities are indeed endless. Ruby is a language that has flourished, endured, and embraced possibility, just like us!
00:40:12.080
As we each take our steps forward, I am hopeful that every single Rubyist will find their path and opportunities. The journey may be long, but there will be countless opportunities for us to craft and embrace a future we want to see. Thank you all for joining me on this ride today! I also extend my appreciation to Ancient Bards, a favorite band of mine, for allowing me to use their music as a backdrop for this conference. It’s a joy to kick off this event while celebrating three of my greatest loves: Ruby, retro games, and epic orchestral metal. I am more excited than ever about the future of Ruby, largely thanks to the combined efforts of all these wonderful groups here. I hope you have a fantastic conference, and I look forward to chatting with many of you about our shared journey with this wonderful, quirky language of ours! Thank you so much.