问题
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