ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

和相关的模型(画面是桌上的玩家牌)

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,除了描述之外并没有说太多。


m
morhook

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。你有两个选择:

在记录中强制实例变量 @readonly 为 false(hack)使用 :include => :card 而不是 :join => :card

Rails 2.3.4 及更高版本

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

使用 Record.find_by_sql 是一个可行的选择

:readonly => true 仅当 :joins 指定时没有显式 :select 或显式(或 finder-scope-inherited ) :readonly 选项时才会自动推断(参见 Rails 2.3 的 active_record/base.rb 中 set_readonly_option 的实现。 4,或者在 Rails 3.0.0 中实现 active_record/relation.rb 中的 to_a 和 active_record/relation/query_methods.rb 中的 custom_join_sql)

然而,: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。

如果要实现 INNER JOIN,请不要使用 :includes(:includes 意味着 LEFT OUTER JOIN,它比 INNER JOIN 的选择性和效率低。)


:include 有助于减少完成的查询数,我不知道;但我试图通过将 Tableau/Deckcards 关联更改为 has_many: 来修复它,现在我收到“找不到关联”的消息;我可能不得不为此发布另一个问题
@codeman,是的, :include 将减少查询的数量并将包含的表带入您的条件范围(一种没有 Rails 将您的记录标记为只读的隐式连接,它会在嗅探任何 SQL 时立即执行-ish 在您的查找中,包括 :join/:select 子句 IIRC
要使 'has_many :a, through => :b' 起作用,还必须声明 B 关联,例如 'has_many :b; has_many :a, :through => :b',我希望这是你的情况?
这在最近的版本中可能已经改变,但您可以简单地添加 :readonly => false 作为 find 方法属性的一部分。
如果您与指定的自定义 :join_table 有 has_and_belongs_to_many 关联,则此答案也适用。
b
balexand

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

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

嗯...我在 Asciicasts 上查找了这两个 Railscast,都没有提到 readonly 函数。
A
Aaron Rustad

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


我不相信是这种情况,至少 2.3.4
它仍然适用于 Rails 3.0.10,这是我自己的代码中的一个示例,它获取具有 :join Fundraiser.donatable.readonly(false) 的范围
b
bronson

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

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


在 Rails 4 中也是如此。或者,您可以执行 select(quoted_table_name + '.*')
那是辉煌的布朗森。谢谢你。
这可能有效,但比使用 readonly(false) 更复杂
佚名

代替 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]


g
grosser

要停用它...

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

Monkey-patching 很脆弱 - 很容易被新版本的 rails 破坏。鉴于还有其他解决方案,绝对不可取。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅