2

I have a Account model with 3 attachments, using Active Storage, has_many_attached :attachments.

I want to know how many attached files the account has, the most efficient way (aka no joins)

The only solution I found is Account.last.attachments.count or .size, but it makes two query: one for the account and one using active_storage_attachments table.

Is there a way to counter cache the number of attachments?

Thank you in advance

EDIT

Of course I can set up my own database field and count it, I want to know if there is some default

EDIT

I tried to do has_many_attached :attachments, counter_cache: true, but it gives an error

alagaesia
  • 1,469
  • 2
  • 14
  • 20
  • https://blog.appsignal.com/2018/06/19/activerecords-counter-cache.html should help you out – Mark Dec 04 '19 at 15:11
  • Hey @Mark, thank you for your response. Unfortunately that article does not talk about Active Storage solution. I know how to use counter cache, the problem is with ActiveStorage – alagaesia Dec 04 '19 at 15:14
  • Sorry I should read more carefuly :) – Mark Dec 04 '19 at 15:19

2 Answers2

2

I had similar issue with a model that had 7 file attachments, which I needed to count using counter cache instead of the a database query. The trick is that you have to specify counter cache reference in the child model, which has the belongs_to :ParentModel - ActiveStorage::Attachment in my case. ActiveStorage::Attachment is a 'behind-the-scenes' model of Rails so I monkey patched it. But instead of implementing counter cache by adding counter_cache: :true I decided to implement it through callbacks. I created a module for the ParentModel with following structure:

module ParentModel::ModuleName
  extend ActiveSupport::Concern

  class ActiveStorage::Attachment
    require 'active_record/counter_cache'

    before_create :after_create_action, if: :record_parent_model?
    before_destroy :after_destroy_action, if: :record_parent_model?

    def after_create_action
      ParentModel.increment_counter(:attachments_count, record_id, touch: true)
    end

    def after_destroy_action
      ParentModel.decrement_counter(:attachments_count, record_id, touch: true)
    end

    def record_parent_model?
      record_type == 'ParentModel'
    end
  end
end

And I created a migration to add :attachments_count column to the ParentModel as in case of a straight forward counter cache implementation.

msuliq
  • 150
  • 1
  • 12
0

Maybe, you can try to use CounterCache ? https://api.rubyonrails.org/classes/ActiveRecord/CounterCache/ClassMethods.html

Ben
  • 660
  • 5
  • 25
  • You suggest to do a migration creating a new db field and use increment / decrement_counter when a new attachment is added / removed? – alagaesia Dec 04 '19 at 15:16
  • Why not ? You can increment your field when you upload an attachment, and decrement the field when you delete it ? – Ben Dec 04 '19 at 15:32
  • Yes it is possible, I wanted to know if there are any default solutions, but probably this is the only way – alagaesia Dec 04 '19 at 16:11