是什么引起此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表中确实有tableau_idhand_id列。

我在rails api中查找了ReadOnlyRecord,并没有太多描述。

Answers:


283

Rails 2.3.3及更低版本

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

介绍只读记录。如果调用object.readonly!然后,如果您调用object.save,它将把对象标记为只读,并引发ReadOnlyRecord。object.readonly?报告对象是否为只读。将:readonly => true传递给任何finder方法会将返回的记录标记为只读。 :joins选项现在暗含: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 一个可行的选择
  • :readonly => true自动推断如果:joins被指定,而不显式:select 也不显式(或取景-范围继承):readonly的选项(见实施set_readonly_option!active_record/base.rb为Rails 2.3.4,或执行to_aactive_record/relation.rb和的custom_join_sqlactive_record/relation/query_methods.rb为Rails 3.0.0)
  • 但是,如果联接表具有两个以上的外键列并且没有显式指定(即,用户提供的值将被忽略-参见中),:readonly => true则始终会自动推断出has_and_belongs_to_many该表。:joins:select:readonlyfinding_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中的答案都适用。
  • 如果您想实现一个(表示a ,它的选择性和效率都低于),请不要使用。:includesINNER JOIN:includesLEFT OUTER JOININNER JOIN

:include有助于减少查询数量,我对此一无所知;但是我试图通过将Tableau / Deckcards关联更改为has_many来解决此问题:通过,现在我收到了“找不到关联”消息。我可能不得不
对此

@codeman,是的,:包括将减少查询的数量,并会带来包括表到你的条件范围(一种隐含的加入没有Rails的标记您的记录为只读,它只要它嗅着什么SQL做中找到-ish,包括:join /:select子句IIRC
vladr

为了使“ has_many:a,通过=>:b”正常工作,还必须声明B关联,例如,“ has_many:b;”。has_many:a,:through =>:b',我希望这是您的情况?
vladr

6
在最新版本中,这可能已更改,但是您可以简单地将:readonly => false添加为find方法属性的一部分。
亚伦·鲁斯塔德

1
如果您具有指定了自定义:join_table的has_and_belongs_to_many关联,则此答案也适用。

172

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

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

1
嗯...我在《 Asciicasts》上查了两个Railscasts,都没有提到该readonly功能。
Purplejacket 2012年

45

在最新版本的Rails中,这可能已更改,但是解决此问题的适当方法是在find选项中添加:readonly => false


3
我认为情况并非如此,至少2.3.4级
Olly 2010年

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
那是出色的布朗森。谢谢。
2014年

这可能有用,但比使用它更复杂readonly(false)
Kelvin

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]


3

要停用它...

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

3
猴子修补很脆弱-很容易被新的rails版本破坏。考虑到还有其他解决方案,绝对是不可取的。
Kelvin 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.