Mapping of filenames to paths using Ruby on Rails’ paperclip

I do not know anything about Ruby on Rails, but I am involved with re-writing an application developed with it by someone else a while ago. I have access to the source code and, while I like to think I know programming (C/C++, etc.), I’m having difficulty trying to figure out Ruby on Rails. And I guess the reason why I can’t figure it out is because the details of it is hidden within a library (paperclip).

(Short version: I want to match filenames in a database column with the physical location of files on disk.)

With my limited knowledge, I know the following…

The application has a Gemfile.gem with this entry: ‘paperclip’, ‘4.3.0’. I’ve looked at the development history of paperclip and realise that this is from April 2016. So, it seems pretty old…

I have a PostgreSQL database with fields such as:

  • attachment_file_name
  • attachment_content_type
  • attachment_file_size
  • attachment_updated_at

And all of this matches the variables mentioned here in the documentation.

The values under attachment_file_name in the database are just filenames, with no paths.

On disk, the files are stored in directories such as attachments/000/003/335/original/abc.txt, which looks exactly like the example in the documentation. So, I guess I am fairly sure that paperclip is being used.

How does the filename translate to the above path?

And, more importantly, if I have a set of a few hundred files, I would like to print out the full path for each file. There are duplicates in the filename that result in different paths. So I guess the calculation (hash function?) is done using both the filename and maybe the contents of the file.

Can someone offer suggestions about how to do this and/or what in the code I should be looking for?

I guess my question is similar to this Issue in the repository, which pointed the author of that Issue to StackOverflow (of course, that was 7 years ago).

Thank you! Any help would be appreciated!

It’s easier with an example:

class User < ActiveRecord::Base
  has_attached_file :attachment

  validates_attachment_content_type :attachment, content_type: /\Aimage\/.*\z/
end

Attach a file:

>> User.create!(attachment: File.open("avatar.png"))
  TRANSACTION (0.1ms)  begin transaction
  User Create (0.3ms)  INSERT INTO "users" ("attachment_file_name", "attachment_content_type", "attachment_file_size", "attachment_updated_at") VALUES (?, ?, ?, ?)  [["attachment_file_name", "avatar.png"], ["attachment_content_type", "image/png"], ["attachment_file_size", 8229], ["attachment_updated_at", "2023-12-01 05:14:12.369915"]]
  TRANSACTION (5.4ms)  commit transaction
=> #<User id: 1, attachment_file_name: "avatar.png", attachment_content_type: "image/png", attachment_file_size: 8229, attachment_updated_at: "2023-12-01 05:14:12.369915603 +0000">

This is the default path where file is stored:

>> Paperclip::Attachment.default_options[:url]
=> "/system/:class/:attachment/:id_partition/:style/:filename"
$ ls public/system/**/*.png
public/system/users/attachments/000/000/001/original/avatar.png
public/
system/
users/       - :class        # `User.name.underscore.pluralize`
                             # https://github.com/thoughtbot/paperclip/blob/main/lib/paperclip/interpolations.rb#L88

attachments/ - :attachment   # pluralized attachment name
                             #                     vvvvvvvvvv
                             # `has_attached_file :attachment`
                             # https://github.com/thoughtbot/paperclip/blob/main/lib/paperclip/interpolations.rb#L191

000/000/001/ - :id_partition # id padded with zeros and split in 3 groups (9 digits total)
                             # https://github.com/thoughtbot/paperclip/blob/main/lib/paperclip/interpolations.rb#L174

original/    - :style        # "original" is default, this is what you've uploaded
                             # https://github.com/thoughtbot/paperclip/blob/main/lib/paperclip/interpolations.rb#L197

avatar.png   - :filename

If you can only work from the database side

sqlite> select * from users;
+----+----------------------+-------------------------+----------------------+----------------------------+
| id | attachment_file_name | attachment_content_type | attachment_file_size |   attachment_updated_at    |
+----+----------------------+-------------------------+----------------------+----------------------------+
| 1  | avatar.png           | image/png               | 8229                 | 2023-12-01 05:14:12.369915 |
+----+----------------------+-------------------------+----------------------+----------------------------+
public/
system/
users/       - :class        # most likely the same as table name

attachments/ - :attachment   # attachment_file_name
                             # ^^^^^^^^^^s

000/000/001/ - :id_partition # padded id - you'll need to work that one out

original/    - :style        # "original" - this is not in the db

avatar.png   - :filename     # attachment_file_name

Leave a Comment