【问题标题】:composite primary key not updating after save保存后复合主键不更新
【发布时间】:2017-09-04 06:44:34
【问题描述】:

这是构成我的问题基础的最小测试用例。为什么即使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] 吗?如果这是一个错误,我应该向谁报告?

【问题讨论】:

  • 我只是想为在单个文件中实际给出一个工作示例而竖起大拇指,我希望更多的问题是这样集中的。
  • 谢谢你,但非常感谢 Jon Leighton 写了这篇出色的文章。 jonathanleighton.com/articles/2011/…

标签: ruby-on-rails rails-activerecord composite-primary-key


【解决方案1】:

事实证明,答案很简单。 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">

【讨论】:

    【解决方案2】:

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

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

    在 sqlite 中,只有一个整数时,您才会获得自动增量行为 列是主键。复合键防止自动增量 生效。

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

    composite_primary_keys 保持这种逻辑。

    这里也有这样做的技巧:sqlite: multi-column primary key with an auto increment column

    【讨论】:

    • 我使用 sqlite 只是为了方便演示问题。我使用 UUID 和 PostgreSQL 得到相同的结果。记录保存良好,并且 id 最终正确,但在保存完成后调用 save 的实例未更新以反映新 id。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-23
    • 1970-01-01
    • 1970-01-01
    • 2011-03-16
    • 1970-01-01
    相关资源
    最近更新 更多