Too many methods in the model? Extra lightweight logic layer?


Given the convention of fat models to handle business logic, is there
a point where you might be justified in using a separate plain ruby
object(s) to orchestrate certain business logic interactions,
essentially a middle layer between your controllers and models for
high level functions?

If you look at the InventoryTransaction model below you will see the
sort of methods I mean. In this case they are similar to factory
methods I suppose. However, I could end up with around 30+ different
method handling different kinds of inventory adjustments.

Most of these methods are non-trivial as the lookup and create other
models and then interact with them in achieving the desired business

What are peoples experience with this? Leave them in the single model
or extract them to plain ruby classes (possibly within the models
folder) with a bunch of class methods to handle high level business
logic? This would also allow grouping methods of related functionality
in to separate classes.

Am I heading for future issues?

class InventoryTransaction < AR::Base

   has_many :inventory_account_entries
   validate :inventory_account_entries_balance

   def manually_add_inventory(product, location, quantity)
      ...create new transaction and suitable entries in to inventory

   def manually_remove_inventory(product, location, quantity)
      ...create new transaction and suitable entries in to inventory

   def report_shortage(product, location, quantity)
      ...create new transaction and suitable entries in to inventory

   def report_surplus(product, location, quantity)
      ...create new transaction and suitable entries in to inventory

   ... plus many more potential inventory interactions or events
   in movement between inventory accounts and the creation of
   a new InventoryTransaction record

Thanks, Andrew.

The idea is fat model *layer*, not necessarily fat model classes. You
should feel free to factor out inventory-handling methods into
separate Ruby modules or classes, and use them from within your
InventoryTransaction class. That way your ActiveRecord class isn't
cluttered with lots of methods. I tend to keep such modules and pure-
Ruby classes in the models directory, but some people prefer to keep
them in lib/ instead.


Thanks Jeff, makes sense.

I've since been digging around one of my goto/"how do they tackle it"
open source rails projects and see similar approaches.

In this case, the Spree e-commerce platform.

Agreed with Jeff.

The AR models really act as a wrapper class to DB tables. This helps
to add an abstraction layer to SQL and marshall SQL selects into
powerful ruby objects (the best part, in my opinion). With that in
mind create non-AR models as needed, and only use the AR models when
the actions really relate to that table/model.

A quick example. Let say you need to encrypt and decrypt fields in a
single AR model. You could write your code within this model, since
it is the only model that currently needs encryption. But, this will
clutter the AR model with methods that don't directly pertain to it,
and it won't scale work well if you later decide other AR models also
should encrypt information. Better to add a non-AR class to work out
the details.


I try to keep only high-level functionality and business logic in the

Here's the drill:

1. identify things that are not specific to your model or application
and then extract them into a plugin

2. identify things that are specific to your application, but not to
your model and extract them into a lib

3. find groups of related methods that are lower level (used only in
this model itself) and extract them into modules (in models dir)

What's left is a concise interface with some behaviours in declarative
style (like acts_as_taxable).

I've seen this working pretty well in largish 7 years old Rails
application. Initially I haven't appreciate such terse models until I
came across some bloated ones. I then couldn't quickly figure out what
I can do with an instance of this model.



Couple methods you listed seem good candidates to remain in model
class. I suspect they are core of business logic and most important
actions called in the controller.

If there's more than a dozen of them, I'd probably try to make them
very short by extracting common parts into utility methods (and move
them to another module).