Qu’est-ce qui cause cette erreur ActiveRecord :: ReadOnlyRecord?

Cela fait suite à cette question préalable, à laquelle il a été répondu. J’ai en fait découvert que je pouvais supprimer une jointure de cette requête.

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

Cela semble fonctionner. Cependant, lorsque j’essaie de déplacer ces DeckCards dans une autre association, j’obtiens l’erreur ActiveRecord :: ReadOnlyRecord.

Voici le code

 for player in @game.players player.tableau = Tableau.new start_card = start_cards.pop start_card.draw_stack = false player.tableau.deck_cards << start_card # the error occurs on this line end 

et les modèles pertinents (tableau sont les cartes de joueurs sur la table)

 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 

Je fais une action similaire juste après ce code, en ajoutant DeckCards à la main du joueur, et ce code fonctionne correctement. Je me demandais si je devais belongs_to :tableau dans le modèle DeckCard, mais cela fonctionne bien pour l’ajout à la main du joueur. J’ai des colonnes tableau_id et hand_id dans la table DeckCard.

J’ai cherché ReadOnlyRecord dans les rails api, et cela ne dit pas grand-chose au-delà de la description.

À partir du CHANGELOG ActiveRecord (v1.12.0, 16 octobre 2005) :

Introduisez des enregistrements en lecture seule. Si vous appelez object.readonly! alors il marquera l’object en lecture seule et déclenchera ReadOnlyRecord si vous appelez object.save. object.readonly? indique si l’object est en lecture seule. Passer: readonly => true à une méthode finder marque les enregistrements renvoyés comme étant en lecture seule. L’option: joins implique désormais: readonly, donc si vous utilisez cette option, l’enregistrement du même enregistrement échouera maintenant. Utilisez find_by_sql pour contourner le problème.

L’utilisation de find_by_sql n’est pas vraiment une alternative car elle renvoie des données de ligne / colonne brutes, et non des ActiveRecords . Vous avez deux options:

  1. Forcer la variable d’instance @readonly à false dans l’enregistrement (hack)
  2. Utilisez :include => :card au lieu de :join => :card

Sept 2010 MISE À JOUR

La plupart de ce qui précède n’est plus vrai. Ainsi, dans Rails 2.3.4 et 3.0.0:

  • utiliser Record.find_by_sql est une option viable
  • :readonly => true n’est automatiquement déduit que si :joins été spécifiées sans explicite :select ni une option explicite (ou finder-scope-inherited) :readonly (voir l’implémentation de set_readonly_option! dans active_record/base.rb pour Rails 2.3). 4, ou l’implémentation de to_a dans active_record/relation.rb et de custom_join_sql dans active_record/relation/query_methods.rb pour Rails 3.0.0)
  • cependant has_and_belongs_to_many :readonly => true est toujours automatiquement déduit dans has_and_belongs_to_many si la table de jointure a plus que les deux colonnes de clés étrangères et que :joins été spécifiées sans explicite :select (c.-à- finding_with_ambiguous_select? dans active_record/associations/has_and_belongs_to_many_association.rb .)
  • En conclusion, à moins de traiter une table de jointure spéciale et has_and_belongs_to_many , la réponse de @aaronrustad s’applique bien aux Rails 2.3.4 et 3.0.0.
  • n’utilisez pas :includes si vous souhaitez atteindre un INNER JOIN ( :includes implique un LEFT OUTER JOIN , qui est moins sélectif et moins efficace que INNER JOIN .)

Ou dans Rails 3, vous pouvez utiliser la méthode readonly (remplacez “…” par vos conditions):

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

Cela a peut-être changé dans la version récente de Rails, mais la manière appropriée de résoudre ce problème consiste à append : readonly => false aux options de recherche.

select (‘*’) semble corriger cela dans Rails 3.2:

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

Juste pour vérifier, omettre select (‘*’) produit un enregistrement en lecture seule:

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

Je ne peux pas dire que je comprends la raison, mais au moins c’est une solution rapide et propre.

Au lieu de find_by_sql, vous pouvez spécifier un: select sur le finder et tout est à nouveau heureux …

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

Pour le désactiver …

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