Pushing business logic into the model

I have a question re: pushing your business logic into your models,
with the intention that the controllers are much more leaner.

Say I have a model object Email. I am tempted to write static methods
as part of the Email which do all the business of creating, validating,
doing some other plumbing, etc.

Out of the box, Rails suggest your controller look like this

(heavily shortened for brevity)

class EmailController

def add
@email = Email.new

def save
   @email = Email.create_with_params(params[:email], session[:foobar])
   if @email.save
     redirect_to :action => "success"
     redirect_to :action => "add"

Email.create_with_params() is a custom method that does more than just
a basic Email.new, it does that, but also takes some session data and
does other stuff with it (related to the creation of the Email record,
its not completely willy-nilly).

The problem with this approach is this: if you go to the save() and the
model has failed validation, whether it be via the native validation or
custom stuff, once you do the redirect you lost your @email instance
variable, so an "error_message_for" on the add.rhtml template doesnt
contain anything. AND you cannot do a render :action => "add" because
lets say you need to hit the DB to do other stuff to get the form
ready, etc. Basically you need to do other stuff before you can just
render the template.

So now I think you need some kind of a data-bus to hold your @email
instance var across the request.

Either session or flash comes to mind. flash is nice because it will
auto-clean up after itself. Whereas with session you would need to
remember to purge it (say, after a successful request).

Am I approaching this from the wrong angle? To clear up my controllers,
I am placing more logic into the model, which makes sense, but when you
are dealing with requests that span methods you need some way to keep
track of it all.

I am tempted to have my custom model objects return two things, a
boolean result flag and the instance itself, which I can directly place
into my data-bus (session/flash?)


def save
    result, @email = Email.create_with_params(params[:email],
    if result
      redirect_to :action => "success"
     flash[:email] = @email
     redirect_to :action => "add"

And then you modify add() to have a look at the flash first.... Am I
totallly crazy here?


Why can't you simply do the following (which I did in one of my apps):

def save
  if ...
    add # The DB stuff you need to get the form ready should be in add.
    [initialise @email with values from params]
    render :action=>"add"


Maybe I’m not following this right… but it sounds like you’re doing a “swiss army knife” method. I am a huge fan of of having methods call other methods instead of doing tons of stuff in one step. That’s what I love about the callbacks in ActiveRecord.

Email object would create the email. It should save it to the database.

The after_create callback would send it out. Your controller only needs to know if the record saved or not. You could put your own message into the error messages if it didn’t work.

That allows the pieces to be tested individually.

You could also look into using an Observer to do this.

Just my thoughts.