ActiveRecord:添加 'fake' 模型 class 的最佳方式?

ActiveRecord: Best way to add a 'fake' model class?

在我们的 Rails 应用程序中,Post 资源可以由 UserAdmin 创建。

因此,我们有一个名为 Post 的 ActiveRecord 模型 class,带有 belongs_to :author, polymorphic: true.

但是,在某些情况下,系统本身应该能够创建帖子。 因此,我正在寻找一种方法来添加例如System 作为 author.

显然,永远只有一个 System,因此它不会存储在数据库中。

天真地尝试将 class System; end 的实例(例如单例实例)添加为作者 returns 错误,例如 NoMethodError: undefined method `primary_key' for System:Class.

解决这个问题最干净的方法是什么?

有没有一种方法可以编写 'fake' 实际上不属于数据库的 ActiveRecord 模型?

我认为有两种方法最有意义:

选项 A:向数据库添加 'system' Author 记录

这不是一个可怕的想法,它只是将负担转移到您身上,以确保某些记录存在于每个环境中。但是,如果您想确保始终创建这些记录,您始终可以在种子文件中创建这些记录。

与选项 B 相比的好处是您可以只使用标准 ActiveRecord 查询来查找系统的所有 Post

选项 B:保留关联 nil 并为 :created_by_system

添加新标志

这是我会选择的。如果 Post 是系统创建的,只需将作者引用留空并设置一个特殊标志以指示该模型是内部创建的。

您仍然可以通过创建一个范围来快速获得所有这些列表的方法:

  scope :from_system, -> { where(created_by_system: :true) }

我认为您选择哪一个取决于您是否希望能够查询 Post.author 并获取有关系统的信息。在这种情况下,您需要选择选项 A。否则,我会使用选项 B。我相信还有一些其他方法可以做到这一点,但我认为这是最有意义的。

最后我创建了以下 'fake' 模型 class,它不需要对数据库架构进行任何更改。

它利用了一些元编程:

# For the cases in which the System itself needs to be given an identity.
# (such as when it does an action normally performed by a User or Admin, etc.)
class System
  include ActiveModel::Model
  class << self
    # The most beautiful kind of meta-singleton
    def class
      self
    end

    def instance
      self
    end

    # Calling`System.new` is a programmer mistake; 
    # they should use plain `System` instead.
    private :new

    def primary_key
      :id
    end

    def id
      1
    end

    def readonly?
      true
    end

    def persisted?
      true
    end

    def _read_attribute(attr)
      return self.id if attr == :id

      nil
    end

    def polymorphic_name
      self.name
    end

    def destroyed?
      false
    end

    def new_record?
      false
    end
  end
end

这里要注意的是 System 既是它自己的 class 又是它自己的实例。 这有以下优点:

  • 我们可以只传递 Post.new(creator: System) 而不是 System.newSystem.instance
  • 任何时候都只有一个系统。
  • 我们可以在 System 本身而不是 Class.
  • 上定义 ActiveRecord 需要 (polymorphic_name) 的 class 方法

当然,你是喜欢这种元编程还是觉得它太复杂是很主观的。

不太主观的是覆盖 ActiveRecord 的 _read_attribute 并不好;我们依赖于 ActiveRecord 的实现细节。不幸的是,据我所知,没有公开的 public API 可用于更干净地执行此操作。 (在我们的项目中,我们有一些规范可以在 ActiveRecord 可能更改时立即通知我们。)