Thursday, July 15, 2010

Rails best practices

I am from Java/C# background and working on my first rails project. I heard in the beginning that rails has a specific style. Now, after spending few months with rails, I see that rails, like other MVC frameworks, has the same gotchas and similar best practices to address them.
I have observed few best practices that we follow on our project. Here is my attempt to consolidate them..

  1. Fat Models and skinny controllers is one of the most talked about best practice in the rails world. It's aligned with the MVC philosophy of having minimum code in the controllers. Putting business logic in model makes the code easily testable and reusable. Apart from the render block, actions in the controllers should get, create or update the models and then invoke at most one method from the models. All other business logic should be pushed in models.

    That being said, we should be careful that our models should not put on too much of fat. The User the model in my current project is 450 lines, and its spec is 1100 lines long ! This clearly indicates that the model is doing too many things. What can we do with such over fatty models ? There are multiple ways to handle this -
    • First thing is to try to identify if it is possible to extract another model. However,it makes sense to do so only if some code clearly belongs to another model. Otherwise, we would be creating an extra model object which does not exists in the domain and we will end up confusing other devs.
    • Extract out the a group of related functionality into mixins. This will make models little more maintainable and will also segregate the specs.
    • User ActiveRecord::Observer to perform certain actions when model object is created or deleted. This is a great way to separate the code that isn't model's core responsibility.
      In my project, we want to send an email when new model is created. Sending email is not the model's responsibility so the code should not be in the model class. In such situations, its better to use Observers. Also, Observers can observe more than one models. One problem with this approach is that model class does not holds any reference of the Observer. So, by looking at the code in models, its not evident that an observer is observing this class.
    • Make sure that the model does have the presentation logic. If the model has code which modifies/combines the model property for view, move that code to view helpers.
    • Use concerned_with plugin which helps to move code into separate files. It allows to segregate the concerns in different files by opening the model class multiple times. Personally, I don't see it to be much different from extracting the code in modules.
  2. Keep your views lightweight. Views should have only HTMLs and minimum rendering code. Move code from views to controllers if the code accesses the database, to model if the code has some business logic which belongs to a specific model and to view helpers if code is about modifying data to show it in specific format on UI. Make sure that views are not unknowing making database calls by accessing associations. Try to avoid putting css and javascript in erbs. They should go in separate files.
  3. Use view helpers effectively. Views, models and controllers should not have presentation logic. Moving the presentation logic to view helpers makes it easily testable.
  4. In modules, avoid accessing instance variables of including class. If the module is included in controller, avoid reference to params hash. Instead of using the instance variables or methods from the class, pass the required data to methods. This reduced coupling between the class and included modules, makes module code reusable and easy to test.
  5. If you are using activerecord finder with same condition in multiple places, move them to model and use named_scope. Named scopes allows you to give a descriptive name to the finder. You can also chain multiple named_scopes together. Move all the activerecord finders from controller to models.
  6. Understand available callbacks on ActiveRecord controller. Consider using activerecord observers.
  7. If you have a common code across multiple actions in a controller, extract out that code in filters. Use before_filter, around_filter and after_filter to keep your controllers DRY.
  8. Ideally, Controller should have only action methods. If you have any method in controller, which is not an action, see if you can move this method to model or to view helper.
  9. Instead of creating the model and then building it from params in controller, pass the hash to the initialize in models. Instead of having any conditional logic in controllers, move it downstream in models or services.
  10. Understand REST principles. Create controller for identifiable resource/domain entity. Do not do update database in index/show action of controller. Use right HTTP methods.
  11. Consider creating service classes to represent operation which does not have state. e.g. The code which talks to some external service can go in a service class.
  12. And most importantly, write tests for all your code !