jeremyevans / sequel

Sequel: The Database Toolkit for Ruby
http://sequel.jeremyevans.net
Other
4.99k stars 1.07k forks source link

Many to one primary key not being set in 5.63.0 #1978

Closed joeosburn closed 1 year ago

joeosburn commented 1 year ago

Complete Description of Issue

This is a new bug that has shown up in 5.63.0. It does not happen in 5.62.0. Based on my debugging, it seems to be caused by this: https://github.com/jeremyevans/sequel/commit/88bcb1f2186e55164a547713751d52f3e33d0434 commit. Specifically, the new lines 58-62 in validate_associated.rb:

            if p_key
               obj.values[key] = p_key
             else
               ignore_key_errors = true
             end

because if I revert them to:

            obj.values[key] = p_key || 0
             key = nil if p_key

the issue goes away. I'm sorry I can't provide a patch or something more helpful, but hopefully this can at least be a starting point.

The bug is that when creating new "many" records via nested attributes, the primary key on their association is not set.

For example:

class Account
  one_to_many :gifts
  nested_attributes :gifts
end

class Gift
  many_to_one :account

  def validate
    puts "account_id: #{account_id.inspect}"
  end
end

Account.create(name: 'Bob', gifts_attributes: [{amount: 5},{amount: 10}])

In this case, the output would be account_id: nil. In 5.62.0, it would be the account_id.

Please let me know if you need further details. Thanks.

jeremyevans commented 1 year ago

While this is a behavior change, it is expected and not a bug, Prior to the commit, it would show an incorrect account_id (0). I think that returning nil is more correct, since at the time of validation of the gifts, the account id is not known.

Here's a safe contained example:

DB.create_table(:accounts) do
  primary_key :id
  String :name, :null=>false
end

DB.create_table(:gifts) do
  primary_key :id
  foreign_key :account_id, :accounts, :null=>false
  Integer :amount, :null=>false
end

class Account < Sequel::Model
  plugin :nested_attributes
  one_to_many :gifts
  nested_attributes :gifts
end

class Gift < Sequel::Model
  many_to_one :account

  def validate
    puts "account_id: #{account_id.inspect}"
  end
end

Account.create(name: 'Bob', gifts_attributes: [{amount: 5},{amount: 10}])

Output with sequel -E sqlite:/ t.rb:

I, [2022-12-28T11:42:25.171955 #39023]  INFO -- : (0.000443s) PRAGMA foreign_keys = 1
I, [2022-12-28T11:42:25.172132 #39023]  INFO -- : (0.000024s) PRAGMA case_sensitive_like = 1
I, [2022-12-28T11:42:25.173166 #39023]  INFO -- : (0.000285s) CREATE TABLE `accounts` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `name` varchar(255) NOT NULL)
I, [2022-12-28T11:42:25.173483 #39023]  INFO -- : (0.000123s) CREATE TABLE `gifts` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `account_id` integer NOT NULL REFERENCES `accounts`, `amount` integer NOT NULL)
I, [2022-12-28T11:42:25.174096 #39023]  INFO -- : (0.000167s) SELECT sqlite_version()
I, [2022-12-28T11:42:25.174440 #39023]  INFO -- : (0.000126s) PRAGMA table_xinfo('accounts')
I, [2022-12-28T11:42:25.180991 #39023]  INFO -- : (0.000111s) PRAGMA table_xinfo('gifts')
account_id: nil
account_id: nil
I, [2022-12-28T11:42:25.182951 #39023]  INFO -- : (0.000027s) BEGIN
I, [2022-12-28T11:42:25.183411 #39023]  INFO -- : (0.000203s) INSERT INTO `accounts` (`name`) VALUES ('Bob') RETURNING *
I, [2022-12-28T11:42:25.184186 #39023]  INFO -- : (0.000100s) INSERT INTO `gifts` (`amount`, `account_id`) VALUES (5, 1) RETURNING *
I, [2022-12-28T11:42:25.184482 #39023]  INFO -- : (0.000090s) INSERT INTO `gifts` (`amount`, `account_id`) VALUES (10, 1) RETURNING *
I, [2022-12-28T11:42:25.184613 #39023]  INFO -- : (0.000043s) COMMIT

On 5.62.0, it would be the same, except account_id: nil would be account_id: 0.

In both cases, the correct account_id is inserted.

You may need to update your validation code to handle the case where account_id is nil.