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


203

これは答えられたこの前の質問に続く。実際に、そのクエリから結合を削除できることを発見したので、機能するクエリは

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

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

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し、プレーヤーの手に追加しました。そのコードは正常に機能しています。belongs_to :tableauDeckCardモデルで必要かどうか疑問に思いましたが、プレイヤーの手札への追加には問題なく機能します。DeckCardテーブルにa tableau_idhand_idcolumnがあります。

私はRails APIでReadOnlyRecordを検索しましたが、説明を超えていません。

回答:


283

Rails 2.3.3以前

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

読み取り専用レコードを導入します。object.readonly!を呼び出すと 次に、オブジェクトを読み取り専用としてマークし、object.saveを呼び出すとReadOnlyRecordを発生させます。object.readonly?オブジェクトが読み取り専用かどうかを報告します。ファインダーメソッドに: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_sql 実行可能なオプションです
  • :readonly => true自動的に推論されるのみ場合:joins、指定されたことなく、明示的に:select 明示的な(またはファインダースコープ-継承):readonlyオプション(の実装を参照してくださいset_readonly_option!active_record/base.rbRailsのための2.3.4、またはの実装to_aではactive_record/relation.rbとのcustom_join_sqlactive_record/relation/query_methods.rbRailsの3.0.0のために)
  • しかし、: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でうまく適用されます。
  • ではない使用:includesあなたが達成したい場合はINNER JOIN:includes意味LEFT OUTER JOIN未満の選択と非効率的です、INNER JOIN。)

:includeは、実行されるクエリの数を減らすのに役立ちますが、それについては知りませんでした。Tableau / Deckcardsの関連付けをhas_many:throughに変更することで修正しようとしましたが、「関連付けが見つかりませんでした」というメッセージが表示されます。そのために別の質問を投稿する必要があるかもしれません
user26270 2009年

@codeman、はい、:includeはクエリの数を減らします 暗黙的の一種がRailsは、読み取り専用、それが何かを盗聴とすぐにそれがないとして、あなたの記録をマークせずに参加(あなたの条件範囲に含まれるテーブルをもたらすSQL :join /:select句を含む-ish / ish IIRC
vladr

'has_many:a、through =>:b'が機能するには、Bアソシエーションも宣言する必要があります。たとえば、 'has_many:b; has_many:a、:through =>:b '、これがあなたのケースだといいのですが?
vladr 2009年

6
これは最近のリリースで変更されている可能性がありますが、findメソッド属性の一部として:readonly => falseを追加するだけで済みます。
Aaron Rustad

1
この回答は、カスタムの:join_tableが指定されたhas_and_belongs_to_many関連付けがある場合にも当てはまります。
Lee

172

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

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

1
うーん...私はAsciicastでそれらのRailscastの両方を調べましたが、どちらもreadonly機能については触れていません。
Purplejacket

45

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


3
私はこれがそうであるとは思わない、少なくとも2.3.4
Olly

2
それはまだRails 3.0.10で動作します。これは、:join Fundraiser.donatable.readonly(false)を持つスコープをフェッチする私自身のコードの例です
Houen

16

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

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


4
Railsの4の同じことがまた、あなたは行うことができます select(quoted_table_name + '.*')
andorov

1
それは素晴らしいブロンソンでした。ありがとうございました。
旅行

これは機能するかもしれませんが、使用するよりも複雑ですreadonly(false)
Kelvin

5

find_by_sqlの代わりに、ファインダーで:selectを指定すると、すべてが再び満足します...

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


3

無効にするには...

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

3
モンキーパッチは壊れやすく、新しいバージョンのRailsで非常に簡単に壊れます。他の解決策があるので、明らかにお勧めできません。
ケルビン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.