我目前正在使用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
当我调用User.active.all
时,返回的用户记录都是只读的,因此,例如,如果我在用户上调用update_attributes!
,则会引发ActiveRecord::ReadOnlyRecord
。
通过阅读各种来源,似乎一种流行的解决方法是在查询中添加:readonly => false
。但是,我想知道以下内容:
SELECT `users`.*
,这似乎是安全的,那么Rails首先想要防范的是什么?看来Rails应该防止实际明确指定:select
的情况,这与实际行为相反,所以我没有正确理解在{{1}上自动设置只读标志的目的}吗 :joins
似乎不合适。如果命名范围与其他命名范围链接,我也害怕副作用。如果我尝试在范围之外指定它(例如,通过执行:readonly => false
或User.active.scoped(:readonly => false)
),它似乎无效。我读过的另一种解决方法是将User.scoped(:readonly => false).active
更改为:joins
。我理解这种行为更好,但对此有任何不利之处(除了不必要地读取:include
表中的所有列)?
最后,我还可以通过调用subscriptions
使用记录ID再次检索查询,但我发现这更像是一种解决方法而不是可能的解决方案,因为它会生成额外的SQL查询。
还有其他可能的解决方案吗?在这种情况下,首选解决方案是什么?我已经阅读了previous StackOverflow question about this中给出的答案,但似乎没有给出具体的指导意见。
提前致谢!
答案 0 :(得分:14)
我认为在这种情况下使用:include
代替:join
是习惯和可接受的。我认为:join
仅用于罕见的特殊情况,而:include
非常常见。
如果您不打算更新所有活动用户,那么添加额外的命名范围或查找条件可能是明智之举,以进一步缩小您正在加载的用户的范围,这样您就不会加载额外的用户&安培;不必要的订阅。例如......
User.active.some_further_limiting_scope(:with_an_argument)
#or
User.active.find(:all, :conditions => {:etc => 'etc'})
如果您确定仍想使用: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')
如果您确实需要加载所有活跃用户,并且您希望坚持: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))
我希望有所帮助。我很好奇您选择了哪个选项,或者您是否找到了更适合您的方案的其他解决方案。
答案 1 :(得分:3)
我认为最好的解决方案是使用.join,然后单独执行find()
使用的一个重要区别:include是它使用外连接而:join使用内连接!所以使用:include可以解决只读问题,但结果可能是错误的!
答案 2 :(得分:2)
我遇到了同样的问题,并且不习惯使用:readonly => false
结果我做了一个明确的选择,即:select => 'users.*'
,觉得这似乎不是一个黑客。
您可以考虑执行以下操作:
class User < ActiveRecord::Base
has_one :subscription
named_scope :active, :select => 'users.*', :conditions => { :subscriptions => { :status => 'active' } }, :joins => :subscription
end
答案 3 :(得分:0)
关于你的子问题:所以我没有正确理解自动设置只读标志的目的:加入?
我相信答案是:使用联接查询,您将获得具有User + Subscription表属性的单个记录。如果您尝试更新Subscription表中的某个属性(比如“subscription_num”)而不是User表,则对User表的update语句将无法找到subscription_num并且会崩溃。因此,默认情况下,连接范围是只读的,以防止发生这种情况。
参考: 1)http://blog.ethanvizitei.com/2009/05/joins-and-namedscopes-in-activerecord.html