Reputation: 146
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
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
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
.
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)
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:
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.
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
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
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