web-dev-qa-db-fra.com

Qu'est-ce qui provoque cette erreur ActiveRecord :: ReadOnlyRecord?

Ceci suit this question précédente, à laquelle une réponse a été donnée. En fait, j'ai découvert que je pouvais supprimer une jointure de cette requête. La requête de travail est maintenant

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, l'erreur ActiveRecord :: ReadOnlyRecord est générée.

Voici le code

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

et les modèles pertinents (le tableau correspond aux cartes des 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 bien. Je me demandais si j'avais besoin de belongs_to :tableau dans le modèle DeckCard, mais cela fonctionne bien pour l’ajout à la main du joueur. J'ai un tableau_id et hand_id colonnes dans la table DeckCard.

J'ai cherché ReadOnlyRecord dans le Rails api, et cela ne dit pas grand chose au-delà de la description.

201
user26270

Rails 2.3.3 et inférieurs

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

Introduisez des enregistrements en lecture seule. Si vous appelez object.readonly! il marquera alors l'objet comme étant en lecture seule et lèvera ReadOnlyRecord si vous appelez object.save. object.readonly? indique si l'objet est en lecture seule. Passing: readonly => true pour n’importe quelle méthode du Finder marquera les enregistrements retournés comme étant en lecture seule. L'option: join implique maintenant: 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 lignes/colonnes brutes, et non pas ActiveRecords. Vous avez deux options:

  1. Forcer la variable d'instance @readonly À false dans l'enregistrement (bidouille)
  2. Utilisez :include => :card Au lieu de :join => :card

Rails 2.3.4 et supérieur

La plupart de ce qui précède n'est plus valable après le 10 septembre 2012:

  • utiliser Record.find_by_sql est une option viable
  • :readonly => true Est automatiquement déduit uniquement si :joins A été spécifié sans un :select explicite 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)
  • toutefois, :readonly => true est toujours automatiquement déduit dans has_and_belongs_to_many si la table de jointure contient plus que les deux colonnes de clé étrangère et que :joins a été spécifié sans explicite :select ( les valeurs :readonly fournies par l'utilisateur sont ignorées - voir finding_with_ambiguous_select? dans active_record/associations/has_and_belongs_to_many_association.rb.)
  • en conclusion, à moins de traiter avec une table de jointure spéciale et avec has_and_belongs_to_many, la réponse de @aaronrustad s'applique alors parfaitement dans Rails 2.3.4 et 3.0.0.
  • do pas utilisez :includes si vous voulez obtenir un INNER JOIN (:includes implique un LEFT OUTER JOIN, qui est moins sélectif et moins efficace que INNER JOIN.)
282
vladr

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

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

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

44
Aaron Rustad

select ('*') semble résoudre ce problème dans Rails 3.2:

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

Juste pour vérifier, omettre de sélectionner ('*') 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 de contournement rapide et propre.

15
bronson

Au lieu de find_by_sql, vous pouvez spécifier un: sélectionnez dans 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]

5
Harold Gimenez

Pour le désactiver ...

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