good morning guys!\nthis is the renegade’s guide to hacking rails internals\n(introduce ourselves)\n
we’re from Intridea\nproducts & services\nscalr, crowdsound, mediaplug & socialspring\n
how many of you have opened up vendor/rails to take a look inside?\nhow many have written a plugin?\nhow many have written something in lib/ that has overridden rails internals?\n
so why would you even want to hack the internals? there are quite a few compelling reasons...\n
you will learn a lot.\nthe rails internals are a veritable treasure trove of excellent Ruby code. you learn the internals and you will pretty much learn all the ins and outs of Ruby syntax and the way different pieces fit together within the language\n
rails is a tool built on ruby. if you use it everyday, it only makes sense to know its limits and the way it’s put together.\n
you will always run into instances where \n
learning the internals lets you do lots of cool stuff -- it’s an investment\n
we can offload stuff to queues (example starling)\nwe can run separate queue processors independent of the rails app that leverage the code you have written for the app\n\n \n \n\n \n
you can really keep things DRY within your codebase by knowing how the Rails class loader works.\n\n \n
knowing the internals will let you extract full components of apps into their own separate, shareable rails apps. we will be attemping this soon.\n
if you know how the Rails stack operates, you can effectively create ANYTHING using the Rails code you have already written. \nthis is when Rails actually fulfills its promise of being a swiss-army knife. \n\n \n \nkeeping this in mind....\n
a few words of advice. you should only hack the internals when you really need to....\n
you can screw things up that you didn’t even mean to, and that’s why you should always...\n
rails has quite an extensive test suite. if you’re modifying a piece of core code, please make sure you look through the test suite to learn how it’s expected to behave. then make sure you write specs or tests to make sure your changes work. then write more tests to make sure your edge cases work. \n
first thing we’re going to do is to run through how rails actually loads itself.\n
\n \n
let’s do a short recap of the important stuff\n
there’s a Rails::Initializer class that’s used to setup all the necessary classes and initialize Rails.\n
you can edit your app’s settings by modifying the Rails::Configuration instance that’s available in the config/environment.rb file\n
the process method contains all the startup steps, in the same order that Rails loads (how convenient!)\n
is your best friend. require config/environment.rb in any file and it’ll load up the Rails stack for you.\n
Now that we know how Rails loads itself, let’s see how the Rails class loader operates.\n
the rails class loader is completely contained within the ActiveSupport::Dependencies module. it’s a really dense piece of code when you first look at it, and it leverages a lot of Ruby magic to get stuff working. \n
Rails uses require, which only loads each specified file once in production mode. \nrails uses load() which loads the same file multiple times in development mode.\n
the dependencies module uses the const_missing hook to identify classes to load.\nThis is a standard Rails hook method that’s called whenever a constant that hasn’t been loaded is found. The behavior of the Dependencies module goes a little something like this...\n
here’s what a typical load trace looks like.\ncode walkthroughs are a little boring -- so let’s do a short exercise. \n
rails’ plugin interface is a great way to re-use code across multiple apps. however the default implementation has one big limitation. there are no app-slice plugins. \n
these are plugins that have their own controllers, models, helpers, views, routes and migrations. IE -- self-contained “apps” that can be extracted out into their own quite easily, and also integrated with multiple rails apps with the option of being heavily customized. \n
the good thing is that Rails’ plugin loading code is quite extensible, and slice handling is not impossible to implement. the engines and the desert plugins are a few released plugins that help with this.\nIntridea’s SocialSpring social networking platform uses tiny_apps, a custom classloader that allows us to put together custom social networks very, very quickly.\n\n \n \nbut for the sake of learning, let’s roll our own.\n
so let’s code ours up -- first we’re going to run through the plugin loader, then we’ll code up our own version\n
\n \n
\n \n
\n \n
\n \n
\n \n
let’s take a 10 minute break. when we come back, michael’s gonna run through hacking actionview and routes!\n
\n \n
I’m going to be presenting with a bit of a different approach. Honestly, I don’t understand a lot about Rails internals. To be even more honest, I don’t really want to understand everything. I’m happy to have a little bit of magic in my life.\n
I don’t hack internals because I think it’s a lark, I do it because I see the potential for real benefit to the projects I’m working on. If you don’t see yourself saving a lot of time and effort down the road for your efforts in working with internals, you probably shouldn’t be doing it.\n
The way to manage that initial intimidation factor is to find an entry point, a place that you can get your bearings and go from there. It could be a blog post, an existing plugin, or if you’re in uncharted waters, you might just have to read the code yourself.\n
When you’re monkey-patching, don’t expect things to go right the first time. It takes time and patience and a whole hell of a lot of restarting your environment.\n
I’m going to go over some of the Rails Routing internals both in terms of how it works and where it’s easy to extend.\n
My knowledge in this area comes from writing the SubdomainFu plugin, which allows Rails apps to easily handle subdomains.\n
So I had a couple of goals for SubdomainFu. One, I wanted to be able to use a subdomain option on any of my route helpers and it would “just work.” Two, I wanted to add a new condition to routes that would allow me to specify things about subdomains in the routing DSL itself.\n
So first up, lets tackle the route helpers.\n
Unfortunately, I wasn’t really able to find a good blog post or existing plugin that worked with adding a universal option to all URL generating methods. I had to do the legwork myself and read through the internals to learn how it worked.\n
I started with something that I was familiar with. I knew from various stack traces that url_for was called pretty much every time a route helper was used; this was my entry point.\n
Once you have a starting place, you have to begin to understand the area. Act like a Google bot, expanding your knowledge by understanding a method, then understanding the methods that that method calls. If a method’s purpose is self-evident and you don’t need to modify it for the task at hand, go ahead and skip past that.\n
You have to know when to quit. It’s a good idea to spelunk for code for a bit until you get a fuzzy general idea of what’s going on, then tackle it from a practical perspective and learn the rest while writing code.\n
OK, so that’s how the general process works when you don’t really have an idea of what you’re dealing with, let’s see some of the fruits of this labor! [LIVECODE TIME]\n
So we’re done with step one, now it’s time to move on to step two! We want to add a new condition to the routes that allows us to specify subdomain-based conditions.\n
Here, I was able to get a lot more help from the early settlers. Jamis Buck’s blog has a couple of really nice looks at routing internals that gave me a general idea of what I was doing.\n
Even better, there was already a subdomain_routing plugin available! The easiest way to hack Rails internals is to have someone else do it for you, but I wanted things to work a little differently so there was still work to be done.\n
The greatest thing about Ruby is that there is just a ton of open source out there. No matter what you’re doing, one of the most helpful things you can do to learn more is track down the most similar open-source project to what you’re working on and read their code. It’s really invaluable experience, even if they aren’t doing things the same way you want to or are working towards a different goal.\n
Ok, so let’s take a look at the route conditions code and how we hook into it. [LIVECODE]\n
So we’ve really only scratched the surface of routing here. To really get into the nuts and bolts I really recommend reading the posts on Jamis Buck’s blog; there’s enough there to be an entire tutorial session on its own.\n
\n \n
Once again my experience with this comes from writing a plugin, this time just to make my life easier when making Rails apps! UberKit provides some UI helpers that make it really easy to build navigation menus and forms in Rails apps.\n
The UberKit includes UberMenus, which is a set of helpers designed to make creating navigation menus super-easy and UberForms, which provides a custom form builder to automatically generate label HTML etc.\n
These things highlight a great aspect of Rails: much of the functionality is built to be extended, modified, and improved. We’ll look at some of the ways this is exposed as we go through each of the parts of creating the UberKit.\n
Helpers are usually used for little methods to dry up little parts of your code. But with the right approach, they can be used as powerful interface building blocks that can be re-used from application to application.\n
So when I started trying to learn about block-enabled helpers, the most obvious place to look was the one that all Rails developers use: form_for.\n
The CaptureHelper is invaluable in advanced helper writing. It allows you to grab the output of arbitrary blocks of view code and store it in a variable. It’s how content_for works, but it can be repurposed to make some powerful helpers for us!\n
What we basically want is a really readable way to write out a menu so that it automatically becomes well formed state-aware semanticHTML that we can easily style.\n
The way we’ll do this is we’ll create a custom builder class that collects input from the user, then builds output. By doing this we give ourselves greater control over the construction of the output than simply outputting as we go.\n
So let’s make it happen! [LIVECODE]\n
Form Builders are a part of Rails that is built to be extended, but not really well documented in that way.\n
So a form builder is a class that generates the components of a form. Most commonly it’s the class instantiated in the form_for helper.\n
So this isn’t exactly a brand new idea. I learned the basics of extending form builders from a tabular form builder plugin.\n
Here we’ll really benefit more from going straight into the code so let’s get started!\n
\n \n
and ruby’s a great language to work with and extend. however\n
most of rails is advanced ruby, and you’re advised to learn the language in and out before you modify the internals. you should have the ruby hook methods at your fingertips, and should be comfortable rolling your own classes and modules.\n
never, ever override the source code directly, always create new files and modules and include them judiciously -- this will help you upgrade later. \n
whatever bit of code you’re modifying, make sure you know it IN AND OUT. don’t touch code that you have no idea what its doing, it’ll save you a lot of hassle. \n
test your changes a lot, and in the same vein, read the tests for whatever code you’re modifying so you know what it’s REALLY about.\n
and finally -- i’m preaching to the choir, but keep your code beautiful, concise and clear -- all three of these characteristics are not mutually exclusive \n