Fat models and validation

Hi all,

I'm trying my best to adhere to the thin controller/fat model philosophy - in particular, I'm trying to make sure my validation stays inside my models, so it can be reused in multiple places.

My current problem is that I have an action that creates several objects all at once. In my app, there are Sources (which represent an entire web site) and Resources (which map to a single URL). Each Source has_many Resources (so each Resource belongs_to a Source).

In my action, I need to create both a Source and a linked Resource. However, each has some validation that needs to occur. If either the Source or Resource has a problem, neither should be created.

My initial thought was to use a transaction. I would use save! so that if anything went wrong, I could just print out the error in the flash. My controller action contained code that looked something like this:

begin   Source.transaction do     @resource =Resource.new(:source => Source.new(:name => params[:name]), :url => params[:url])     @source = @resource.source     @source.save!     @resource.save!    end    # do some stuff    rescue ActiveRecord::RecordInvalid, AssertionFailure => error      # put the error message in the flash      render :action => 'new' end

If there is something wrong in Source (e.g. the name is empty), this works fine - it prints out the nice error message I have in the Source model.

However, if something is wrong in the Resource, Source just gives the following error: 'Validation failed: Resources is invalid'. That's not very helpful to the user.

Of course, I can do @resource.save! first, but if there is a problem with the Source, ActiveRecord complains that the source_id cannot be null (since it could not save).

Clearly my approach is very wrong here. My goal is to

1. Only save the Source and Resource if both are valid 2. Not duplicate a bunch of validation logic in the controller

Anyone have ideas for how I can accomplish this?

Thanks in advance!

Ben

Ryan,

You're right, there is definitely some unnecessary code in there. However, even if I simplify it as you suggested, there is still the problem that doing @source.save! gives the following exception

'Validation failed: Resources is invalid'.

which isn't very useful. I'd like it to give the actual validation failure in the Resource ... something like 'URL is not valid'. Am I totally approaching this problem the wrong way? It feels like I'm working way too hard to do something reasonably simple - which usually means I'm doing something pretty boneheaded :).

Ben

Ryan,

You're right, there is definitely some unnecessary code in there. However, even if I simplify it as you suggested, there is still the problem that doing @source.save! gives the following exception

'Validation failed: Resources is invalid'.

which isn't very useful. I'd like it to give the actual validation failure in the Resource ... something like 'URL is not valid'. Am I totally approaching this problem the wrong way? It feels like I'm working way too hard to do something reasonably simple - which usually means I'm doing something pretty boneheaded :).

Ben

Whoops, it looks like my browser did something funky. Sorry about that.

If the Source has a validation error, the problem with calling @resource.save! is that it throws an exception saying it cannot write the resource to DB because source_id is null. Which makes sense - since the Source is not valid, it won't write to the DB and therefore does not have a valid ID.

Hopefully this one will not double post...

Ryan,

Hey thanks for the tip. It's not working in my example quite yet (probably due to some other factors I didn't include in my post), but it looks promising. Unfortunately, I'm leaving for a trip tomorrow, so I may not get to work on it until Monday. In any case, I'll report back with what I find.

Thanks! Ben

Apparently your problem is with telling the user what the problem is, right?

Wouldn't adding <%= error_messages_for 'source' %> and <%= error_messages_for 'resource' %> in the view help you with that?

About the Source being really invalid but the user getting errors for the Resource.source_id also, you could do

instead of @resource.save! use @resource.save! if @source.valid?