O que está causando esse erro ActiveRecord :: ReadOnlyRecord?
-
11-07-2019 - |
Pergunta
Isso segue isto Pergunta anterior, que foi respondida. Na verdade, descobri que poderia remover uma junção dessa consulta, então agora a consulta de trabalho é
start_cards = DeckCard.find :all, :joins => [:card], :conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", @game.deck.id, true]
Isso parece funcionar. No entanto, quando tento mover esses cartões de deck para outra associação, recebo o erro ActiveRecord :: ReadOnLyRecord.
Aqui está o código
for player in @game.players
player.tableau = Tableau.new
start_card = start_cards.pop
start_card.draw_pile = false
player.tableau.deck_cards << start_card # the error occurs on this line
end
e os modelos relevantes (Tableau são os cartões de jogadores na mesa)
class Player < ActiveRecord::Base
belongs_to :game
belongs_to :user
has_one :hand
has_one :tableau
end
class Tableau < ActiveRecord::Base
belongs_to :player
has_many :deck_cards
end
class DeckCard < ActiveRecord::Base
belongs_to :card
belongs_to :deck
end
Estou fazendo uma ação semelhante logo após este código, adicionando DeckCards
Para a mão dos jogadores, e esse código está funcionando bem. Eu me perguntei se eu precisava belongs_to :tableau
No modelo Deckcard, mas funciona bem para adicionar à mão do jogador. Eu tenho um tableau_id
e hand_id
colunas na mesa do cartão de deck.
Eu olhei para a API do ReadOnLyLyRend na API do Rails, e ela não diz muito além da descrição.
Solução
Rails 2.3.3 e inferior
De ActiveRecord CHANGELOG
(v1.12.0, 16 de outubro de 2005):
Introduzir registros somente leitura. Se você chama Object.readonly! Em seguida, marcará o objeto como somente leitura e aumentará o ReadOnLyRecord se você chamar o Object.Save. object.readonly? relata se o objeto é somente leitura. Passagem: ReadOnly => True a qualquer método do localizador marcará os registros retornados como somente leitura. A opção: Juns agora implica: ReadOnly; portanto, se você usar essa opção, salvar o mesmo registro agora falhará. Use find_by_sql para contornar.
Usando find_by_sql
não é realmente uma alternativa, pois retorna dados de linha/coluna RAW, não ActiveRecords
. Você tem duas opções:
- Forçar a variável da instância
@readonly
para false no registro (hack) - Usar
:include => :card
ao invés de:join => :card
Rails 2.3.4 e acima
A maioria dos itens acima não é mais verdadeira, após 10 de setembro de 2012:
- usando
Record.find_by_sql
é uma opção viável :readonly => true
é deduzido automaticamente só E se:joins
foi especificado sem um explícito:select
nem um explícito (ou localizador-scope-scope):readonly
opção (consulte a implementação deset_readonly_option!
dentroactive_record/base.rb
para Rails 2.3.4, ou a implementação deto_a
dentroactive_record/relation.rb
e decustom_join_sql
dentroactive_record/relation/query_methods.rb
para Rails 3.0.0)- Contudo,
:readonly => true
é sempre deduzido automaticamente emhas_and_belongs_to_many
Se a tabela de junção tiver mais do que as duas colunas de chaves estrangeiras e:joins
foi especificado sem um explícito:select
(ou seja, fornecido pelo usuário:readonly
Os valores são ignorados - vejafinding_with_ambiguous_select?
dentroactive_record/associations/has_and_belongs_to_many_association.rb
.) - em conclusão, a menos que lide com uma tabela de junção especial e
has_and_belongs_to_many
, então@aaronrustad
A resposta se aplica muito bem nos trilhos 2.3.4 e 3.0.0. - Faz não usar
:includes
Se você quiser alcançar umINNER JOIN
(:includes
implica aLEFT OUTER JOIN
, que é menos seletivo e menos eficiente do queINNER JOIN
.)
Outras dicas
Ou no Rails 3, você pode usar o método readonly (substitua "..." por suas condições):
( Deck.joins(:card) & Card.where('...') ).readonly(false)
Isso pode ter mudado no lançamento recente do Rails, mas a maneira apropriada de resolver esse problema é adicionar : readonly => false para as opções de localização.
select('*') seems to fix this in Rails 3.2:
> Contact.select('*').joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly?
=> false
Just to verify, omitting select('*') does produce a readonly record:
> Contact.joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly?
=> true
Can't say I understand the rationale but at least it's a quick and clean workaround.
Instead of find_by_sql, you can specify a :select on the finder and everything's happy again...
start_cards = DeckCard.find :all,
:select => 'deck_cards.*',
:joins => [:card],
:conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", @game.deck.id, true]
To deactivate it...
module DeactivateImplicitReadonly
def custom_join_sql(*args)
result = super
@implicit_readonly = false
result
end
end
ActiveRecord::Relation.send :include, DeactivateImplicitReadonly