这遵循 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_id
和 hand_id
列。
我在rails api中查找了ReadOnlyRecord,除了描述之外并没有说太多。
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 的选择性和效率低。)
或者在 Rails 3 中,您可以使用 readonly 方法(将“...”替换为您的条件):
( Deck.joins(:card) & Card.where('...') ).readonly(false)
readonly
函数。
这可能在最近发布的 Rails 中有所改变,但解决此问题的适当方法是将 :readonly => false 添加到查找选项。
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
不能说我理解其中的原理,但至少这是一个快速而干净的解决方法。
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]
要停用它...
module DeactivateImplicitReadonly
def custom_join_sql(*args)
result = super
@implicit_readonly = false
result
end
end
ActiveRecord::Relation.send :include, DeactivateImplicitReadonly
不定期副业成功案例分享