【问题标题】:Squeel query syntax to retrieve users' elements sharing the same groupSqueel 查询语法以检索共享同一组的用户元素
【发布时间】:2013-04-07 10:35:30
【问题描述】:

使用 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

在字符模型中,我想检索当前用户可见的所有字符,由以下条件确定:

  1. 角色属于当前用户或
  2. 角色是公开的或
  3. 当前用户与角色用户共享群组

结果必须是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::Relationmerge 产生匹配两个查询的元素,+ 返回一个Array 而不是一个@ 987654330@)。

非常感谢任何有关如何在单个 Squeel 查询中处理此问题的帮助。

【问题讨论】:

  • 天哪,为此弄了个风滚草:没人知道,没人在乎...

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


【解决方案1】:

让我们尝试对您的问题进行一些重构,并将has_and_belongs_to_many 关联替换为has_many, through 关联,我们将在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 模型表示UserGroup 之间的关系——本质上是使用has_and_belongs_to_many 时隐藏的连接表。我更喜欢看到这种关系(特别是如果它很重要的话)。

我们还在Character 模型上与与用户关联的Groups 建立了关联。当我们尝试加入我们的范围时,这很有帮助。

充实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 的实例并找到以下Characters:

  • 所有公共角色。
  • 所有用户的字符。
  • 属于用户组的所有字符。

然后我们只从这些集合中获取不同的字符列表。

从 Rails 控制台:

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

我认为这会让你到达你想去的地方。

【讨论】:

  • 非常感谢您的回答。但是,有两个问题:distinct 范围会产生 ActiveRecord::StatementInvaliderror,我将 joins{groups} 替换为 joins{groups.outer},以便在用户不在组中时获取自己的字符。需要更多测试,但如果没有 distinctscope,它似乎可以与 sifters 一起使用。
  • 不同的范围适用于我使用 Postgres,但我将编辑答案并改用 uniq。那对你有用吗?并且很好地抓住了外部加入组。
  • 我又做了一些测试,一切正常。即使没有uniq。再次感谢您的回答,非常感谢。
猜你喜欢
  • 2014-07-29
  • 1970-01-01
  • 2018-05-19
  • 1970-01-01
  • 1970-01-01
  • 2020-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多