Rails: Alterar a `primary_key` de um modelo quebra o SQL.

Criado em 3 jun. 2017  ·  3Comentários  ·  Fonte: rails/rails

Passos para reproduzir

  1. rails new example --database=postgresql
  2. rails g model Thing uid flag:boolean
  3. Adicione self.primary_key = 'uid' a app/models/thing.rb
  4. Crie uma instância do Thing com um uid e tente atualizá-lo.

Comportamento esperado

Após a atualização, espero que o valor tenha sido atualizado com sucesso.

Comportamento real

Usando rails console , eu pego uma coisa que criei e tento atualizá-la. A atualização parece ter sido bem-sucedida, mas assim que recarrego minha instância do Thing, fica claro que a atualização não funcionou.

2.4.0 :002 > t = Thing.first
  Thing Load (0.4ms)  SELECT  "things".* FROM "things" ORDER BY "things"."uid" ASC LIMIT $1  [["LIMIT", 1]]
 => #<Thing id: "HI", uid: "HI", flag: nil, created_at: "2017-06-03 17:15:06", updated_at: "2017-06-03 17:15:06">
2.4.0 :003 > t.update(flag: true)
   (0.3ms)  BEGIN
  SQL (1.1ms)  UPDATE "things" SET "flag" = $1, "updated_at" = $2 WHERE "things"."uid" = $3  [["flag", "t"], ["updated_at", "2017-06-03 17:28:35.485354"], ["uid", "1"]]
   (0.4ms)  COMMIT
 => true
2.4.0 :004 > t
 => #<Thing id: "HI", uid: "HI", flag: true, created_at: "2017-06-03 17:15:06", updated_at: "2017-06-03 17:28:35">
2.4.0 :005 > t.reload
  Thing Load (0.5ms)  SELECT  "things".* FROM "things" WHERE "things"."uid" = $1 LIMIT $2  [["uid", "HI"], ["LIMIT", 1]]
 => #<Thing id: "HI", uid: "HI", flag: nil, created_at: "2017-06-03 17:15:06", updated_at: "2017-06-03 17:15:06">

A propriedade flag retorna para nil após recarregar. Você pode ver que a chamada para atualizar está tentando encontrar o registro w/ ["uid", "1"] , que é o id serial original, e não o campo uid especificado.

Isso funcionou antes de atualizar para o Rails 5 :-(

Além disso, se eu tentar validar uniqueness em :uid , não consigo nem save registros existentes. O ActiveRecord acaba encontrando o mesmo registro no banco de dados e não percebe que eles são iguais. No entanto, acredito que isso decorre do mesmo problema de misturar o primary_key (que foi definido como uid ) e o padrão id .

Configuração do sistema

Versão do Rails : 5.1.1

Versão do Ruby : 2.4.0

activerecord attached PR

Comentários muito úteis

Isso funcionou antes de atualizar para o Rails 5 :-(

Fiquei intrigado com isso, pois a correção proposta (https://github.com/rails/rails/pull/29378) altera o código que existe há muito mais tempo do que isso. Transformei o novo teste daquele PR em um script de reprodução e dividi a falha.

Começou a falhar em https://github.com/rails/rails/commit/16ae3db5a5c6a08383b974ae6c96faac5b4a3c81 , quando id_was foi substituído por id_in_database aqui . Verificar esse commit e reverter essa linha fez o teste passar.

No entanto, fazer a mesma alteração no master não corrigiu o teste. Eu o rastreei para https://github.com/rails/rails/commit/b5eb3215a68f94bb8cb20739366232c415744b83 , que alterou o método id_was para usar _read_attribute em vez de passar pelo id e pegando a chave primária.

Para resumir: id_was costumava ler sempre a chave primária, id_in_database foi introduzido e sempre lia a coluna id , e então id_was foi alterado para se comportar como id_in_database . https://github.com/rails/rails/pull/29378 faz com que ambos os métodos funcionem como id_was antes do 5.1.

Todos 3 comentários

Estou tendo o mesmo problema. Querendo saber se você encontrou uma solução alternativa. O id é um número inteiro, mas o primary_key é uma string e está tentando usar o id como primary_key para fazer referência ao registro para atualizações . Modelo:

# Table name: people
#
#  id         :integer          not null
#  orn        :string           not null, primary key
#  notes      :text
#

class Person < ActiveRecord::Base
  self.primary_key = :orn
  attr_readonly :orn
> person = Person.find 'ORN00008079837'
  Person Load (0.2ms)  SELECT  "people".* FROM "people" WHERE "people"."orn" = $1 LIMIT $2  [["orn", "ORN00008079837"], ["LIMIT", 1]]
> person.update! notes: nil
   (0.1ms)  BEGIN
  SQL (0.4ms)  UPDATE "people" SET "updated_at" = $1, "notes" = $2 WHERE "people"."orn" = $3  [["updated_at", "2017-06-06 21:41:13.814079"], ["notes", nil], ["orn", "8079849"]]
   (1.4ms)  COMMIT
=> true

então o Rails acha que deu certo, mas o banco de dados não foi atualizado, pois a cláusula WHERE não corresponde a nenhum registro. no banco de dados id é de fato 8079849 mas no Rails id é ORN00008079837 ...

eu encontrei uma solução alternativa para atualizações. talvez ajude a entender o problema subjacente:

def id_in_database
  self[self.class.primary_key]
end

eu estava olhando para a seguinte fonte e adivinhando o que acontece a seguir:
activerecord-5.1.0/lib/active_record/persistence.rb:575

Isso funcionou antes de atualizar para o Rails 5 :-(

Fiquei intrigado com isso, pois a correção proposta (https://github.com/rails/rails/pull/29378) altera o código que existe há muito mais tempo do que isso. Transformei o novo teste daquele PR em um script de reprodução e dividi a falha.

Começou a falhar em https://github.com/rails/rails/commit/16ae3db5a5c6a08383b974ae6c96faac5b4a3c81 , quando id_was foi substituído por id_in_database aqui . Verificar esse commit e reverter essa linha fez o teste passar.

No entanto, fazer a mesma alteração no master não corrigiu o teste. Eu o rastreei para https://github.com/rails/rails/commit/b5eb3215a68f94bb8cb20739366232c415744b83 , que alterou o método id_was para usar _read_attribute em vez de passar pelo id e pegando a chave primária.

Para resumir: id_was costumava ler sempre a chave primária, id_in_database foi introduzido e sempre lia a coluna id , e então id_was foi alterado para se comportar como id_in_database . https://github.com/rails/rails/pull/29378 faz com que ambos os métodos funcionem como id_was antes do 5.1.

Esta página foi útil?
0 / 5 - 0 avaliações