web-dev-qa-db-ja.com

このActiveRecord :: ReadOnlyRecordエラーの原因は何ですか?

これは、 this 前の質問に続き、答えられました。私は実際にそのクエリから結合を削除できることを発見したので、今では作業クエリは

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

これは動作するようです。ただし、これらのDeckCardを別の関連付けに移動しようとすると、ActiveRecord :: ReadOnlyRecordエラーが発生します。

ここにコードがあります

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

および関連するモデル(テーブルはテーブル上のプレーヤーカードです)

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

このコードの直後に同様のアクションを実行し、DeckCardsをプレーヤーの手に追加しますが、そのコードは正常に機能しています。 DeckCardモデルにbelongs_to :tableauが必要かどうか疑問に思いましたが、プレイヤーの手札に追加するのには問題ありません。 DeckCardテーブルにtableau_id列とhand_id列があります。

Rails apiでReadOnlyRecordを検索しましたが、説明以上のことは言っていません。

201
user26270

Rails 2.3.3以前

ActiveRecord CHANGELOG から(v1.12.0、2005年10月16日)

読み取り専用レコードを導入します。 object.readonly!を呼び出す場合その後、オブジェクトを読み取り専用としてマークし、object.saveを呼び出すとReadOnlyRecordを発生させます。 object.readonly?オブジェクトが読み取り専用かどうかを報告します。 Finderメソッドに:readonly => trueを渡すと、返されたレコードが読み取り専用としてマークされます。 :joinsオプションは:readonlyを意味するようになったため、このオプションを使用すると、同じレコードの保存は失敗します。 find_by_sqlを使用して回避します。

find_by_sqlの使用は、ActiveRecordsではなく、生の行/列データを返すため、実際には代替手段ではありません。次の2つのオプションがあります。

  1. レコード内のインスタンス変数@readonlyを強制的にfalseにします(ハック)
  2. :include => :cardの代わりに:join => :cardを使用します

Rails 2.3.4以降

2012年9月10日以降、上記のほとんどは当てはまりません。

  • Record.find_by_sqlisを使用可能なオプション
  • :readonly => trueは自動的に推測されますonly:joinsが指定された場合without明示的な:selectnor明示的な(またはFinder-scope継承):readonlyオプション( Rails 2.3.4のset_readonly_option!でのactive_record/base.rbの実装、またはRails 3.0.0のto_aでのactive_record/relation.rbおよびcustom_join_sqlでのactive_record/relation/query_methods.rbの実装
  • ただし、結合テーブルに2つ以上の外部キー列があり、:readonly => trueが明示的なhas_and_belongs_to_manyなしで指定された場合、:joinsは常に:selectで自動的に推測されます(つまり、ユーザーが指定した:readonly値は無視されます-finding_with_ambiguous_select?active_record/associations/has_and_belongs_to_many_association.rbを参照してください)
  • 結論として、特別な結合テーブルとhas_and_belongs_to_manyを処理しない限り、@aaronrustadの答えはRails 2.3.4および3.0.0でうまく適用されます。
  • donot:includesを実現する場合はINNER JOINを使用します(:includesLEFT OUTER JOINを意味します。これはINNER JOINよりも選択性が低く、効率が劣ります。)
282
vladr

または、Rails 3では、読み取り専用メソッドを使用できます(「...」を条件に置き換えます)。

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

これは最近のRailsのリリースで変更された可能性がありますが、この問題を解決する適切な方法は、検索オプションに:readonly => falseを追加することです。

44
Aaron Rustad

select( '*')はRails 3.2でこれを修正するようです:

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

確認するために、select( '*')を省略すると、読み取り専用レコードが生成されます。

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

私はその理論的根拠を理解しているとは言えませんが、少なくともそれは迅速かつクリーンな回避策です。

15
bronson

Find_by_sqlの代わりに、Finderで:selectを指定すると、すべてが再び幸せになります...

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

無効にするには...

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