How to model this complex test for uniqueness on combo fields

A link has two components : componenta_id and componentb_id . For this purpose, in the link model file, I have:

 belongs_to :componenta, class_name: "Component" belongs_to :componentb, class_name: "Component" validates :componenta_id, presence: true validates :componentb_id, presence: true validates :componenta_id, uniqueness: { scope: :componentb_id } validates :componentb_id, uniqueness: { scope: :componenta_id } 

And in the migration file:

 create_table :links do |t| t.integer :componenta_id, null: false t.integer :componentb_id, null: false ... end add_index :links, :componenta_id add_index :links, :componentb_id add_index :links, [:componenta_id, :componentb_id], unique: true 

Question: It all works. Now I want the combination of componanta and componentb be unique, regardless of their order. Thus, no matter which componenta componenta , and which componentb (after all that is the same link, there is a connection between two identical components). Thus, the two entries below should not be allowed, as they represent the same link and therefore are not unique:

  • componenta_id = 1; componentb_id = 2
  • componenta_id = 2; componentb_id = 1

How can I create this uniqueness check? I am working on model validation (see below), but I ask the question, should and how to add confirmation at the migration level / db ...?


Model check
I have a model check that works with the code below:

 before_save :order_links validates :componenta_id, uniqueness: { scope: :componentb_id } private def order_links if componenta_id > componentb_id compb = componentb_id compa = componenta_id self.componenta_id = compb self.componentb_id = compa end end 

The following test confirms the above work:

  1. test "combination of two links should be unique" do 2. assert @link1.valid? 3. assert @link2.valid? 4. @link1.componenta_id = 3 #@link2 already has combination 3-4 5. @link1.componentb_id = 4 6. assert_not @link1.valid? 7. @link1.componenta_id = 4 8. @link1.componentb_id = 3 9. assert_raises ActiveRecord::RecordNotUnique do 10. @link1.save 11. end 12.end 

Migration check / db:
As an added layer of security, is there a way to enable validation for this at db level? Otherwise, you can still write to the database the following two records: componenta_id = 1 ; componentb_id = 2 componenta_id = 1 ; componentb_id = 2 , as well as componenta_id = 2 ; componentb_id = 1 componenta_id = 2 ; componentb_id = 1 .

+6
source share
2 answers

Perhaps you can control the creation of links with:

 def create_unique_link( comp_1, comp_2 ) if comp_1.id > comp_2.id first_component = comp_1 second_component = comp_2 end link = Link.find_or_create_by( componenta_id: first_comp.id, componentb_id: second_comp.id ) end 

If you need a check, you can configure it:

 def ensure_uniqueness_of_link if comp_1.id > comp_2.id first_component = comp_1 second_component = comp_2 end if Link.where( componenta_id: first_component.id, componentb_id: second_component ).first errors.add( :link, 'Links should be unique' ) end end 
+4
source
  validates :componenta_id, uniqueness: { scope: :componentb_id } validates :componentb_id, uniqueness: { scope: :componenta_id } 
+2
source

All Articles