Rails:多个模型操作同一张表

4
我正在构建一个系统,其中一些表中的记录是模板记录,可以由所有账户查看,并且稍后可以复制以创建单个账户的实时记录。
这种设计决策背后的原因是模板记录和实时记录共享95%以上相同的代码,因此我不想创建一个单独的表来跟踪大部分相同的字段。
例如,我有一个名为 workflows 的表:
- id:整数 - account_id:整数 - name:字符串(必需) - is_a_template:布尔值(默认值:false) - is_in_template_library:布尔值(默认值:false)
在这张表中,我有一些模板记录。当我要创建新的实时记录时,我可以使用模板记录。
# workflows_controller.rb (pseudo-code, not fully tested)
def create
  @workflow_template = Workflow.where(is_a_template: true).find_by(id: params[:workflow_template_id])
  @workflow = current_account.workflows.new(workflow_params.merge(@workflow_template.dup))

  if @workflow.save
    ...
  else
    ...
  end
end

随着我构建更多的功能,我发现我真的需要两个在表格上运作方式不同的模型。虽然还有几个区别,但以下列出的足以显示它们之间的差异:

class Workflow < ApplicationRecord
  default_scope -> { where(is_a_template: false) }

  belongs_to :account

  validates :account, presence: true
  validates :name, presence: true
end

class WorkflowTemplate < ApplicationRecord
  default_scope -> { where(is_a_template: true) }

  validates :name, presence: true
end

class WorkflowLibraryTemplate < ApplicationRecord
  default_scope -> { where(is_a_template: true, is_in_template_library: true) }

  validates :name, presence: true
end

正如您所看到的,workflows表有三种不同类型的记录:
  1. 属于账户的“实时”工作流程
  2. 模板工作流,也属于一个帐户,并被复制以创建“实时”工作流程
  3. 库模板工作流,不属于任何帐户,可以被任何帐户查看,所以他们可以将其复制到自己的模板列表中
问题 我试图弄清楚的是,在什么时候我要把这个单一的表分成多个表,而不是保持相同的表并拥有多个模型,或者是否有其他解决办法?
令人沮丧的是,还有5个以上的其它表是workflows表的“子”关联。所以,如果我决定需要为每个表格单独的表,那么我最终会从6张表变成18张表,并且每次我添加一个字段时,我都必须针对3个版本进行操作。
因此,我非常不愿意走多个表格的路线。
如果我保留单个表并使用多个模型,则最终在表格中会出现不同版本的数据,但这并不是世界末日。 我只通过我的应用程序(或我控制的未来API)与数据进行交互。
我正在考虑的另一个解决方案是向表中添加一个role:string字段,这个字段的操作方式非常类似于Rails中的type字段。 但是,我不想使用STI,因为Rails中有太多内置要求会和我的需求冲突。
我的设想是:
class Workflow < ApplicationRecord
  scope :templates, -> { where(role: "template") }
  scope :library_templates, -> { where(role: "library_template") }

  validates :account, presence: true, if: :account_required?
  validates :name, presence: true

  # If record.role matches one of these, account is required
  def account_required
    ["live", "template"].include?(role.to_s.downcase)
  end
end

这似乎解决了一些问题,让我只使用一个表格和一个模型,但在模型中开始出现有条件的逻辑,这对我来说似乎是一个不好的想法。
是否有更简洁的方法在表格中实现模板系统?

你目前的方法遇到了或期望遇到什么特定的困难? - EJAg
我现在生产中拥有的是一些非常丑陋的条件逻辑。如果我采用STI方法,我将不得不创建几个不同的模型文件,分别为每个文件实现逻辑。我认为这比多个表更容易维护。使用STI的一个大问题是我将不得不为所有关联关系创建单独的模型文件,这将增加10个以上的模型文件。 - Dan L
2个回答

5
您所看到的是单表继承,模型称为多态
至于何时将STI分成不同的表,答案是:当您有足够的分歧,开始具有专门的列时。 STI的问题在于,假设工作流和工作流模板开始分化。也许模板开始获得许多额外的属性作为列,这些属性与普通工作流不对应。现在,您有很多数据为空的一个类(或不需要),对另一个类来说则非常有用且必要。在这种情况下,我可能会将表分开。您应该问的真正问题是:
1. 这些模型在需求方面将有多大分歧? 2. 这会很快发生吗?
如果它在我的应用程序的生命周期非常晚发生:
由于我有多少行/多少数据,迁移这些表是否会很困难/不可能?
编辑:

有更好的方法吗?在这种特定情况下,我认为没有,因为模板和该模板的副本很可能彼此紧密耦合。


记录将始终相似95%以上;如果模板变得截然不同,那么我会将它们分成自己的资源,但我永远看不到这种情况发生。目前,它们只在account_id、is_a_template、is_in_template_library方面有所不同,如果我使用“角色/类型”字段,那么模板字段就会折叠。我可以接受2-3个不同的字段,但我不认为会超过5个,但我不能总是预测未来 :) - Dan L
没错。我还要补充一点:给自己留些余地。Metz说,如果你可以避免做出决定,那就这么做,如果可以的话,推迟决定。 - apanzerj

1
我采用的方法是责任分解。
责任分解:
目前,您有3个不同的数据源和2种不同的创建/验证工作流程的方式。
为了实现这一点,您可以引入“Repositories”和“FormObject”的概念。
Repositories是包装器对象,将抽象查询模型的方式。它不关心是否是相同的表或多个表。它只知道如何获取数据。
例如:
class Workflow < ApplicationRecord
  belongs_to :account
end

class WorkflowRepository
  def self.all
    Workflow.where(is_a_template: false)
  end
end

class WorkflowTemplateRepository
  def self.all
    Workflow.where(is_a_template: true)
  end
end

class WorkflowLibraryTemplateRepository
  def self.all
    Workflow.where(is_a_template: true, is_in_template_library: true)
  end
end

这样做可以确保无论您将来决定做什么,都不会更改代码的其他部分。
现在让我们讨论FormObject FormObject将抽象验证和构建对象的方式。现在可能不是很好,但通常会在长期内产生回报。
例如:
class WorkFlowForm
  include ActiveModel::Model

  attr_accessor(
    :name,
    :another_attribute,
    :extra_attribute,
    :account
  )

  validates :account, presence: true
  validates :name, presence: true

  def create
    if valid?
      account.workflows.create(
        name: name, is_a_template: false,
        is_in_template_library: false, extra_attribute: extra_attribute)
    end
  end
end

class WorkflowTemplateForm
  include ActiveModel::Model

  attr_accessor(
    :name,
    :another_attribute,
    :extra_attribute
  )

  validates :name, presence: true

  def create
    if valid?
      Workflow.create(
        name: name, is_a_template: true,
        is_in_template_library: false, extra_attribute: extra_attribute)
    end
  end
end

class WorkflowLibraryTemplateForm
  include ActiveModel::Model

  attr_accessor(
    :name,
    :another_attribute,
    :extra_attribute
  )

  validates :name, presence: true

  def create
    if valid?
      Workflow.create(
        name: name, is_a_template: true,
        is_in_template_library: true, extra_attribute: extra_attribute)
    end
  end
end

这种方法有助于扩展性,因为每个对象都是独立的。 唯一的缺点是,在我看来,WorkflowTemplate和WorkflowLibraryTemplate在语义上是相同的,只是多了一个布尔值,但这是一个可选的东西,你可以选择接受或放弃。

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