What is the best way to get an empty temporary directory in Ruby on Rails?

后端 未结 9 555

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.

相关标签:
9条回答
  • 2020-12-28 13:09

    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.

    0 讨论(0)
  • 2020-12-28 13:09

    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
    
    0 讨论(0)
  • 2020-12-28 13:12
    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.

    0 讨论(0)
  • 2020-12-28 13:12

    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.

    0 讨论(0)
  • 2020-12-28 13:24

    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'.

    0 讨论(0)
  • 2020-12-28 13:26

    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.

    0 讨论(0)
提交回复
热议问题