什么导致此ActiveRecord :: ReadOnlyRecord错误?

这跟在这个先前的问题,这是回答。 我实际上发现我可以从该查询中删除一个连接,所以现在工作查询是

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

这似乎工作。 但是,当我尝试将这些DeckCards移动到另一个关联中时,出现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_idhand_id列。

我在rails api里查了一下ReadOnlyRecord,并没有说太多的描述。

从ActiveRecord CHANGELOG (v1.12.0,2005年10月16日)

引入只读logging。 如果你调用object.readonly! 那么它会将该对象标记为只读,并在调用object.save时引发ReadOnlyRecord。 object.readonly? 报告对象是否是只读的。 传递:readonly => true任何发现者方法将返回的logging标记为只读。 现在:连接选项意味着:只读,所以如果使用此选项,保存相同的logging现在将失败。 使用find_by_sql来解决。

使用find_by_sql不是真正的替代方法,因为它返回原始行/列数据,而不是ActiveRecords 。 你有两个select:

  1. 强制实例variables@readonly在logging中为false(黑客)
  2. 使用:include => :card代替:join => :card

2010年9月更新

以上大部分不再成立。 因此,在Rails 2.3.4 3.0.0中:

  • 使用Record.find_by_sql 一个可行的select
  • :readonly => true 只有:joins 情况下自动推断:readonly => true :joins 没有明确指定:joins :select 也不显式(或finder-scope-in​​herited) :readonly选项(请参阅active_record/base.rb针对Rails 2.3的set_readonly_option!的实现。 4,或者在active_record/relation.rb执行to_a在Rails 3.0.0中执行active_record/relation/query_methods.rb
  • 然而,如果连接表有两个以上的外键列,那么: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 ,这个select性比INNER JOIN更lessselect性和效率)。

或者在Rails 3中,你可以使用readonly方法(用你的条件replace“…”):

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

在最近的Rails版本中,这可能已经发生了变化,但解决这个问题的恰当方法是在查找选项中添加:readonly => false

select('*')似乎解决了这个在Rails 3.2中:

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

只是为了validation,省略select('*')确实产生只读logging:

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

不能说我理解的理由,但至less这是一个快速和干净的解决方法。

取而代之的是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]

要停用它…

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