Rails 4 Paperclip FactoryGirl file uploading - ruby-on-rails

I have a FactoryGirl :product factory that uses fixture_file_upload to set image, which is a Paperclip attachment.
image { fixture_file_upload "#{Rails.root}/spec/fixtures/images/product.png", 'image/png' }
fixture_file_upload works fine, but every time a test creates a new Product using the factory, Paperclip creates a new file in publicproducts/<id>/original.png. This is the issue.. Filling a the folder publicproducts on each test run is not acceptable.
The first workaround I can think of is the solution mentioned in https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Cleanup-after-your-Rspec-tests
Have you solved this problem in another way?

The solution, also mentioned by Deep is to:
specify that paperclip in test environment should upload files to folder test_uploads,
modify factory_girl factories to upload a fixture from ex. spec/fixtures/images/filename.extension,
add an after all cleanup block in rails_helper.rb
In code:
config/environments/test.rb
...
config.paperclip_defaults = {
path: ':rails_root/test_uploads/:class/:id/:attachment/:filename.:extension',
url: ':rails_root/test_uploads/:class/:id/:attachment/:filename.:extension'
}
...
spec/factories/products.rb
image { fixture_file_upload "#{Rails.root}/spec/fixtures/images/product.png", 'image/png' }
rails_helper.rb
...
include ActionDispatch::TestProcess
config.after(:all) do
if Rails.env.test?
test_uploads = Dir["#{Rails.root}/test_uploads"]
FileUtils.rm_rf(test_uploads)
end
end
...

Related

How to solve ActiveStorage::FileNotFoundError: when running tests with rspec?

I'm getting a file not found error using ActiveStorage when running rspec and I don't know what's wrong.
This is how it's setup.
storage.yml
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
config/environments/test.rb
config.active_storage.service = :test
Factory
factory :user do
membership_approval { Rack::Test::UploadedFile.new("#{Register::Engine.root}/spec/fixtures/membership.pdf", 'application/pdf') }
end
Mail example
def mail_example(user:)
filename = user.membership_approval.filename.to_s
attachments[filename] = user.membership_approval.download
mail(
...
)
end
When I run rspec I get the following error message when it tries to download the file.
Error message
ActiveStorage::FileNotFoundError:
# ------------------
# --- Caused by: ---
# Errno::ENOENT:
# No such file or directory # rb_sysopen - /Users/myuser/Projects/my_project/tmp/storage/nd/5h/nd5hlet2n2xa673f8v11ljbnru72
It works fine otherwise but I just can't seem to get it to work with rspec. When I'm debugging in rspec it looks like the file is attached to the object but not stored physically anywhere. Any ideas?
Best regards.
EDIT
I have also tried this approach in the factory but no luck, I still get the same error.
after(:build) do |user|
user.membership_approval.attach(
io: File.open("#{Register::Engine.root}/spec/fixtures/membership.pdf"),
filename: 'membership.pdf',
content_type: 'application/pdf'
)
end

Carrierwave testing - clean up or separate file uploads?

I'd like some feedback and/or help please.
I'm having this test
scenario 'can create a new post' do
attach_file('Image', 'spec/files/hello-world.png')
fill_in 'Caption', with: 'Hello World! This is the first post!'
click_button 'Create Post'
expect(page).to have_css("img[src*='hello-world.png']")
expect(page).to have_content('Post was successfully created')
end
This test uploads the image to uploads/post/image/1/hello-world.png through Carrierwave, which is probably the same path on development environment etc, so I wonder if this is going to cause any issues on between files and posts.
My question is do I need to keep separate the file uploads on each Environment, or is it something that Rails can manage internally?
The location where the files are stored is defined in a Carrierwave Uploader by the method store_dir. If you need to separate the files created in different environments, the simplest solution is to add Rails.env to the path:
def store_dir
#store_dir ||= File.join(
'public',
'uploads',
Rails.env,
model.class.table_name.to_s,
mounted_as.to_s,
model.id.to_s
)
end
This will create a path like: public/uploads/production/posts/image/1/image.png

Rails custom environment: keep certain subdirectories in app/models from loading

I've created a custom environment in the Rails app I'm working on called nhl_test. The models for this environment are in app/models/nhl namespace. There are a number of other models in app/models/[other_subdir] that I don't want to autoload in this environment. So far I tried modifying the config.paths in my environment file similarly to how I modified the db/migrate location, like so:
config/environments/nhl_test.rb
MyApp::Application.configure do
config.eager_load = false
config.paths["db/migrate"] = ["db/migrate/nhl"]
config.paths["app/models"] = ["app/models/nhl"]
end
However, the other subdirectories of app/models are still being loaded. I can tell because there is code in those models that will break in this environment and I'm unable to run my tests using RAILS_ENV=nhl_test m test/models/nhl - they break with a stack trace that points to app/models/mlb/base.rb.
How can I keep any of the models except what's in app/models/nhl from being loaded in this environment??
EDIT It turns out it's this line in test_helper.rb that is causing the problem:
class ActiveSupport::TestCase
fixtures :all
end
Rather than loading all fixtures, I just need to load the fixtures in test/fixtures/nhl somehow...
I've tried the following but it doesn't seem to be working:
class ActiveSupport::TestCase
fixture_path = Rails.root.join('test', 'fixtures', 'nhl')
fixtures :all
end
I was able to fix the problem by setting the fixture_path this way:
# test_helper.rb
ActiveSupport::TestCase.fixture_path = Rails.root.join('test', 'fixtures', Rails.env.gsub('_test', ''))
class ActiveSupport::TestCase
fixtures :all
end

Rails carrierwave testing - how to remove file after test?

I am testing the carrierwave upload functionality using rspec and capybara. I have something like:
describe "attachment" do
let(:local_path) { "my/file/path" }
before do
attach_file('Attachment file', local_path)
click_button "Save changes"
end
specify {user.attachment.should_not be_nil}
it { should have_link('attachment', href: user.attachment_url) }
end
And this works great. The problem is that after testing the uploaded image remains in my public/uploads directory. How can I remove it after the test is done? I tried something like this:
after do
user.remove_attachment!
end
but it did not work.
You're not the only one having issues to delete the files in carrierwave.
I ended up doing:
user.remove_attachment = true
user.save
I got this tip reading this.
A cleaner solution that seems to work for me is the following in spec/support/carrierwave.rb:
uploads_test_path = Rails.root.join('uploads_test')
CarrierWave.configure do |config|
config.root = uploads_test_path
end
RSpec.configure do |config|
config.after(:suite) do
FileUtils.rm_rf(Dir[uploads_test_path])
end
end
This would set the whole root folder specific to the test environment and delete it all after the suite, so you do not have to worry about store_dir and cache_dir separately.
Ha! I found the answer to this today.
The auto-removal of the downloaded file is done in an after_commit hook.
These do not get run by default in rails tests. I never would have guessed that.
It is however documented offhandedly in a postscript note here:
http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#method-i-after_commit
I discovered this by deep diving into the carrierwave code with a
debugger and just happened to notice it in the comments above the
source code to after_commit when I stepped into it.
Thank goodness ruby libraries are not stripped of comments at runtime like JS. ;)
The workaround suggested in the docs is to include the 'test_after_commit'
gem in your Gemfile BUT ONLY IN THE TEST ENVIRONMENT.
i.e.
Gemfile:
...
gem 'test_after_commit', :group => :test
...
When I did this, it completely solved the problem for me.
Now, my post-destruction assertions of cleanup pass.
The latest CarrierWave documentation for this technique is as follows:
config.after(:suite) do
if Rails.env.test?
FileUtils.rm_rf(Dir["#{Rails.root}/spec/support/uploads"])
end
end
Note that the above simply assumes youre using spec/support/uploads/ for images and you dont mind deleting everything in that directory. If you have different locations for each uploader, you may want to derive the upload and cache directories straight from the (factory) model:
config.after(:suite) do
# Get rid of the linked images
if Rails.env.test? || Rails.env.cucumber?
tmp = Factory(:brand)
store_path = File.dirname(File.dirname(tmp.logo.url))
temp_path = tmp.logo.cache_dir
FileUtils.rm_rf(Dir["#{Rails.root}/public/#{store_path}/[^.]*"])
FileUtils.rm_rf(Dir["#{temp_path}/[^.]*"])
end
end
or, if you want to delete everything under the CarrierWave root that you set in an initializer, you can do this:
config.after(:suite) do
# Get rid of the linked images
if Rails.env.test? || Rails.env.cucumber?
FileUtils.rm_rf(CarrierWave::Uploader::Base.root)
end
end

Monkeypatch a model in a rake task to use a method provided by a plugin?

During some recent refactoring we changed how our user avatars are stored not realizing that once deployed it would affect all the existing users. So now I'm trying to write a rake task to fix this by doing something like this.
namespace :fix do
desc "Create associated ImageAttachment using data in the Users photo fields"
task :user_avatars => :environment do
class User
# Paperclip
has_attached_file :photo ... <paperclip stuff, styles etc>
end
User.all.each do |user|
i = ImageAttachment.new
i.photo_url = user.photo.url
user.image_attachments << i
end
end
end
When I try running that though I'm getting undefined method `has_attached_file' for User:Class
I'm able to do this in script/console but it seems like it can't find the paperclip plugin's methods from a rake task.
the rake task is probably not be loading the full Rails environment. You can force it to do so by doing something like this:
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
where the path leads to your environment.rb file. If this were to fix the issue, you should include it inside this task specifically, because you probably do not want all your rake tasks to include the environment by default. In fact, a rake task may not even be the best place to do what you're trying to do. You could try creating a script in the script directory as well.

Resources