I've been spending a fair amount of time digging around the Rails source as of late, particularly to stay up to date with changes in Rails 4. For example, earlier today I was curious how the new update method was implemented as compared to its update_attributes predecessor. I find going directly to the source is the best way to answer these types of questions.

Tracking down the definition of the update method seems easy enough. Given that we have all the Rails source files, we can just run a quick search for occurrences of def update. Trouble is, with that broad of a search you end up sifting through around 50 matches across 37 files. But of course we're only interested in the update method of an ActiveRecord model. Ideally we could go directly to the source of that method. Thankfully, Ruby 1.9 lets us do exactly that.

Suppose we have a Person ActiveRecord model and in the Rails console we've created a new object like so:

>> person = Person.new

In Rails 4, we can call the update method on the person object to update that object's attributes and save it to the database:

>> person.update(name: "Fred")

We're interested in how that update method is implemented. The first step is to get ahold of the method itself. We do that by calling the method method on the person object, passing in the name of the method we want:

>> method = person.method(:update)
=> #<Method: Person(ActiveRecord::Persistence)#update>

Notice that this returns a Method object that represents the update method. Methods aren't natively represented as objects in Ruby, but you can objectify a method using this technique.

Here's the cool part: Given a method object, we can ask it for its source location:

>> location = method.source_location
=> ["/Users/mike/.rvm/gems/ruby-1.9.3-p385@rails4/bundler/gems/rails-5d58948fe72e/activerecord/lib/active_record/persistence.rb", 
     215]

The source_location method returns an array with two elements: the name of the source file that defines the method and the line number where it's defined. Pretty cool indeed!

It sure would be convenient if we could automatically open that file in our favorite editor and position the cursor on the exact line where the method is defined. Indeed, we can! With SublimeText, for example, you just call the subl command and separate the file name and the line number with a colon. So from inside of the Rails console, we can launch SublimeText and go directly to the update method using:

>> `subl #{location[0]}:#{location[1]}`

If you happen to be a TextMate user, you use the -l option to pass in the line number:

>> `mate #{location[0]} -l #{location[1]}`

And for the die-hard MacVim users:

>> `mvim #{location[0]} +#{location[1]}`

Taking that a step further, we can bottle up these steps in a method so it's easy to hop straight into the source of any method. Just toss this method into your .irbrc file:

def source_for(object, method)
  location = object.method(method).source_location
  `subl #{location[0]}:#{location[1]}` if location
  location
end

Then from your Rails console you can use:

>> source_for(Person.new, :update)

The Person class also defines an update class method, and to see the source for it you'd use:

>> source_for(Person, :update)

Taking it a step further, you can make the source_for method a bit more flexible. For example, suppose we wanted to look up the source for instance methods defined in Ruby modules, such as a Rails helper. We'd like to be able to use this

>> source_for(ApplicationHelper, :some_helper)

But that won't work because the some_helper method isn't defined on the ApplicationHelper module. It's an instance method, not a class method. And modules can't be instantiated, so we can't pass in ApplicationHelper.new.

Based on Jim Weirich's suggestion, here's a slightly more involved version of the source_for method that handles both cases:

def source_for(object, method_sym)
  if object.respond_to?(method_sym, true)
    method = object.method(method_sym)
  elsif object.is_a?(Module)
    method = object.instance_method(method_sym)
  end
  location = method.source_location
  `subl #{location[0]}:#{location[1]}` if location
  location
rescue
  nil
end

First it checks if the method is defined on the object. If so, we go ahead and call the method method to get ahold of the method just like before. Otherwise, we check to see if the object is a module. In that case, we then use the instance_method method to get ahold of the instance method defined on that module. Problem solved!

Supporting modules has an interesting side effect. Suppose we want to look up a method on an object that requires a bunch of initialization parameters. In the original version of the source_for method, we'd need to call source_for something like this

>> source_for(MotherOfAllClasses.new(a, b, c, d), :some_method)

It's inconvenient to have to supply all those initialization parameters just to get an object for the sake of finding the intended method. But given the revised source_for we can instead use

>> source_for(MotherOfAllClasses, :some_method)

This works simply because classes in Ruby are also modules. Let's trace through the source_for method for this case. First, we check whether MotherOfAllClasses responds to the some_method method. It doesn't because some_method is an instance method, not a class method. So then we check if MotherOfAllClasses is a module, which it is because all classes in Ruby are also modules. That being the case, the instance_method method returns the some_method method we're interested in.

We've been using source_for from within the Rails console, but you can use this method from within any irb session to view the source for methods in any gems, external libraries, or even your own code!

Thanks to Jim Weirich and Ernie Miller for their suggestions!