扩展ActiveStorage::Attachment - 添加自定义字段。

15

我想扩展ActiveStorage::Attachment类,并添加一个用于附件可见性的枚举属性。

我的初始方法是在\app\models目录中创建一个名为attachment.rb的新文件,内容如下:

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

这不起作用。

欢迎提供建议。如何在Rails中扩展类?

更新

我现在有一个部分解决方案。为此,我创建了一个扩展active_storage_attachment_extension.rb并将其放置在\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

在extensions.rb中的初始化期间加载了该扩展程序。

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

很遗憾,它只部分地起作用: 虽然枚举方法publicly_visible?和privately_visible?在视图中可用,但在控制器中不可用。当在控制器中调用任何方法时,该枚举似乎已经消失了。我得到一个“NoMethodError - undefined method”的错误。 令人惊讶的是,一旦在控制器中调用了枚举方法,则它们也不再在视图中可用。 我认为ActiveStorage::Attachment类会被动态重新加载,并且扩展名会因为只在初始化期间添加而丢失。

有什么想法吗?


我从未尝试过,但我认为您应该将文件命名为active_storage_attachment.rb,然后迁移向active_storage_attachments表添加visibility列。 - iGian
4个回答

10
我假设ActiveStorage :: Attachment类会被动态重新加载,并且扩展程序在初始化期间仅添加,因此会丢失。
您是正确的。使用 Rails.configuration.to_prepare,可以在应用启动后每次代码重新加载时混合模块:
Rails.configuration.to_prepare do
  ActiveStorage::Attachment.send :include, ::ActiveStorageAttachmentExtension
end

3

如我在评论中所提到的,需要文件app/models/active_storage_attachment.rb,其内容如下:

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

然后您还需要将类型为整数的列可见性添加到表active_storage_attachments中。

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

  end
end

访问ActiveStorageAttachment的新列

我以我的模型为例:我有一个用户,其中有一个:avatar附件。

我可以通过user.avatar.attachment.inspect访问active_storage_attachments表。例如,它返回#<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>

请注意,visibility列的值是一个纯整数,而不是由visibility数组转换的(我仍然在想为什么)。

一种可能的解决方法是在User模型中定义一个像这样的方法:avatar_attachment

class User < ApplicationRecord
  has_one_attached :avatar

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

现在,user.avatar_attachment.inspect返回#<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">

现在所有与可见性数组相关的方法都可用。 同时记录更新也可以正常工作:

user.avatar_attachment.publicly_visible! # => true

我还有一个迁移,它会在ActiveStorage::Attachment模型中添加该字段,并且该整数值的字段也可用。然而,使用这种方法时枚举对我不可用。我还尝试了给定不同类名ActiveStorage::Attachment的方法,因为它实际上是一个模块的一部分。但仍然没有成功。请参见我的更新原问题。 - Patrick Frey
@PatrickFrey,我正在使用User,它具有has_one_attached:avatar。使用我在答案中展示的设置,我可以通过user.avatar.attachmentactive_storage_attachments表中访问附件记录。无论是从控制器还是视图。所以你也可以。但是,我注意到visibility列的内容是纯整数,没有通过emun数组进行转换。而如果我直接访问active_storage_attachments表,我可以看到列可见性通过枚举数组正确转换。我必须找出原因。 - iGian
@PatrickFrey,我想我找到了一个解决方案。请看更新后的帖子。 - iGian
你所描述的方法对我也有一定的作用,但只是部分地。当调用控制器中的任何方法时,枚举似乎已经消失了。目前,我没有使用枚举,而只是使用整数。我没有枚举的便利性,但它可以工作。 - Patrick Frey

3

这对我来说在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

0

如果有人遇到这个问题,我不喜欢这里提供的解决方案,所以我想出了另一种方法。我不能百分之百确定它是否像Rails.configuration.to_prepare解决方案那样好,但我喜欢它的一点是,它只是app/models目录中的一个文件,因此在您的项目中没有任何其他配置文件中进行魔术。

我创建了一个名为:app/models/active_storage/attachment.rb的文件。因为它在你的项目中,所以它比Gem版本优先加载。然后在内部我们加载Gem版本,然后使用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

稍微有点棘手的是定位原始文件,因为我们通常无法找到它,因为新文件优先。这在生产环境中不是必需的,所以如果您愿意,可以使用if Rails.env.production?来包裹它。


我以前曾经做过类似的事情,覆盖了宝石中的一个文件。通常情况下这是不必要的——因为在上游文件定义之后,你可以简单地重新打开该类——但在某些罕见的情况下,如果你需要在上游文件之前运行一些代码(而且没有提供钩子),这可能是必要的。我希望有一种更简单/更干净的方式来加载原始文件(就像require的"super"一样)。但当你将某个东西放在相同的路径下时,你会“遮蔽”库中的路径,这使得很难访问具有相同路径的上游文件。 - Tyler Rick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接