Forum: Ruby on Rails Validating associated objects

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
A0f6b57661bc57b6520010a2a333faba?d=identicon&s=25 R. Elliott Mason (eleo)
on 2007-07-03 11:11
I am having a weird problem

class Post
  has_many :uploads
end

class Upload
  belongs_to :post
end

In the form in my view the user can upload multiple files at once.  In
my model
there are multiple validations for an Upload, such as being under a
certain filesize or not having absurd dimensions if it's an image, etc.

The problem is that if one of the uploads fails validation, the Post
object fails validation as well.  Previously, I thought I had to specify
validates_associated :uploads in the Post model to get this behavior,
but seems like that is not the case.  If one of the uploads fails I get
the error message "Uploads is invalid" in the Post object's errors,
which prevents the Post and all other Uploads from saving whether or not
they are valid; so basically one bad upload ruins the whole thing.  This
is kind of bad since it can take a user a while to upload multiple
files, and I think it's silly to say "one upload out of ten failed,
start all over" when some of the uploads might be good.

What I would -like- to do is accept as many uploads as possible, having
the Post object fail to save only if zero uploads were valid.  Any
invalid uploads get thrown away (although ideally I could somehow keep
their errors stored somewhere [in the Post object?] so I can show the
user what uploads failed and why).
A0f6b57661bc57b6520010a2a333faba?d=identicon&s=25 R. Elliott Mason (eleo)
on 2007-07-04 06:39
Bumping this before it fades into obscurity forever.
C64e63b70be7dfed8b0742540b8b27e5?d=identicon&s=25 Mark Reginald James (Guest)
on 2007-07-04 07:33
(Received via mailing list)
R. Elliott Mason wrote:
> In the form in my view the user can upload multiple files at once.  In
> they are valid; so basically one bad upload ruins the whole thing.  This
> is kind of bad since it can take a user a while to upload multiple
> files, and I think it's silly to say "one upload out of ten failed,
> start all over" when some of the uploads might be good.
>
> What I would -like- to do is accept as many uploads as possible, having
> the Post object fail to save only if zero uploads were valid.  Any
> invalid uploads get thrown away (although ideally I could somehow keep
> their errors stored somewhere [in the Post object?] so I can show the
> user what uploads failed and why).

The file_column plugin, and perhaps other file upload plugins,
will automatically store the valid uploads in temporary files
so that they do not have to be uploaded again when the form
is re-presented.

But if you want the partial save to proceed you'll have to
loop through the upload params, creating upload objects,
calling valid? on them, and adding them to the post's uploads
collection if valid and to a reject array if not, then
saving the post (along with the valid uploads) if at least one
upload is valid.  Re-present the invalid uploads to the user.

If you still need to get rid of the automatic associate validity
check that is causing your "Uploads is invalid" message, just add:

      class Post
        validate_associated_records_for_uploads() end
      end


--
We develop, watch us RoR, in numbers too big to ignore.
A0f6b57661bc57b6520010a2a333faba?d=identicon&s=25 R. Elliott Mason (eleo)
on 2007-07-04 08:42
Had no idea such a method would exist.  Thanks, I'll try it out.
A0f6b57661bc57b6520010a2a333faba?d=identicon&s=25 R. Elliott Mason (eleo)
on 2007-07-05 08:53
Hm, the problem I'm finding in this approach is that validation is being
called twice; once when I call valid? for the object and a second time
before the Post is saved.  This wouldn't be too much of an issue if my
validations didn't require database queries to be run, but unfortunately
because they are run it causes an unnecessary about of redundancy.

I would use file_column but I think I might be too far along in
development for that.  Maybe I should just try to mimic how it saves
files temporarily before re-presenting the form.  I just figured if a
user makes an invalid upload, the upload is probably going to stay
invalid.
C64e63b70be7dfed8b0742540b8b27e5?d=identicon&s=25 Mark Reginald James (Guest)
on 2007-07-06 16:37
(Received via mailing list)
R. Elliott Mason wrote:
> Hm, the problem I'm finding in this approach is that validation is being
> called twice; once when I call valid? for the object and a second time
> before the Post is saved.  This wouldn't be too much of an issue if my
> validations didn't require database queries to be run, but unfortunately
> because they are run it causes an unnecessary about of redundancy.

save(false) saves without validation; and the automatic validation
of has_many associates can be turned off in the way described in
my last post.

--
We develop, watch us RoR, in numbers too big to ignore.
A0f6b57661bc57b6520010a2a333faba?d=identicon&s=25 R. Elliott Mason (eleo)
on 2007-07-07 01:39
It seems like the uploads, associated with the post, are being validated
as the post is saved.  Both the post and its associated objects having
not been saved, all the objects go through their validations, which
makes sense I guess.  Overriding the
validate_associated_records_for_uploads method prevents the validation
from being run twice, but as each upload object is saved, it is
validated.

I tried to instead use your save(false) method and save the uploads
-before- I saved the post object, after calling valid? on those objects.
I separated the good uploads from the bad and left the bad in the Post
object as post.bad_uploads.  However, saving the uploads ahead of time
didn't work out well either.  Validation was still run as the post
object was saved.

I could try to save the post and upload completely separate from each
other but the problem is their validations are related.  A post
shouldn't be saved if its uploads are bad, and an upload shouldn't be
saved if the post is bad, so it's turning out to be somewhat of a mess.

So overriding validate_associated_records_for_uploads() just really
prevents validation from being run THREE times in my case (once with
that method, once with valid? and a third time when the post is saved.)

I guess I can try to use file_column and perhaps modify it to suit my
needs.
C64e63b70be7dfed8b0742540b8b27e5?d=identicon&s=25 Mark Reginald James (Guest)
on 2007-07-10 18:40
(Received via mailing list)
R. Elliott Mason wrote:
> I separated the good uploads from the bad and left the bad in the Post
> prevents validation from being run THREE times in my case (once with
> that method, once with valid? and a third time when the post is saved.)

Yes, Rails 1.2 incorrectly runs validation twice on cascaded
saves, even if the parent object is saved with save(false)
or save_without_validation. Blocking the validate_associated_records...
method gets rid of one, but each new child will still be validated
before it's inserted into the DB.

To avoid this you have to do your own save cascade: save the
parent using save(false)/save_without_validation without any
new children added to its collections. Then separately build
and save each child the same way.

To save only if everything's valid you have to first run valid?
on each record.

   if post.valid? && uploads.inject(true) { |v, u| u.valid? && v }
     post.save(false)
     uploads.each { |u| u.post = post; u.save(false) }
   end

This can be simplified if post is not a new record.

--
We develop, watch us RoR, in numbers too big to ignore.
A0f6b57661bc57b6520010a2a333faba?d=identicon&s=25 R. Elliott Mason (eleo)
on 2007-07-19 11:26
I seem to have figured it out with your help.  I'd post the code but
it's disturbingly tacky, but at least it gets the job done.  I don't
think there's anymore room for elegance with what I have.

Maybe I can later figure out how to do the temporary file save for
re-presenting the form to the user, but I'm guessing the code behind
that isn't exactly trivial.
This topic is locked and can not be replied to.