ActionDispatch::Http::UploadedFile.content_type not being initialized in Rspec test

readyornot picture readyornot · May 28, 2014 · Viewed 10.8k times · Source

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

Answer

readyornot picture readyornot · Jun 14, 2014

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