составной первичный ключ не обновляется после сохранения
Вот минимальный тестовый случай, который лежит в основе моего вопроса. Почему, несмотря на то, что 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
frankjmattia@lappy-i686(ttys005)[4146] % ./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]
после первого сохранения? Если это ошибка, кому я должен сообщить об этом?
2 ответа:
SQLite не поддерживает автоматическое приращение для составного первичного ключа. Вы можете найти соответствующие вопросы в SO: 1, 2.
Вот ответ @SingleNegationElimination из второй ссылки:
В sqlite вы получаете поведение автоинкремента только тогда, когда только одно целое число столбец-это первичный ключ. составные ключи предотвращают автоинкремент от вступает в силу.
Вы можете получить аналогичный результат, определив id в качестве единственного первичного ключа, но затем добавляя дополнительный уникальное ограничение на id, col3.
И
composite_primary_keys
сохраняют эту логику.Также здесь существует трюк, чтобы сделать это: sqlite: многоколоночный первичный ключ с автоинкрементным столбцом
Как оказалось, ответ был прост. Rails обычно получает возвращенный первичный ключ из create и обновляет модель с его помощью. Составной ключ не перезагружается сам по себе, поэтому я должен это сделать. В основном используется логика из
reload
в крюке after_create для извлечения созданной записи и обновления атрибутов соответственно.#!/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}"
А теперь:
frankjmattia@lappy-i686(ttys003)[4108] % ./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">