Skinny controller, mincemeat model

Written . Tagged OS X, Ruby, Ruby on Rails, TextMate.

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

1
2
3
begin :authentication
  
end

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

1
2
3
4
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:

1
2
3
4
5
class Symbol
  def code(&block)
    block.call
  end
end

Added to config/environment.rb:

1
require 'ar_groups'

And then you can do:

1
2
3
4
5
6
7
8
9
10
11
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

1
2
3
4
5
6
7
8
module ActiveRecord
  class Base
  protected
    def self.category(name=nil, &block)
      block.call
    end
  end
end

and

1
2
3
4
5
6
7
8
9
10
11
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{ 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.