RailsConf 2023

Rails on Ruby: How Ruby Makes Rails Great

Rails on Ruby: How Ruby Makes Rails Great

by Noel Rappin

In the talk titled "Rails on Ruby: How Ruby Makes Rails Great," delivered by Noel Rappin at RailsConf 2023, the relationship between Ruby programming language features and the Ruby on Rails framework is explored. The speaker emphasizes how Ruby's flexibility and metaprogramming capabilities enhance Rails, making it user-friendly and powerful. The presentation covers five specific Ruby features utilized in Rails:

  • String Inquirer: This feature allows developers to query the Rails environment with methods like rails.env.production? instead of traditional equality checks. It leverages Ruby's method_missing to dynamically handle method calls based on their names.

  • View Assignments: Rails automatically passes instance variables from controllers to views, providing a seamless data flow. This is achieved by using Ruby’s instance_variables and instance_variable_get to convert controller instance variables into a hash that is then used in views.

  • Callbacks: Rails implements a callback system for actions in controllers through dynamically-defined methods using define_method. This allows for hooks like before_action and after_action, enabling code to run before or after certain actions.

  • Active Record Dirty: This feature tracks changes to model attributes in Active Record. Using metaprogramming, it provides methods such as attribute_was and attribute_changed? that dynamically respond to attribute names, keeping track of their previous values.

  • Migrations: To simplify database schema changes, Rails migrations allow defining the change method, enabling the system to infer the corresponding rollback actions. This is done using Ruby’s class_eval to define methods dynamically in the context of the migrations class.

The conclusion emphasizes the trade-offs of dynamic metaprogramming, such as reduced discoverability of methods defined in a non-static manner. Despite this, Rappin argues that the benefits in flexibility and reduction of boilerplate code in Rails significantly enhance the developer experience. He encourages attendees to explore these metaprogramming techniques within their own coding practices, highlighting their utility and expression in Ruby programming.

00:00:18.539 Hi everybody! Welcome to this talk. I know there's a lot of good stuff going on right now, so I appreciate you spending some time with me. Let's get right into it and look at some code.
00:00:30.359 This is an interesting setup here because I can’t easily see my laptop nor the screen. So, we're just going to be flying by the instruments. It's going to be great, I promise!
00:00:36.660 They often tell speakers to open with a joke, and so I am starting with the only piece of Ruby code that has ever caused a room full of Ruby trainees to break out in laughter. We'll see if it works here or if you just had to be there.
00:00:50.280 Here it goes... yeah, I guess you had to be there. Does anybody know this code? This is from the Rails Active Support codebase—specifically, a class called StringInquirer.
00:01:03.059 StringInquirer is part of the core pieces of Rails infrastructure. It allows you to type `Rails.env.production?` instead of `Rails.env == production`. It's a core feature, and as Eileen mentioned this morning, it is Rails magic. Today, we will look behind the scenes at some pieces of this Rails magic and discover the secret that Eileen referred to: it is all Ruby.
00:01:22.740 This talk is titled 'Rails on Ruby: How Ruby Makes Rails Great'. I'm Noel Rappin, and I work for Chime Financial. There are about ten of us floating around here. If you're a Chime person, please raise your hand. Great! We love to talk about Chime engineering, so come hit us up. You can find me online in a couple of places, but I promised myself I would only get self-promotional at the end.
00:01:41.700 Today, we're going to discuss five tricks in the Rails codebase—five different ones. We're not going to explain all of Rails internals since this is only a 40-minute talk, so I have biased my examples towards those that are easy to explain in a single slide. However, I won’t be defensive about Rails design like Eileen was this morning. While some of these metaprogramming techniques within Rails have trade-offs, I’m really happy that they exist. They make the Rails framework easier to use, and we will discuss the trade-offs as we go along.
00:02:24.480 The first piece of code we're looking at is StringInquirer. This class is a subclass of String, which means that it inherits all of String’s functionality—over 200 instance methods and its instance variable, which essentially holds the string value. It adds two methods: `respond_to_missing?` and `method_missing`.
00:03:14.040 Method_missing is a key piece of core Ruby. All Ruby objects have method_missing, which is used as a last-resort method lookup. If Ruby can’t find a method definition in the class or any superclasses, it looks for a method called method_missing to handle it.
00:03:33.060 The key function employed in StringInquirer checks if the method name ends with a question mark. If it does, it compares it against the value of the string. If it matches, it returns true; otherwise, it returns false. If the method name does not end with a question mark, it falls back to the superclass.
00:04:13.200 Rails previously used this heavily for Active Record finders, allowing dynamic building of finder methods like `find_by_name_and_email`. However, with the introduction of keyword arguments, this has changed to a more manageable and readable approach.
00:04:44.160 The use of method_missing allows you to create dynamic method names that can be checked against the string values without directly calling a comparison. StringInquirer also has `respond_to_missing?`, which ensures that the Ruby check using `respond_to?` behaves consistently, even when doing something dynamic with the method name.
00:06:14.940 You might wonder why someone would create such a metaprogramming technique just to avoid using equality checks. In this case, it's about design flexibility and reducing error potential by allowing querying of the environment that has dynamic names without explicit definitions.
00:06:50.220 This makes it easy to work with different environment names without having to explicitly inform Rails about these new names. Moreover, it encapsulates knowledge of the internals, which prevents potential mistakes that could come from using equality checks without knowing whether they were strings or symbols.
00:08:22.480 Now let’s move to the second piece of code, which involves view assignment. A core feature of Rails from day one is that instance variables defined in a controller method are available as instance variables in the view. This often leads new Rails developers to wonder how this is possible.
00:08:34.320 Here’s a snippet from the controller path that illustrates how view assigns work. It starts by calling the Ruby core method `instance_variables`, which returns all defined instance variables. It then filters out certain protected variables that are too private for view access.
00:09:10.560 Using `each_with_object`, it converts all instance variables into a hash, allowing the instance variable names to be used as keys, stripping the '@' sign for presentation.
00:09:39.540 The resulting hash is passed to the view, where it gets unpacked back into instance variables. The Rails convention provides a simple API for passing variables from the controller to the view, effectively abstracting some complexity.
00:10:06.540 How's it going so far? Good? Okay, we're on to the third trick—callbacks. The implementation is quite interesting. There's an Active Support module that almost could serve as its own gem that allows you to create callbacks in your application.
00:10:38.260 For instance, in the controller, methods like `before_action`, `after_action`, and `around_action` are created dynamically using Ruby's `define_method`. This dynamically defines methods at runtime while also linking them to the corresponding callback sequences.
00:11:35.260 The important mechanism here is that at runtime, Rails tracks the defined callbacks and ensures they are executed in the proper order during action processing. This technique is powerful in Rails applications, allowing for comprehensible workflow management.
00:11:59.400 The fourth piece we will discuss is the Active Record Dirty module, which tracks changes to model attributes. When an attribute changes, methods like `has_attribute_changed?` and `get_previous_value` can be called to retrieve the original value. This is done dynamically using `method_missing` and method pattern matching.
00:12:57.120 The dynamic pattern matching allows Rails to define methods like `first_name_changed?` without them being explicitly defined at coding time; instead, they are generated based on attribute names at runtime.
00:13:22.320 In this way, Active Record closely ties together traits and methods associated with the attributes in a highly dynamic context, facilitating robustness in mutation detection.
00:14:13.820 Finally, let's discuss migrations in Rails. In earlier versions, developers had to define both an up migration and a down migration. This setup led to potential bugs if the methods didn't exactly mirror each other. To mitigate this, Rails introduced the `change` method, allowing Rails to infer the down migration automatically.
00:14:56.740 This process utilizes `class_eval`, which evaluates a string in the context of the current class. The result is that methods become dynamically created to handle actions like creating or dropping tables, significantly reducing boilerplate and enforcing consistency.
00:15:42.960 With these five techniques, Rails takes on complexity so that developers can focus more on building features rather than struggling with the intricacies of the framework’s design.
00:16:03.180 One common critique of metaprogramming is discoverability. Due to methods being dynamically defined, static method lookup tools may not work as expected. While static tools have their limitations, Ruby also provides dynamic tools for code inspection.
00:16:49.320 For example, you can utilize `source_location` to trace method definitions in dynamic environments, which might take a little longer than a direct jump to a definition in an IDE but still allows for manageable exploration.
00:17:59.640 Moreover, Rails APIs are designed to abstract complexity effectively, reducing the amount of boilerplate code developers need to write while enhancing their capabilities. Consequently, this flexibly allows for significant improvements in development productivity.
00:18:42.960 I encourage you to explore Ruby metaprogramming techniques in your codebase. If you can design around flexible APIs that respond dynamically to your data, it can provide substantial benefits and enhance your development experience.
00:19:29.280 As we approach the end of this talk, I want to share some personal news. I'm currently the co-author of the new version of the Programming Ruby book, also known as the pickaxe book. I wanted to inform you that the draft is complete, and we're hoping to release a beta version soon.
00:19:53.880 Though still in progress, the physical book is available for pre-order on Amazon. If you're interested in reviewing the material once it's finalized, feel free to reach out to me. You can also find my online presence at norappin.com and connect with me at Ruby social.
00:20:33.660 Thank you very much for being part of this conference! I hope you found this session valuable. Enjoy the next few days of the event, and really thank you for being a part of this community.