Talks
Reinvesting in Ruby

http://www.rubyconf.org.au

As our community matures, and technology evolves around us, how can we ensure Ruby remains vital?

Speaker: Tim Riley

RubyConf AU 2017

00:00:09.920 Cool, okay, thank you for bearing with that. Hello, I am Tim, and it really is great to be speaking here today, and also to be speaking before this wonderful community that I'm lucky to be a part of.
00:00:15.440 Today, I want to look at three broad things. I want to examine Ruby in the present and how we got here. I also want to consider Ruby from a different perspective and finally explore what we can do to build Ruby's future.
00:00:29.679 So let’s start by talking about Ruby in the present moment and about our community here in Australia. We have come a long way—this is our fifth straight Ruby conference and it’s the biggest ever.
00:00:42.760 Another really amazing fact is that we’ve just had our 20th Rails Camp, which is an event that we've collectively run together for ten years straight now. This is a stunning achievement and a real credit to everyone who's here.
00:01:00.960 Ruby itself has come a long way too; it's over 21 years old now. I thought we could take a quick look at how we got here, so to get started, we need to go back to the 90s—1995 in particular.
00:01:12.320 So there we were, working on our 33 MHz machines with one screaming single core, writing some HTML 2.0 and working from some CGI-bin directories. Oh, well, that looks interesting! This thing called Ruby just had its first release.
00:01:26.479 Then, six years later, in 2001, we all put a new book on our shelves; one with this pickaxe on the cover. 'Programming Ruby' counted for a lot in the history of Ruby; it showed the world that Ruby was really worth paying attention to.
00:01:43.600 And then Rails happened, and that changed our world forever. Version one came out in 2005, and we were all making blogs in 15 minutes. Then one thing led to another, and here we are, 12 years later in 2017, with so much to celebrate.
00:01:55.880 Ruby has gone from strength to strength; it's running our text editors, and it's running our phones. It is everywhere! The number of jobs is trending up and to the right, showing no signs of turning back.
00:02:20.160 Rails has scaled in the hearts and minds of web developers everywhere. The term 'rails' no longer represents website downtime; they're just the majestic sea creatures that they are. And those Rails Camps we hold—people are still going to them to write Rails.
00:02:39.760 We've truly made it! Now, this is not quite our present. Instead, I think Ruby faces a number of challenges.
00:02:48.560 The first is competition. 1995 was a remarkable year for programming languages. We saw the first releases of Java, JavaScript, PHP, and Ruby—all of these still in widespread use today.
00:03:01.920 But since then, we all know the computing world has changed; Moore's law has leveled out, and Ruby fell on the wrong side of it. It’s a multicore world now, and Ruby doesn’t really have a strong story on parallel programming.
00:03:14.599 We’re seeing many other languages that do have a strong narrative; take Elixir for example. I’m sure we all know more than a handful of our Ruby friends who work with Elixir in Phoenix whenever they get the chance.
00:03:26.560 And that’s not the end of it. In the last decade, we’ve seen functional programming become useful for more everyday tasks. Languages like Erlang and Scala help us model our data and systems in practical and robust ways.
00:03:39.760 Just like Elixir, they open up new possibilities for concurrency in our apps. Then we have the compiled languages too: Rust and Go. These were originally penned as systems languages, but they're seeing much wider use because people want their code to run reliably and fast.
00:03:54.120 And of course, we have JavaScript. Our friend JavaScript is not only on the server now; it's literally everywhere. The thread that ties all of this together is that building for the web is such a common thing that all these languages and their communities have an interesting story to tell about it.
00:04:05.799 Meanwhile, over here in Ruby, I think we’ve been telling the same story for quite a long time. We’ve settled into a comfortable groove around Rails. I have great respect for the Rails project and the team, and we have much to thank them for.
00:04:19.519 It’s still a very compelling platform for many people today. But I think the truth is that, as a result of Rails' wild success, it has come to dominate Ruby—through no fault of its own—but in many ways, it's become our opiate, dulling any widespread effort in our Ruby community to push our app architecture in new directions.
00:04:39.479 We’ve also seen Ruby make a transition. As our language and our community mature, many of our pioneers have left. Those people who led the way in the beginning and laid the foundations for our ecosystem have moved on to other communities and other projects.
00:05:09.520 Many of our friends have left, and given how social software is, this is no small thing. We’ve reached the point where we can actually be comfortable but also complacent. That busy, heady period of early adoption is long gone.
00:05:21.560 Our foundations have been laid, and we’ve reached a time when many people—and more than ever before—are quietly getting their work done with these largely settled tools. DHH calls this reaching the late majority.
00:05:40.680 In a way, it’s a deserved place, and it’s come off the back of decades of toil. What do we have to show for this? We have a lot of mature tools. So many things—we have Rails, of course. We have Bundler, RubyGems, Rack, RSpec, Capybara—this list goes on.
00:05:59.720 Many of these tools have gone on to serve as inspiration for similar tools on other platforms, which is truly a credit to Ruby. We have a healthy culture too. Just like Liam and Mel were talking about, Rubyists are practical developers.
00:06:20.200 We have a real focus on getting things done, and of course, we have a real focus on testing as part of our practice. I’ve learned so much about testing and TDD through my time as a Rubyist.
00:06:36.280 We also focus on making our tools a joy to use. This concept of programmer happiness is not a given; it’s part of our DNA as Rubyists. We also work to make our culture ever more inclusive. We care about more than just technology; we also consider its broader social consequences.
00:06:54.160 These elements have real value. There’s so much that’s good in our community, and these are things that are worth preserving. Now, you might ask, is anything really at risk?
00:07:09.760 It’s true: yes, we’re now in this crowded world with all these other language options, but we’re already onto a good thing with Ruby, right? Do we need to change anything? I think we actually do because our community is really its people.
00:07:30.560 We need to keep our people engaged, and we need to do this because open source communities are non-exclusive. People’s attention will always be spread, and prior achievements only really count for so much.
00:07:49.760 Matt sees this too; he’s put it this way: open source communities are like sharks; they need to keep moving forward. So we should reconsider Ruby in this light—look at all those other things that are capturing people's attention and see how we stack up against them.
00:08:05.279 We should ask ourselves if we're still offering enticing, innovative, and productive ways to build software for the modern web and beyond. So at this point, we might pause to reflect upon Ruby itself and ask, how do we stack up against all those modern languages?
00:08:21.240 When we have our design constraints for Ruby still anchored in that '90s single core world, how can we catch up to those other languages when it’s a stated goal of Matt’s and the Ruby core team to evolve our language only slowly?
00:08:36.600 I think firstly we can be grateful for that. A stream of steady improvements and strong compatibility from release to release is a good thing because it keeps our community together and using the latest that Ruby has to offer.
00:08:54.560 We won’t have to face any schisms around major language readjustments in our future. And that is a real problem that plagues other languages. It means that when the big improvements do come, things like Ruby 3’s promised concurrency and typing features, adoption should be more straightforward.
00:09:10.400 And we can all move forward together. But we don’t need to wait for this because Ruby is ours. It’s an open ecosystem, and our contributions count.
00:09:24.000 It was the contribution of Rails in the early days that brought Ruby that explosion in popularity and then set up everything else that followed. Yes, we do reap the benefits of this famously friendly, pliable language at our core.
00:09:39.760 But it’s what we’ve built around it that makes it attractive to so many people. This pliability of Ruby has given us room to explore how different modes of productivity might look, and this kind of exploration has led to real shifts in how Ruby is used over time.
00:09:57.920 Ruby's idiom can change, and it has changed. We had the early days: that wild west moment back in the beginning when people were coming from all different backgrounds and just figuring things out.
00:10:12.160 Apps back then took many forms, and many of them wouldn’t resemble the kind of Ruby we expect to see today. That’s because after Rails came along, our idiom really crystallized around Rails’ own style and design emphases.
00:10:26.800 As we gained more experience, our apps grew up, and we sought even greater consistency. We hopped onto the idea of shared common style documented through the Ruby Style Guide and enforced by tools like RuboCop.
00:10:42.760 But now, I think we’re starting to see another shift in this timeline as ideas from other languages and ecosystems start to be explored in Ruby, resulting in code that takes yet another form.
00:10:57.560 We’re starting to see a different kind of Ruby, and this is what I’d like to share with you today. I’d like to introduce you to Dry.rb.
00:11:10.240 It’s an organization of gems founded in late 2015. We call them next-generation gems because they’re informed by our experience in working with Ruby to build for the web. Over many years, they’re our attempt to nudge the idiom a little to offer a new and better way to build your apps in Ruby.
00:11:22.000 Now, I’ve been working with the Dry.rb core team every day for the last year and a half, building out these gems as well as using them to ship real working apps.
00:11:35.920 The reason I want to share this with you today is that I think it holds a few keys as to how we can build a better future for Ruby. So that's been a lot of talk, but this is a programming conference, right? I know I love talking through some code, so let's talk through some code.
00:11:54.680 Let’s shift our minds a bit and take a look at Dry.b. One important thing to know first is that Dry.rb approaches Ruby from a slightly different perspective.
00:12:10.160 We follow a hybrid of functional and object-oriented programming. If we think about functional programming, what are a few things from that world that can help our Ruby apps?
00:12:27.440 Firstly, I think if we want to orient our app around functions, we need to treat those functions as values so we can combine them to build larger systems.
00:12:42.760 Our app becomes simpler to work with if we favor immutability wherever possible, avoiding any sort of internal mutable state in our objects. We also want to avoid external side effects as much as possible.
00:12:54.680 Together, these things help us better understand the flow of data through any part of our app. We can satisfy all of these criteria by building functional objects in Ruby. Let’s take a look at one now.
00:13:05.040 Here’s the simplest functional object: it has a core method just like Ruby's own procs and lambdas. That core method accepts an input and returns an output; that’s it.
00:13:17.280 It doesn’t mutate the object's state and shouldn’t mutate the input data. Unlike classical objects, functional objects make a clear separation between data and behavior.
00:13:29.680 The only data they hold in their state is other objects acting as collaborators or static configuration. This is state that will never need to change.
00:13:44.560 We set that via the constructor, only if we can, just like with this repo object. In fact, this is an example of constructor dependency injection, a classic object-oriented design technique that works effectively with our functional objects.
00:14:00.520 The end result is that we can initialize one of these objects just once and then go on to use it many times over. Regardless of how or when these things get constructed, they can become collaborators for others.
00:14:18.080 Now that we’ve come to grips with that technique, we can put Dry.rb to use and build something. Let’s build our own blog in 15 minutes here in 2017. We’re going to look at both reading and writing articles to our blog.
00:14:34.479 We’ll do it step by step, and hopefully, this will give you some sense for the design possibilities, both in the small and in the large for systems like this.
00:14:51.680 We are going to work through a lot of gems here, and together they form our application stack as it is. However, it's not a one-size-fits-all situation like we’ve come to expect in many cases.
00:15:07.200 This is why I’ll introduce the gems one by one—because they do work great together, but they’re only ever loosely assembled, so you can swap things in and out as needed.
00:15:23.680 Let's get started with the GET request so we can get our list of articles. We need to tackle four things here: routing and HTTP for the incoming request, object dependency management, rendering views, and modeling the data that we pass to our views.
00:15:37.040 The first thing in handling the HTTP request is to use a gem called Dry::Web::Router. This brings together two gems: Dry::Web, which serves as the high-level organizing umbrella around our application, and Rota, which is the flexible routing toolkit from Jeremy Evans.
00:15:55.680 To get started, we can install this gem and then run a command to generate a new project for our blog. It creates a structured project directory for you. Then we jump right into a routes file.
00:16:09.200 This is the part of the app that's powered by Rota. Rota lets us organize our routes into a tree of nested blocks, and in larger apps, this can lead to some really concise logic.
00:16:24.560 In our simple situation, we just need a GET route for our articles. Inside of it, we will render the list of articles. This view method in our routes actually loads and calls a view controller object for us.
00:16:38.320 This is taken care of by the Dry::System gem. Dry::System sits at the heart of any Dry::Web app and it’s a high-level organizing system for our app’s objects. It uses a simple naming convention to make these objects available from a central container.
00:16:52.480 What does this actually mean? It means that when we call r.view with articles index in our routes, this becomes a fetch from our container using this identifier. That returns to us a pre-built view controller object.
00:17:07.840 This object is loaded from a matching source file automatically, and that’s all taken care of for us by some basic language-level features. Dry::System relies on good old require and load path, making this loading technique deterministic, really easy to understand, and straightforward to work with.
00:17:25.880 After all of this, we have a view controller object, and this part is powered by Dry::View, which combines functional view controller objects with templates to render our HTML. Here’s how it looks to start with.
00:17:42.000 We need to configure this view controller to tell it which template to work with. Right now, that’s certainly not enough to fetch something from our database and render a list of articles; this is actually the job of a dependency.
00:17:59.760 Here we reach the other critical part of an app built around Dry::System, and that’s Dry::AutoInject. This is a streamlined way for a class to declare its own dependencies and have them resolved automatically from our application’s container.
00:18:12.880 We can set up a dependency in the view controller by including this import mixin, and we specify that we want a repository for our articles. This object acts as our interface to the database, allowing us to expose an article's listing to our view.
00:18:28.800 We want this listing to contain rich entities for our view to work with, and we can build these with Dry::Types and Dry::Struct. Dry::Types gives us a system of data types and coercions to use in Ruby, ensuring we have stricter typing in the areas that matter most.
00:18:45.080 One of those areas of concern is modeling our application’s entities. Here we have an article, which is a Dry::Struct. We want this entity to contain only a certain kind of attributes, and we want those attributes to only contain valid data.
00:19:01.560 Dry::Struct ensures that these objects get initialized only with 100% valid data, allowing us to work with them with complete confidence about their contents. This means much less second-guessing in our views.
00:19:18.800 Unlike ActiveRecord models, we don’t mutate these structs. They’re simple value objects, making them straightforward to pass around our app without the worry of accidental mutation or side effects.
00:19:33.280 Knowing all this, we can make our template call our view controller back in the routes, and we get the output that we want: our list of articles. If we look back at that routing shortcut again, remember that it’s just a shortcut for a call on our view controller object fetched from the container.
00:19:48.240 Now we have the basis for completely standalone view rendering. We can use this same mechanic not just for our web pages, but for many things across our app, like emails or anything else that's disconnected from a normal HTTP cycle.
00:20:05.880 In the end, we can come all the way back up to our routes and find that our job’s done. We've served our list of articles. We handled routing, HTTP, dependency management with Dry::System, rendered our views, and modeled our data with Dry::Struct.
00:20:22.160 Now, let’s move to the other side and see how to post an article. There are two extra things we need to cover here: validation of input data as well as success and error handling.
00:20:37.120 We can return to our routes and add a new handler for our POST request. Inside of this, we can resolve another object from our container, getting a create article functional command object.
00:20:50.480 Once we have it, we call it with the form POST parameters. Just like before, this object has been made available to us by Dry::System, so from our resolve helper in the routes, it becomes a fetch from our container.
00:21:07.560 We get back a pre-built instance ready to work with, loaded automatically from its corresponding source file. Let’s take a look at that file—it’s just a regular functional object.
00:21:22.800 To start with, we’re going to validate some input data, and we use Dry::Validation for this. It's a powerful standalone validation library that helps us model our validations with precision and work with any kind of data.
00:21:35.560 It’s based around schemas, so in this case let’s say we expect a title and we want it to be at least three characters long. We also want a body, and a published flag.
00:21:51.920 The previous two keys are strings, but in this case, we’re setting a type expectation for published as a Boolean, and the same for published_at, which we want to be a time object.
00:22:07.680 Now that this schema is built, we can take our form post data and send it through the schema. On the other side, we see that Dry::Validation has made some really helpful data coercion for us, courtesy of Dry::Types.
00:22:23.200 If we look back at that form post, we have stringy keys and values; out the other side, we get symbolized keys and values coerced into their appropriate Ruby types.
00:22:36.960 We have a real true value and a real time object. The form schema acts as a critical boundary in our system; it handles all the baggage of HTTP and on the other side it lets our core objects work with properly structured and typed data.
00:22:53.680 We also receive friendly error messages, just as you’d expect, along with hints about checks that couldn’t run if the types weren’t satisfied. The user gets all the necessary information to correct their errors in one pass.
00:23:05.840 With that in mind, we can go back into our command and check for successful validation. When it succeeds, we can save this new article into our database. To do this, we inject this same repository as a dependency and call create with our valid data to save the article.
00:23:21.360 This is just the successful path, though. We also want to handle failure. This is where monads come in; it’s another concept from functional programming.
00:23:37.200 You might be wondering why I’m hitting you with monads in the first talk of the day, but here we are in 2017! We put them to good use for modeling success and failure using the Dry::Monads and Dry::Matcher gems.
00:23:51.920 We can go back to our core method for this operation and start giving it some meaningful, workable output for both these cases. We can wrap our successful result in a right object and our failure result in a left object.
00:24:07.680 These are both instances of the either monad, but in practice, it provides a return object that we can work with consistently in either case. This enables features like flexible result matching courtesy of Dry::Matcher.
00:24:23.200 This works well with our Rota routes. Previously, we had a single call to create, but now we want to handle different outcomes. We can pass a block to match our results.
00:24:39.760 When the article posts successfully, we will redirect back to the list so we can see it. We can also handle failure by matching onto that case and re-rendering the form with the validation object passed to it.
00:24:56.640 This allows the user to see error messages, elevating failure handling to a first-class concern across our app. We can handle failure just like our success case, and this matcher won’t run unless we cover all these cases.
00:25:10.960 We have truly robust result handling. The routes are the only place we've dealt with HTTP today, acting as another important boundary in our app because our app's core objects can focus on doing their own jobs.
00:25:25.040 They can return success or failure as appropriate, and the results can be handled elsewhere in a way that makes sense. In our routes, we deal with those HTTP results.
00:25:41.760 That’s it! We’ve posted our article, and Dry::Validation helped us take care of validation. Dry::Monads and Dry::Matcher took care of the result handling.
00:25:56.960 This is a tiny slice of an app built with Dry.rb, and it may not look like the Ruby you’re used to. We’ve seen a lot of gems working together in concert, with the integration of those gems happening at a level much closer to the surface of our app.
00:26:12.800 Rather than tucked away deep within a framework, we have the responsibility of our app broken up over many small classes, many of which are modeled around verbs rather than nouns.
00:26:30.680 We have these containers serving as organizers and vendors of our app’s objects, using this auto-injection technique to make these objects readily available to one another.
00:26:47.200 If this has felt unusual or a little uncomfortable, it’s been for a reason: to make change easier for ourselves. We all know that getting version one of our app out the door is just the beginning.
00:27:03.680 The real challenge is in growing and maintaining it from there. Let’s look at one example of how we might need to change this little blog that we built.
00:27:19.760 Let’s say we need to add a new feature: sending an email notification to subscribers when an article is posted. There are various ways you might handle a change like this, some better than others.
00:27:35.200 We’re all familiar with callbacks or adding conditional logic throughout our code, but instead, we have a truly elegant option. We can use Dry::Transaction to model this change.
00:27:53.480 We can do this because we’ve already laid some solid groundwork. We have our container of functional objects; each of those objects returns right or left, representing success or failure.
00:28:10.880 This leads us to compose them in interesting ways using Dry::Transaction to model behavior as a bus transaction.
00:28:26.080 For our transaction, we can define a sequence of functional objects that get called one after the other, with the output of each one becoming the input for the next.
00:28:43.440 This sequence continues as long as each step succeeds, making it the perfect arrangement for this new change. We can create a new standalone 'NotifySubscribers' operation after creating the article.
00:29:02.400 These operations aren’t coupled in any way, allowing us to reuse them throughout the app. Dry::Transaction also gives us an enhanced result-matching API.
00:29:16.560 We can handle success just like before, but we can also handle failures from specific steps and address each accordingly. This is a powerful feature made possible by the design of this app.
00:29:32.240 That’s one example of change. You might have noticed that all modifications involve adding new code while leaving existing code untouched, allowing everything to function reliably.
00:29:48.200 When we need to modify existing code, we can do that confidently because much of our app can be tested in true unit test style, in complete isolation.
00:30:03.400 With dependency injection, we can pass in dependencies as doubles in our tests, focusing on the code under test in each case. This is particularly useful for database-backed apps.
00:30:19.280 You can avoid the database for many tests, eliminating the need for globally-stated database calls just to retrieve the data you want. The end result is faster tests and more confidence in our work.
00:30:36.240 What’s made all this possible is that we’ve made simplicity a driving focus and applied it at every level—from the gems we choose to use to the individual objects that comprise our app’s behavior, right up to the broad systems that we use to organize our app.
00:30:54.000 It boils down to prioritizing a clear separation of concerns at every point while maintaining a clear flow of data from place to place.
00:31:11.600 We’ve also designed around critical boundaries in our app, building appropriately around them. All our HTTP handling occurs in one place—in our routes.
00:31:27.600 Our validation schemas act as another boundary, taking input data from external sources and providing properly structured and typed arrangements for it. All our database persistence logic also happens in its own layer.
00:31:43.680 I must mention persistence, but mostly, because we’ll be treated to an excellent talk on ROM later today. However, I can say this fits perfectly with the kind of app we’ve been building, separating commands and queries and allowing explicit modeling of data.
00:32:01.920 This ensures that we write an API to suit our app’s data requirements and nothing else. We’re able to place all of this behind these repository objects, which serve as the sole interface between our app’s core and its persistence layer.
00:32:15.600 Because we’ve placed these external systems behind clearly defined boundaries, we preserve our app’s core, focusing on its main tasks in its domain.
00:32:30.560 We’re not just building a web app; we’re building a Ruby app first for a specific purpose, then offering a web interface around it.
00:32:46.360 By building for our domain first, we've also chosen tools that serve our app rather than contorting our app to fit the requirements of some external tool.
00:33:02.680 None of these ideas in isolation are new. In fact, you probably already know this if you look outside of Ruby. Dependency inversion and inversion of control containers are common in the C and Java worlds.
00:33:18.960 Monads and pattern matching stem from various functional programming languages, while Dry::System pulls inspiration from a similar library for Clojure.
00:33:35.440 You would even recognize similarities between ROM and Elixir’s Ecto. What we’ve done here is take these approaches—common in other realms—and found a way for them to be consistently and naturally applied across an entire Ruby codebase.
00:33:51.760 This represents a new kind of Ruby. The culmination of our focus on simplicity, our domain, proper boundaries, and small components working in concert has created an app that’s easy to change.
00:34:09.440 We’ve created a change-positive architecture. Given how much of our job involves managing change, this advantage helps us significantly by following the methodologies we’ve explored today.
00:34:27.680 A change-positive app is more maintainable. We can understand it at any point and adjust it as necessary. A change-positive app is sustainable—we can work on it just as easily at day 1,000 as on day one.
00:34:44.800 Everything amounts to a change-positive app being truly joyful to work on. That’s my pitch! What does it look like? You might be asking.
00:35:02.000 What does it look like if you go ahead and jump in and do this? I can at least share my experience from IceLab, where I work.
00:35:15.680 We’re a team of 16 people, half of whom write Ruby every day, while the other half are involved in our apps through design and frontend development.
00:35:30.640 The Dry.rb and ROM stories were compelling enough that over a year ago we adopted these gems as our primary web stack. For each of us working with these apps, it's been truly revelatory.
00:35:47.360 Personally, I feel that I’ve leveled up in my skills as a software developer. It finally feels like I can consistently apply those object-oriented design principles such as SOLID.
00:36:03.760 It’s hard to describe how unproductive it felt before—where I had this gap between the work I was doing and what I wanted to achieve, all because I was unable to cross that gap with my tools.
00:36:19.440 Yes, I can look Sandy Metz in the eye again! In building these apps with the Dry.rb gems, we’ve learned how to better design our own gems.
00:36:35.760 We’re now more intentionally designing APIs, thinking about flexible, general-purpose usage first, then integration second.
00:36:50.640 We’ve also recognized and avoided some of the anti-patterns that have held us back in the past. The net result is that I think we will become better developers and a better-executing team.
00:37:06.080 This growth isn’t just for our longtime Rubyists. We’ve also welcomed juniors to IceLab, allowing them to become productive with Dry.rb quickly.
00:37:20.560 This means we can ship without guilt! We build software under commercial pressure just like any other business out there.
00:37:38.720 Before, our tools led us to make bad shortcuts in the name of expedience, often without the chance to rectify those issues later. But now we have tools that encourage better behavior from start to finish.
00:37:54.040 We ship things that feel 100% solid, with code we’re genuinely happy to maintain. This is a wonderful feeling. We’ve been in Ruby development for a long time, and working with Dry.rb has shown us that we don’t need to leave Ruby; it is our future.
00:38:09.680 I think IceLab’s approach can serve as an attractive pathway for any similar small or mature Ruby team — for the teams wanting to design the best-engineered applications.
00:38:25.920 You don’t need to look elsewhere or try emerging technology merely because it seems to facilitate certain design patterns better. Instead, you can stay with Ruby and leverage your existing deep knowledge of the entire ecosystem.
00:38:41.840 This allows you to build better architectured web apps. But what if you’ve grown beyond this? Let’s say you did start small at one point.
00:38:57.280 You’ve worked a lot with Ruby, but now your business is all grown up. You have squads, microservices, and a diverse technology mix that seems inevitable.
00:39:14.000 If that diversification was aimed at better architecture, performance, correctness, or type safety—things you believed you couldn't do easily with Ruby—then perhaps some approaches from today will give you reason to keep Ruby around longer.
00:39:28.800 As Dan McKinley puts it, every company receives three innovation tokens to spend as they see fit. This supply is fixed for an extended period.
00:39:51.440 You only have so much innovation within you and better to spend these on your company’s core mission, not just on the technology that underpins it.
00:40:07.440 Adding technology comes with a cost. New or different options might make sense from the context of a single app or team, but multiplying that across your organization leads to inefficiencies, losing the ability to optimize globally.
00:40:23.360 By sticking with familiar and proficient technology, your company can deliver faster and more reliably. Staying with Ruby means using an ecosystem that has had decades of work put into it.
00:40:39.520 These future possibilities aren’t limited to highly evolved Ruby organizations either; they’re available to learners at all stages of their journeys. Ruby is a fantastic learning language–it’s light, expressive, and allows exploration of many paradigms.
00:40:56.000 We’re spoiled with a wealth of high-quality learning material and many fantastic teachers who choose Ruby as their medium of expression.
00:41:10.720 When building web apps with Ruby, we now have approachable alternatives that offer many more opportunities for teaching fundamental concepts, serving programmers throughout their careers.
00:41:27.440 This direction for Ruby is one that many in our community are already taking. We’ve seen a big wave of interest in Dry.rb, and the gems are making their way into production apps, including some really large ones.
00:41:42.280 We’ve also witnessed some critical collaborations developing across projects in our community. Take Hanami, for example.
00:41:56.760 It’s a modern, integrated, batteries-included web framework for Ruby. They’ve chosen to use Dry::Validation for their validation layer, and ROM has become the foundation for their persistence layer.
00:42:12.960 Adopting these elements has allowed Hanami to offer better features and become significantly more capable.
00:42:26.920 It’s also freed up the team behind Hanami to focus more on the development experience they provide to their users. This has drawn extra attention back to the Dry.rb projects.
00:42:40.560 It’s improved stability, fixed some bugs, and fleshed out features. This is a virtuous cycle, and we're at the beginning.
00:42:55.520 That includes Trailblazer, which offers a high-level architecture for web apps designed to work with Rails, Hanami, and many other web front ends. Trailblazer also uses Dry::Validation for their form models.
00:43:12.760 In their big 2.0 release, which is already out, they embraced a functional Ruby approach, fully supporting features like auto-injection and apps built around containers like Dry::System.
00:43:27.120 If you want to create great web apps with Ruby, you have ample options. These options are better now than ever, improving every day.
00:43:42.800 Reflecting on how Ruby can remain a compelling option in today's crowded market, I believe it's through approaches like this, where we have a diverse range of competing offerings.
00:43:57.560 This diversity will drive innovation in our community and offer life for years to come. However, it falls on us to make this happen.
00:44:12.560 As I said before, Ruby is ours. The job of improving its future is our responsibility, and there are a few things that can help with this.
00:44:29.680 Firstly, I think we need to be more curious about trying out some of these unusual approaches we see in Ruby. Some of today’s code could help you, and the cost of trying them is low.
00:44:45.760 We shouldn’t be insular; we should look further afield, at other languages and other frameworks, drawing inspiration from the techniques they offer.
00:45:03.880 We should also be critical of ourselves. Look for weaknesses in our tools and gaps we can fill, and strive to improve our systems.
00:45:18.240 Yes, Ruby is over 20 years old now, and we have many well-established gems, but that doesn’t mean we're done. We can still improve these tools or offer a new take.
00:45:34.640 I hope the one-year-old Dry.rb project serves as an example of this. Of course, we should be smart about our work. We should cooperate when we can.
00:45:50.880 Open source software takes time; let’s spend it wisely. We want healthy competition without tribalism. If there's something out there you can build upon or incorporate into your work, that's a win for everyone.
00:46:06.080 Just like we've seen with Dry.rb and Hanami and Trailblazer. What helps with this is building with interoperability in mind so anyone can use your code, regardless of how their app or libraries are put together.
00:46:23.240 I encourage you to strive to create something useful and standalone first—that’s your code at its purest, most interoperable form. Then, only after that should you build integrations with various frameworks or other niceties.
00:46:38.560 Finally, let’s celebrate the joy we find in using Ruby. For many of us, this is our day-to-day programming language, and it remains so for good reason.
00:46:54.680 We should communicate this as often as possible. We’re fortunate that we don’t need to compete for mindshare around the new and shiny.
00:47:10.200 Instead, we can promote real, subtler innovations that enhance our everyday lives as developers. When we discover these improvements, let's celebrate them and share our discoveries.
00:47:25.480 Through blog posts, talks, meetups, and word of mouth, we grow our community. So let’s summarize those points one more time.
00:47:42.680 We need a greater curiosity about new things in our ecosystem and developments outside it. We need a willingness to find our faults and actively address them.
00:47:56.760 Furthermore, we should do this work with a spirit of cooperation that pervades even the code that we write, enabling the work of others.
00:48:12.320 Let’s shout out for more Tada emojis and celebrations! This is Ruby! We love this stuff. When we actively participate in these activities, we reinvest in Ruby itself.
00:48:26.040 We contribute to ensuring it has a vital and vibrant future because, after all, something drew us all to Ruby in the beginning—and to conferences like this one.
00:48:43.680 It could be any particular technical feature or something deeper and more visceral. As we say here, it’s about the vibe of the thing.
00:49:00.000 It’s about that programmer happiness; it’s about enabling the modes of thought, design, and expression like no other language can.
00:49:14.800 It’s this little quirk from the 90s that somehow became a part of our lives today. And it’s something that can have a bright, multifaceted future—and that’s something we can build together.
00:49:30.000 Thank you very much!