Working with cyclic associations

36 views
Skip to first unread message

Andre Nathan

unread,
Nov 6, 2014, 9:58:38 AM11/6/14
to fabrica...@googlegroups.com
Hello

I'm using Sequel to map a legacy database and Fabrication for the factories in my tests.

I have the following model definitions:

  class DNSKeyRecord < Sequel::Model
    set_dataset :dnskey_records
    set_primary_key :id
    one_to_one :rrsig_record, class: :RRSigRecord, key: :dnskey_record_id
  end

  class RRSigRecord < Sequel::Model
    set_dataset :rrsig_records
    many_to_one :dnskey_record, class: :DNSKeyRecord
  end

The database constraints enforce the presence of rrsig_record_id in table dnskey_records entries and of dnskey_record_id in the rrsig_records table entries.

I managed to work around the cyclic associations by using Fabricate.build and an after_build hook in the dnskey_record factory:

Fabricator(:rrsig_record, from: :RRSigRecord) do
  dnskey_record { Fabricate.build(:dnskey_record) }
end

Fabricator(:dnskey_record, from: :DNSKeyRecord) do
  after_build do |record|
    record.rrsig_record = Fabricate.build(:rrsig_record, dnskey_record: record)
  end
end

However, when running rspec this generates the following error:

Sequel::Error:
       associated object #<DNSKeyRecord @values={}> does not have a primary key

I can make the error go away by setting a fake id for the records with

  id { Faker::Number.number(6) }

Is this expected? Is there a cleaner way around this?

Thanks,
Andre

Paul Elliott

unread,
Nov 6, 2014, 10:12:08 AM11/6/14
to fabrica...@googlegroups.com
Hey Andre,

If the database has constraints forcing both ids to be present, that means they are being set externally instead of using the internal database mechanism for ids. In that case, they don't mean "id" in the same sense that we normally would. What I think you'll have to do is provide the ids manually when you persist the records. Here is an example of how you can do that without using the after_build hooks:

```
Fabricator(:rrsig_record, from: :RRSigRecord) do
  id { Faker::Number.number(6) }
  dnskey_record do |attrs|
    Fabricate.build(:dnskey_record, rrsig_record_id: attrs[:id])
  end
end

Fabricator(:dnskey_record, from: :DNSKeyRecord) do
  id { Faker::Number.number(6) }
  rrsig_record do |attrs|
    Fabricate.build(:rrsid_record, dnskey_record_id: attrs[:id])
  end
end
```

Although, I would strongly recommend you either remove one of these constraints or merge the two tables. Seems like they always have to exist in tandem anyway, so why not just have them as one record?

-- Paul

--
You received this message because you are subscribed to the Google Groups "fabrication" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fabricationge...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Andre Nathan

unread,
Nov 6, 2014, 10:40:48 AM11/6/14
to fabrica...@googlegroups.com
On Thursday, November 6, 2014 1:12:08 PM UTC-2, Paul Elliott wrote:
 What I think you'll have to do is provide the ids manually when you persist the records. Here is an example of how you can do that without using the after_build hooks:

```
Fabricator(:rrsig_record, from: :RRSigRecord) do
  id { Faker::Number.number(6) }
  dnskey_record do |attrs|
    Fabricate.build(:dnskey_record, rrsig_record_id: attrs[:id])
  end
end

Fabricator(:dnskey_record, from: :DNSKeyRecord) do
  id { Faker::Number.number(6) }
  rrsig_record do |attrs|
    Fabricate.build(:rrsid_record, dnskey_record_id: attrs[:id])
  end
end
```

Doing it that way also causes an infinite loop ("stack level too deep" error while running the tests).

Paul Elliott

unread,
Nov 6, 2014, 10:52:05 AM11/6/14
to fabrica...@googlegroups.com
Oh, sorry! You're right about that. That's the trouble with writing this stuff off the top of my head :)

I just tried to give you a better solution and everything I can come up with is worse than using the callbacks like you were doing to begin with.

I would seriously consider merging those tables. I think it will save you more trouble in the long run.

-- Paul

--

Andre Nathan

unread,
Nov 6, 2014, 10:59:15 AM11/6/14
to fabrica...@googlegroups.com
Currently I can't do that, but I'll keep it in mind for the future. Thanks!
Reply all
Reply to author
Forward
0 new messages