DEV Community

Jeremy Friesen for The DEV Team

Posted on • Originally published at takeonrules.com on

Carrier Wave and How to Test Uploading a New Image to a ActiveRecord::Base Model

Managing Object State is Hard

We’re a Rails shop at Forem. In the Forem code base we use the CarrierWave gem to help with our file uploads.

I was working to ensure an article’s cache updates when we update an organization’s image. To fix this, I needed to produce a failing test.

Below is a rough approximation of the spec I wrote. And the spec failed; which is what I wanted because now I could use that test to fix the bug.

RSpec.describe Organization do
  context "when changing organization's profile image" do
    it "updates the cached profile image for each associated article" do
      organization = create(:organization, profile_image: File.open("/path/to/image"))
      article = create(:article, organization: organization)

      expect do
        organization.update(profile_image: File.open("/path/to/other/image"))
      end.to change { article.reload.cached_organization.profile_image }
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

I made the changes that I thought should have worked, but it didn’t. So I made a few other changes, and the spec continued to fail. Why? With a bit of debugging, I came to realize that the organization.update(profile_image: File.open("/path/to/other/image")) was not updating the organization’s profile image.

What I found was that I couldn’t perform two uploads to the same object instance of the organization. In other words, once I uploaded a file to an organization object, for the lifecycle of that instance, I couldn’t upload another file.

Saying this a third way, to update an image, I needed to find the record from the database, then change the image.

What follows is the more verbose test, with extensive inline comments to explain “why” I’m doing each of these things.

RSpec.describe Organization do
  context "when changing organization's profile image" do
    it "updates the cached profile image for each associated article" do
      # What is going on with this spec? Follow the comments.
      #
      # tl;dr - verifying fix for https://github.com/forem/forem/issues/17041

      # Create an organization and article, verify the article has cached the initial profile
      # image of the organization.
      original_org = create(:organization, profile_image: File.open(Rails.root.join("app/assets/images/1.png")))
      article = create(:article, organization: original_org)
      expect(article.cached_organization.profile_image_url).to eq(original_org.profile_image_url)

      # For good measure let's have two of these articles
      create(:article, organization: original_org)

      # A foible of CarrierWave is that I can't re-upload for the same model in memory. I need to
      # refind the record. #reload does not work either. (I lost sleep on this portion of the
      # test)
      organization = described_class.find(original_org.id)

      # Verify that the organization's image changes. See the above CarrierWave foible
      expect do
        organization.profile_image = File.open(Rails.root.join("app/assets/images/2.png"))
        organization.save!
      end.to change { organization.reload.profile_image_url }

      # I want to collect reloaded versions of the organization's articles so I can see their
      # cached organization profile image
      articles_profile_image_urls = organization.articles
                      .map { |art| art.reload.cached_organization.profile_image_url }.uniq

      # Because of the change `{ organization.reload... }` the organization's profile_image_url is
      # the most recent change.
      expect(articles_profile_image_urls).to eq([organization.profile_image_url])
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

TL;DR: For each mounted CarrierWave uploader, you may only reliably upload once per ActiveRecord::Base object instance. If you want to replace, make sure you get a fresh instance. And no, using ActiveRecord::Base#reload will not work.

Top comments (0)