I have 2 cucumber scenarios that simulate paperclip image upload. I want to remove those folders again once scenarios are complete.
I have the following attachment folder structure:
:url => "/system/:attachment/:listing_id/:id/:style_:filename"
Paperclip automatically deletes the :id/:style_:filename folder but not the parent folder.
I have a the following in my listings controller (1 listing has many images) which works great to remove the image folder with the listing id when the listing is deleted. I need to simulate the same in Cucumber after the step is run.
def destroy
#listing = Listing.find(params[:id])
# if destroy was a success, remove the listing image folder
if #listing.destroy
end
require 'fileutils'
dir = Rails.root + '/system/photos/' + #listing.id.to_s()
FileUtils.rm_rf(dir)
respond_to do |format|
format.html { redirect_to(listings_url) }
format.xml { head :ok }
end
end
I could a) tell cucumber to delete the :listing_id folder name after running through the scenario or b) tell cucumber to delete the listing as the final step?
I've tried adding this to my cucumber env.rb file:
AfterStep('#paperclip') do
# This will only run before steps within scenarios tagged
# with #cucumis AND #sativus.
# delete folders that were created with paperclip during the test
require 'fileutils'
##listing.id = 55
#dir = Rails.root + '/system/photos/' + #listing.id.to_s()
dir = Rails.root + '/system/photos/55' # NOT WORKING
FileUtils.rm_rf(dir)
end
But that causes problems because 1) I don't know how to get the #listing.id from that scenario, and 2) even when I hardcode it (as above) it doesn't remove it.
Any thoughts?
Already a bit older, but as I just ran over the same issue myself here is what I did:
dir = Rails.root + 'images/'
dir.rmtree if dir.directory?
# or the short form, if you know the directory will be there
(Rails.root + 'images/').rmtree
So I guess the problem was your '/' at the beginning of the folder. At least for me it didn't work with that slash.
I would leave a comment, but don't have the points (or so I would assume) to do so.
There really is no reason that this shouldn't work. Have you confirmed that FileUtils.rm_rf(dir) does in fact remove the directory in the test environment?
You can test this in 'script/console test'.
You can hook into Cucumber's at_exit to remove any folders you like. I'm using the attached code in features/support/uploads_cleaner.rb.
# Removes uploaded files when all scenarios for the current test process
# are finished. Ready for parallel_tests, too.
require 'fileutils'
at_exit do
directory_name = "#{ Rails.env }#{ ENV['TEST_ENV_NUMBER'] }"
uploads_path = Rails.root.join('public/system', directory_name)
FileUtils.remove_dir(uploads_path) if uploads_path.directory?
end
Reposted from this makandra Card.
Related
I recently upgraded to Rails 6 with webpack. This is when I started to notice this issue. I have a send_data method in a controller that works with either a csv or pdf (Prawn) format. Currently experiencing this issue with a csv.
In development, I made a method for self.to_csv in models/user.rb and used it to download some data into a CSV file. I deployed the file to my production server. And downloaded the file there.
Then I made a change to the columns that would be printed in that file. I saw the changes immediately in development. I deployed these changes to production, but still get the old columns. I checked the files on the server and they have the updates.
In Rails 5, I would see these changes immediately in production, too. Is there a way I can speed the server along here? Re-cache the files? Etc.
Here are my files.
In controllers/users_controller.rb:
def index
respond_to do |format|
format.csv {
send_data User.all.to_csv
}
end
end
In models/user.rb:
def self.to_csv
attributes = %w{first_name last_name id}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |obj|
csv << [obj.first_name, obj.last_name, obj.id]
end
end
end
I originally had it just print first and last name to the CSV. That's what I still get in production.
And here is my deployment pipeline:
$ yarn install --check-files
$ rails db:migrate
$ RAILS_ENV=production rails assets:precompile
$ touch tmp/restart.txt
Running Rails 6.0.2.2, Webpack 4.42.0, and Ruby 2.5.5 on Passenger. Hosted by Dreamhost.
This worked for me and was thanks to max's suggestion in the comments.
I placed a stale? block around the send_data line and when I made an update it showed up instantly.
The new controllers/users_controller.rb looks like this:
def index
#users = User.all
respond_to do |format|
format.csv {
if stale?(#users)
send_data #users.to_csv
end
}
end
end
This block came from here:
https://thoughtbot.com/blog/take-control-of-your-http-caching-in-rails
For speed up development process, I'm using file zze.rb, which I edit in my IDE and reload in pry console with short command zze (zz - uncommon beginning for variable/method names; e - execute). This way I can stop execution process in any point of my application by binding.pry and can execute code many times, not waiting of full Rails environment restart each time.
I have this code in my .pryrc file:
Pry.config.commands.command "zze", 'Execute all from .pry_exec/zze.rb' do
Dir['.pry_exec/autoload/**/*.rb']
.delete_if {|file| File.basename(file) =~ /^_/} # inore files that starting form underscore '_'
.each { |f| load(f) }
file_name = File.absolute_path '.pry_exec/zze.rb'
code = File.open(file_name, 'r') {|f| f.read}
eval(code, #target, file_name)
end
and have folder <myproject>/.pry_exec/:
<myproject>/.pry_exec/ # ignored from CVS
autoload/ # this folder loads automatically each `zze` call in cosnole
some_class1.rb
some_code2.rb
zze.rb # this file loads automatically each `zze` call in console.
Sometimes there are cases, when it would be good to use binding.pry inside <myproject>/zze.rb or <myproject>/autoload/some_class1.rb but it does not working. It just ignore that.
I also tried to rewrite zze-code this way:
# ~/.pryrc
def zze(name = nil)
Dir['.pry_exec/autoload/**/*.rb']
.delete_if {|file| File.basename(file) =~ /^_/} # inore files that starting form underscore '_'
.each { |f| load(f) }
load ".pry_exec/#{name || 'zze'}.rb"
# instance_eval(File.read(".pry_exec/#{name || 'zze'}.rb"))
end
but it also ignore binding.pry inside <myproject>/zze.rb or <myproject>/autoload/some_class1.rb
Also when zze is not pry-command, but is method, code from zze.rb executes inside another scope, and I will not have access to local variables defined from zze.rb in my pry-main context. Didn't find how to fix that yet with short way.
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
I'm trying to speed up a large RSpec project's tests. In addition to using RSpec's --profile option I wanted to get the longest running test files [1] printed out.
In my spec_helper.rb I dump the classes being tested and total time to a file, however as we have spec/model and spec/request directories I'd really like to be able to print the current test's filename and not just the class name (described_class), so that the user can disambiguate between model/foo_spec.rb and request/foo_spec.rb when optimizing.
In a before block in the spec/spec_helper.rb, how can I get the current test file's filename?
My (heavily trimmed) spec_helper looks like this:
config.before :all do
#start_time = Time.now
end
config.after :all do |test|
timings.push({ :name => test.described_class,
:file => 'I do not know how to get this',
:duration_in_seconds => (Time.now - #start_time) })
end
config.after :suite do
timing_logfile_name = 'log/rspec_file_times.log'
timing_logfile = "#{File.dirname(__FILE__)}/../#{timing_logfile_name}"
file = File.open(timing_logfile, 'w')
timings.sort_by{ |timing| timing[:duration_in_seconds].to_f }.reverse!.each do |timing|
file.write( sprintf("%-25.25s % 9.3f seconds\n",
timing[:name], timing[:duration_in_seconds]) )
end
file.close
tell_if_verbose("Overall test times are logged in '#{timing_logfile_name}'")
end
This doesn't seem to be available in the curretn RSpec meta-data, but I'm hoping someone more familiar with the internals can think of a way to expose it. Thanks,
Dave
[1] Often a file with, say, 100 examples in it yields more speed up than a single example from --profile - when that large file's before :each / before :all blocks are targetted, obviously even a ms saved is multiplied up by the number of tests in the file. Using this technique in addition to --profile helped me a lot.
As long as you're just using this for profiling your tests to identify which files need to be improved, you should be able to toss this into your spec_helper.rb file (and remove it afterwards). I fully understand that this is not pretty/clean/elegant/acceptible in production environments and I disavow that I ever wrote it :)
config.before(:each) do |example|
path = example.metadata[:example_group][:file_path]
curr_path = config.instance_variable_get(:#curr_file_path)
if (curr_path.nil? || path != curr_path)
config.instance_variable_set(:#curr_file_path, path)
puts path
end
end
What is the best way to get a temporary directory with nothing in it using Ruby on Rails? I need the API to be cross-platform compatible. The stdlib tmpdir won't work.
The Dir object has a method mktmpdir which creates a temporary directory:
require 'tmpdir' # Not needed if you are using rails.
Dir.mktmpdir do |dir|
puts "My new temp dir: #{dir}"
end
The temporary directory will be removed after execution of the block.
The Dir#tmpdir function in the Ruby core (not stdlib that you linked to) should be cross-platform.
To use this function you need to require 'tmpdir'.
A general aprox I'm using now:
def in_tmpdir
path = File.expand_path "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/"
FileUtils.mkdir_p path
yield path
ensure
FileUtils.rm_rf( path ) if File.exists?( path )
end
So in your code you can:
in_tmpdir do |tmpdir|
puts "My tmp dir: #{tmpdir}"
# work with files in the dir
end
The temporary dir will be removed automatically when your method will finish.
Ruby has Dir#mktmpdir, so just use that.
require 'tempfile'
Dir.mktmpdir('prefix_unique_to_your_program') do |dir|
### your work here ###
end
See http://www.ruby-doc.org/stdlib-1.9.3/libdoc/tmpdir/rdoc/Dir.html
Or build your own using Tempfile tempfile that is process and thread unique, so just use that to build a quick Tempdir.
require 'tempfile'
Tempfile.open('prefix_unique_to_your_program') do |tmp|
tmp_dir = tmp.path + "_dir"
begin
FileUtils.mkdir_p(tmp_dir)
### your work here ###
ensure
FileUtils.rm_rf(tmp_dir)
end
end
See http://www.ruby-doc.org/stdlib-1.9.3/libdoc/tempfile/rdoc/Tempfile.html for optional suffix/prefix options.
require 'tmpdir' # not needed if you are loading Rails
tmp_dir = File.join(Dir::tmpdir, "my_app_#{Time.now.to_i}_#{rand(100)}")
Dir.mkdir(tmp_dir)
Works for me.
You can use Dir.mktmpdir.
Using a block will get rid of the temporary directory when it closes.
Dir.mktmpdir do |dir|
File.open("#{dir}/foo", 'w') { |f| f.write('foo') }
end
Or if you need multiple temp directories to exist at the same time, for example
context 'when there are duplicate tasks' do
it 'raises an DuplicateTask error' do
begin
tmp_dir1 = Dir.mktmpdir('foo')
tmp_dir2 = Dir.mktmpdir('bar')
File.new("#{tmp_dir1}/task_name", 'w+')
File.new("#{tmp_dir2}/task_name", 'w+')
expect { subject.filepath('task_name') }.to raise_error(TaskFinder::DuplicateTask)
ensure
FileUtils.remove_entry tmp_dir1
FileUtils.remove_entry tmp_dir2
end
end
end
Dir.mktmpdir creates a temporary directory under Dir.tmpdir (you'll need to require 'tmpdir' to see what that evaluates to).
If you want to use your own path, Dir.mktmpdir takes an optional second argument tmpdir if non-nil value is given. E.g.
Dir.mktmpdir(nil, "/var/tmp") { |dir| "dir is '/var/tmp/d...'" }
I started to tackle this by hijacking Tempfile, see below.
It should clean itself up as Tempfile does, but doesn't always yet..
It's yet to delete files in the tempdir.
Anyway I share this here, might be useful as a starting point.
require 'tempfile'
class Tempdir < Tempfile
require 'tmpdir'
def initialize(basename, tmpdir = Dir::tmpdir)
super
p = self.path
File.delete(p)
Dir.mkdir(p)
end
def unlink # copied from tempfile.rb
# keep this order for thread safeness
begin
Dir.unlink(#tmpname) if File.exist?(#tmpname)
##cleanlist.delete(#tmpname)
#data = #tmpname = nil
ObjectSpace.undefine_finalizer(self)
rescue Errno::EACCES
# may not be able to unlink on Windows; just ignore
end
end
end
This can be used the same way as Tempfile, eg:
Tempdir.new('foo')
All methods on Tempfile , and in turn, File should work.
Just briefly tested it, so no guarantees.
Update: gem install files, then
require "files"
dir = Files do
file "hello.txt", "stuff"
end
See below for more examples.
Here's another solution, inspired by a few other answers. This one is suitable for inclusion in a test (e.g. rspec or spec_helper.rb). It makes a temporary dir based on the name of the including file, stores it in an instance variable so it persists for the duration of the test (but is not shared between tests), and deletes it on exit (or optionally doesn't, if you want to check its contents after the test run).
def temp_dir options = {:remove => true}
#temp_dir ||= begin
require 'tmpdir'
require 'fileutils'
called_from = File.basename caller.first.split(':').first, ".rb"
path = File.join(Dir::tmpdir, "#{called_from}_#{Time.now.to_i}_#{rand(1000)}")
Dir.mkdir(path)
at_exit {FileUtils.rm_rf(path) if File.exists?(path)} if options[:remove]
File.new path
end
end
(You could also use Dir.mktmpdir (which has been around since Ruby 1.8.7) instead of Dir.mkdir but I find the API of that method confusing, not to mention the naming algorithm.)
Usage example (and another useful test method):
def write name, contents = "contents of #{name}"
path = "#{temp_dir}/#{name}"
File.open(path, "w") do |f|
f.write contents
end
File.new path
end
describe "#write" do
before do
#hello = write "hello.txt"
#goodbye = write "goodbye.txt", "farewell"
end
it "uses temp_dir" do
File.dirname(#hello).should == temp_dir
File.dirname(#goodbye).should == temp_dir
end
it "writes a default value" do
File.read(#hello).should == "contents of hello.txt"
end
it "writes a given value" do
# since write returns a File instance, we can call read on it
#goodbye.read.should == "farewell"
end
end
Update: I've used this code to kickstart a gem I'm calling files which intends to make it super-easy to create directories and files for temporary (e.g. unit test) use. See https://github.com/alexch/files and https://rubygems.org/gems/files . For example:
require "files"
files = Files do # creates a temporary directory inside Dir.tmpdir
file "hello.txt" # creates file "hello.txt" containing "contents of hello.txt"
dir "web" do # creates directory "web"
file "snippet.html", # creates file "web/snippet.html"...
"<h1>Fix this!</h1>" # ...containing "<h1>Fix this!</h1>"
dir "img" do # creates directory "web/img"
file File.new("data/hello.png") # containing a copy of hello.png
file "hi.png", File.new("data/hello.png") # and a copy of hello.png named hi.png
end
end
end # returns a string with the path to the directory
Check out the Ruby STemp library: http://ruby-stemp.rubyforge.org/rdoc/
If you do something like this:
dirname = STemp.mkdtemp("#{Dir.tmpdir}/directory-name-template-XXXXXXXX")
dirname will be a string that points to a directory that's guaranteed not to exist previously. You get to define what you want the directory name to start with. The X's get replaced with random characters.
EDIT: someone mentioned this didn't work for them on 1.9, so YMMV.