I ran into a little gotcha today, using Sequel. I’m writing an importer, you know the kind: read record from database A, apply some transformations, write to database B. No rocket science required. But, Sequel has a little gotcha that stumped me for a bit. My script looked like this:
1 DBa = Sequel.connect "..." 2 DBb = Sequel.connect "..." 3 4 class APerson < Sequel::Model(DBa[:people]) 5 end 6 7 class BContact < Sequel::Model(DBb[:contacts]) 8 end 9 10 contacts = Hash.new 11 APerson.all.each do |person| 12 contact = BContact.create( 13 :name => person.last_name + ", " + person.first_name, 14 :tenant_code => ENV["TENANT_CODE"], 15 :updated_by => "import", 16 :updated_at => Time.now) 17 contacts[ person.id ] = contact.contact_id 18 end 19 20 # Now I can map A's IDs to the correct value in database B, such as 21 # for attaching email addresses, phone numbers, etc.
The Contact model in the B database is declared like this:
1 create_table :contacts do 2 column :tenant_code, :integer, :null => false 3 column :contact_id, :serial, :null => false 4 column :name, "varchar(240)", :null => false 5 6 primary_key [:tenant_code, :contact_id] 7 foreign_key [:tenant_code], :tenants 8 end
Notice tenant_code and contact_id are part of the primary key. I don’t write to contact_id because I want the sequence’s value to be returned to me. But I must write my own value to the tenant_code column. I was receiving an exception on the #create call:
1 /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:1491:in `block in set_restricted': method tenant_code= doesn't exist or access is restricted to it (Sequel::Error) 2 from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:1486:in `each' 3 from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:1486:in `set_restricted' 4 from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:1077:in `set' 5 from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:1456:in `initialize_set' 6 from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:764:in `initialize' 7 from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:134:in `new' 8 from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:134:in `create' 9 from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:248:in `find_or_create' 10 from script/import:65:in `block (2 levels) in <top (required)>' 11
I was very much stumped, and turned to the excellent documentation. I eventually found my way to #set_all, and changed my code accordingly:
1 APerson.all.each do |person| 2 contact = BContact.new.set_all( 3 :name => person.last_name + ", " + person.first_name, 4 :tenant_code => ENV["TENANT_CODE"], 5 :last_updated_by => "import", 6 :last_updated_at => Time.now) 7 contacts[ person.id ] = contact.contact_id 8 end
Even though the Sequel RDoc says #set_all ignores restricted columns, I was still receiving the same exception. I was now doubly stumped, until I found a reference to #unrestrict_primary_key. I added the declaration to BContact and was able to proceed:
1 class BContact < Sequel::Model(DBb[:contacts]) 2 unrestrict_primary_key 3 end
You know the drill though: where you import one model, you’ll have more to import shortly. Ruby to the rescue!
1 class Sequel::Model 2 # Open classes win every time! 3 unrestrict_primary_key 4 end
Problem solved!