使用Ruby on Rails 3.2.13和Squeel我有以下模型:
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
has_many :characters, :dependent => :destroy
end
class Character < ActiveRecord::Base
belongs_to :user
end
字符具有布尔属性:public
。
在Character模型中,我想检索当前用户可见的所有字符,由以下条件确定:
- 字符属于当前用户或
- 字符为public OR
- 当前用户与角色的用户共享一个组
结果必须是ActiveRecord::Relation
。
匹配前两个条件非常简单:
def self.own_or_public user_to_check
where{
(user_id == user_to_check.id) |
(public)
}
end
对于第三个条件,下面的查询产生正确的结果,但可能不是最好的方法:
def self.shares_group_with user_to_check
user_groups = Group.joins{users}.where{users.id == user_to_check.id}
joins{user.groups}.
where{
user.groups.id.in(user_groups.select(id))
}.uniq
end
此外,我找不到一种方法来连接产生包含两个查询结果的ActiveRecord::Relation
的两个结果(merge
产生匹配两个查询的元素,+
返回Array
而不是ActiveRecord::Relation
)。
关于如何在一个单一的Squeel查询中处理此问题的任何帮助都非常感谢。
让我们试着稍微重组一下你的问题,用has_many, through
关联替换has_and_belongs_to_many
关联,我们将在Character
模型上添加另一个has_many, through
关联,如下所示:
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through: :memberships
has_many :characters
end
class Character < ActiveRecord::Base
belongs_to :user
has_many :groups, through: :user
end
Membership
模型是User
和Group
之间关系的表示-本质上是使用has_and_belongs_to_many
时隐藏的连接表。我更愿意看到它们之间的关系(尤其是重要的关系)。
我们还在Character
模型上建立了与用户关联的Group
模型的关联。当我们尝试加入作用域时,这很有帮助。
充实Character
模型,让我们添加以下内容:
sifter :by_user do |user|
user_id == user.id
end
sifter :public do
public
end
使用筛选器作为我们的构建块,我们可以添加以下内容来获得可见的字符(如您所定义的):def self.get_visible(user)
Character.uniq.joins{groups.outer}.where{(sift :public)|(sift :by_user, user)|(groups.id.in(user.groups))}
end
这个方法接受User
的一个实例,并找到以下Character
:
- 所有公共字符。
- 用户的所有字符。
- 属于用户组的所有字符。
然后我们只取这些集合中不同的字符列表。
From rails console:
irb(main):053:0> Character.get_visible(User.find(4))
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 4]]
Group Load (0.7ms) SELECT "groups".* FROM "groups" INNER JOIN "memberships" ON "groups"."id" = "memberships"."group_id" WHERE "memberships"."user_id" = 4
Character Load (0.9ms) SELECT DISTINCT "characters".* FROM "characters" LEFT OUTER JOIN "users" ON "users"."id" = "characters"."user_id" LEFT OUTER JOIN "memberships" ON "memberships"."user_id" = "users"."id" LEFT OUTER JOIN "groups" ON "groups"."id" = "memberships"."group_id" WHERE ((("characters"."public" OR "characters"."user_id" = 4) OR "groups"."id" IN (2)))
[
[0] #<Character:0x00000005a16b48> {
:id => 4,
:user_id => 4,
:name => "Testiculies",
:created_at => Tue, 13 Aug 2013 14:35:50 UTC +00:00,
:updated_at => Tue, 13 Aug 2013 14:35:50 UTC +00:00,
:public => nil
},
[1] #<Character:0x00000005d9db40> {
:id => 1,
:user_id => 1,
:name => "conan",
:created_at => Mon, 12 Aug 2013 20:18:52 UTC +00:00,
:updated_at => Tue, 13 Aug 2013 12:53:42 UTC +00:00,
:public => true
}
]
要查找特定User
具有的所有字符,请向User
模型添加实例方法:
def get_visible_characters
Character.get_visible(self)
end