I was quite confused when I saw lines like this in controllers that I was working on:
class SessionController < ApplicationController def create user = User.find_by_email(params[:session][:email]) password = params[:session][:password] if user && user.authenticate(password) sign_in user redirect_to user else redirect_to sign_in_path end end end
Sign in user? Is it that easy? Do I just type what I want in plain text, and the computer will know what I’m talking about?! Well, no, of course not...but as someone new to the system, these sorts of conventions can be really confusing.
The key thing to remember with Ruby is that, not only is inheritance easy (‘ class Foo < class Bar’), but that classes and modules can be opened and modified again and again throughout its life. This is a wonderful and flexible feature of the language, but it also makes it difficult to track down where something was defined (and more importantly what it does!). This problem is especially pronounced when you are a Rails newbie jumping in on a team project, as there are often third-party libraries and custom architecture at work in deeper levels of the application where they may not be apparent to you.
I’ve found it fruitful to check the following three places if you’re wondering, “where the heck did this method come from?” :
1) Is it a built-in Rails method?
Don’t forget that almost all of the objects you will use to architect your application inherit from a Rails class. These parent classes, aside from gluing together different Rails sub-systems, provide the developer's subclasses with tons of useful instance methods. Seemingly free-floating methods you might call in your controller (‘params’, ‘session’, or ‘render’), for example, were all defined upstream of your particular controller by Rails and thus are automatically available as instance methods, even though you don’t explicitly see them defined. For those of us from JS backgrounds, it can be helpful to remember that these method calls could all be rewritten as ‘self.params’, ‘self.session’, or ‘self.render’, (the ‘self’ referencing the controller instance just as ‘this’ references a new instance object in JS). The Rails documentation is a great place to look to get a list of these methods and their purpose, as well as the relationships among Rails objects.
- For models you’ll want to start with ActiveRecord::Base. All of your models inherit from this by default.
- Controllers inherit from ActionController::Base, but they are flush with methods that are provided by a wide array of ActionController modules.
- Views - The ActionView::Helper module will get you started on learning all of the helper methods Rails exposes to your view templates.
One thing to keep in mind here is that much of the functionality available in a particular class will be provided to it by a module. The list above references some modules, like the ActionView::Helper module. This module is included in the ActionView::Base class, which is why methods such as ‘link_to’, etc. are available in your templates. Reference the ‘Included Modules’ section of the documentation above to start to map out what modules are available in these classes by default.
2) Is it provided by a file in your lib/ or app/helpers directory?
As we’ve been discussing, Rails makes great use of Ruby’s classical inheritance features to concisely propagate functionality throughout the system. A developer will also take advantage of this feature of Ruby to provide various parts of his or her application with custom methods. Code written in files in your lib/ or app/helpers directory are available to the rest of your application. The convention is to package code written here as modules.
So, with our example above of the ‘sign_in’ method, this might be provided by the file lib/auth.rb:
module AuthorizationMethods def sign_in cookies.permanent[:remember_token] = user.remember_token end end
There is still one more step to solving this mystery, though. Where, in the inheritance chain, was this module provided to the controller? In this case, it was included in the application_controller.rb file.
class ApplicationController < ActionController::Base include AuthorizationMethods end
Since our controllers inherit from ApplicationController, these AuthorizationMethods will be available to our controllers.
3) Is it provided by a gem?
So far, our strategy to locating the source of a method has been to climb back up the inheritance chain in the case of developers’ custom methods, or to read the Rails source or documentation to understand Rails’ default object map. In Rails, however, you’ll often use at least a few gems in your project to easily plug in more functionality. One challenge, especially when joining a project that you didn’t write yourself, is that gems can be a bit of a ‘black box’. Methods seem to magically appear in places you might not have anticipated.
In our earlier example, it could actually have been a gem that provided that ‘sign_in’ method. The Devise gem, for example, is a fully-featured authentication solution for Rails that provides a ‘sign_in’ method automatically to all of your controllers during the Rails initialization process. This process takes place unseen, behind the scenes. If the method you are confused about is neither a Rails default nor a module written by your dev team, then it is likely functionality created by a gem. In this case, it’s best to ask your fellow developers or read the documentation for the gems in your Gemfile.
Many developers will tout the productivity gains they’ve made by using Rails. After getting used to it, the elegance and power of the system have become apparent. But Rails is also undoubtedly a complex system, and it can be quite difficult for the uninitiated to navigate the plentiful and ever-changing object landscape of a Rails application. The above is a framework I’ve found helpful to working through this confusion and I hope that it helps you as well!