The Pug Automatic

Skinny controller, mincemeat model

Written July 25, 2007. Tagged Ruby, OS X, TextMate, Ruby on Rails.

I'm all for skinny controllers, but fat models can get unmanageable. After adding a couple of methods for this, that and the other – perhaps a User with authorization, password reset and serialization – the model can be quite fat indeed.

There are many conceivable ways of making fat models more manageable. Something very simple I've been trying lately is to just group code in blocks, and fold them in TextMate as needed. The simplest way is to just use begin/end:

class User < ActiveRecord::Base

def full_name
[first_name, last_name].join(' ')
end

begin # authentication

def self.authenticate(username, password)
# do it
end

end

begin # serialization

def to_xml(options={})
# do it
end

end

end

I prefer self-documenting code to comments, though. You can fake it by doing

begin :authentication

end

The :authentication symbol isn't actually a method argument – this is equivalent to

begin
:authentication

end

so I suppose whether it can be described as more self-documenting than comments is debatable.

:foo.code do

Anyway, I do this instead:

As lib/ar_groups.rb:

class Symbol
def code(&block)
block.call
end
end

Added to config/environment.rb:

require 'ar_groups'

And then you can do:

class User < ActiveRecord::Base

:authentication.code do

def self.authenticate(username, password)
# do it
end

end

end

category :foo do

If abusing symbols bothers you, try something like

module ActiveRecord
class Base
protected
def self.category(name=nil, &block)
block.call
end
end
end

and

class User < ActiveRecord::Base

category :authentication do

def self.authenticate(username, password)
# do it
end

end

end

Highlighting

To make things more manageable still, TextMate can be made to highlight these blocks. Merge this rule into the Ruby grammar (with thanks to Ciarán Walsh):

{ name = 'meta.code-cat';
begin = '^(\s*)(:.+)\.code (do)\b.*';
end = '^\1(end)\s*(#.*)?\n?';
beginCaptures = {
2 = { name = 'constant.other.symbol.ruby'; };
3 = { name = 'keyword.control.ruby.start-block'; };
};
endCaptures = {
1 = { name = 'keyword.control.ruby.end-block'; };
2 = { name = 'comment.line.number-sign.ruby'; };
};
patterns = (
{ include = '$base'; }
);
},

and theme source.ruby meta.code-cat to taste. Et voilà:

[Screenshot]

Adapting the grammar for other kinds of blocks should be pretty straightforward.