正确的方法来阻止ActiveRecord :: ReadOnlyRecord?

Cla*_*law 30 activerecord ruby-on-rails

我目前正在使用Rails 2.3.9.我知道:joins在没有显式的查询中指定选项会:select自动生成任何以只读方式返回的记录.我有一个情况,我想更新记录,虽然我已经阅读了不同的方法,我想知道哪种方式是首选或"正确"的方式.

具体来说,我的情况是我有User一个带有active命名范围的以下模型,该范围与subscriptions表执行JOIN :

class User < ActiveRecord::Base
  has_one :subscription

  named_scope :active, :conditions => { :subscriptions => { :status => 'active' } }, :joins => :subscription
end
Run Code Online (Sandbox Code Playgroud)

当我调用时User.active.all,返回的用户记录都是只读的,因此,例如,如果我调用update_attributes!用户,ActiveRecord::ReadOnlyRecord则会引发用户记录.

通过阅读各种来源,似乎一种流行的解决方法是添加:readonly => false到查询中.但是,我想知道以下内容:

  • 这样安全吗? 我理解为什么Rails首先将它设置为只读是因为根据Rails文档,"它们将具有与表的列不对应的属性".但是,从这个调用生成的SQL查询仍然使用SELECT `users`.*,这似乎是安全的,那么Rails首先想要防范什么呢? 看起来Rails应该防范:select实际明确指定的情况,这与实际行为相反,所以我没有正确理解自动设置只读标志的目的:joins吗?
  • 这看起来像是黑客吗? 命名范围的定义应该关注显式设置似乎不合适:readonly => false.如果命名范围与其他命名范围链接,我也害怕副作用.如果我尝试在范围之外指定它(例如,通过执行User.active.scoped(:readonly => false)User.scoped(:readonly => false).active),它似乎不起作用.

我读过的另一种解决方法是将其更改:joins:include.我理解这种行为更好,但这有什么不利之处(除了不必要的读表中的所有列subscriptions)?

最后,我还可以通过调用使用记录ID再次检索查询User.find_all_by_id(User.active.map(&:id)),但我发现这更像是一种解决方法而不是可能的解决方案,因为它会生成额外的SQL查询.

还有其他可能的解决方案吗?在这种情况下,首选解决方案是什么? 我已经阅读了之前关于此问题StackOverflow问题中给出的答案,但它似乎没有给出具体的指导意见.

提前致谢!

Jon*_*vin 14

我相信在这种情况下习惯性和可接受性:include而不是使用:join.我认为这:join只是在罕见的特殊情况下使用,而:include非常常见.

如果您不打算更新所有活动用户,那么添加额外的命名范围或查找条件可能是明智之举,以进一步缩小您正在加载的用户的范围,这样您就不会不必要地加载额外的用户和订阅.例如...

User.active.some_further_limiting_scope(:with_an_argument)
  #or
User.active.find(:all, :conditions => {:etc => 'etc'})
Run Code Online (Sandbox Code Playgroud)

如果您决定仍然想要使用:join,并且只会更新一小部分已加载的用户,那么最好在此之前重新加载您想要更新的用户.如...

readonly_users = User.active
# insert some other code that picks out a particular user to update
User.find(readonly_users[@index].id).update_attributes(:etc => 'etc')
Run Code Online (Sandbox Code Playgroud)

如果您确实需要加载所有活动用户,并且您希望坚持使用:join,并且您可能会更新大多数或所有用户,那么您想要使用ID数组重新加载它们可能是您的最佳选择.

#no need to do find_all_by_id in this case. A simple find() is sufficient.
writable_users_without_subscriptions = User.find(Users.active.map(&:id))
Run Code Online (Sandbox Code Playgroud)

我希望有所帮助.我很好奇您使用哪个选项,或者您是否找到了更适合您的方案的其他解决方案.

  • 对于我正在尝试做的事情,我确实需要加载每个活跃的用户并且到目前为止还没有真正听到过使用`:join`的好参数,特别是因为我的用例更通常匹配当使用`时:join`(如[此Railscast](http://railscasts.com/episodes/181-include-vs-joins)中所述).出于这个原因,我目前正在选择使用记录ID再次重新获取查询,但我仍然认为它暂时是一种解决方法. (2认同)