【问题标题】:How to make sure that relation is unique from both sides of many to many如何确保多对多双方的关系是唯一的
【发布时间】:2018-01-25 23:24:59
【问题描述】:

我有一个充当多对多关系的模型。类名是RelatedDocument,这是不言自明的,我基本上用它来关联Document类实例。

我遇到了验证问题,例如我在 RelatedDocument 类中有这个问题:

validates :document, presence: true, uniqueness: { scope: :related_document }
validates :related_document, presence: true

这项工作我无法创建重复的document_id/related_document_id 行。但是,如果我想从关系的另一端使其独一无二并将验证更改为:

validates :document, presence: true, uniqueness: { scope: :related_document }
validates :related_document, presence: true, uniqueness: { scope: :document }

这在另一边是不一样的。当我注意到这一点时,我正在编写 rspec 测试。如何编写验证或自定义验证方法来防止保存相同的 id 组合,无论它们来自哪一侧?

更新

根据评论部分的第一条评论,第一次唯一性验证将照顾双方,我说这不仅仅是因为我的 rspec 测试失败,它们是:

describe 'relation uniqueness' do
    let!(:base_doc) { create(:document) }
    let!(:another_doc) { create(:document) }
    let!(:related_document) { described_class.create(document: another_doc, related_document: base_doc) }

    it 'raises ActiveRecord::RecordInvalid, not allowing duplicate relation links' do
      expect { described_class.create!(document: another_doc, related_document: base_doc) }
        .to raise_error(ActiveRecord::RecordInvalid)
    end

    it 'raises ActiveRecord::RecordInvalid, not allowing duplicate relation links' do
      expect { described_class.create!(document: base_doc, related_document: another_doc) }
        .to raise_error(ActiveRecord::RecordInvalid)
    end
  end

第二次测试失败。

【问题讨论】:

  • 您的第一个验证强制执行唯一的组合。您从哪个“方面”查看组合并不重要。因此,您不需要第二次验证。
  • @jvillian 但我的测试结果并非如此。在示例中,如果我有 doc1 和 doc2,如果我有 RelatedDocument.create!(document: doc1, related_document: doc2) 会触发唯一性验证。然而,当我尝试RelatedDocument.create!(document: doc2, related_document: doc1) 时,这并不是我想说的。第一次验证不考虑双方,至少在我的 rspec 测试中
  • 噢噢噢噢!我懂了。您可以创建一个自定义验证来“从另一边”检查关系。这有一些二阶含义,您需要确保自己能够接受。
  • @jvillian 我尝试了类似where(doc1 and doc2 OR doc2 and doc2).any? 的方法,没有用,有什么想法吗?
  • @rantingsonrails 的答案看起来很可靠。您是否想做类似:@document_1.related_documents 并取回所有相关文档 - 无论关系的“方向”如何?对于您当前的标题,这将非常棘手。

标签: ruby-on-rails ruby-on-rails-4 rspec


【解决方案1】:

如果您想考虑替代已提供的自定义验证,您可以在每次添加新记录时创建互惠关系并使用现有验证。

例如,当我说“文档 A 与文档 B 相关”时,我也会插入一条记录,说明“文档 B 与文档 A 相关”。您可以保持验证简单,并且可以在将来更轻松地实现逻辑,以便在某些情况下想要互惠关系(也许只有当文档来自不同的文档时,关系才会互惠)作者)。

以下是您将实现的模型回调类型的未经测试的示例:

create_table :documents do |t|
    t.string :title, null: false
end

create_table :related_documents do |t|
    t.integer :source_document_id, null: false
    t.integer :related_document_id, null: false
end

add_index :related_documents, [:source_document_id, :related_document_id], unique: true
add_foreign_key :related_documents, :documents, column: :source_document_id
add_foreign_key :related_documents, :documents, column: :related_document_id

class Document < ActiveRecord::Base
    # We have many document relationships where this document is the source document
    has_many :related_documents, foreign_key: :source_document_id

    validates :title,
        presence: true
end

class RelatedDocument < ActiveRecord::Base
    after_create :add_reciprocal_relationship
    after_destroy :remove_reciprocal_relationship

    belongs_to :source_document, class_name: Document
    belongs_to :related_document, class_name: Document

    validates :source_document,
        presence: true

    validates :related_document,
        presence: true,
        uniqueness: {
            scope: :source_document
        }

    private

    # Creates a reciprocal relationship
    def add_reciprocal_relationship
        RelatedDocument.find_or_create_by(
            related_document: self.source_document,
            source_document: self.related_document
        )
    end

    # Safely removes a reciprocal relationship if it exists
    def remove_reciprocal_relationship
        RelatedDocument.find_by(
            related_document: self.source_document,
            source_document: self.related_document
        )&.destroy
    end
end

【讨论】:

    【解决方案2】:

    自定义验证器应该在这里工作

    validate :unique_document_pair
    
    def unique_document_pair
      if RelatedDocument.exists?(:document => [self.document,self.related_document], :related_document => [self.document, self.related_document])
          errors.add :base, "error"
      end
    end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-03
      • 1970-01-01
      • 1970-01-01
      • 2013-09-15
      • 2014-01-26
      • 2011-06-04
      • 1970-01-01
      • 2014-03-25
      相关资源
      最近更新 更多