什么导致此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_id
和hand_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:
- 强制实例variables
@readonly
在logging中为false(黑客) - 使用
: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-inherited):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