是什么导致这个ActiveRecord::ReadOnlyRecord错误?

这遵循之前的问题,该问题已得到回答。实际上我发现我可以从那个查询中删除一个连接,所以现在工作的查询是

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

这似乎奏效了。然而,当我试图将这些deck移动到另一个关联时,我得到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 :tableau在DeckCard模型,但它工作得很好,添加到玩家的手。在DeckCard表中确实有tableau_idhand_id列。

我在rails api中查找了ReadOnlyRecord,除了描述之外,它并没有提供太多信息。

71072 次浏览

Rails 2.3.3及以下版本

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

引入只读记录。如果你调用object.readonly!那么它就会 将对象标记为只读并引发 ReadOnlyRecord如果你呼叫 object.save。object.readonly吗?报告 对象是否只读。 传递:readonly => true to any 查找器方法将标记返回 记录为只读。< >强:连接 选项现在暗示:readonly,所以如果 您使用此选项,保存相同的内容 记录现在将失败。使用find_by_sql

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

  1. 强制实例变量@readonly在记录中为false (hack)
  2. 使用:include => :card代替:join => :card

Rails 2.3.4及以上版本

在2012年9月10日之后,上述大部分都不再成立:

  • 使用Record.find_by_sql 是一个可行的选项
  • 如果:joins被指定为:joins1,则:joins1是显式的:select:joins2是显式的(或finder-作用域继承的):readonly选项(参见active_record/base.rb (for Rails 2.3.4)中的set_readonly_option!的实现,或active_record/relation.rb中的to_aactive_record/relation/query_methods.rb (for Rails 3.0.0)中的custom_join_sql的实现)
  • 然而,如果连接表有两个以上的外键列,并且没有显式地指定:joins(即用户提供的:readonly值被忽略——参见active_record/associations/has_and_belongs_to_many_association.rb中的finding_with_ambiguous_select?), :readonly => true总是在has_and_belongs_to_many中自动推断出来。
  • 总之,除非处理一个特殊的连接表和has_and_belongs_to_many,否则@aaronrustad的答案在Rails 2.3.4和3.0.0中适用得很好。
  • 如果你想实现INNER JOIN使用:includes (:includes意味着LEFT OUTER JOIN,它的选择性和效率比INNER JOIN低。)

而不是find_by_sql,你可以在查找器上指定一个:select,一切都很愉快…

start_cards = DeckCard。发现:, :select => 'deck_cards.*', :joins => [:card], :conditions => ["deck_cards.deck_id = ? "和卡片。Start_card = ?", @game.deck.id, true]

.

这可能在最近的Rails版本中发生了变化,但解决这个问题的适当方法是将:readonly => false添加到find选项中。

或者在Rails 3中,你可以使用readonly方法(用你的条件替换"…"):

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

要解除它…

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

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

我不能说我理解其中的原理,但至少这是一个快速而干净的解决方法。