2009-03-12 12 views
198

Esto sigue a this pregunta previa, que fue respondida. De hecho, me descubrí que podía quitar una combinación desde esa consulta, por lo que ahora la consulta de trabajo es¿Qué está causando este error ActiveRecord :: ReadOnlyRecord?

start_cards = DeckCard.find :all, :joins => [:card], :conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", @game.deck.id, true] 

Esto parece funcionar. Sin embargo, cuando intento mover estos DeckCards a otra asociación, obtengo el error ActiveRecord :: ReadOnlyRecord.

Aquí está el 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 

y los modelos relevantes (cuadro son las tarjetas de los jugadores en la 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 

que estoy haciendo una acción similar justo después de este código, añadiendo DeckCards a la jugadores, y ese código funciona bien. Me preguntaba si necesitaba belongs_to :tableau en el modelo DeckCard, pero funciona bien para agregar a la mano del jugador. Tengo una tableau_id y hand_id columnas en la tabla DeckCard.

Busqué ReadOnlyRecord en la API apéndices, y no dice mucho más allá de la descripción.

Respuesta

275

Desde el ActiveRecord CHANGELOG(v1.12.0, 16 de octubre de 2005):

Introducir registros de sólo lectura. Si llama a object.readonly! entonces marcará el objeto como de solo lectura y aumentará ReadOnlyRecord si llama a object.save. object.readonly? informa si el objeto es de solo lectura. Pasando: readonly => verdadero a cualquier método de buscador marcará los registros devueltos como de solo lectura. La opción: une ahora implica: solo lectura, por lo que si utiliza esta opción, al guardar el mismo registro , ahora fallará. Utilice find_by_sql para evitar el problema.

Usando find_by_sql no es realmente una alternativa, ya que devuelve datos de fila/columna primas, no ActiveRecords. Tiene dos opciones:

  1. fuerza de la variable de instancia @readonly en false en el registro (truco)
  2. Uso :include => :card en lugar de :join => :card

Sep 2010 Actualización

La mayoría de las arriba ya no es verdad. Por lo tanto, en Rails 2.3.4 y 3.0.0:

  • usando Record.find_by_sqles una opción viable
  • :readonly => true se infiere automáticamente solamente si :joins se especificó sin un explícito :selectni un explícito (o buscador-scope-heredado) :readonly opción (consulte la implementación de set_readonly_option! en active_record/base.rb para Rails 2.3.4, o la implementación de to_a en active_record/relation.rb y de custom_join_sql en active_record/relation/query_methods.rb de rieles 3.0.0), sin embargo
  • , :readonly => true siempre se infiere automáticamente en has_and_belongs_to_many si la tabla de unión tiene más de las dos teclas de columnas exteriores y :joins se ha especificado sin una explícita :select (es decir, :readonly los valores suministrados por el usuario se ignoran - ver finding_with_ambiguous_select? en active_record/associations/has_and_belongs_to_many_association.rb)
  • en conclusión, a no ser que se trata de una combinación especiales has_and_belongs_to_many mesa y, a continuación, @aaronrustad 's respuesta se aplica muy bien en Rails 2.3.4 y 3.0.0..
  • hacer no uso :includes si se quiere lograr un INNER JOIN (:includes implica una LEFT OUTER JOIN, que es menos selectiva y menos eficientes que INNER JOIN.)
+0

el: incluir es útil para reducir el n. ° de consultas realizadas, no lo sabía; pero traté de arreglarlo cambiando la asociación de Tableau/Deckcards a un has_many: through, y ahora recibo un mensaje 'could not find association'; Puede que tenga que publicar otra pregunta para ese – user26270

+0

@codeman, sí, el: incluir reducirá el número de consultas * y * incluirá la tabla incluida en el alcance de su condición (una especie de combinación implícita sin que Rails marque sus registros como leídos). solamente, que lo hace tan pronto como huele algo SQL-ish en su hallazgo, incluyendo: join /: select clauses IIRC – vladr

+0

Para que 'has_many: a, through =>: b' funcione, la asociación B debe declararse también , por ejemplo, 'has_many: b; has_many: a,: a través =>: b', espero que este sea tu caso? – vladr

5

En lugar de find_by_sql, se puede especificar un: seleccionar en el buscador y feliz de todo de nuevo ...

start_cards = DeckCard.find :all, :select => 'deck_cards.*', :joins => [:card], :conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", @game.deck.id, true]

43

Esto podría haber cambiado en los últimos liberación de los carriles, pero la apropiada La forma de resolver este problema es agregar : readonly => false a las opciones de búsqueda.

+3

No creo que este sea el caso, con 2.3.4 al menos – Olly

+2

Todavía funciona con Rails 3.0.10, aquí hay un ejemplo de mi propio código que busca un ámbito que tiene: join Fundraiser.donatable.readonly (false) – Houen

+0

Funciona con 2.3.15 – TlmaK0

167

O en Rails 3 se puede utilizar el método de sólo lectura (sustituir "..." con sus condiciones):

(Deck.joins(:card) & Card.where('...')).readonly(false) 
+0

funcionó sin problemas para mí. –

+1

Hmmm ... Miré ambos Railscasts en Asciicasts, y ninguno menciona la función 'readonly'. – Purplejacket

+1

funciona en Rails 4 también – nocache

3

para desactivarlo ...

module DeactivateImplicitReadonly 
    def custom_join_sql(*args) 
    result = super 
    @implicit_readonly = false 
    result 
    end 
end 
ActiveRecord::Relation.send :include, DeactivateImplicitReadonly 
+3

Monkey-patching es frágil, se rompe muy fácilmente con nuevas versiones de rieles. Definitivamente desaconsejable dado que hay otras soluciones. – Kelvin

15

seleccione ('* ') parece solucionar este problema en Rails 3.2:

> Contact.select('*').joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly? 
=> false 

Sólo para verificar, omitiendo seleccione (' * ') no producirá un registro de sólo lectura:

> Contact.joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly? 
=> true 

No puedo decirlo Entiendo el razonamiento, pero al menos es una solución rápida y limpia.

+4

Lo mismo en Rails 4. Alternativamente, puede hacer 'select (quoted_table_name + '. *')' – andorov

+1

Eso fue genial Bronson. Gracias. – Trip

+0

Esto puede funcionar, pero es más complicado que usar 'readonly (false)' – Kelvin

Cuestiones relacionadas