206

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

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到玩家手中,并且该代码运行良好。我想知道我是否需要belongs_to :tableauDeckCard 模型,但它可以很好地添加到玩家的手牌中。我在 DeckCard 表中有一个tableau_idhand_id列。

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

4

6 回答 6

287

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 来解决。

Usingfind_by_sql并不是真正的替代方案,因为它返回原始行/列数据,而不是ActiveRecords. 你有两个选择:

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

Rails 2.3.4 及更高版本

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

  • 使用Record.find_by_sql 一个可行的选择
  • :readonly => true只有在没有显式显式(或finder-scope-in​​herited)选项指定时才会自动推断(参见Rails 2.3.4的in实现,或Rails 3.0.0的in和in的实现):joins:select :readonlyset_readonly_option!active_record/base.rbto_aactive_record/relation.rbcustom_join_sqlactive_record/relation/query_methods.rb
  • 但是,如果连接表有两个以上的外键列并且没有明确指定(即忽略用户提供的值 - 参见.) ,:readonly => true则总是自动推断in 。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 中适用。
  • 如果:includes您想实现INNER JOIN(:includes意味着 a LEFT OUTER JOIN,它的选择性和效率低于INNER JOIN.) ,请不要使用
于 2009-03-12T18:15:06.923 回答
172

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

( Deck.joins(:card) & Card.where('...') ).readonly(false)
于 2010-08-09T23:50:45.930 回答
45

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

于 2009-06-07T00:32:46.280 回答
17

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

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

于 2013-03-01T22:17:09.960 回答
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]

于 2009-03-27T00:32:54.397 回答
3

要停用它...

module DeactivateImplicitReadonly
  def custom_join_sql(*args)
    result = super
    @implicit_readonly = false
    result
  end
end
ActiveRecord::Relation.send :include, DeactivateImplicitReadonly
于 2012-08-10T22:31:29.697 回答