【问题标题】:Rails uniqueness constraint on two column values in different modelsRails对不同模型中两列值的唯一性约束
【发布时间】:2017-12-25 19:58:14
【问题描述】:

我想知道对 Rails 中的两个相关模型属性实施唯一性约束的最佳方法它们都不是主键

class Parent > ApplicationRecord
  has_many :children
  :name
end

class Child > ApplicationRecord
  :name
end

我想强制 (parent.name, child.name) 对每个父母都是唯一的。例如

  • 允许(parent1, child1)(parent2, child1)
  • (parent1, child1)(parent1, child1) 是违规行为

理想情况下,我会在 Postgres 中强制执行此操作,但是我只看到了在同一个表的多个列上添加唯一性约束的选项。

另外,我为 Rails 编写了一个自定义验证器,可以满足我的要求,但这很麻烦。需要有更好的解决方案...

为了完整起见,这里是约束验证器,它需要将children 函数添加到返回子列表的模型中。

class NamePairValidator < ActiveModel::Validator
  def validate(record)
    record.children.values.each do |model_children|
      names = model_children.to_a.collect {|model| model.name}
      if (names.select{|name| names.count(name) > 1 }.size > 0)
        record.errors[:name] << 'Path leading to this resource has no unique name'
      end
    end
  end
end

(在 Parent.rb 中)

def children
  {children: :children}
end

迁移:

class CreateDomains < ActiveRecord::Migration[5.0]
  def change
    create_table :domains do |t|
      t.string :name
      t.string :domain_type
      t.timestamps
    end
  end
end

class CreateSubjects < ActiveRecord::Migration[5.0]
  def change
    create_table :subjects do |t|
      t.string     :name
      t.string     :subject_type
      t.timestamps
    end
  end
end

class CreateJoinTableDomainSubject < ActiveRecord::Migration[5.0]
  def change
    create_join_table :domains, :subjects do |t|
      t.index [:domain_id, :subject_id]
      t.index [:subject_id, :domain_id]
    end
  end
end

【问题讨论】:

  • 你能展示你是如何为你的两个模型编写迁移文件的吗?因为您还可以添加索引以确保数据库级别的完整性,例如robots.thoughtbot.com/the-perils-of-uniqueness-validations
  • 如果您想在数据库级别执行此操作,您可以将 parent_id 列和 child_id 更改为 parent_namechild_name 列并在它们上添加唯一约束。使用一些 ActiveRecord 黑客可以将这些列用作外键。我不确定这个解决方案只是一个想法。
  • 谢谢!我想你有一个数据透视表!这就是为什么尼廷的回答不起作用的原因。请查看此链接:guides.rubyonrails.org/…。它可能会帮助您处理语法和关系
  • 很遗憾,我的解决方案不适用于数据透视表。
  • 有什么建议可以实现超过 2 个级别的通用解决方案吗?例如A有很多B,B有很多C,C有很多D。A、B、C、D都有名字属性。该路径类似于 REST-full 资源的路径。所有 A、B、C 和 D 型号也有一个 ID 字段。例如a/b 必须是唯一的,而且 a/b/c 和 a/b/c/d 也一样

标签: ruby-on-rails ruby postgresql validation ruby-on-rails-5


【解决方案1】:

我只是在我的代码中使用了类似的一个

validates :child_name, uniqueness: { scope: :parent_id }

更多..

(i)https://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of

(ii)Validate uniqueness of multiple columns

【讨论】:

  • 为清楚起见,我应该在哪个模型中添加此验证(父或子)?
  • Child 模型,child_name 也是一个例子 attr
  • 建议在我的情况下似乎不起作用。得到ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column subjects.domain_id does not exist(在我的情况下,我将域作为父级,将主题作为子级)。运行时出错db:migrate db:seed
  • 那么在你的代码中连接主题到域的外键列的名称是什么?改用这个。
  • @RobertPankowecki 我已将迁移添加到最初的问题中。这应该阐明域如何连接到我希望的主题。
【解决方案2】:

由 ruby​​ on rails 官方文档的the-has-many-through-association 启发:

class CreateAppointments < ActiveRecord::Migration[5.0]
  def change
    create_table :domains do |t|
      t.string :name, null: false
      t.string :domain_type
      t.timestamps
    end

    create_table :subjects do |t|
      t.string     :name, null: false
      t.string     :subject_type
      t.timestamps
    end

    create_table :fields do |t|
      t.belongs_to :domain, index: true
      t.belongs_to :subject, index: true
      t.timestamps
    end
  end
end

注意

  • 我主动将您的模型 JoinTableDomainSubject 重命名为 Field 以提高可读性。

  • 我还强制 name 字段不是 nil 来检查唯一性。 (在迁移文件中添加null: false,在两个模型中添加validates :name, presence: true

现在是专门的课程:

class Subject < ApplicationRecord
  has_many :fields
  has_many :domains, through: :fields

  validates :name, presence: true
end

class Domain < ApplicationRecord
  has_many :fields
  has_many :subjects, through: :fields

  validates :name, presence: true
end

class Field < ApplicationRecord
  belongs_to :domain
  belongs_to :subject

  validate :domain_and_subject_names_uniqueness

  private

  def domain_and_subject_names_uniqueness
    if class.includes(:domain, subject)
            .where(domain: { name: domain.name }, subject: { name: subject.name })
            .exists?
      errors.add :field, 'duplicity on names'
    end
  end
end

由于模型是关联的,我可以使用Field.first.domain 访问给定FieldDomain 模型,反之亦然

【讨论】:

  • 用法更改为将主题添加到域。我做到了:@domain.subjects &lt;&lt; Subject.new(params) @domain.save。这似乎不再起作用了......
  • 是的。得到这个错误:NoMethodError in SubjectsController#createundefined method subject_name' for #&lt;Field:0x00000005efca68&gt; Did youmean? subject
  • 将验证移至主题模型,并将范围限定为 domain_id 也无法正常工作...
  • 谢谢。事实上,subject_name 不存在。您可以删除validates_uniqueness_of :subject_name scope: domain_name 并进行自定义检查,这不是很有效。抱歉,我正在做更好的事情
  • @KevinvandenBekerom 我尝试使用自定义验证器编辑我的答案,但我不确定它会做什么。如果成功请通知我。
猜你喜欢
  • 2019-01-10
  • 2012-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多