It's been nearly a month since the Ruby on Rails workshop and I almost feel like it's an entirely new year because I have been learning things that have nothing to do with Ruby programming or computers in fact. They're all very interesting and these are things I wish I knew 10 years ago. I have also been traveling a lot, so much that I feel so disconnected with my work but I will get back soon to work and do things I usually do (in a few days).
The REAL reason why I am posting this is a need to help everyone stop making the same dumb mistakes I (and probably thousands of other developers) have done in the past. This technical debt is writing bad code which could hardly be discerned and changed by anyone including yourself after a few years. Stop and think about what you are doing. It is not about timeframe and budget and how you don't have time to think. It is not about perfection. It is about being able to look at yourself and like what you have done.
This post is simply about "how to avoid fat controllers and fat models."
A very good talk that explains the importance of this was done by Corey Haines on Arrrcamp.
View the video as my words are insufficient to convince you why this is so important.
Accumulating fat code, similar to getting physically fat, makes us feel bad and makes us (or someone else) spend more money and time trying to revert the mistakes.
How do we fix or avoid having fat controllers, models or generally classes in Ruby?
Use ActiveSupport Concerns
This feature has always existed in Ruby on Rails for a long time. It is an improvement to the normal way of extending a class or including instance methods through a new module. It is a fundamental concept in Ruby. See: Module.included.
For Ruby on Rails 4, one of the primary changes is the new folder called "concerns" which is meant for all the modules that use ActiveSupport::Concern.
If your model file looks like this:
class Itinerary < ActiveRecord::Base belongs_to :trip belongs_to :user validates :user_id, presence: true validates :location, presence: true class << self def for_user(user) self.where(user_id: user) end def for_year(date=Time.zone.now) self.where("travel_on > ? and travel_on < ?", date.beginning_of_year, date.end_of_year) end def total_yearly_estimated_cost(user, date=Time.zone.now) self.for_user(user).for_year(date).map(&:estimated_cost).reduce(:+) || 0.00 end end end
You should create a new module and make sure it is on app/models/concerns folder.
# app/models/concerns/calculate_estimated_cost.rb module CalculateEstimatedCost extend ActiveSupport::Concern module ClassMethods def for_user(user) self.where(user_id: user) end def for_year(date=Time.zone.now) self.where("travel_on > ? and travel_on < ?", date.beginning_of_year, date.end_of_year) end def total_yearly_estimated_cost(user, date=Time.zone.now) self.for_user(user).for_year(date).map(&:estimated_cost).reduce(:+) || 0.00 end end end
Naturally, we will include that module on the model file which is a class.
# app/models/itinerary.rb class Itinerary < ActiveRecord::Base belongs_to :trip belongs_to :user validates :user_id, presence: true validates :location, presence: true include CalculateEstimatedCost end
This is the least recommended way primarily because it is a "mixin" (a class with a module included). A mixin is inheritance and we were told to avoid inheritance. This was mentioned on day one of the workshop. We need to include the module on the model file.
Use Plain Old Ruby Objects
If you've been doing enough Ruby or reading, you've probably come across similar articles or books. This idea is very old but only a few like Avdi Grimm wrote about it.
It is great way to improve controllers. This does not as simplistic as requiring a class on another class or module. You have to read more about design patterns like the use of decorators.
Does this all sound gibberish to you but you want to learn Ruby on Rails?
We came up with a decent guide for beginners. Please read the Geekcamp Baguio Ruby on Rails Workshop guide.
Artiom Diomin, our overall technical lead for Assembla.comments powered byDisqus