A file-upload suddenly seems to be nil

Hi there,

I posted this issue at the carrierwave-group, but I’m beginning to think this rather is a rails-issue than a problem with carrierwave. The problem’s this:

I have 2 models, ‘article’ and ‘upload’. article has_many :uploads. In my article_controller i have an action named upload:

def upload

@article = Article.find(params[:id])

@article.uploads.create(params[:file])

render :nothing => true

end

this should save a file-upload to article.uploads. I’m using carrierwave to save uploads in the upload-model, but that’s probably not the problem. Before I created the has_many-association I saved the upload directly in the article-model using params[:file] in that exact same action and that worked great.

Now that I created the association there seems to be a problem with params[:file]. I keep getting a NoMethodError:

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.delete

app/controllers/articles_controller.rb:95:in `upload’


where line 95 is @article.uploads.create(params[:file])


if I remove the parameter no errors get thrown. But the console tells me params[:file] DOES exists and has proper values. Also params[:file] worked great before I used the has_many association.

I have no clue what the problem could be.

Can anyone give me a hint?

Thanks,

Lukas

params[:file] is not the issue. Its the create method you are calling for @article.uploads that is throwing the error. In your code, you are saying that the create method is part of the upload model, because of your has_many association, so it is looking there. Also, since you are doing a has_many association between article and upload you will receive back an array. If you want to access the method for an object in that array you will need to loop over it or call the object in the array directly.

examples:

Loop over the array @article.uploads.each {|u| u.create(params[:file])}

Call object in array by position @article.uploads[0].create(params[:file])

Call first object in the arra

@article.uploads.first.create(params[:file])

B.

So what is the value of params[:file] ?

Fred

Hmmm, this does work, at least that error doesn’t occur anymore. But now I’m getting the error

Attempt to call private method

where I call create. Also calling create on an instance of an upload doesn’t seem right to (n00by) me. Is that the correct way to add a new instance to the collection associated with has_many? In the rails getting started guide they do it the way I tried it:

@comment = ``@post``.comments.create(params[``:comment``])

(Where :comments is a has_many reference in post) but obviously that didn’t work. I mean am I getting something wrong? What’s the correct way to create and add instances to a has_many association?

“file”=>#<ActionDispatch::Http::UploadedFile:0x000001050b4ea0

full parameters:

Parameters: {“name”=>“Screenshot 2011-02-12 um 12.48.33.png”, “authenticity_token”=>“PUhOOUAHLx+FUnuMI9jY7zwXXxId68v06MbyiImkaSM=”, “file”=>#<ActionDispatch::Http::UploadedFile:0x000001050b4ea0 @original_filename=“Screenshot 2011-02-12 um 12.48.33.png”, @content_type=“image/png”, @headers=“Content-Disposition: form-data; name="file"; filename="Screenshot 2011-02-12 um 12.48.33.png"\r\nContent-Type: image/png\r\n”, @tempfile=#<File:/var/folders/fn/fnhQ4mIBGqO3kQnHeiVzD++++TI/-Tmp-/RackMultipart20110312-5746-19c2n8j>>, “id”=>“1”}

"file"=>#<ActionDispatch::Http::UploadedFile:0x000001050b4ea0

So you're probably passing the wrong thing to foo.uploads.create. Unless you've overridden all sorts of crazy internal active record stuff, rails is expecting the argument to that create method to be a hash of attribute names to attribute values, whereas your just giving it a file.

Fred

Create the upload by calling Upload.create after that you can do @article.uploads << uploadyoujustcreated that would create the association

Thanks!

That was the problem, thanks a lot! I changed it to

@upload = Upload.new

@upload.file = params[:file]

@upload.save!

@article.uploads << @upload

works like a charm. @upload.file = params[:file] works because I’m using carrierwave to manage uploads.

However, I’m wondering how you have to write your constructor to be able to pass the file when instantiating the model. I mean like this:

@upload = Upload.create(params[:file])

This didn’t work:

def initialize (f)

@file = f

end

You have to pass a hash of attribute names to values to create - you can't just pass a value. Files are just like any other attribute - you can't for example do Person.create(params[:name]), you have to do Person.create(:name => params[:name]). In the same way you would do foo.uploads.create :file => params[:file]

Fred

Thanks! One last question though, how would the constructor signature look for that. Apperently

def initialize(file)

do stuff

end

doesn’t work

Thanks! One last question though, how would the constructor signature look for that. Apperently

I wouldn't override the initialiser if I were you. You need to preserve existing semantics so that (for example) active record can load your objects from the database. If you really want to, make sure that you preserve existing calling conventions (ie number of arguments, pass through the block etc.) and don't forget to call super

Fred

you forgot that you need to pass in key value pairs (as mentioned lower )

def upload

@article = Article.find(params[:id])

@article.uploads.create(:file => params[:file])

render :nothing => true

end

There is nothing wrong with using create on uploads - it is added to the collection when you use has many - it in the right way.

you could also do this whole thing in the update action for the article controller if you simply use ‘fields_for’ inside of a form_for @article

and set ‘accepts_nested_attributes_for :uploads’

then you could do the RESTful way.

def update

@article = Article.find(params[:id])

@article.update_attributes(params[:article])

end