保存后复合主键不更新
composite primary key not updating after save
这是构成我的问题基础的最小测试用例。为什么 user
保存正确,属性 user.id
没有更新?尝试在数据库中重新查找记录可以毫无问题地获取它,并且 id
属性已正确设置。
AFAICT,这不是尝试在 sqlite 中自动递增复合主键的问题。 uuid/PostgreSQL 组合也会出现同样的问题。该模式只有 id
作为主键,[ :account_id, :id ]
是一个单独的唯一索引。
#!/usr/bin/env ruby
gem "rails", "~> 5.0.2"
gem "composite_primary_keys"
require "active_record"
require "composite_primary_keys"
ActiveRecord::Base.establish_connection(
adapter: "sqlite3",
database: ":memory:"
)
ActiveRecord::Schema.define do
create_table :accounts, force: true do |t|
end
create_table :users, force: true do |t|
t.references :account
t.index [ :account_id, :id ], unique: true
end
end
class User < ActiveRecord::Base
self.primary_keys = [ :account_id, :id ]
belongs_to :account, inverse_of: :users
end
class Account < ActiveRecord::Base
has_many :users, inverse_of: :account
end
account = Account.create!
puts "created account: #{account.inspect}"
user = account.users.build
puts "before user.save: #{user.inspect}"
user.save
puts "after user.save: #{user.inspect}"
puts "account.users.first: #{account.users.first.inspect}"
运行 该脚本的结果是:
~/src
% ./cpk-test.rb
-- create_table(:accounts, {:force=>true})
-> 0.0036s
-- create_table(:users, {:force=>true})
-> 0.0009s
created account: #<Account id: 1>
before user.save: #<User id: nil, account_id: 1>
after user.save: #<User id: nil, account_id: 1>
account.users.first: #<User id: 1, account_id: 1>
第一次保存后 user.id 不应该是 [1,1]
吗?如果这是一个错误,我应该向谁报告?
SQLite 不支持复合主键的自动递增。您可能会在 SO 中找到相关问题:1, 2.
这是来自第二个 link 的@SingleNegationElimination 回答:
In sqlite, you only get autoincrement behavior when only one integer
column is the primary key. composite keys prevent autoincrement from
taking effect.
You can get a similar result by defining id as the only primary key,
but then adding an additional unique constraint on id, col3.
并且 composite_primary_keys
保持这种逻辑。
这里也存在这样做的技巧:sqlite: multi-column primary key with an auto increment column
事实证明,答案很简单。 Rails 通常从创建中获取返回的主键并使用它更新模型。复合键不会自行重新加载,所以我必须这样做。基本上在 after_create 挂钩中使用 reload
中的逻辑来获取创建的记录并相应地更新属性。
#!/usr/bin/env ruby
gem "rails", "5.0.2"
gem "composite_primary_keys", "9.0.6"
require "active_record"
require "composite_primary_keys"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Schema.define do
create_table :accounts, force: true
create_table :users, force: true do |t|
t.integer :account_id, null: false
t.string :email, null: false
t.index [ :account_id, :id ], unique: true
t.index [ :account_id, :email ], unique: true
end
end
class User < ActiveRecord::Base
self.primary_keys = [ :account_id, :id ]
belongs_to :account, inverse_of: :users
after_create do
self.class.connection.clear_query_cache
fresh_person = self.class.unscoped {
self.class.find_by!(account: account, email: email)
}
@attributes = fresh_person.instance_variable_get('@attributes')
@new_record = false
self
end
end
class Account < ActiveRecord::Base
has_many :users, inverse_of: :account
end
account = Account.create!
user = account.users.build(email: "#{SecureRandom.hex(4)}@example.com")
puts "before save user: #{user.inspect}"
user.save
puts "after save user: #{user.inspect}"
现在:
% ./cpk-test.rb
-- create_table(:accounts, {:force=>true})
-> 0.0045s
-- create_table(:users, {:force=>true})
-> 0.0009s
before save user: #<User id: nil, account_id: 1, email: "a54c2385@example.com">
after save user: #<User id: 1, account_id: 1, email: "a54c2385@example.com">
这是构成我的问题基础的最小测试用例。为什么 user
保存正确,属性 user.id
没有更新?尝试在数据库中重新查找记录可以毫无问题地获取它,并且 id
属性已正确设置。
AFAICT,这不是尝试在 sqlite 中自动递增复合主键的问题。 uuid/PostgreSQL 组合也会出现同样的问题。该模式只有 id
作为主键,[ :account_id, :id ]
是一个单独的唯一索引。
#!/usr/bin/env ruby
gem "rails", "~> 5.0.2"
gem "composite_primary_keys"
require "active_record"
require "composite_primary_keys"
ActiveRecord::Base.establish_connection(
adapter: "sqlite3",
database: ":memory:"
)
ActiveRecord::Schema.define do
create_table :accounts, force: true do |t|
end
create_table :users, force: true do |t|
t.references :account
t.index [ :account_id, :id ], unique: true
end
end
class User < ActiveRecord::Base
self.primary_keys = [ :account_id, :id ]
belongs_to :account, inverse_of: :users
end
class Account < ActiveRecord::Base
has_many :users, inverse_of: :account
end
account = Account.create!
puts "created account: #{account.inspect}"
user = account.users.build
puts "before user.save: #{user.inspect}"
user.save
puts "after user.save: #{user.inspect}"
puts "account.users.first: #{account.users.first.inspect}"
运行 该脚本的结果是:
~/src
% ./cpk-test.rb
-- create_table(:accounts, {:force=>true})
-> 0.0036s
-- create_table(:users, {:force=>true})
-> 0.0009s
created account: #<Account id: 1>
before user.save: #<User id: nil, account_id: 1>
after user.save: #<User id: nil, account_id: 1>
account.users.first: #<User id: 1, account_id: 1>
第一次保存后 user.id 不应该是 [1,1]
吗?如果这是一个错误,我应该向谁报告?
SQLite 不支持复合主键的自动递增。您可能会在 SO 中找到相关问题:1, 2.
这是来自第二个 link 的@SingleNegationElimination 回答:
In sqlite, you only get autoincrement behavior when only one integer column is the primary key. composite keys prevent autoincrement from taking effect.
You can get a similar result by defining id as the only primary key, but then adding an additional unique constraint on id, col3.
并且 composite_primary_keys
保持这种逻辑。
这里也存在这样做的技巧:sqlite: multi-column primary key with an auto increment column
事实证明,答案很简单。 Rails 通常从创建中获取返回的主键并使用它更新模型。复合键不会自行重新加载,所以我必须这样做。基本上在 after_create 挂钩中使用 reload
中的逻辑来获取创建的记录并相应地更新属性。
#!/usr/bin/env ruby
gem "rails", "5.0.2"
gem "composite_primary_keys", "9.0.6"
require "active_record"
require "composite_primary_keys"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Schema.define do
create_table :accounts, force: true
create_table :users, force: true do |t|
t.integer :account_id, null: false
t.string :email, null: false
t.index [ :account_id, :id ], unique: true
t.index [ :account_id, :email ], unique: true
end
end
class User < ActiveRecord::Base
self.primary_keys = [ :account_id, :id ]
belongs_to :account, inverse_of: :users
after_create do
self.class.connection.clear_query_cache
fresh_person = self.class.unscoped {
self.class.find_by!(account: account, email: email)
}
@attributes = fresh_person.instance_variable_get('@attributes')
@new_record = false
self
end
end
class Account < ActiveRecord::Base
has_many :users, inverse_of: :account
end
account = Account.create!
user = account.users.build(email: "#{SecureRandom.hex(4)}@example.com")
puts "before save user: #{user.inspect}"
user.save
puts "after save user: #{user.inspect}"
现在:
% ./cpk-test.rb
-- create_table(:accounts, {:force=>true})
-> 0.0045s
-- create_table(:users, {:force=>true})
-> 0.0009s
before save user: #<User id: nil, account_id: 1, email: "a54c2385@example.com">
after save user: #<User id: 1, account_id: 1, email: "a54c2385@example.com">