Validation with has_many

I have two problems. I have a comment that has_many uploads. Before
saving the comment, I want to be sure that the upload(s) has passed
validation, but I also need to validate in other ways. For example, I
do not want to save the comment if there is no comment or upload. Or, I
do not want to save the comment if the image has been uploaded
previously (comparing md5s with past upload md5s within the scope of an
IP).

Previously I had all of the upload attributes combined with my Comments
table, which made validation extremely easy. Then when I decided that I
would allow multiple uploads per comment, I had to separate the uploads
and comments into two different tables, which created the problems I am
describing.

Models

class Comment < ActiveRecord::Base
belongs_to :board
has_many :uploads, :dependent => :destroy
end

class Upload < ActiveRecord::Base
belongs_to :comment
end

Controller

def index
if request.post?
comment = @board.comments.build(params[:comment])

if comment.save
  upload = comment.uploads.create(params[:upload])
  redirect_to "/#{@board.path}"
end

end
end

In fact, how the hell am I supposed even theoretically pull this off?

The image needs to reference the comment it belongs to during its
validation. However, I cannot reference the comment until the comment
is saved. But I should not save the comment until the image is
validated. Catch 22.

Maybe my entire way of going about this is incorrect.

Try validates_associated
http://api.rubyonrails.com/classes/ActiveRecord/Validations/
ClassMethods.html#M000820

        - dan


Dan K. mailto:[email protected]
http://www.dankohn.com/ tel:+1-415-233-1000

The way to get a feel for this is to create your models, and then run
script/console. From there. you can create your objects, and try
different combinations of method names to see what works. This will
give you the realtime insight into how Rails deals with attributes
which you can then use for writing your custom validation.

        - dan


Dan K. mailto:[email protected]
http://www.dankohn.com/ tel:+1-415-233-1000

I looked at that, but I didn’t really understand it.

For example, what if my validation of the Upload object involves looking
at the Comment object? I can’t do it, as far as I know, since te
association isn’t real until the Comment object is saved and has an ID.
So if I need to look at any of the Comment object’s attributes within
the Upload model I cannot, since I haven’t saved the Comment object.

Dan K. wrote:

Try validates_associated
Peak Obsession
ClassMethods.html#M000820

        - dan


Dan K. mailto:[email protected]
http://www.dankohn.com/ tel:+1-415-233-1000

If you do comment.valid? (and ignore the results), are you then able
to do the validations you want?

        - dan


Dan K. mailto:[email protected]
http://www.dankohn.com/ tel:+1-415-233-1000

I cannot find any documentation on this sort of thing.

It seems like an unsaved object does not have an ID when it is built;
but at some point between when it is created and saved it gains one.
Associations don’t work properly until it has one. My Upload object
doesn’t know which Comment object it belongs to until Comment has an ID
number; and again, I don’t want to save EITHER object until BOTH are
validated, which has led to a headache indeed.

I cannot reference an associated comment from my Upload model within the
before_validation_on_create method, but I CAN do so within the
before_create method. Right now I have something sort-of-working. What
I have is like this. I have changed my forms so that my file field is
comment[upload] instead of upload[upload]:

class Comment < ActiveRecord::Base
belongs_to :board
has_many :uploads, :dependent => :destroy
acts_as_tree :order => “created_on ASC”, :dependent =>
:destroy
validates_associated :uploads

def upload=(upload)
uploads.build({:upload => upload}) if upload.size > 0
end
end

That catches the uploaded file and sends it to the Upload model ->
class Upload < ActiveRecord::Base
belongs_to :comment

def upload=(upload)
@upload = upload
end

def before_validation_on_create
create_image
end

def create_image
#this is some RMagick nonsese.
end

def before_create
@thumbnail = ImageList.new.from_blob(@image.first.to_blob)

if !comment.parent_id
  x = comment.board.setting(:parent_thumbnail_at_width)
  y = comment.board.setting(:parent_thumbnail_at_height)
else
  x = comment.board.setting(:reply_thumbnail_at_width)
  y = comment.board.setting(:reply_thumbnail_at_height)
end

self[:thumbnail_width]  = @thumbnail.columns
self[:thumbnail_height] = @thumbnail.rows

if @thumbnail.columns > x or @thumbnail.rows > y
  @thumbnail.change_geometry!("#{x}x#{y}") {|cols, rows, img| 

img.resize!(cols, rows)}
end
end
end

As you can see, it was imortant that I could reference the associated
comment, because that determined the size of the thumbnail, which I
needed to save to the uploads table.

So I’m closer than when I was before; but still have a long way to go.
For example, I’m only dealing with one uploaded file at a time, when I
need to be dealing with multiple uploads :frowning:

I wish I could say so, but right now I’ve got code all over the place;
it’s like I’m editing film or something.