Rails对不同模型中两列值的唯一性约束

时间:2017-07-20 08:24:57

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

我想知道在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中强制执行此操作,但是我只看到了在同一个表的多个列上添加唯一性约束的选项。

或者,我已经编写了一个自定义验证器,用于执行我想要的操作,但这很麻烦。需要一个更好的解决方案......

为了完整性,这里是约束验证器,它需要一个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

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

由铁路上的红宝石官方文件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的{​​{1}}重新命名,以便更具可读性。

  • 我还强制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的{​​{1}}模型,反之亦然