My last stab at mincemeat models wasn’t very pretty – wrapping different method sets in blocks and folding them.
This is what I’ve been doing lately instead, to keep fat Rails models manageable.
In Ruby, there is the concept of the load path. Basically, if you
require "foo", Ruby will first look for “foo.rb” in your current directory, then in each of the directories in the load path.
Rails adds several directories to your load path, such as
Furthermore, Rails does some magic with
Module#const_missing: if you use a module (classes are modules, too, by inheritance) that isn’t known, Rails will automatically try to
require a file.
The filename is assumed to be the name of the module. If the module is nested in other modules, any but the right-most module are assumed to be directories. CamelCase is converted to snake_case. So
FooBar::Baz translates to
This is why you don’t have to explicitly require your models or controllers: their directories are in the load path, and their files follow this naming convention.
To break some code out of a model, create a directory under
app/models with the same name as the model, then put your extension files in this directory, and name them according to the convention.
So to extend the
User model, you could create
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
Though this file is automatically loaded when you refer to the module, you still have to refer to it, and explicitly include it in the model class. Like so:
1 2 3
The module we defined is
User::AuthenticationExtension, but within the
User class you don’t have to fully qualify the name. Note that this means you have to be careful what names you use: if you define a
User::Forum module for code related to a forum system, and also have a
Forum model, you will have to use
::Forum to refer to the latter from within the
I use the
FooExtension naming scheme to avoid these conflicts.
In addition to including modules, you can also use the same idea to e.g. store a bunch of related constants.
If you, say, find yourself using a lot of raw SQL in a model, you could create a class
1 2 3 4 5 6 7 8
and then refer to the constants as e.g.
User::SQL::SOME_QUERY, or just
SQL::SOME_QUERY from within the model.
The same caveat about class name conflicts applies.
As was brought up in comments on my last post on this subject, keep in mind that it is sometimes better to create more models, rather than fattening the ones you have. But if they must be fat, cut them up.