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.
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
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
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à:
Adapting the grammar for other kinds of blocks should be pretty straightforward.