The title kind of says it all. I prefer to modularize code into mixins and include them into the model. Other people like to put the code directly into the model, potentially growing the models into MOUS's, models of unusual size.
I'm wondering what you guys do/what's better practice out there in dev land.
Modularizing code as a means to encapsulate independent concerns or behaviors is a good practice, but be careful of correlating short class definitions with “small” objects. The “size” of an object is better measured by the surface area exposed to other objects; in the case of Ruby, methods that are not marked protected or private are a great way of measuring this.
You must also be aware of and account for objects that are tightly coupled to other objects. Modules can help this when used as mixins, but that is not always the case. ActiveRecord breaks behavior into mixins for easy composition (e.g. Validations, Callbacks, and Dirty Tracking), but the Base
object combines these in a way that makes ActiveRecord models tightly coupled to the underlying codebase.
This is not always a bad thing. David Heinemeier Hansson (@dhh) makes a compelling case for why this sort of “violation” can sometimes create very useful, beneficial interfaces and objects.
In practice, I avoid delegating much additional behavior to ActiveRecord models. When additional behavior is necessary, more specific objects can be wrapped around the model, enhancing them while separating the behavior and code. For example:
class UserDecorator
attr_reader :user
delegate :first_name, :last_name, to: :user
def initialize(user)
@user = user
end
def name
"#{first_name} #{last_name}"
end
end
name
is a pretty common method to be on a User
object, but the behavior doesn't relate to anything that is persisted. By adding user#name
to the public interface of an ActiveRecord object, there is an implicit expectation that user#name=
also exists, and that these map to a datastore. By moving even simple methods like this to a more appropriate object, the interface is clearer, more extensible, and more readily tested.
Note: If you have zero other methods defined on User
, and you're not already using Decorators, it would probably make more sense to start by defining the name
method on User
and extracting it later as complexity is added. Reaching for the most complex solution is a surefire way to build a needlessly complicated application. ;)