Design Patterns

Summarized using AI

Keynote: The Magic of Rails

Eileen M. Uchitelle • October 05, 2023 • Amsterdam, Netherlands

The video features a keynote presentation titled "The Magic of Rails" by Eileen Uchitelle, a Rails Core member and Senior Staff Software Engineer at Shopify, at the Rails World 2023 event. In her talk, Eileen explores the intrinsic philosophy and architecture of the Ruby on Rails framework, distinguishing the community it fosters as being equally essential to its success.

Eileen delves into several key points:
- Rails as More Than Just a Framework: She emphasizes that Rails is significantly a community-driven initiative alongside being a coding framework.
- Navigating the Rails Codebase: Eileen reflects on the perception that Rails can be a daunting maze, while asserting that it is actually designed with deliberate complexity to better serve developers.

- Philosophy and Components: She discusses how Rails is modular, allowing developers to only use necessary components without sacrificing cohesion, a design element decision that was established post the merger with Merb in 2008.
- Agnostic Interfaces: Eileen explains that Rails provides flexibility through interfaces that accommodate external tools seamlessly. She illustrates this by explaining how components like Active Record and Active Storage are designed to support multiple database systems and file storage services without locking the users into one specific option.

- Community Contribution: Eileen shares her journey in contributing to the Rails framework and how the nurturing community significantly impacts its growth. She mentions the importance of evolving Rails to meet the changing needs of applications over time.
- Metaprogramming and Design Choices: Eileen discusses metaprogramming as a powerful tool utilized in Rails to create elegant APIs and reduce complexity for users, exemplified by how associations in Active Record are defined dynamically.
- Reflection on Rails' Evolution: Concluding her talk, she emphasizes the 20-year journey of Rails, recognizing its imperfections and the contributions from the community that shape its continued success.

The main takeaway from Eileen’s presentation is the call to recognize Rails not just as a programming tool but as a community and a culture, encouraging attendees to actively engage with one another and contribute to the framework's ongoing legacy.

Keynote: The Magic of Rails
Eileen M. Uchitelle • October 05, 2023 • Amsterdam, Netherlands

Rails Core member and @shopify Senior Staff Software Engineer Eileen Uchitelle explores the magic of Rails in her keynote at Rails World. From taking a look at the philosophy behind the framework to exploring some of the common patterns that Rails uses to build agnostic and beautiful interfaces, Eileen helps you navigate the Rails codebase to better understand the patterns it uses to create the framework we all know and love.

But Rails is so much more than its design and architecture. Eileen also shares her motivation for working on the framework and why the community is so important to the long term success of Rails.

Slides available at: https://speakerdeck.com/eileencodes/rails-world-2023-day-1-closing-keynote-the-magic-of-rails

Other links:
https://rubyonrails.org/
https://github.com/rails
https://rubyonrails.org/community

#RailsWorld #RubyonRails #rails #Rails7 #opensource #oss #community #RailsCore

Rails World 2023

00:00:15.599 Hi everyone! This music makes me feel very cool.
00:00:21.720 Anyway, let's get started. I'm so happy to be here at the first-ever Rails World and I'm honored to be your closing keynote for the first day. Aaron is closing tomorrow, so I don't mean to take away your spotlight.
00:00:32.520 I want to say thank you to Amanda and the Rails Foundation, as well as everyone volunteering at the conference, for organizing this year. I'm thrilled to be here with all of you. Ruby on Rails is so much more than just a framework; it's a community, and I am honored to be a part of it.
00:00:51.399 I'm Eileen Uchitelle. I used to blog at eileencodes.com, and if I ever start again, you can find posts there. For social media, you can find me on Twitter at @EileenCodes. No, I will not use the new name; it will be Twitter forever!
00:01:10.159 I work at Shopify as a Senior Staff Software Engineer, which is quite a mouthful of a title. I'm on the Ruby and Rails Infrastructure Team. For years, Shopify has been investing in Ruby and Rails, and I'm proud to work somewhere that deeply cares about this community.
00:01:32.520 I spend most of my time ensuring Rails can scale for Shopify. While this work benefits us directly, the work we do aims to improve Rails and Ruby for everyone in the community.
00:01:45.640 I've been contributing to Rails since 2014, and I've been a member of the Rails core team since 2017. The core team is responsible for the future of the Rails framework, and we work together to plan new features and releases.
00:01:58.399 If you want to know more about how we work and what the Rails core team does, we'll be hosting a panel tomorrow morning at 9:45 AM in this room.
00:02:21.840 When you've been working on Rails as long as I have, you start to take for granted the way the framework is designed and built. From the outside, Rails seems like a big black box full of secrets only a few know about. It can be difficult to find your way around the codebase through the maze of gems.
00:02:47.879 While Rails might look like a mess from the outside, it's actually a deliberate, complex, and beautiful framework. It is not designed to be perfect or suitable for every use case. Of course, it has flaws like any other framework. The premise of Rails is to provide a more complete experience compared to other ecosystems.
00:03:01.080 Rails was built to empower developers and unlock our productivity. Today, we're going to explore the magic of Rails. We'll look at the philosophy behind the framework as well as the overall structure of the components.
00:03:12.400 We'll explore common patterns that Rails uses to build agnostic interfaces and techniques it implements to hide complexity, allowing you to stay focused on building your application.
00:03:27.560 By the end of this talk, you'll feel more confident navigating the Rails codebase and better understand the patterns it uses to create the framework we all know and love.
00:03:42.720 But Rails is also so much more than its design and architecture. We'll dive into my motivations for working on the framework and why the community is so important to the long-term success of Rails.
00:04:05.439 So what is Ruby on Rails? When I ask this question, I don't want to know the technical definition of Rails as an MVC framework, but rather what makes Rails seem like magic. What are the driving principles behind its design? What sets Rails apart from other frameworks?
00:04:25.720 Rails is modular but not fractured. While the framework is a set of components, we should be careful not to confuse modularity with a lack of cohesion.
00:04:42.960 In the early days, Rails was tightly coupled, and it was nearly impossible to use one component without the rest of the framework. When Merb and Rails merged in 2008, it changed the architecture of the Rails framework. From that day forward, we had built-in modularity and clear lines between gems.
00:05:08.479 Today, we maintain strict rules for each gem and what components they can depend on. For example, Active Model only depends on Active Support, and we won't accept changes that break that contract.
00:05:29.919 We want developers to choose between using the full Rails framework or just the parts they need. This empowers Sinatra users to use Active Support if they desire while ensuring that the framework is bundled as a cohesive codebase.
00:05:50.840 By default, Rails is designed to have agnostic interfaces. While the framework provides a set of defaults for applications, we do not require you to use the tools that we've built in support for.
00:06:07.320 We accomplish this by building interfaces that any tool can work with out of the box. For instance, if you don't like Minitest, you can use RSpec without much effort. If your application requires SQL Server instead of one of the default supported databases, you can simply add a gem and update your database configuration.
00:06:25.760 For the most part, Active Record will do the rest. We've implemented similar interfaces for file upload services, Active Storage, job queues, and Active Job.
00:06:46.440 These agnostic interfaces require that external gems work with our provided APIs. The upside is that this empowers you to make whatever decisions you'd like about your application, and the best part is you can change your mind mid-development without rewriting all of your existing code.
00:07:09.639 The Rails framework is unique in that a lot of its functionality has been directly extracted from existing production applications. The very first iteration of the framework came from Basecamp Classic.
00:07:24.639 Since then, we've upstreamed multiple database behaviors from GitHub. The sharding functionality was influenced by Shopify's monolith, and many other features have been upstreamed from our applications over the years.
00:07:39.760 Because we build and design Rails based on the real needs of production applications, we can ensure that features in the framework are stable, resilient, and performant. We test our APIs to see how they look and feel before they become permanent parts of the framework.
00:07:56.953 This is especially important because once a feature is released, we can't remove or change it without communication. We want to avoid unnecessary churn or an API that doesn't feel like it belongs.
00:08:14.320 Extracting functionality that we've proven in our applications is a great way to build solid and reliable APIs.
00:08:28.560 There is one thing that's hard to deny: Rails has aesthetic taste. You might not love the aesthetic, but it's easy to see that Rails aims to create beautiful, simple, and self-documenting APIs.
00:08:47.760 The core team spends a lot of time debating interfaces that applications will consume. We endlessly discuss the right words and how to use them, not because we love bike shedding, but because we want new features to feel like they've always belonged in Rails.
00:09:05.440 The framework's interfaces are not accidental; they are deliberate and focused on look and feel above all else.
00:09:21.960 You might be wondering, though, if Rails cares so much about aesthetics, then why is the codebase so difficult to navigate? Why is it easy to get lost in the framework's internals?
00:09:43.360 The reason is that Rails cares more about your application than it does about its own internals. The framework will gladly take on complexity so that you don't have to.
00:10:00.000 To build your application, you don't need to know how to parse the database YAML, how to connect to databases, how to talk to websockets, how to implement a test harness, or how to build an image uploader.
00:10:17.040 Why should you need to write the same boilerplate functionality for every application you build? Why should you even need to think about what the MySQL command for creating a database is when all you really want to do is get up and running for your future customers?
00:10:34.320 Rails will handle all of this and more for you, allowing you to focus on building your application. The way Rails hides complexity is a feature, not a bug.
00:10:52.800 Rails is not simply defined by these five tenets, but they represent the motivations and goals of the core team and how we think about the framework. The architecture of Rails didn't happen by accident; the way it's built is a deliberate choice to abandon certain ways of doing things in order to optimize for the human experience of building web applications.
00:11:11.919 Because of everything Rails tries to do for you, the codebase can be incredibly difficult to navigate. There is no one person on the core team who understands how every part of Rails works, but because we all understand the patterns that govern development, we can usually figure out how any part of the framework is implemented.
00:11:31.360 Once you understand how it's designed and architected, finding your way around becomes a lot easier.
00:11:46.360 Rails is made up of 12 components, as seen here. The first version had just four gems: Active Record, Action Mailer, Action Pack, and Railties. Out of the 12 current components, four are original, three are extracted from those, and five are additions.
00:12:01.720 The decision to add new components doesn't happen often. We want to be mindful about what does and does not end up in the framework because anything we do add needs to be maintained by the core team.
00:12:22.520 There are only 12 of us, and while that sounds like we have one person per component, it doesn't work out that way. Each one of us on the core team has an area of expertise, but none of us own any particular component.
00:12:34.600 One of the things that comes up often when talking about how Rails is designed is the significance of the naming convention. Why are some gems prefixed with 'Active' and others prefixed with 'Action'? You might wonder why Railties does not follow the same convention.
00:12:56.040 The libraries prefixed with 'Active' are generally ones that support backend behavior. These include Active Record, Active Support, Active Model, Active Job, and Active Storage; they are all responsible for doing things behind the scenes.
00:13:09.680 The naming convention was originally influenced by the Active Record pattern, after which Active Record is named. It made sense to use the same prefix for other backend-like components. DHH wanted all the gems to follow a similar naming convention, so 'Action' followed 'Active' to indicate the parts of the framework that are more user-facing.
00:13:27.440 These are Action Mailer, Action Pack, Action View, Action Cable, Action Text, and Action Mailbox. The rule for naming a framework is sometimes a little bit blurry.
00:13:47.680 You could argue that Action Cable isn't nearly as user-facing as this diagram implies, but hardly anything is perfect.
00:14:02.440 Lastly, the piece that ties all the frameworks together is Railties, hence its name. A railroad tie is the crossbrace that supports the metal rail on a railroad track, so it makes sense that Railties would not follow the active and action naming convention since it is the part that ties everything together.
00:14:21.440 Railties defines the Rails constant and is our entry point into an application. One important thing to know is that while Railties is the core of the framework, none of the gems in Rails depend on it.
00:14:37.760 This is what allows you to use parts of Rails without committing to using the entire framework. The modularity of Rails is a conscious choice to give you the freedom to use only what you need.
00:14:56.960 Rails implements a variety of common patterns and techniques to build the interfaces and APIs that applications consume. Ruby on Rails makes heavy use of factory patterns, inheritance, metaprogramming, and uses a series of hooks and callbacks to control the load order of components and their configuration.
00:15:20.680 Navigating Rails code can be incredibly difficult, but because it takes on so much complexity in order to provide a consistent interface and simple APIs, the techniques that Rails uses enable the framework to provide an application development experience unmatched by any other framework.
00:15:38.720 Let's first look at how the components interact with Railties to get a better sense of the role it plays in the initialization process and how it ties the framework components together.
00:15:56.240 The entry point from an application to a component is Railties. Any gem that needs to hook into the initialization process will contain a file called railtie.rb. This is where we define the load hooks for the framework, applying configuration and settings to the application.
00:16:14.000 In addition, the config environments and config initializers are how your application hooks into Railties and the components. Not every component in the Rails framework implements a Railtie; it depends on whether the gem needs to extend or modify the initialization process.
00:16:39.760 External gems that need to interact with the initialization and configuration also need to provide a Railtie. Think of it as an API for hooking into Rails.
00:16:56.560 While your application boots, the Railtie functionality in a component can only be used in a Rails application. If you're using Active Record with Sinatra, for example, you'll need to apply the configuration yourself.
00:17:11.480 Some of the things that just work out of the box with Rails will need to be manually implemented when using components outside of the framework.
00:17:29.600 So let's take a look at a quick diagram of how Railties works. When you boot your Rails application, Railties will register the load hooks for each component from the defined initializers. Then it waits until each component is loaded, and once they are done loading, those hooks registered before the application was booted will then be run.
00:17:53.760 In general, Railties initializers are run in the order that they are defined, therefore anything in your application will run after Rails' initializers. This functionality also enables lazy loading of Rails components, which makes boot time faster.
00:18:08.680 Let's take a look at some code to see how this works in practice. If you look at the railtie.rb file in any of the components, you'll see that they define a bunch of initializer blocks that look something like this.
00:18:31.440 If we take a look at the definition in Rails, we can see that the initializer takes two arguments: a name and an options hash. The name is simply used as an identifier and the only options that are accepted are 'before' and 'after', which can be used to control the load order of initializers.
00:18:48.720 In most cases, we don’t set any options because we want the initializers to run as they are defined. However, 'before' and 'after' options are useful if we want one component’s initializer to run after another component’s initializer.
00:19:04.400 The initializers take a block that returns the application we are working on. This can be used to interact with the methods defined on the application object or access the config object as seen here.
00:19:23.120 Within these initializer blocks, you'll often see an 'onload' hook that tells Rails to register an action, but wait until a specific component is loaded to call the code inside the initializer. Onload hooks enable lazy application configuration by registering a hook that will be called when the library is loaded.
00:19:43.920 Rails initializers are often used to set up required functionality as an application boots. For example, if you've ever used Active Record outside of Rails, you probably noticed that you had to call 'establish_connection' in order to run queries. However, in a Rails application, you may never call 'establish_connection' unless you're using multiple databases.
00:20:07.680 So where does that initial connection come from? In an initializer, of course! If we look at the railtie.rb in Active Record, we can find the initializer that is responsible for establishing connections to the Active Record-based class.
00:20:23.680 When we boot our Rails application, the name of the initializer is 'active_record_initializes_database.' Because we have an 'onload' hook, this initializer will not load until the Active Record component has fully loaded.
00:20:39.640 When this code is run, it will first find the database.yaml from the application's path and parse the ERB. It will then pass the result to the Active Record-based configuration setter, which will turn the YAML into database configuration objects.
00:20:57.040 Once the database configurations are set, 'establish_connection' is called, and it will use whatever the current environment is. And that's it! That's how Rails sets configurations and sets up the initial connection.
00:21:15.360 Establishing this initial connection for you is one of the many things that Rails does when you boot your Rails application. Initializers also let us avoid calling components if they aren't included in your Rails application.
00:21:38.560 In an initializer called 'active_record_log_runtime,' we can see that Active Record will include the controller runtime module only if Action Controller is loaded, and it will include the job runtime module only if Active Job is loaded. If an application doesn't include Action Controller or Active Job, these modules would never be included in your application.
00:22:03.680 This avoids coupling Active Record to Active Job or Action Controller and means we don't need to depend on any of these components if we don't want to.
00:22:15.420 In an initializer, we can also define the component's dependencies. Here, we can see this initializer is using the 'before' option, indicating that it should run before the load environment config initializer.
00:22:30.920 The argument passed must match the identifier as it was defined, and in this case, 'load_environment_config' is defined in Railties as a symbol.
00:22:46.800 You'll notice that in most cases, it will be a string; it just depends on how and where the initializer was defined. It's just an identifier; it's not that complex.
00:23:00.560 While these are simple examples that fit on my slides, there are plenty of more complex initializers in Rails. The next time you're curious what Rails is doing on boot, pop open one of the railtie.rb files in a component to find out.
00:23:16.840 The important thing to take away from this is that Railties is the core of the framework, and it is what ties our applications to each component. Most of the major components in Rails will implement a Railtie to apply configuration and set up the application during the initialization process.
00:23:39.760 Without the Railtie initializers, we would have to establish the database connection in our app, set up our own dependencies, load the schema cache ourselves, and a lot more. The role of Railties is to handle configuration and initialization for you, so you don't have to think about all these moving parts.
00:24:04.680 Rails uses initializers with onload hooks to control the load order and ensure that hooks are run at the right time. This prevents a component from being loaded too early and is one of the ways we ensure Rails' configuration does not override application configuration.
00:24:25.520 Lastly, Railties enables components to interact with other components without making them dependencies. Because Railties waits until a component is loaded to call its code, we can define behavior for when a particular gem is available in an application.
00:24:39.640 For example, Active Record will not include the job runtime module if Active Job is not loaded. This means we do not create an unnecessary dependency between Active Record and Active Job in your application.
00:24:55.760 While we only looked at a couple of simple examples, this just barely scratches the surface of the power of load hooks. The next time you're wondering where configuration is coming from, look in the component's Railties for the answer.
00:25:14.480 Earlier, we talked about how Rails provides agnostic interfaces which enable you to swap out default functionality as long as external tools implement the interfaces that Rails provides.
00:25:37.040 An example of this is how the Active Record database adapters are implemented. By default, Rails provides support for four database adapters: SQLite, PostgreSQL, MySQL, and Triliog.
00:25:57.920 Trilogy is a new adapter for MySQL that was built at GitHub and will be available in the 7.1 release. If you look at Active Record's codebase, you'll notice that we never call 'is_a?' on the database connections.
00:26:12.440 This is because the adapters are implemented in such a way that their interface is agnostic to the database client that we use in our application.
00:26:27.840 We achieve this by building an abstract adapter that implements the API; then all concrete adapters inherit from that abstract adapter. The way this works is that the abstract adapter defines the default interface.
00:26:42.280 You should never interact with the abstract adapter directly in your application, because all the adapters respond to the same methods. That's why we built them this way, so you should only ever interact with a concrete adapter directly.
00:27:01.880 Each concrete adapter then inherits from the abstract adapter and therefore defines the same interface through inheritance. The default behavior for each method is stored on the abstract class, since that is where we define that interface.
00:27:21.640 Concrete adapters, including the ones external to Rails, are required to inherit from the abstract adapter to get this shared interface and implement the same public APIs.
00:27:44.200 The pattern we use to create agnostic interfaces in Rails means that we can call a method like 'supports_foreign_keys' without needing an 'is_a?' check. All the adapters respond to that same method.
00:28:08.520 If a concrete adapter wants to have different behavior from the default, it can reimplement the method to change behavior. For example, here we have the definition for 'supports_foreign_keys' on the abstract adapter.
00:28:25.880 By default, it's set to false. If we look at the PostgreSQL adapter, we'll see this method reimplemented to set 'supports_foreign_keys' to true. The MySQL 2, Trilogy, and SQLite 3 adapters all do the same thing.
00:28:45.720 The abstract adapter sets this to false rather than true because we don't know if external adapters that we don't control will be able to support foreign keys. This creates an interface where foreign keys are opt-in for any database adapter in Rails.
00:29:01.240 Another example of where we use a similar pattern to create agnostic interfaces is Active Storage. Active Storage implements this interface slightly differently than Active Record.
00:29:17.560 Here, we have a class called Service, which is equivalent to our abstract adapter. It's our abstract class that defines the interface, but instead of defining default behavior in the abstract service, we raise a 'not implemented' error.
00:29:34.120 This signals to external gems implementing an Active Storage interface that they must implement this method for their service to work with Rails. We don't raise a 'not implemented' error for Active Record on most methods because the default behavior is less complex to implement.
00:29:49.600 It's also a much older component with different patterns, and we have changed how we want to implement them over the years.
00:30:02.480 There are many other methods in here, but I've pasted just the delete method as an example. If we open up Google Cloud Storage, we can see the implementation for the delete functionality.
00:30:21.480 Every available service for Active Storage will implement this method in the concrete service, where the abstract service raises a 'not implemented' error.
00:30:37.120 This agnostic interface allows us to call the delete method on the service object without having to check which service we're using. Whatever service we have in our configuration, Rails will call these methods on it, and our service will respond.
00:30:54.960 Using this pattern reduces code complexity and clarifies what the interface is. We can guarantee that Rails will just work as long as the concrete service classes inherit from the abstract service and implement the behavior for each method.
00:31:09.520 To recap, Rails uses abstract classes and inheritance to implement agnostic interfaces and consistent interfaces for the services we support. It also provides an easy way for library authors to build their own service or adapter.
00:31:32.040 Rails implements an abstract class that all those concrete classes will inherit from. The abstract class is responsible for defining the interface, while the concrete classes are responsible for changing behavior by redefining those methods.
00:31:45.280 You will see this pattern anywhere in the Rails codebase where we need to implement a single interface that responds to the same methods. One of the benefits of this pattern is that it simplifies the Rails codebase by avoiding 'is_a?' checks everywhere.
00:32:01.040 We can assume that any call to any adapter or service will implement the interface that is defined. Anytime we call a method, Rails already knows how to respond.
00:32:18.200 This also avoids undefined method errors for any external adapter or service; they can either fall back to the default defined behavior or receive a 'not implemented' error to indicate that they must define that method.
00:32:35.040 In addition to simplifying the Rails codebase, the patterns we use to create agnostic interfaces make it easy for applications to swap out adapters and classes. If you start your application with PostgreSQL, you can easily switch to MySQL 2 by updating the gemfile and database configuration.
00:32:53.840 This applies to Active Storage services as well. Don’t like Google Cloud? That's fine! You can switch to Azure by adding a gem and updating the storage config.
00:33:12.480 Everything just works unless you're doing something custom. These interfaces also allow us to provide a set of defaults without forcing you to be locked into what we think is best.
00:33:29.520 Lastly, implementing a default set of interfaces that any external provider can hook into lowers the maintenance burden for the core team. We don’t need to know how every available service on the planet works.
00:33:43.720 Instead, we provide the ones that we are comfortable maintaining. Rails is a big framework with a small maintainer team, so we need to ensure that we're not taking on more of a maintenance burden than we're able to support.
00:34:01.040 Agnostic interfaces that implement a stable API for external gems allow us to avoid taking on support for an infinite number of providers while not locking you into the options that we've provided.
00:34:16.280 In addition to the patterns we've looked at, Rails makes heavy use of metaprogramming to build simple and beautiful APIs. Because of this, it can be difficult to find where behavior is defined.
00:34:29.880 A lot of what you think of as Rails magic is powered by metaprogramming. While it can be challenging to trace the code that uses this technique, it's essential to building APIs that are beautiful and hide complexity.
00:34:43.920 There are many examples of metaprogramming in Rails, but today we're only going to look at one small example of how we use class_eval to build the association getter and setter APIs.
00:35:00.840 Let's say we have two models: Post and Comment. A Post has many Comments, and a Comment belongs to a Post.
00:35:15.760 Have you ever wondered how we're able to call 'post.comments' without defining this method in our model? How does Post even know about the Comments method at all?
00:35:35.160 Active Record has no idea that you're going to add this to your model, so it’s not hardcoded in Rails. Figuring out where this is defined can be a little difficult.
00:35:50.200 When I don't know where something is defined in Rails, I use Ruby's `source_location` method which will give us the file name and line number of where any method is defined.
00:36:08.720 We can find the definition of components by calling `method` on Post and passing :comments as a symbol. This will work for anything that's defined on Post.
00:36:27.720 Then we can call 'source_location' which will output the information we need. If source_location returns nil, it means that either the method is defined in a C extension or C Ruby itself.
00:36:43.480 This tells us the 'comments' method is defined in associations.rb on line 103, and if we open that file, we see that it points to a method in Active Record’s Association class named 'define_readers'.
00:37:02.040 This method takes a mixin and a name. Using the mixin passed in, Rails calls class_eval on it. Now, if we don't know how this metaprogramming works, we might assume that the mixin here is the Post model class, but it’s actually a dynamic module called 'PostGeneratedAssociationMethods'.
00:37:19.560 This module is dynamically created when the Post model is loaded. This module is where we will store all the instance methods we need to define on Post to access our associations.
00:37:32.840 I'm pointing this out because in a lot of cases, the magic behind associations in Active Record comes from methods, classes, or modules that are dynamically generated when you boot your Rails application and your models are loaded.
00:37:50.600 It’s not obvious how associations work if you haven't spent time in the code. Okay, back to define_readers.
00:38:05.560 Inside the class_eval block, a method is defined using the name that we called, in this case, it becomes 'def comments'. Active Record looks up the association from the association cache and then calls reader on it.
00:38:19.920 Using metaprogramming, Rails provides the API to access comments on Post rather than forcing you to implement your own comments method or calling this line manually.
00:38:36.560 The writer method has a similar-looking definition, except for that it calls writer and passes value. You'll notice that these definitions are almost the same but each is implementing a different instance method.
00:38:57.040 You’ll also see that in class_eval, we can see file and line plus one. Without this, we wouldn't be able to find the source location of the comments method. This is a feature of Ruby that lets us define the file and line number of a metaprogrammed API to make debugging easier.
00:39:14.360 There are a lot more methods that are dynamically generated when working with associations in Rails, but we can't cover more of that today.
00:39:31.760 My hope is that by showing you how we use metaprogramming to build one of the beautiful and simple APIs in Rails, the next time you use an association, it will feel less magical.
00:39:48.720 Association accessors are just one example of how we use metaprogramming in Rails to provide your application with beautiful, simple, and clean APIs. Without utilizing this functionality, we wouldn't have methods like 'post.comments'.
00:40:01.920 Active Record metaprogramming makes Rails a lot more complex internally, but the trade-off is it enables us to hide complexity from your application. The alternative would be running your own queries and defining your own comments getters and setters, which doesn't sound very fun, does it?
00:40:13.680 Rails doesn’t want you to have to do that yourself; we want to provide you with sensible, self-documenting, simple APIs that just work. It's the beauty of Rails, and it’s where a lot of the so-called magic comes from.
00:40:31.920 I’ve spent a lot of this talk telling you how great I think Rails is and all the things it does for you, but I can't say it’s without flaws.
00:40:47.440 Rails aims to be a framework that meets most users' needs. It doesn’t try to do everything under the sun; it wants to make you productive by giving you most of what you need.
00:41:02.720 Rails is also imperfect because it's 20 years old. It's no longer an unruly teenager; it’s a mature and stable framework. Sometimes, maturity means imperfection, however, because we can't just change functionality if applications depend on how it works today.
00:41:17.360 Rails is imperfect because the people working on it are imperfect. We make mistakes; we add features we thought were a good idea at the time; we’ve broken builds, we’ve broken apps, and we've created security vulnerabilities.
00:41:34.080 But imperfection is a part of being human. Rails is also the applications we build with it. From small businesses to large enterprises, Rails powers many of the applications we interact with daily.
00:41:50.760 There are thousands and thousands of applications in the world using this framework. They are just one piece of what makes up Rails.
00:42:04.720 Rails is the team behind it. As a maintainer of Rails, my goal is to see the framework evolve and ensure that the community of contributors continues to grow.
00:42:20.560 The maintainers are the ones making sure that releases happen on a regular cadence, that features get merged, that we help new contributors, and that we answer bug reports. Without the 12 core team members and the 27 other maintainers, we wouldn’t have Rails as it is today.
00:42:42.040 In the years since Rails has been open source, 6,512 individuals have contributed. Every single person who has made a change to the framework is a part of Rails.
00:43:02.520 And of course, Rails is the community. The community is not just the core team or just the framework; it's all of us. We make the community what it is.
00:43:18.280 Without a community of users and contributors, Rails is just thousands of lines of Ruby code. But we all know that Rails is so much more than that. It's human-centered design, agnostic interfaces, and beautiful APIs that are extracted from applications.
00:43:34.640 Rails inspires and empowers developers. It's imperfect and flawed. It's the applications we build, the team behind it, and the community.
00:43:51.760 And maybe it's a little sentimental to say, but I actually do think Rails is magic. The role each of us plays in Rails' development community and future is what makes it magic.
00:44:04.760 It's up to all of us to continue building this framework and community so that new people come to join us and those who are already here stay.
00:44:22.360 At Rails World today, I want you to introduce yourself to someone that you don't know. Go out of your way to connect with each other.
00:44:39.400 It's really easy to sub-tweet or complain on social media; it’s a lot harder to form real connections with real people. We've lost a lot of that in the past few years, and we need to work hard to get it back.
00:44:56.560 Connecting with people is the reason that we're all here. It’s what makes this all worth it. We might have different goals, but we all care about the same thing: the Ruby on Rails framework.
00:45:10.520 I hope you'll join me in being a present and active part of Rails' future. Let's work together to ensure this framework and this community are something that we want to keep coming back to.
Explore all talks recorded at Rails World 2023
+25