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

Hi,

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 function.

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 accounts    end

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

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

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

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

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.

Jeff

purpleworkshops.com

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.

Andrew

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

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.

Regards, Wojciech

P.S.

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).