更新:这可能是不可行的。 See this
TLDR:您如何有条件地加载关联(例如,只加载当前用户的关联),同时还包括根本没有该关联的记录?
Rails 3.1,这里大致是我正在使用的模型。
class User
has_many :subscriptions
has_many :collections, :through => :subscriptions
end
class Collection
has_many :things
end
class Thing
has_many :user_thing_states, :dependent => :destroy
belongs_to :collection
end
class Subscription
belongs_to :user
belongs_to :collection
end
class UserThingState
belongs_to :user
belongs_to :thing
end
存在许多具有许多东西的集合。用户订阅了许多集合,因此他们订阅了很多东西。用户具有关于事物的状态,但不一定,并且即使他们没有碰巧拥有状态,他们仍然订阅了事物。当用户订阅集合及其相关事物时,不会为每个事物(可能是数百个)生成状态。相反,当用户首次与给定事物交互时,会生成状态。现在,问题是:我想在为状态存在的每个事物加载用户的状态时选择所有用户的订阅事物。
从概念上讲,这并不难。作为参考,将为我提供所需数据的SQL是:
SELECT things.*, user_thing_states.* FROM things
# Next line gets me all things subscribed to
INNER JOIN subscriptions as subs ON things.collection_id = subs.collection_id AND subs.user_id = :user_id
# Next line pulls in the state data for the user
LEFT JOIN user_thing_states as uts ON things.id = uts.thing_id AND uqs.user_id = :user_id
我只是不知道如何将它拼凑在一起。在Thing课上会发生什么? Thing.includes(:user_thing_states)将为所有用户加载所有状态,并且看起来像是唯一的工具。我需要这样的东西,但我不确定如何(或者是否可能):
class Thing
has_many :user_thing_states
delegates :some_state_property, :to => :state, :allow_nil => true
def state
# There should be only one user_thing_state if the include is correct, state method to access it.
self.user_thing_states.first
end
end
我需要类似的东西:
Thing.includes(:user_question_states, **where 'user_question_state.user_id => :user_id**).by_collections(user.collections)
然后我可以做
things = User.things_subscribed_to
things.first.some_state_property # the property of the state loaded for the current user.
答案 0 :(得分:2)
您无需做任何事情。
class User
has_many :user_thing_states
has_many :things, :through => :user_thing_states
end
# All Users w/ Things eager loaded through States association
User.all.includes(:things)
# Lookup specific user, Load all States w/ Things (if they exist for that user)
user = User.find_by_login 'bob'
user.user_thing_states.all(:include => :things)
对此使用includes()已加载相关对象(如果存在)。
没有必要为没有关联对象的用户进行任何过滤或添加额外行为。
答案 1 :(得分:0)
*不,没解决:( Here's a treatment of the specific issue I seem to have hit.
认为我已经拥有它,比预期更容易:
class Thing
has_many :user_thing_states
delegates :some_state_property, :to => :state, :allow_nil => true
scope :with_user_state, lambda { |user|
includes(:user_thing_states).where('user_thing_states.user_id = :user_id
OR user_thing_states.user_id IS NULL',
{:user_id => user.id}) }
def state
self.user_thing_states.first
end
end
所以:
Thing.with_user_state(current_user).all
将加载所有内容,每个东西只有一个user_question_state可以通过状态访问,并且不会排除没有状态的东西。
答案 2 :(得分:0)
两次回答我自己的问题......但是反正有点尴尬。
Rails似乎不允许您为includes()语句指定其他条件。如果确实如此,我之前的回答是可行的 - 您可以在includes()语句中添加一个条件,使where条件正常工作。要解决这个问题,我们需要让includes()使用类似下面的SQL(获得'AND'条件就是问题):
LEFT JOIN user_thing_states as uts ON things.id = uts.thing_id AND uqs.user_id = :user_id
我现在正在诉诸这一点,这有点糟糕。
class User
...
def subscribed_things
self.subscribed_things_with_state + self.subscribed_things_with_no_state
end
def subscribed_things_with_state
self.things.includes(:user_thing_states).by_subscribed_collections(self).all
end
def subscribed_things_with_no_state
Thing.with_no_state().by_subscribed_collections(self).all
end
end
答案 3 :(得分:0)
只是我们自己遇到了这个问题,我的同事指出,Rails 6现在似乎包括对此的支持:https://github.com/rails/rails/pull/32655