3

Hier ist ein minimaler Testfall, der die Grundlage für meine Frage bildet. Warum wird user korrekt gespeichert, obwohl das Attribut user.id nicht aktualisiert wird? Der Versuch, den Datensatz in der Datenbank erneut zu finden, ruft ihn ohne Fehler ab, und das Attribut id wird ordnungsgemäß festgelegt.Composite Primärschlüssel nicht nach Speichern aktualisieren

AFAICT, es handelt sich nicht darum, einen zusammengesetzten Primärschlüssel in sqlite automatisch zu inkrementieren. Das gleiche Problem tritt auch bei der UUID/PostgreSQL-Kombination auf. Das Schema hat nur id als Primärschlüssel, wobei [ :account_id, :id ] ein separater eindeutiger Index ist.

#!/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}" 

Und das Ergebnis dieses Skript ausgeführt ist:

~/src 
[email protected](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> 

nicht sein sollte user.id [1,1] nach dem ersten retten? Wenn dies ein Fehler ist, an wen sollte ich ihn melden?

+1

Ich wollte nur einen Daumen für tatsächlich bis geben ein funktionierendes Beispiel in einer einzigen Datei zu geben, möchte ich mehr Fragen waren diese fokussiert. – Iceman

+1

Vielen Dank, aber vielen Dank an Jon Leighton für das Schreiben dieses ausgezeichneten Beitrags. http://www.jonathaneighton.com/articles/2011/awesome-active-record-bug-reports/ –

Antwort

0

Wie sich herausstellte, war die Antwort einfach. Rails ruft normalerweise den zurückgegebenen Primärschlüssel vom Create ab und aktualisiert das Modell damit. Der zusammengesetzte Schlüssel wird nicht von alleine geladen, also muss ich es tun. Im Grunde verwendet die Logik von reload in einem After_create Hook, um den erstellten Datensatz abzurufen und die Attribute entsprechend zu aktualisieren.

#!/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}" 

Und jetzt:

[email protected](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: "[email protected]"> 
after save user: #<User id: 1, account_id: 1, email: "[email protected]"> 
0

SQLite unterstützt keine automatische Erhöhung des zusammengesetzten Primärschlüssels. Sie können verwandte Fragen in SO finden: 1, 2.

Hier eine @SingleNegationElimination Antwort aus dem zweiten Link:

In SQLite, nur autoincrement Verhalten bekommen, wenn nur eine ganze Zahl Spalte der Primärschlüssel ist. Zusammengesetzte Schlüssel verhindern, dass Autoinkrement von wirksam wird.

Sie können ein ähnliches Ergebnis erhalten, indem Sie id als einzigen Primärschlüssel definieren, , aber dann eine zusätzliche eindeutige Integritätsbedingung für id, col3 hinzufügen.

Und composite_primary_keys hält diese Logik.

Auch hier existiert, dies zu tun Trick: sqlite: multi-column primary key with an auto increment column

+0

Ich benutze nur sqlite für die Leichtigkeit des Demonstrieren des Problems. Ich bekomme die gleichen Ergebnisse mit UUID und PostgreSQL. Der Datensatz wird ordnungsgemäß gespeichert und die ID ist letztendlich korrekt, aber die Instanz, an der 'save' aufgerufen wird, wird nicht aktualisiert, um die neue ID nach Abschluss des Speichervorgangs wiederzugeben. –

Verwandte Themen