Extending ActiveStorage::Attachment - Adding custom fields

浪尽此生 提交于 2020-01-13 13:04:36

问题


I want to extend the class ActiveStorage::Attachment and add an enum attribute for visibility of attachments.

My initial approach was to create a new file attachment.rb in the \app\models directory as follows.

class ActiveStorage::Attachment < ActiveRecord::Base
    enum visibility: [ :privately_visible, :publicly_visible]
end

This doesn't work.

Any suggestions are welcome. What's the Rails way to extend classes?

Update

I have a solution that works partially now. For this, I have created an extension active_storage_attachment_extension.rb and placed it in \lib

module ActiveStorageAttachmentExtension

  extend ActiveSupport::Concern

  included do
    enum visibility: [ :privately_visible, :publicly_visible]

    def describe_me
      puts "I am part of the extension"
    end

  end
end

The extension is loaded during initialization in extensions.rb

ActiveStorage::Attachment.send(:include, ::ActiveStorageAttachmentExtension)

Unfortunately, it is only working partly: While the enum methods publicly_visible? and privately_visible? are available in the views, they are not available in the controller. When invoking any of the methods in the controller, then the enum seems to have disappeared. I get a "NoMethodError - undefined method" error. Surprisingly, once the enum methods are called once in the controller, they are also not available any more in the views. I assume that the ActiveStorage::Attachment class gets reloaded dynamically and that the extensions are lost as they are only added during initialization.

Any ideas?


回答1:


I assume that the ActiveStorage::Attachment class gets reloaded dynamically and that the extensions are lost as they are only added during initialization.

You’re correct. Use Rails.configuration.to_prepare to mix your module in after application boot and every time code is reloaded:

Rails.configuration.to_prepare do
  ActiveStorage::Attachment.send :include, ::ActiveStorageAttachmentExtension
end



回答2:


As mentioned in my comment, it requires the file app/models/active_storage_attachment.rb with this content:

class ActiveStorageAttachment < ApplicationRecord
  enum visibility: [ :privately_visible, :publicly_visible]
end

Then you also need to add the column visibility of type integer to the table active_storage_attachments.

class AddVisibilityToActiveStorageAttachments < ActiveRecord::Migration[5.2]
  def change
    add_column :active_storage_attachments, :visibility, :integer

  end
end

Accessing the new column of ActiveStorageAttachment

I make an example using my model: I have a User which has_one_attached :avatar.

I can access the active_storage_attachments table through user.avatar.attachment.inspect which returns for example #<ActiveStorage::Attachment id: 1, name: "avatar", record_type: "User", record_id: 1, blob_id: 3, created_at: "2018-06-03 13:26:20", visibility: 0>.

Note that the value of the column visibility is a pure integer, not converted by the visibility array (I'm still wondering why).

One possible workaround is to define a method like avatar_attachment in User model like this:

class User < ApplicationRecord
  has_one_attached :avatar

  def avatar_attachment
    ActiveStorageAttachment.find_by(name: 'avatar', record_type: 'User', record_id: self.id)
  end
end

Now user.avatar_attachment.inspect returns #<ActiveStorageAttachment id: 1, name: "avatar", record_type: "User", record_id: 1, blob_id: 3, created_at: "2018-06-03 13:26:20", visibility: "privately_visible">

Now all the methods related to visibility array are available. Also the record update works:

user.avatar_attachment.publicly_visible! # => true



回答3:


If someone else runs into this, I didn't like the solutions entered here so I came up with another way. Not 100% sure if it's as good as the Rails.configuration.to_prepare solution, but the thing I like about it is that it's just one file in the app/models directory, so no magic going on in configuration files somewhere else in your project.

I make a file named: app/models/active_storage/attachment.rb. Because it's in your project it takes loading precedence over the Gem version. Then inside we load the Gem version, and then monkeypatch it using class_eval:

active_storage_gem_path = Gem::Specification.find_by_name('activestorage').gem_dir
require "#{active_storage_gem_path}/app/models/active_storage/attachment"

ActiveStorage::Attachment.class_eval do
  acts_as_taggable on: :tags
end

The slightly nasty part is locating the original file, since we can't find it normally because our new file takes precedence. This is not necessary in production, so you could put a if Rails.env.production? around it if you like I think.




回答4:


This works for me in Rails 6.

# frozen_string_literal: true

module ActiveStorageAttachmentExtension
  extend ActiveSupport::Concern

  included do
    has_many :virus_scan_results
  end
end

Rails.configuration.to_prepare do
  ActiveStorage::Attachment.include ActiveStorageAttachmentExtension
end


来源:https://stackoverflow.com/questions/50770660/extending-activestorageattachment-adding-custom-fields

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!