00:00:10.400
Anyways, I want to get through this talk because I've got a lot of code to show you guys—some really good code, some really sophisticated code, and I want to bring everyone along on that journey.
00:00:16.279
The idea behind this talk is to discuss Yehuda Katz, who has done a lot of refactoring in Rails 3 over the past year and a half.
00:00:22.680
I took a look inside Rails 3 to identify some of the really interesting design patterns that are used and to explain them in a way that everyone could understand, not just Yehuda.
00:00:28.080
When I first started researching, I found that some of the more interesting design patterns were not done just by Yehuda but also by others like Carl Lurch, Josh Peek, and Carl Huda.
00:00:33.480
If you're not familiar, Carl Huda is sort of the alter ego when Yehuda and Carl pair at Engine Yard; they pair under the GitHub name Carl Huda, also known as Emo Cow.
00:00:40.480
Our topics today will look a little something like this: method compilation, microkernel architecture, alias_method_chain vs. super, ActiveSupport concern, catch/throw in Bundler, and increased Rack compatibility.
00:00:46.520
There are going to be a ton of code examples here, and I realize this is before lunch, but if you really focus on the code I explain, you'll find some interesting patterns.
00:00:52.359
Before we get our feet wet, I ask the experts in the room to bear with me for just a minute; I know there are some beginner Ruby developers here, and I don't want to leave them out.
00:01:00.760
So before we dive into advanced topics, let’s revisit the basics to ensure everyone is on the same page.
00:01:06.680
For instance, an attribute accessor is equivalent to saying 'def city' and 'def city='.
00:01:10.480
When we call `initialize`, that's when we use `SimplePrint.new`, and it calls the initialize method.
00:01:16.040
We can set variables within the `initialize` block. When we have `method_missing`, it means that if there's a method call like 's.ruby_conf' and that method doesn’t exist, it will invoke the `method_missing` method.
00:01:21.160
So when we go to the command line and do `SimplePrint.new('Taiwan')`, it sets the city variable, and if we call 's.ruby_conf', we get back 'RubyConf Taiwan'.
00:01:26.280
Now, looking at our code example, we've got a hash, and we're going through each of the keys; we're doing something with eval to create instance variables like name and location.
00:01:34.960
If we do `puts name location`, we're going to get 'BarCamp Orlando'.
00:01:40.479
If we look at instance eval, if we create an instance of a class that prints hello using `puts` and we call that instance, we will receive 'Hello'.
00:01:46.000
Another way to achieve this is through `Test.new.instance_eval` and calling `print`, which yields the same result—it's essentially running the print method in the context of the class.
00:01:51.719
If we create another instance afterwards, though, it won't work because we only added the method to that particular instance.
00:01:56.560
Let's discuss the concept of Classy Val by considering a class called Test. We can extend it to add methods and modify its behavior.
00:02:02.079
If we print the modified class, we’ll see that it outputs 'Ruby 5'.
00:02:10.560
We can achieve this by simply doing `Test.class_eval` as well, which gives us the same output.
00:02:16.960
Now that we have that established, let’s jump into the actual content.
00:02:22.880
We will first look at method compilation. Let's examine a simple example of where we might use this effectively with some straightforward code and then see where this is implemented in Rails 3.
00:02:31.960
For instance, if we use `SimplePrint.new`, we can define methods like 'push', 'pop', and 'top'. Let's say we want a specific output from this code. What code would need to be written inside `SimplePrint` to achieve that?
00:02:39.680
One solution might involve accepting actions as an attribute accessor, creating a print method, and using `method_missing`.
00:02:45.120
When these methods are called, it checks to see if the method matches one initially sent in. If it does, it prints it out.
00:02:50.560
If we run this code 30,000 times, we would find it averages about 5 seconds on my processor.
00:02:56.679
Now, we could write this with method compilation. Here’s how that would look: we define a class called `BetterPrint`, initialize it, and iterate through the items in the provided array.
00:03:04.039
In this case, we would use `instance_eval` to define the methods for each item sent in. This method generates them on-the-fly.
00:03:11.360
When we call these methods, we're invoking the ones generated dynamically, leading to significantly faster performance.
00:03:17.639
Using `method_missing`, it took around 5 seconds. Using `instance_eval`, however, improved execution time to about 3.5 seconds—about 30% faster.
00:03:25.280
Now, you might be wondering what happens under the hood of this performance enhancement. I investigated using Ruby Prof to profile both approaches.
00:03:32.360
In the profiling data, you can see that the `method_missing` version is called repeatedly, checking each time if the string matches before invoking the method.
00:03:40.320
With method compilation, however, methods are merely called, reducing overhead, which ultimately leads to better performance.
00:03:47.440
The first commit that Yehuda made to Rails was implementing method compilation for the mime types, enhancing their performance significantly.
00:03:55.640
Another implementation of method compilation in Rails 3 can be seen with callbacks such as `before_filter`, `after_filter`, and `round_filter`.
00:04:02.680
Rather than checking every filter for every action, Rails compiles a single method that specifies which methods should be called for each action.
00:04:08.880
This method compilation results in a significant speed improvement of about 10 times.
00:04:16.759
For layouts in Rails 3, the old way involved checking types of layout through numerous conditionals each time an action was called.
00:04:25.000
Using method compilation, we can optimize this process, so when the action is called, only the previously defined layout methods are invoked.
00:04:35.560
In the Rails source code, there's a process where if a layout is a string, a method is dynamically created to handle this without repeated checks.
00:04:43.000
This results in the action only needing to call the already defined methods, which improves performance.
00:04:51.000
Now, let's discuss microkernel architecture. A great example of this is seen in the AbstractController in Rails 3, with considerable work done by Carl Lurch.
00:04:58.560
The action controller stack previously included a lot of functionality in one package, which has been effectively separated into more manageable components.
00:05:06.720
For instance, routing and dispatch functionality was moved into Action Dispatch, and a certain hierarchy of modules was established for better organization.
00:05:13.560
In the Rails source, the AbstractController directory illustrates this structure, showcasing one-to-one mapping for common components.
00:05:20.080
By organizing this way, it simplifies learning about callbacks or other functionalities contained in specific files. This provides a clear, concise structure for developers.
00:05:26.560
Now, let’s dive into the code behind the AbstractController base class.
00:05:32.680
The guts of this abstract class is all encapsulated within a single method called 'process'. This method essentially handles requests by calling the relevant action and rendering its output.
00:05:39.320
In addition to the process, it contains a few other necessary methods making up the core functionalities for dispatching.
00:05:45.560
With the microkernel architecture, you layer additional modules on top of the base to enhance its capabilities while keeping things minimal.
00:05:51.880
This layering principle allows for much lighter controllers as we can pick and choose the necessary components.
00:05:59.680
In the context of ActionMailer, it also inherits from AbstractController, reusing much of the foundational code and promoting DRY (Don't Repeat Yourself) principles.
00:06:07.560
Now shifting back to the action controller structure, we see that the 'Metal' layer is where Rack compatibility starts to enter the scene.
00:06:14.680
The core functionality within Metal revolves around the call method, which embodies the entry point for Rack requests.
00:06:22.800
When a request comes in, it calls an action that traverses through the middleware stack before reaching the dispatching method.
00:06:30.560
Importantly, Metal also features methods that facilitate converting requests into Rack-compliant responses.
00:06:38.280
This structure, layered with modules, allows for a more organized and manageable application architecture and contributes to a smoother Rails experience.
00:06:45.760
Let’s take a broader look at the whole stack architecture, starting with AbstractController base all the way up to ActionController base.
00:06:52.760
At the very bottom, we have AbstractController base along with its modules, followed by ActionController Metal, which can be layered as needed.
00:07:01.680
Sitting at the top is ActionController base, including both abstract and metal modules, providing a comprehensive Rails experience.
00:07:07.560
Overall, this microkernel architecture enhances the readability of Rails and encourages developers to engage with the source code.
00:07:15.560
The modular structure allows developers to focus on specific components without feeling overwhelmed.
00:07:22.760
Next, let's make sure we all understand modules and how they function in Ruby, especially in regard to including and extending modules.
00:07:29.640
When including a module, it adds the instance methods, while using 'extend' adds the methods as class methods.
00:07:36.520
To include both instance and class methods simultaneously, you need to employ a modified structure that includes self-included blocks.
00:07:43.520
This is a slightly trickier method that allows for both methods to be available in the respective contexts.
00:07:50.800
With that foundational knowledge out of the way, we can dive deeper into alias method chain versus super.
00:07:57.360
Let's do a quick code challenge. We have a class User that inherits from GenericUser, and we want to output 'Mr. Greg Pollock' when the name method is called.
00:08:04.360
To achieve this without modifying the initial classes, we will leverage metaprogramming to fulfill our goal.
00:08:11.760
One approach would involve creating a module called Mister that utilizes self-included to define our own name method.
00:08:19.560
We could use alias name without Mister and define a new name with Mister, which calls the original method while adding the prefix.
00:08:25.480
However, a more concise way to achieve the same result is to implement alias_method_chain.
00:08:32.720
Using alias_method_chain simplifies the two alias lines into one line, making the code clearer.
00:08:39.960
Nevertheless, there’s an even simpler approach to this, using super. By calling super, we can easily achieve the expected output.
00:08:47.080
This serves as a reminder that when coding properly, you don’t always need to rely on alias_method_chain—super is a cleaner option.
00:08:54.960
In AbstractController, we can see examples of both helpers and their functionalities that utilize super for cleaner code.
00:09:02.880
The abstract controller helpers go through and return a list of all helper modules, while the metal version adds functionality on top by also providing a `helper_all` method.
00:09:10.960
This `helper_all` method allows the controller to access all available helpers, effectively merging functionalities.
00:09:18.360
With these pieces neatly stacked, we can see the benefits of layering modules and the ease of reading the code!
00:09:25.760
Let’s move on to another important topic, which is ActiveSupport Concern, a concept predominantly developed by Josh Peek.
00:09:32.560
For instance, let’s say we have a module called BarCamp2010 with some methods. What goes in that module to make everything work?
00:09:41.560
We can solve it using self-included class eval to set a class method, allowing us to use our module effectively.
00:09:50.680
By incorporating ActiveSupport Concern, we can simplify the process even further.
00:09:58.920
Essentially, calling `included` would internally handle class methods and wrapping what we run with class eval.
00:10:07.240
Active Support performs four main tasks to ease the integration of modules: extending class methods, including instance methods, and running code encapsulated within the included block.
00:10:15.560
Additionally, it ensures that if you have nested modules, the `included` methods run correctly in the base class.
00:10:25.960
This improvement streamlines adding functionality to classes without requiring extra manual efforts.
00:10:34.760
Next, let's look at the concept of catch/throw, which is quite handy for control flow in Ruby.
00:10:43.440
Catch allows you to declare a block where you can subsequently throw, effectively bypassing the remaining code within the block.
00:10:50.960
Interestingly, you can also return values with throws, which introduces a unique aspect of control flow.
00:10:58.560
In projects like Bundler, catch/throw serves as a backend mechanism for dealing with dependencies, allowing easy navigation through complex code.
00:11:06.840
For instance, in resolving a gem requirement, throws enable meaningful jumps in the code flow based on specific conditions.
00:11:12.560
This efficiency makes it easier to manage conflicting dependencies within application structures.
00:11:19.640
In essence, while managing dependencies within depths of Bundler, the use of catch and throw is a powerful technique to simplify that complexity.
00:11:26.480
Now, moving toward the end of the talk, I’d like to leave you with a crucial takeaway: don’t hesitate to dive into the source code.
00:11:33.680
Rails has become much more readable, enabling developers to customize their applications easily.
00:11:41.360
With microkernel architecture and modular design, you have the flexibility to create tailored solutions that cater specifically to your needs.
00:11:48.560
Whether it's crafting a controller for JSON responses for mobile applications or building unique Rails variants, the sky’s the limit.
00:11:57.360
As a final note, feel free to grab the slides from the URL I will provide and don’t forget to rate the talk for feedback.
00:12:05.200
I appreciate everyone coming and participating today, and I also have some stickers available—just come up and grab them.
00:12:12.360
As I conclude, if you are involved in open-source projects or have releases to promote, feel free to talk to me about them.
00:12:20.360
Now, before we wrap up completely, I welcome any questions you might have, and if you're eager to head out for lunch, that's perfectly fine.
00:12:29.160
If you have queries, don't hesitate to approach me afterward or during breaks.
00:12:36.960
Thank you all for being here. Enjoy the rest of the event!