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.

Foi útil?

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:

  1. Forçar a variável da instância @readonly para false no registro (hack)
  2. 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 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 de set_readonly_option! dentro active_record/base.rb para Rails 2.3.4, ou a implementação de to_a dentro active_record/relation.rb e de custom_join_sql dentro active_record/relation/query_methods.rb para Rails 3.0.0)
  • Contudo, :readonly => true é sempre deduzido automaticamente em has_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 - veja finding_with_ambiguous_select? dentro active_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 @aaronrustadA resposta se aplica muito bem nos trilhos 2.3.4 e 3.0.0.
  • Faz não usar :includes Se você quiser alcançar um INNER JOIN (:includes implica a LEFT OUTER JOIN, que é menos seletivo e menos eficiente do que INNER 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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top