Concerned With: Skinny Controller, Skinny Model

2:33 PM EDT Saturday, August 30 2008

A small Rails technique that I've been using lately is concerned_with. I first saw Matthew Bass use it on the => 3-2-1, but I've gleaned from a couple of articles on the web that it was invented by Rick Olsen.

The purpose of concerned_with is to break up larger models up into concerns. Let's say you have a User model, and you have several macros, includes, validations, callbacks, class methods and instance methods all related to authentication, maybe sort of like this:

class User << ActiveRecord::Base
  include AuthenticationSupport
  validates_presence_of :login
  validates_uniqueness_of :login
  before_create :set_default_password
  class << self
    def authenticate(login, password)
      login == "root" && password == "too_many_secrets"
    end
  end
  def set_default_password
    self.password = "pa$$w0rd"
  end
end

As your model grows in complexity, eventually what will happens is that you will have more validations, callbacks, class methods, instance methods, etc. They will get mixed in with the authentication ones, app/models/user.rb will grow to become 500 lines of code and it will be hard to keep track of what's what. Instead, just put this into app/models/user.rb:

class User << ActiveRecord::Base
  concerned_with :authentication
end

And then throw all of that into app/models/user/authentication.rb:

class User
  include AuthenticationSupport
  validates_presence_of :login
  validates_uniqueness_of :login
  before_create :set_default_password
  class << self
    def authenticate(login, password)
      login == "root" && password == "too_many_secrets"
    end
  end
  def set_default_password
    self.password = "pa$$w0rd"
  end
end

So now your app/models/user.rb is clean and can have other stuff in there, and all the authentication related stuff gets loaded from app/model/user/authentication.rb. All you have to do to enable this is is throw this snippet into an initializer, like config/initializers/concerned_with.rb:

class << ActiveRecord::Base
  def concerned_with(*concerns)
    concerns.each do |concern|
      require_dependency "#{name.underscore}/#{concern}"
    end
  end
end

Posted in  | Tags Rails, Ruby

Comments Feed

1. neat idea, but i cringe every time i see see it. i'm not sure why.

# Posted By bryanl on Sunday, August 31 2008 at 10:43 EDT

2. maybe i cringe because that its just another way of including a module. why not just do include AuthByLocalPassword or include AuthByLDAP.

the concerned_with just looks a bit more english like i guess.

# Posted By bryanl on Sunday, August 31 2008 at 10:45 EDT

3. @bryanl

It's a little cleaner than a module because by opening up the actual user class, you can use exact same syntax. If you use a module, you have to sort of re-organize things a bit:

http://gist.github.com/8199

Since there's no reason to re-use this code, there no need for it to be a module. If there are methods you plan to share across models, then making it a module makes more sense.

# Posted By Paul Barry on Sunday, August 31 2008 at 01:13 EDT

4. @Paul

I guess I think of modules as encapsulated behavior, and not a way of sharing behavior. This is why "concerned_with" seems a bit extra to me.

# Posted By bryanl on Sunday, August 31 2008 at 04:04 EDT

5. I'm with Paul here. Modules seem to fit more as a way of encapsulating behavior for the purpose of reusing it amongst many classes. Maybe it's a matter of taste, but I like the "concerned_with" syntax because it's still connected directly to the class that it's part of, so the functionality can remain specific to that class rather than being abstracted for inclusion by any class.

# Posted By Ethan Vizitei on Saturday, September 06 2008 at 11:51 EDT

6. I'm not sure I buy into this. What value does this bring over the traditional method (using a module with include)?

# Posted By ActsAsFlinn on Wednesday, September 17 2008 at 10:00 EDT

7. In my pastie, I extend the user model with authentication validations. How exactly would you do that with a module?

module UserStuffs
def self.included(base)
base.validates_presence_of :foo
end
end

Yuck.

But if you're relying on this too much, you may be in danger of bloating your models too much (and neither 'concerns' nor modules will help you out here)

See trevor's comments on http://giantrobots.thoughtbot.com/2008/5/1/skinny-controllers-skinny-models

# Posted By rick on Wednesday, September 17 2008 at 11:27 EDT

8. I'm really struggling to see why this isn't significantly worse than sticking the code in a module and including it. Isn't it just obfuscation? Why make life harder for people than it already is (by forcing them to learn your new pattern, when it doesn't add anything over and above "include")?

Just because you can, it doesn't mean you should.

# Posted By Graham Ashton on Friday, September 19 2008 at 09:11 EDT

9. Isn't this really just an implementation of partial classes? http://en.wikipedia.org/wiki/Partial_type

- Mark

# Posted By Mark Richman on Wednesday, September 24 2008 at 07:09 EDT

10. I think this is a good idea. Don't let the above detractors discourage you. Yes it is like doing an include but I feel that this is cleaner. My only concern is performance. I'm going to work with it and see if it affects the speed too much.

@the_detractors
What is the difference between this and using named_scope, has_many, many of the lamba shortcuts, orany other shortcuts or tricks? This is just a cleaner way of only editing what you need, and finding the place to edit faster. I see no reason not to take this approach. This falls much more into the category of doing things the rails way; whereas the other way is a lot more in line with the ruby way.

# Posted By Michael Christenson II on Wednesday, September 24 2008 at 05:06 EDT

11. nice discussion. I've tried a couple of methods, also those mentioned above, to split up and organize code but haven't found the one to fit-all.
I think its more important to stick to self given conventions throughout one app which leads in any way to a cleaner layout and better handling.

# Posted By Schorsch on Wednesday, September 24 2008 at 07:38 EDT

12. Saying "skinny model" is a little misleading. The class still has the same amount of behavior, it's just split across multiple files.

# Posted By Dan Manges on Wednesday, September 24 2008 at 10:07 EDT

13. > Saying "skinny model" is a little misleading...

Fine then, how about "minced model"

# Posted By Andrew Vit on Thursday, September 25 2008 at 12:52 EDT

14. I do something similar but module-based (meaning it can be shared between different models) here:
http://henrik.nyh.se/2008/09/augmentations

# Posted By Henrik N on Thursday, September 25 2008 at 04:41 EDT

15. Oh, and Augmentations handles stuff like validations (like Rick mentions in comment #7) nicely.

# Posted By Henrik N on Thursday, September 25 2008 at 04:45 EDT

16. Wouldn't this also serve to break up your unit tests into more manageable and understandable chunks?

# Posted By Larry on Friday, September 26 2008 at 09:47 EDT

17. I winced when I first saw this, but then something clicked.

You are not taking out methods (etc.) just for the sake of cleaning up your model. You aren't making a skinny model just to say that you have skinny models. You are taking out functionality that _doesn't belong_ in the the model in the first place. An ActiveRecord model should handle only ActiveRecord stuff. The idea is that authentication, to follow the example from the article, has nothing to do with the ActiveRecord User model. This is just a separation of concerns (hence the concerned_with name). For the same reason that it is not good to do an AR find inside a view, or to have search logic inside a controller, it is not a good idea to put authentication stuff in a model. And its worth noting that you are not wanting to just hide some methods and stuff (though that is nice), you want to extract a class; there's an extra class hidden inside your model and you are taking it out. A module cannot do this.

This is a great refactoring tool. Thank you.

# Posted By Fabio on Monday, September 29 2008 at 11:28 EDT

18. I don't like how its all lumped into the models directory. I like how with Rails you know just where to go to find stuff. With this approach I don't know (without opening the file and looking carefully) whether something is a model or not.

Why not store these in a "concerns" directory?

# Posted By Sean on Thursday, October 02 2008 at 04:39 EDT

19. This is a great pattern and makes my user model much nicer.

Thanks!

# Posted By Sascha Konietzke on Monday, October 13 2008 at 11:10 EDT

20. Would there be any reason to not use this pattern for controllers as well?

My application controller grew bigger and bigger. Could I just open ActionController::Base and add the concerned_with functionality?

# Posted By Sascha Konietzke on Monday, October 13 2008 at 12:07 EDT

21. As much as I love a good Internet argument...

This is an interesting idea for covering up a bad design with a tube of lipstick. A user is a thing. Authentication is a behaviour. A user authenticates:

class User < ActiveRecord::Base
authenticates
end

module Authenticates
def self.included(base)
base.extend ClassMethods
base.send :include, InstanceMethods
end

module ClassMethods
def authenticates(options = {})
...
end
...
end

module InstanceMethods
...
end
end

It's not a rails v. ruby thing. This is a pattern that is repeated over and over in rails and there is already a place where these things go: vendor/plugins

If you find yourself extending or changing the platform, especially one as well thought out and rich as rails, you should first assume that the problem lies with your design and not with some deficiency in the platform.

If you refactor your design, not just for authentication but for any sort of behaviour, you not only get code which is easy to understand, easy to test and easy to reuse. If you load your models down with all sorts of implied behaviour then you get fat code which can't be easily tested and which can't be easy reused. That is what skinny models means. Its why we have: acts_as_list, will_paginate and thousands of other behavioural plugins. You might even find you don't need to write any code at all because there may be a plugin to do it already.

What if you wanted to authenticate both robots and users? Authenticate with OpenID. Authenticate with XML for an API? If you implement it as a behaviour then everything that uses that plugin gets this capability for free, fully tested and free of known bugs.

# Posted By Erik Petersen on Tuesday, October 28 2008 at 10:14 EDT

22. Hi May i know what is the diff b/w require and require_dependency...........?

# Posted By manikandan on Tuesday, December 30 2008 at 01:22 EST

Add a Comment