Ruby-on-rails 是什么导致了这个 ActiveRecord::ReadOnlyRecord 错误?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/639171/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 21:03:19  来源:igfitidea点击:

What is causing this ActiveRecord::ReadOnlyRecord error?

ruby-on-railsrubyactiverecordjoinassociations

提问by user26270

This follows thisprior question, which was answered. I actually discovered I could remove a join from that query, so now the working query is

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

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

This appears to work. However, when I try to move these DeckCards into another association, I get the ActiveRecord::ReadOnlyRecord error.

这似乎有效。但是,当我尝试将这些 DeckCard 移动到另一个关联中时,出现 ActiveRecord::ReadOnlyRecord 错误。

Here's the code

这是代码

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

and the relevant Models (tableau are the players cards on the table)

和相关的模型(画面是桌子上的球员牌)

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

I am doing a similar action just after this code, adding DeckCardsto the players hand, and that code is working fine. I wondered if I needed belongs_to :tableauin the DeckCard Model, but it works fine for the adding to player's hand. I do have a tableau_idand hand_idcolumns in the DeckCard table.

在此代码之后,我正在执行类似的操作,将其添加DeckCards到玩家手中,并且该代码运行良好。我想知道我是否需要belongs_to :tableauDeckCard 模型,但它可以很好地添加到玩家的手牌中。我在 DeckCard 表中有一个tableau_idhand_id列。

I looked up ReadOnlyRecord in the rails api, and it doesn't say much beyond the description.

我在 rails api 中查找了 ReadOnlyRecord,它没有说太多超出描述的内容。

回答by vladr

Rails 2.3.3 and lower

Rails 2.3.3 及更低版本

From the ActiveRecord CHANGELOG(v1.12.0, October 16th, 2005):

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

Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail.Use find_by_sql to work around.

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

Using find_by_sqlis not really an alternative as it returns raw row/column data, not ActiveRecords. You have two options:

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

  1. Force the instance variable @readonlyto false in the record (hack)
  2. Use :include => :cardinstead of :join => :card
  1. 强制实例变量@readonly在记录中为假(hack)
  2. 使用:include => :card代替:join => :card

Rails 2.3.4 and above

Rails 2.3.4 及更高版本

Most of the above no longer holds true, after September 10 2012:

2012 年 9 月 10 日之后,以上大部分内容不再适用:

  • using Record.find_by_sqlisa viable option
  • :readonly => trueis automatically inferred onlyif :joinswas specified withoutan explicit :selectnoran explicit (or finder-scope-inherited) :readonlyoption (see the implementation of set_readonly_option!in active_record/base.rbfor Rails 2.3.4, or the implementation of to_ain active_record/relation.rband of custom_join_sqlin active_record/relation/query_methods.rbfor Rails 3.0.0)
  • however, :readonly => trueis always automatically inferred in has_and_belongs_to_manyif the join table has more than the two foreign keys columns and :joinswas specified without an explicit :select(i.e. user-supplied :readonlyvalues are ignored -- see finding_with_ambiguous_select?in active_record/associations/has_and_belongs_to_many_association.rb.)
  • in conclusion, unless dealing with a special join table and has_and_belongs_to_many, then @aaronrustad's answer applies just fine in Rails 2.3.4 and 3.0.0.
  • do notuse :includesif you want to achieve an INNER JOIN(:includesimplies a LEFT OUTER JOIN, which is less selective and less efficient than INNER JOIN.)
  • 使用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(即用户提供的:readonly价值被忽略-看到finding_with_ambiguous_select?active_record/associations/has_and_belongs_to_many_association.rb)。
  • 总之,除非处理特殊的连接表 and has_and_belongs_to_many,否则@aaronrustad的答案适用于 Rails 2.3.4 和 3.0.0。
  • 如果您想实现(意味着 a ,它的选择性和效率低于.) ,请不要使用。:includesINNER JOIN:includesLEFT OUTER JOININNER JOIN

回答by balexand

Or in Rails 3 you can use the readonly method (replace "..." with your conditions):

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

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

回答by Aaron Rustad

This might have changed in recent release of Rails, but the appropriate way to solve this problem is to add :readonly => falseto the find options.

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

回答by bronson

select('*') seems to fix this in Rails 3.2:

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

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

Just to verify, omitting select('*') does produce a readonly record:

只是为了验证,省略 select('*') 确实会产生只读记录:

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

Can't say I understand the rationale but at least it's a quick and clean workaround.

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

回答by bronson

Instead of find_by_sql, you can specify a :select on the finder and everything's happy again...

您可以在查找器上指定一个 :select 来代替 find_by_sql,然后一切都会再次变得愉快...

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

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

回答by grosser

To deactivate it...

要停用它...

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