Forget fat models: it’s time for skinny controllers and skinny models

Leo Allen
Makers
Published in
2 min readJul 26, 2016

--

When learning to code and build applications on the web, particularly when using MVC frameworks like Ruby on Rails, you may well have come across the mantra “Fat Models, Skinny Controllers”. On the surface this appears pretty sound advice — in our experience controllers can easily get out of hand if you’re not strict in keeping as little code in them as possible. Similarly, skinny controllers make it more likely our controllers comply with the Single Responsibility Principle, only handling incoming requests/responses and delegating any all other responsibilities to other classes.

Let’s use an example of a fat Rails controller that sends a user an SMS via Twilio to explore how this works in practice:

class SMSController < ApplicationController
def create
user = User.find(params[:id)
account_id = Rails.application.config.twilio_account_id
twilio = Twilio::REST::Client.new(account_id)
args = {
to: user.phone_number,
body: params[:message]
}
client.messages.create(args)
end
end

Following the “Fat Models, Skinny Controllers” dictum this code can be moved into the User model:

class User < ActiveRecord::Base

# …
def send_sms(message)
account_id = Rails.application.config.twilio_account_id
twilio = Twilio::REST::Client.new(account_id)
args = {
to: phone_number,
body: message
}
client.messages.create(args)
end
# …
end

Our controller is now significantly skinnier:

class SMSController < ApplicationController
def create
user = User.find(params[:id)
user.send_sms(params[:message])
end
end

But our User model now has an additional responsibility of sending text messages, again violating the Single Responsibility Principle. Instead of moving the code there, let’s move it into another object:

class SMS  def initialize(config = nil, client = nil)
@config = config || Rails.application.config
@client = client || Twilio::REST::Client.new(config.twilio_account_id)
end
def deliver(to, message)
args = {
to: to,
body: message
}
client.messages.create(args)
end
private attr_reader :client, :config
end

There’s nothing wrong with writing objects like this that are neither models nor controllers — the lib directory in Rails exists for precisely for this purpose. We would argue that this approach should be actively encouraged, since object-oriented code works best when building small objects without too many responsibilities. Not only does it prevent our code becoming too complex and unmaintainable, but testing becomes much easier as well: note how the dependencies to this object have been injected at the top, making it trivial to mock calls to the Twilio client in our unit tests.

All that’s left now is a simple call to our new object in our controller:

class SMSController < ApplicationController
def create
user = User.find(params[:id)
sms = SMS.new
sms.deliver(user.phone_number, params[:message])
end
end

We’ve achieved our Holy Grail — a skinny controller and a skinny model.

--

--