0

There are a number of similar questions, but none of the answers worked for me.

I'm running Rails 6.0.3.2, Ruby 2.6.6, and SQLite3 on Windows 10 version 2004 (19041.388). I followed the Getting Started guide on the official Rails site to install Ruby on Rails and and everything should be up to date.

I can delete the files normally, and I'm logged in with an administrator account — not that it should be necessary.

I'm new to Ruby and Rails, so detailed answers would be appreciated.


Code

Here's what causes the error:

def destroy
    book = Book.find(params[:id])
    
    begin
        File.open(book.cover_url, 'w') do |f|
            File.delete(f)
        end
    rescue Errno::ENOENT
    end

    book.destroy
    redirect_to books_path
end

What this does it first delete the cover image for a book and then delete the book itself from the database.


Errors

The error screen:

Error screen screenshot

If the picture doesn't load, here are the error messages:

Errno::EACCES in BooksController#destroy

Permission denied @ apply2files - D:/Projects/Web/RoR/ecommerce/app/assets/images/covers/circles_scaling_anim_positioning.png

File.delete(f) is the culprit.


Attempted Solutions

  • The only actionable answer I could find for Windows was this, which advocated adding a 'lib' gem, but it did not work at all.

  • I also tried changing file mode from 'w' to 'wb+', but that didn't work either.

  • EDIT 2: As per Dave Newton's suggestion in the comments (if that's what he meant), I moved the image storage directory outside the 'app' folder; to 'public/uploads/covers'. It hasn't worked either.

  • EDIT 3: I copied the delete code to a new script in another directory entirely and tried it on a sample file. I got the same error. In other words, the problem is not with Rails but Ruby (or my OS).

  • I called rm on the file from the terminal and that worked just fine, so I don't know if it's a file permissions problem.


EDIT: I checked the file in question and though it still remains, it's 0 bytes large now, so I presume it was overwritten by empty data. However, the rest of the code that should've execuded on destroy — i.e. the destruction of the object in the databse — seems not to have run, because the object is still in there.

verified_tinker
  • 625
  • 5
  • 17
  • How are you running it? Note that you should *not* be deleting files *within the app* from the app itself: files should be stored outside of the app, for a variety of reasons. – Dave Newton Jul 20 '20 at 12:57
  • @DaveNewton I'm not quite sure what you mean by your question? As for storage: I had no idea. I could use a different directory no problem, but what about when I host it to, say, Heroku (new to that, too)? Does anything other than the root folder exist there? I'm building this project just for practice, so I shouldn't need S3 storage or such. – verified_tinker Jul 20 '20 at 13:02
  • @DaveNewton "Note that you should not be deleting files within the app from the app itself: files should be stored outside of the app, for a variety of reasons." — wait, do you mean the "app" directory? I recall the code I copied for uploading files saved them in the "public" folder. – verified_tinker Jul 20 '20 at 13:07
  • I mean “how are you running it”. Normally web apps don’t have the ability (or reason) to delete files within their own hierarchy. – Dave Newton Jul 20 '20 at 13:22
  • @DaveNewton This is all from development build (or whatever you call development.rb). What I'm trying to do is list books "for sale", and each book has a cover. I'm saving those cover images inside `assets/images/covers/`, and I'm trying to delete them when a book object is destroyed. – verified_tinker Jul 20 '20 at 13:27
  • @verified_tinker : Could it be that the file is still open, perhaps by a different process? – user1934428 Jul 20 '20 at 14:49

2 Answers2

1

The file handling enforced by the operating system is different on Windows compared to most UNIXy operating systems (such as macOS, Linux, or the various BSDs).

On the UNIXy operating systems, the file entry in the filesystem is just a pointer to the "real" file stored on disk. There are other possible pointers, such as a file handle when the file is opened by a process, or a hardlink (i.e. a different filesystem entry pointing to the the exact same file). The file exists on disk as long as there is at least one valid pointer to that file.

As such, on UNIXy systems, you can delete (or rename / move) a file from the filesystem while it is opened from a process. The file itself will only actually be deleted once the last filehandle is closed.

Windows however is more strict in this regard by default. It will not allow to delete a file as long as there is any process which has a file handle on the file (i.e. if any process has opened the file). This explains why you can't delete the file while you have a filehandle on it

With that being said, since Ruby 2.3.0 there is a flag you can set when opening the file which instructs Windows to allow deleting (or moving) the file while it is opened:

# the flags for the normal 'w' mode
file_mode = IO::WRONLY | IO::CREAT | IO::TRUNC

# Add flags to allow deleting (or moving) the opened file
file_mode |= IO::BINARY | IO::SHARE_DELETE

File.open(book.cover_url, mode: file_mode) do |f|
  File.delete(f)
end

Note that the IO::SHARE_DELETE flag is only taken into account for files opened in binary mode (rather than text mode) take this into account when working with this opened file. On UNIXy systems the IO::SHARE_DELETE is ignored.

There is some documentation of the various flags available.

As a final remark: I assume that your code is a shortened example where you have left out some code actually interacting with the opened file in addition to deleting it.

If all you want is to delete the file, there is no need to opened it first. Just delete it with File.delete(book.cover_url). Alternatively, if you you don't care for any errors (such as if the file doesn't exist in the first place, you can also use FileUtils.rm_f(book.cover_url).

Holger Just
  • 52,918
  • 14
  • 115
  • 123
  • I didn't in fact need to do anything with the file, so I'll stick to just `File.delete()`. The syntax was a remnant from similar code I found when I looked up deleting files with Ruby. But good answer for others! – verified_tinker Jul 20 '20 at 14:35
0

Changed the code to this...

begin
    File.delete(book.cover_url)
rescue Errno::ENOENT
end

...and it works now. No opening any files; just File.delete(URL).


If anyone knows why the original version didn't work, or why it's so commonly suggested, please post an answer or comment on this answer.

verified_tinker
  • 625
  • 5
  • 17
  • Can’t delete an open file. Never seen it suggested anywhere; it’s non-sensical. – Dave Newton Jul 20 '20 at 14:03
  • @DaveNewton For example: https://stackoverflow.com/a/25270245/7151327. EDIT: I should've looked at the question before the answer... – verified_tinker Jul 20 '20 at 14:07
  • Probably. Plus the comment on the answer raises the exact issue you're seeing--it's a poor answer because it solves one problem and causes another. The other answer is correct. I'd add that this was a self-answering question by spending a few minutes looking at either the Ruby docs or searching for "Ruby delete file" and not simply picking the first result's code w/o understanding it :) – Dave Newton Jul 20 '20 at 14:15
  • @DaveNewton In my defense, I saw this kind of syntax in a number of places, but I didn't inspect them too closely, so I didn't think to change it until later. But yes, not my proudest moment. – verified_tinker Jul 20 '20 at 14:30
  • I'd be curious to know where you saw this, because it will fail under most circumstances--if it works at all it's platform-dependent behavior and should *never* be recommended, especially since it doesn't make any sense. – Dave Newton Jul 20 '20 at 14:35
  • @DaveNewton: Creating a file with a cryptographically secure pseudo-random name and restrictive permissions, and then *immediately* deleting it is a standard way to create temporary files with at least a little bit of security. (There is still a race condition between creating the file and deleting it, where an attacker could gain access to it.) It is documented in the [`tempfile`](https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html#class-Tempfile-label-Unlink+after+creation) docs, for example. The docs do mention the problems with Windows, though. – Jörg W Mittag Jul 21 '20 at 04:13
  • @JörgWMittag Ok, but it makes no sense to delete a file while it's open. – Dave Newton Jul 21 '20 at 14:16
  • 1
    @DaveNewton: At least in Unix, creating and opening a temporary file, then *immediately* deleting it is completely common. It will remove the file from the public namespace but still allow any process that has an open file handle to use it. – Jörg W Mittag Jul 21 '20 at 14:22
  • @JörgWMittag Oh, I understand what you're talking about now--gotcha. – Dave Newton Jul 21 '20 at 14:25