Fahad
Fahad

Reputation: 146

How to find total size of user attachments in active storage

I'm using Rails 6. How to get the total size of user attachments in active storage?

======

Update:

If I have User A and User B

How to get the total size of User A attachments?

Upvotes: 13

Views: 8837

Answers (4)

mechnicov
mechnicov

Reputation: 15248

If you want to get size of all attachments associated with some record (e.g. user = User.first) you can use this:

ActiveStorage::Attachment.where(record: user).map(&:byte_size).sum

or more efficient with such query

ActiveStorage::Attachment.joins(:blob).where(record: user).sum(:byte_size)

or like this

ActiveStorage::Blob.
  joins(:attachments).
  where(active_storage_attachments: { record_id: user.id, record_type: user.class.name }).
  sum(:byte_size)

For example this user has_many_attached :images

In this case you can get size only of all images of this user as:

user.images.map(&:byte_size).sum

or more efficient with such query

user.images.joins(:blob).sum(:byte_size)

or like this

user.images_blobs.sum(:byte_size)

Using include ActionView::Helpers::NumberHelper you can convert byte size (integer) to human format:

number_to_human_size(size_of_all_attachments)

Upvotes: 19

sandre89
sandre89

Reputation: 5898

You can do it directly on the database, which is much more performant than the other answers posted here.

Let's say your User has_many_attached :images.

Finding the size of one user's images

You can find the total size of one user's images with:

size_of_all_attachments = user.images_blobs.sum("active_storage_blobs.byte_size")

And then, as mechnicov pointed out, you can then include ActionView::Helpers::NumberHelper to convert byte size (integer) in human format:

number_to_human_size(size_of_all_attachments)

Finding the size of all user's images

Also very easily:

User.joins(:images_blobs).sum("active_storage_blobs.byte_size")

The way this works is, as per the documentation, the has_many_attached macro actually creates two associations in your User model, just like if you had declared them yourself:

has_many :images_attachments, ...
has_many :images_blobs

And these :images_blobs are regular ActiveRecord models named ActiveStorage::Blob, which the table name is active_storage_blob and they contain a column named byte_size, with type :bigint, containing the byte size.

The other answers here suggested using ActiveStorage::Attachment.where(record: User.first).map(&:byte_size).sum. However, this is not efficient for two reasons:

  1. It will first return all ActiveStorage::Attachment models; these are a join model used by Rails to connect your User model to the ActiveStorage::Blob model, the latter actually containing the byte_size column we're after. Calling ActiveStorage::Attachement#byte_size will work, but it does so due to the delegate_missing_to :blob declaration on ActiveStorage::Attachment, so it uses method_missing which is much slower.

  2. Due to #1, since ActiveStorage::Attachment doesn't have the byte_size column, it's calling .map, creating a needless array and loading and instanciating all these models needlessly.

So, this answer goes straight to the database and asks for the sum that's already waiting for you there.

Upvotes: 0

Frexuz
Frexuz

Reputation: 4933

This is not a solution for the question; but this helped me solve my own case. So I'm posting it to possibly help others, as I think my case is quite common.

If you have this nested structure:

class Album < ApplicationRecord
  has_many :album_items, dependent: :destroy
end

class AlbumItem < ApplicationRecord
  belongs_to :album, counter_cache: :album_items_count
  before_destroy :purge_attachment

  has_one_attached :photo # automatically adds a `with_attached_photo` scope to prevent n+1 queries

  private
    def purge_attachment
      photo.purge_later
    end
end

To count total album size:

Album.first.album_items.with_attached_photo.map { |album_item| album_item.photo.byte_size }.sum

Note the added with_attached_photo scope, to prevent n+1 queries.

Upvotes: 0

Fahad
Fahad

Reputation: 146

Here is how I do it based in @Yshmarov and @mechnicov answers

In model

after_create_commit :add_user

private
def add_user
files.attachments.update(user_id: self.user.id)
end

In controller

    @checksize = ActiveStorage::Blob.where(id: current_user.id).map(&:byte_size).sum

In view

  <%= number_to_human_size(@checksize) %>

Upvotes: 0

Related Questions