Ruby on rails 保存后未更新复合主键

Ruby on rails 保存后未更新复合主键,ruby-on-rails,rails-activerecord,composite-primary-key,Ruby On Rails,Rails Activerecord,Composite Primary Key,下面是一个最小的测试用例,它构成了我问题的基础。为什么即使正确保存了user,属性user.id也没有更新?尝试在数据库中重新查找记录时,会毫无问题地获取该记录,并且id属性设置正确 首先,这不是试图在sqlite中自动增加复合主键的问题。uuid/PostgreSQL组合也会出现同样的问题。架构只有id作为主键,[:account\u id,:id]是一个单独的、唯一的索引 #!/usr/bin/env ruby gem "rails", "~> 5.0.2

下面是一个最小的测试用例,它构成了我问题的基础。为什么即使正确保存了
user
,属性
user.id
也没有更新?尝试在数据库中重新查找记录时,会毫无问题地获取该记录,并且
id
属性设置正确

首先,这不是试图在sqlite中自动增加复合主键的问题。uuid/PostgreSQL组合也会出现同样的问题。架构只有
id
作为主键,
[:account\u 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}"
#/usr/bin/env ruby
gem“rails”,“~>5.0.2”
gem“复合主密钥”
需要“活动记录”
需要“复合主密钥”
ActiveRecord::Base.build\u连接(
适配器:“sqlite3”,
数据库:“:内存:”
)
ActiveRecord::Schema.define do
创建表:帐户,强制:真do | t|
结束
创建表格:用户,强制:真do | t|
t、 参考资料:账户
t、 索引[:account\u id,:id],唯一:true
结束
结束
类用户
运行该脚本的结果是:

~/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>
~/src
%./cpk-test.rb
--创建_表(:accounts,{:force=>true})
->0.0036s
--创建_表(:users,{:force=>true})
->0.0009s
已创建帐户:#
在user.save之前:#
在user.save之后:#
account.users.first:#

在第一次保存后,user.id不应该是
[1,1]
?如果这是一个bug,我应该向谁报告?

SQLite不支持复合主键上的自动递增。您可以在SO中找到相关问题:

这里是第二个链接的@SingleNegationElimination答案:

在sqlite中,只有当只有一个整数时才能获得自动增量行为 列是主键。复合关键点可防止自动递增 正在生效

通过将id定义为唯一的主键,可以得到类似的结果, 但随后在id col3上添加了一个额外的唯一约束

而复合主键保持了这种逻辑


这里也有这样做的诀窍:

事实证明,答案很简单。Rails通常从create获取返回的主键,并用它更新模型。复合密钥不会自行重新加载,因此我必须这样做。基本上使用了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}"
#/usr/bin/env ruby
gem“rails”、“5.0.2”
gem“复合按键”、“9.0.6”
需要“活动记录”
需要“复合主密钥”
ActiveRecord::Base.建立\u连接(适配器:“sqlite3”,数据库:::内存:)
ActiveRecord::Schema.define do
创建表格:帐户,强制:true
创建表格:用户,强制:真do | t|
t、 整数:帐户id,null:false
t、 字符串:email,null:false
t、 索引[:account\u id,:id],唯一:true
t、 索引[:帐户id,:电子邮件],唯一:true
结束
结束
类用户
现在:

% ./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">
%./cpk-test.rb
--创建_表(:accounts,{:force=>true})
->0.0045s
--创建_表(:users,{:force=>true})
->0.0009s
保存用户之前:#
保存用户后:#

我使用sqlite只是为了便于演示问题。我用UUID和PostgreSQL得到了相同的结果。记录保存得很好,id最终是正确的,但是在保存完成后,
save
被调用的实例没有更新以反映新的id。我只是想为在单个文件中实际给出一个工作示例而竖起大拇指,我希望更多的问题都能如此集中。谢谢你,但非常感谢乔恩·莱顿写了这篇关于它的精彩文章。