Background: I have a Book
model with a cover_file
attribute that gets set with an uploaded file via one of my Rails controllers. I'm using Rails v4.0.4.
Goal: I want to test that only files with certain content types get saved. I plan to create Rspec test examples with ActionDispatch::Http:UploadedFile
objects set with different content_type
attributes.
Problem: When I initialize a new ActionDispatch::Http::UploadedFile
with a content_type
, it doesn't seem to get set (see test & output below that confirms that it's nil). It seems that I can only set it with a setter AFTER the UploadedFile has been initialized. I don't see any mention of this behavior in the docs nor could I find a similar Q&A on SO, so I'd appreciate anyone's help in determine what I'm doing wrong. Thanks!
Code:
describe Book do
let(:book) {FactoryGirl.build(:book)}
describe "Save" do
context "with valid data" do
before do
cover_image = File.new(Rails.root + 'spec/fixtures/images/cover_valid.jpg')
book.cover_file = ActionDispatch::Http::UploadedFile.new(tempfile: cover_image, filename: File.basename(cover_image), content_type: "image/jpeg")
puts book.cover_file.content_type.nil?
book.cover_file.content_type = "image/jpeg"
puts book.cover_file.content_type
end
specify{expect(book.save).to be_true}
end
end
end
Output:
true
image/jpeg
I looked at the Rails source file for the UploadedFile
class and I found the issue. For the @content_type attribute, for example, while the getter and setters are named as expected (.content_type
), the initialize
method looks for an attribute called type
in the options hash. The same thing happens for @original_filename; initialize looks for filename
instead of original_filename
. This seems to have been the case since the Rails 3 codebase.
So, if I change my code above to the following, everything works as expected:
book.cover_file = ActionDispatch::Http::UploadedFile.new(tempfile: cover_image, filename: File.basename(cover_image), type: "image/jpeg")
Relevant section of rails/actionpack/lib/action_dispatch/http/upload.rb...
class UploadedFile
# The basename of the file in the client.
attr_accessor :original_filename
# A string with the MIME type of the file.
attr_accessor :content_type
# A +Tempfile+ object with the actual uploaded file. Note that some of
# its interface is available directly.
attr_accessor :tempfile
alias :to_io :tempfile
# A string with the headers of the multipart request.
attr_accessor :headers
def initialize(hash) # :nodoc:
@tempfile = hash[:tempfile]
raise(ArgumentError, ':tempfile is required') unless @tempfile
@original_filename = encode_filename(hash[:filename])
@content_type = hash[:type]
@headers = hash[:head]
end