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.