如果存在于rails查询中,则包括关联

时间:2011-09-02 18:20:39

标签: ruby-on-rails ruby-on-rails-3 activerecord

更新:这可能是不可行的。 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.

4 个答案:

答案 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