【问题标题】:ActiveRecord: Best way to add a 'fake' model class?ActiveRecord:添加“假”模型类的最佳方法?
【发布时间】:2020-04-09 10:37:39
【问题描述】:

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

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

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

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

天真地尝试添加class System; end 的实例(例如单例实例),因为作者返回NoMethodError: undefined method `primary_key' for System:Class 之类的错误。

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

有没有办法编写一个实际上不属于数据库的“假”ActiveRecord 模型?

【问题讨论】:

  • 这不是 Active Record 的问题,而是数据库的问题。您必须为posts 表提供一些外键。因此,其中一个解决方案是(通过种子)创建名为“系统”的第一个管理员并将其用作“系统”作者。
  • 您可以向您的Post 模型添加一个名为created_by_system 的布尔属性。然后您必须将 optional: true 添加到您的 belongs_to :author 关联中,以便可以将关联留空。

标签: ruby-on-rails singleton rails-activerecord


【解决方案1】:

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

选项 A:将“系统”Author 记录添加到数据库

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

相对于选项 B 的好处是您可以使用标准的 ActiveRecord 查询来查找系统的所有 Posts。

选项 B:保持关联为零并为 :created_by_system 添加新标志

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

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

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

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

【讨论】:

  • 复制 Fabian 的评论时,您忘记了optional: true。当然还有null: true 迁移author_id 列。而且我什至不谈论可空引用的想法本身。
  • 他从未说过引用是否已经设置为可选,所以我假设它已经是可选的。但是,是的,我的意思是暗示你需要在这里进行迁移。
  • 另外,我敢打赌,99% 的 Rails 程序员都会想出这两种解决方案之一来解决这个问题,所以不,我没有复制你的评论。
【解决方案2】:

最后,我创建了以下不需要对数据库架构进行任何更改的“假”模型类。

它利用了一点元编程:

# 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 既是它自己的类又是它自己的实例。 这样做有以下优点:

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

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

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

【讨论】:

  • 这对你真的有用吗?我在current_scope 丢失时收到错误消息。谢谢!
  • 是的,这确实有效。也许 Rails 7 中的更改会破坏它?
猜你喜欢
  • 1970-01-01
  • 2010-12-21
  • 1970-01-01
  • 1970-01-01
  • 2011-05-28
  • 1970-01-01
  • 1970-01-01
  • 2014-10-05
  • 1970-01-01
相关资源
最近更新 更多