导致此ActiveRecord :: ReadOnlyRecord错误的原因是什么?

时间:2009-03-12 15:28:34

标签: ruby-on-rails ruby activerecord join associations

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

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,并没有超出描述范围。

6 个答案:

答案 0 :(得分:281)

Rails 2.3.3及更低

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

  

介绍只读记录。如果你调用object.readonly!然后它会   将对象标记为只读并引发   ReadOnlyRecord如果你打电话   object.save。 object.readonly?报告   对象是否为只读。   传递:readonly =&gt;对任何人都忠诚   finder方法将标记返回   记录为只读。 :加入   选项现在意味着:readonly,所以如果   你使用这个选项,保存相同   记录现在将失败。使用find_by_sql   到处工作。

使用find_by_sql并不是一种替代方法,因为它返回原始行/列数据,而不是ActiveRecords。您有两种选择:

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

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

    • 使用Record.find_by_sql 是一个可行的选项
    • :readonly => true会自动推断如果指定了:joins 没有明确的:select 显式(或finder-scope-in​​herited):readonly选项(请参阅set_readonly_option!中针对Rails 2.3.4的active_record/base.rb实现,或to_aactive_record/relation.rb的实现1}}和custom_join_sql中的active_record/relation/query_methods.rb for Rails 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中应用得很好。
    • 如果你想要:includesINNER JOIN隐含:includes,那么
    • 不能使用LEFT OUTER JOIN,选择性更低,效率更低比INNER JOIN。)

答案 1 :(得分:169)

或者在Rails 3中,您可以使用readonly方法(将“...”替换为您的条件):

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

答案 2 :(得分:44)

这可能在最近发布的Rails中有所改变,但解决此问题的适当方法是添加:readonly =&gt;错误到查找选项。

答案 3 :(得分:15)

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 :(得分:5)

而不是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 :(得分:3)

停用它......

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