首页 > 解决方案 > Rails 模型指的是新的假模型

问题描述

我有以下模型结构,我在其中存储了 Books 和 SellingInfo(代表售前和售后成本):

class Books < ApplicationRecord
  STATUSES = %w[planned purchased].freeze

  has_many :selling_infos, dependent: :destroy
end

class SellingInfo < ApplicationRecord
  belongs_to :book
end

由于 ActiveAdmin 的要求,我希望与一个与selling_infos表相同的模型(相同的列、类型等)建立新关系,但我只会在Book.status == 'purchased'. 为了不使数据库过载,我不想创建新模型,比如说purchased_selling_info,具有相同的数据结构,但创建某种“假模型”并将其用作purchased_selling_info.

我试图在 Book 模型中添加如下内容:

# models/book.rb
  has_one :purchased_selling_info,
          -> { where(status: 'purchased') },
          class_name: 'SellingInfo',
          dependent: :destroy

但是当我尝试检查它是否在 Rails 控制台中工作时,我收到了一个错误:

2.7.2 :009 > Book.purchased_selling_info.size

NoMethodError (undefined method `purchased_selling_info' for #<Class:0x00007f9916f33200>)

标签: ruby-on-railsrubyrails-activerecord

解决方案


将具有不同行为的记录存储在一个表中的解决方案是Single Table Inheritance。通过添加一type列,您可以拥有不同的关联:

class Book < ApplicationRecord
  has_many :selling_infos, dependent: :destroy
end

class PurchasedBook < Book 
  has_one :purchased_selling_info,
          class_name: 'SellingInfo',
          dependent: :destroy
end

当您从数据库加载记录时,ActiveRecord 读取继承列(type默认情况下)并将初始化该类。

如果您不想使用 STI,您可以purchased_selling_info通过自定义验证关联回调来限制创建。

class SellingInfo
  validates :validates_is_purchased, if: ->{ |si| si.purchased? }

  def validates_is_purchased
    errors.add(:book, 'wrong book type')
  end
end

你目前正在做的事情不会起作用,因为:

  1. 将 lambda 添加到关联只是将过滤器应用于关联。关联是类级别的,您不能使它们依赖于实例的属性。
  2. Book.purchased_selling_info.size给出 NoMethod 错误,因为您在类上调用实例方法。

为了不使数据库超载,我不想创建新模型,比如说购买_销售信息,具有相同的数据结构,但创建某种“假模型”并将其用作购买销售信息。

您应该仔细权衡“重载数据库”与 hacky 解决方案增加的复杂性和维护成本。对我来说,这听起来像是一个过早优化的案例。


推荐阅读