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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With