Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/ruby-on-rails/67.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/image-processing/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ruby on rails Rails查询,基于不相关模型的范围_Ruby On Rails - Fatal编程技术网

Ruby on rails Rails查询,基于不相关模型的范围

Ruby on rails Rails查询,基于不相关模型的范围,ruby-on-rails,Ruby On Rails,我想找到一个用户的车队中没有连接的地方 我有一个convas表,其中有一个sender\u id和receiver\u id,这两个都是对用户id的引用 # app/models/user.rb has_many :convos, ->(user) { unscope(:where).where("sender_id = :id OR recipient_id = :id", id: user.id) } 注意:CONVA可以属于发送方id或接收方id的用户 # app/model

我想找到一个用户的
车队
中没有
连接的地方

我有一个
convas
表,其中有一个
sender\u id
receiver\u id
,这两个都是对用户id的引用

# app/models/user.rb

has_many :convos, ->(user) {
  unscope(:where).where("sender_id = :id OR recipient_id = :id", id: user.id)
}
注意:CONVA可以属于发送方id或接收方id的用户

# app/models/convo.rb

class Convo < ApplicationRecord
  belongs_to :sender, :foreign_key => :sender_id, class_name: 'User'
  belongs_to :recipient, :foreign_key => :recipient_id, class_name: 'User'

  has_many :msgs, dependent: :destroy

  validates_uniqueness_of :sender_id, :scope => :recipient_id

  scope :involving, -> (user) do
    where("convos.sender_id =? OR convos.recipient_id =?",user.id,user.id)
  end

  scope :between, -> (sender_id,recipient_id) do
    where("(convos.sender_id = ? AND convos.recipient_id =?) OR (convos.sender_id = ? AND convos.recipient_id =?)", sender_id,recipient_id, recipient_id, sender_id)
  end
end
我想找到用户的
conva
中没有
connect

我试过这样的方法:

user = User.first
user.convos.where.not(Connect.between(self.requestor_id, self.requestee_id).length > 0 )

# NoMethodError (undefined method `requestor_id' for main:Object)


然后我尝试根本不引用用户,只是尝试在没有连接的情况下获取所有车队

Convo.where("Connect.between(? ,?) < ?)", :sender_id, :recipient_id, 1)

# ActiveRecord::StatementInvalid (SQLite3::SQLException: near "between": syntax error: SELECT "convos".* FROM "convos" WHERE (Connect.between('sender_id' ,'recipient_id') < 1)))

您可以使用
LEFT JOIN
获取用户行,其中
id
corbas.发送者id
corbas.接收者id
不是
NULL
,而是
连接之间的匹配。请求者id
连接之间的匹配。请求者id
NULL

SELECT *
FROM users
LEFT JOIN connects
ON users.id IN (connects.requester_id, connects.requestee_id)
LEFT JOIN convos
ON users.id IN (convos.sender_id, convos.recipient_id)
WHERE connects.requester_id IS NULL AND
      connects.requestee_id IS NULL AND
      convos.sender_id      IS NOT NULL AND
      convos.recipient_id   IS NOT NULL
AR实施:

User.joins('LEFT JOIN connects ON users.id IN (connects.requester_id, connects.requestee_id)
            LEFT JOIN convos   ON users.id IN (convos.sender_id, convos.recipient_id)')
    .where(connects: { requester_id: nil, requestee_id: nil })
    .where.not(convos: { sender_id: nil, recipient_id: nil })

考虑到这样的DB结构:

db=# \d+ users
                                                              Table "public.users"
   Column   |              Type              | Collation | Nullable |              Default              | Storage  | Stats target | Description
------------+--------------------------------+-----------+----------+-----------------------------------+----------+--------------+-------------
 id         | bigint                         |           | not null | nextval('users_id_seq'::regclass) | plain    |              |
 name       | character varying              |           |          |                                   | extended |              |
 created_at | timestamp(6) without time zone |           | not null |                                   | plain    |              |
 updated_at | timestamp(6) without time zone |           | not null |                                   | plain    |              |
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)

db=# \d+ convos
                                                              Table "public.convos"
    Column    |              Type              | Collation | Nullable |              Default               | Storage | Stats target | Description
--------------+--------------------------------+-----------+----------+------------------------------------+---------+--------------+-------------
 id           | bigint                         |           | not null | nextval('convos_id_seq'::regclass) | plain   |              |
 sender_id    | integer                        |           |          |                                    | plain   |              |
 recipient_id | integer                        |           |          |                                    | plain   |              |
 created_at   | timestamp(6) without time zone |           | not null |                                    | plain   |              |
 updated_at   | timestamp(6) without time zone |           | not null |                                    | plain   |              |
Indexes:
    "convos_pkey" PRIMARY KEY, btree (id)

db=# \d+ connects
                                                              Table "public.connects"
    Column    |              Type              | Collation | Nullable |               Default                | Storage | Stats target | Description
--------------+--------------------------------+-----------+----------+--------------------------------------+---------+--------------+-------------
 id           | bigint                         |           | not null | nextval('connects_id_seq'::regclass) | plain   |              |
 requestor_id | integer                        |           |          |                                      | plain   |              |
 requestee_id | integer                        |           |          |                                      | plain   |              |
 created_at   | timestamp(6) without time zone |           | not null |                                      | plain   |              |
 updated_at   | timestamp(6) without time zone |           | not null |                                      | plain   |              |
Indexes:
    "connects_pkey" PRIMARY KEY, btree (id)
记录如下:

db=# select * from users;
 id | name |         created_at         |         updated_at
----+------+----------------------------+----------------------------
  1 | seb  | 2019-11-27 09:59:53.762911 | 2019-11-27 09:59:53.762911
  2 | sab  | 2019-11-27 09:59:55.455096 | 2019-11-27 09:59:55.455096
  3 | foo  | 2019-11-27 10:07:19.760675 | 2019-11-27 10:07:19.760675
  4 | bar  | 2019-11-27 10:07:36.18696  | 2019-11-27 10:07:36.18696
  5 | meh  | 2019-11-27 10:07:38.465841 | 2019-11-27 10:07:38.465841
(5 rows)

db=# select * from convos;
 id | sender_id | recipient_id |         created_at         |         updated_at
----+-----------+--------------+----------------------------+----------------------------
  1 |         1 |            2 | 2019-11-27 10:09:36.742426 | 2019-11-27 10:09:36.742426
  2 |         1 |            3 | 2019-11-27 10:09:40.555118 | 2019-11-27 10:09:40.555118
(2 rows)

db=# select * from connects;
 id | requestor_id | requestee_id |         created_at         |         updated_at
----+--------------+--------------+----------------------------+----------------------------
  1 |            1 |            2 | 2019-11-27 10:07:07.76146  | 2019-11-27 10:07:07.76146
  2 |            2 |            1 | 2019-11-27 10:07:11.380084 | 2019-11-27 10:07:11.380084
  3 |            1 |            4 | 2019-11-27 10:07:47.892944 | 2019-11-27 10:07:47.892944
  4 |            5 |            1 | 2019-11-27 10:07:51.406224 | 2019-11-27 10:07:51.406224
(4 rows)
以下查询将只返回第二个CONVA,因为id为3的用户没有任何connect

SELECT convos.*
FROM convos
LEFT JOIN users
ON users.id IN (convos.sender_id, convos.recipient_id)
LEFT JOIN connects
ON users.id IN (connects.requestor_id, connects.requestee_id)
WHERE connects.requestor_id IS NULL AND connects.requestee_id IS NULL

 id | sender_id | recipient_id |         created_at         |         updated_at         | id | name |         created_at         |         updated_at         | id | requestor_id | requestee_id | created_at | updated_at
----+-----------+--------------+----------------------------+----------------------------+----+------+----------------------------+----------------------------+----+--------------+--------------+------------+------------
  2 |         1 |            3 | 2019-11-27 10:09:40.555118 | 2019-11-27 10:09:40.555118 |  3 | foo  | 2019-11-27 10:07:19.760675 | 2019-11-27 10:07:19.760675 |    |              |              |            |
(1 row)
Rails查询可以如下所示:

Convo
  .joins('LEFT JOIN users ON users.id IN (convos.sender_id, convos.recipient_id)
          LEFT JOIN connects ON users.id IN (connects.requestor_id, connects.requestee_id)')
  .where(connects: { requestor_id: nil, requestee_id: nil })

因此,如果我理解正确,从一个用户与之对话的用户列表中,您需要一个他们没有连接的用户列表

简单地说,这可能类似于:

users_conversed_with = user.convos.map{|c| [c.sender_id, c.recipient_id]}.flatten.uniq 
users_connected_with = user.connections.map{|c| c.requestor_id, c.requestee_id}.flatten.uniq
这两个集合也包含
user.id
,但我们可以忽略这一点,因为我们对差异感兴趣:这将是我们与之交谈的一组人,没有连接(而且
user.id
将同时包含在这两个集合中,除非其中一个为空,否则我们不必从这些集合中单独删除
user.id

这不是一种最佳方法,因为我们进行两次查询,从数据库中检索所有用户ID,然后丢弃大部分检索到的数据。我们可以通过创建自定义查询来改进这一点,并让数据库为我们完成工作,如下所示

 sql = <<-SQL
   (select distinct user_id from 

     (select sender_id as user_id from convos where sender_id=#{user.id} or recipient_id=#{user.id}
      union
      select recipient_id as user_id from convos where sender_id=#{user.id} or recipient_id=#{user.id}
     )
   )
   except
   (
   (select distinct user_id from          
     (select requestor_id as user_id from connections where requestor_id=#{user.id} or requestee_id=#{user.id}
      union
      select requestee_id as user_id from convos where requestor_id=#{user.id} or requestee_id=#{user.id}
     )
   ) 
 SQL  

 result = Convo.connection.execute(sql)
 users_ids_in_convo_without_connection = result.to_a.map(&:values).flatten 
sql=使用当前设置回答
如果您正在查找当前用户,您需要从他们的
车队开始,左连接到
连接
,然后选择
连接
空的行。在表设置中,我们必须在可能的用户id组合上手动执行此操作:

current_user.convos.joins("
  LEFT JOIN connects ON
    (connects.requestor_id = convos.sender_id AND connects.requestee_id = convos.recipient_id)
    OR
    (connects.requestor_id = convos.recipient_id AND connects.requestee_id = convos.sender_id)
  ").where(connects: {id: nil})
左连接为您提供与COVA相同的两个用户之间的任何连接,这必然涉及
当前用户
,因为我们从
当前用户.COVA开始。从那里,我们过滤到只有
连接
字段为
的行,得到的行具有不匹配连接的COVA


暗示 在Rails应用程序中,这么多原始SQL有点代码味道,这是因为我们在这里尝试使用按原样设置的模型。我建议重构数据模型,使查询更容易。我想到了两个想法:

  • 始终为connect和COVA创建对称记录,这样您就可以通过单个列进行查找,而不是使用所有的
    s。也就是说,每当在用户1和用户2之间创建
    连接时,也要在用户2和用户1之间创建一个连接。更多的簿记,因为你必须同时销毁和编辑它们。但是它让你。定义没有所有环的简单关联
  • 使用单独的表来引用唯一的用户对(顺序无关紧要)。为此,使用
    user\u 1\u id
    user\u 2\u id
    创建一个
    UserPair
    模型,其中
    user\u 1\u id
    始终设置为两个用户id中的较低者。这样,通过
    用户对id
    可以更容易地识别conva,
    用户对可以
    拥有多个:conva
    拥有多个:connections
    ,并且您可以在
    conva->user\u pairs->connections之间建立直轨连接
  • 图2中的模型看起来像

    class UserPair < ApplicationRecord
      belongs_to :user_1, class_name: "User"
      belongs_to :user_2, class_name: "User"
    
      before_save :sort_users
    
      scope :between, -> (user_1_id,user_2_id) do
        # records are always saved in sorted id order, so sort before querying
        user_1_id, user_2_id = [user_1_id, user_2_id].sort
        where(user_1_id: user_1_id, user_2_id: user_2_id)
      end
    
      # always put lowest id first for easy lookup
      def sort_users
        if user_1.present? && user_2.present? && user_1.id > user_2.id
          self.user_1, self.user_2 = user_2, user_1
        end
      end
    end
    
    class Convo < ApplicationRecord
      belongs_to :sender, :foreign_key => :sender_id, class_name: 'User'
      belongs_to :recipient, :foreign_key => :recipient_id, class_name: 'User'
      belongs_to :user_pair
    
      before_validation :set_user_pair
    
      scope :involving, -> (user) do
        where("convos.sender_id =? OR convos.recipient_id =?",user.id,user.id)
      end
    
      # since UserPair records are always user_id sorted, we can just use
      # that model's scope here without need to repeat it, using `merge`
      scope :between, -> (sender_id,recipient_id) do
        joins(:user_pair).merge(UserPair.between(sender_id, recipient_id))
      end
    
      def set_user_pair
        self.user_pair = UserPair.find_or_initialize_by(user_1: sender, user_2: recipient)
      end
    end
    
    类用户对(用户\u 1\u id,用户\u 2\u id)之间
    #记录总是按已排序的id顺序保存,所以在查询之前进行排序
    用户\u 1\u id,用户\u 2\u id=[用户\u 1\u id,用户\u 2\u id]。排序
    其中(用户\u 1\u id:用户\u 1\u id,用户\u 2\u id:用户\u 2\u id)
    结束
    #始终将最低id放在第一位,以便于查找
    def sort_用户
    如果用户_1.present?&&用户_2.present?&用户_1.id>用户_2.id
    self.user_1,self.user_2=用户_2,用户_1
    结束
    结束
    结束
    类conva:sender\u id,class\u name:'User'
    属于:收件人,:外键=>:收件人id,类别名称:'User'
    属于:用户对
    验证前:设置用户对
    范围:涉及,->(用户)do
    其中(“convas.sender\u id=?或convas.receiver\u id=?”,user.id,user.id)
    结束
    #由于用户对记录总是按用户id排序,所以我们可以使用
    #该模型在这里的作用域不需要使用“merge”重复`
    范围:介于,->(发送方id、接收方id)之间
    联接(:user\u pair).merge(UserPair.between(发送者\u id,接收者\u id))
    结束
    def设置用户对
    self.user\u pair=UserPair.find\u或\u initialize\u by(用户\u 1:发送方,用户\u 2:接收方)
    结束
    结束
    
    我将首先编写SQL查询来执行此操作。在你的情况下,你也许想要

    SELECT convos.*
    FROM convos
    WHERE (sender_id = :user_id
            AND NOT EXISTS (
             SELECT 1
             FROM connects
             WHERE (requestor_id = sender_id AND requestee_id = recipient_id) OR (requestor_id = recipient_id AND requestee_id = sender_id)
            ))
      OR        
        (recipient_id = :user_id
           AND NOT EXISTS (
             SELECT 1
             FROM connects
            WHERE (requestor_id = recipient_id AND requestee_id = sender_id) OR (requestor_id = sender_id AND requestee_id = recipient_id)
        ))
    
    
    
    然后可以将其转换为AR查询。

    class Convaclass Convo < ApplicationRecord
      def self.no_connects(user_id = nil)
        q = joins('
          LEFT JOIN connects ON
            sender_id IN (connects.requestor_id, connects.requestee_id)
              OR
            recipient_id IN (connects.requestor_id, connects.requestee_id)
        ')
        q = q.where('connects.requestor_id IS NULL AND connects.requestee_id IS NULL')
        q = q.where("convos.sender_id = :user_id OR convos.recipient_id = :user_id", user_id: user_id) if user_id
        q
      end
    end
    
    def self.no_connects(用户id=nil) q=连接(' 左连接连接打开 中的发件人\u id(connects.requestor\u id,connects.requestee\u id) 或 中的收件人\u id(connects.requestor\u id,connects.requestee\u id) ') q=q.where('connects.requestor_id为NULL,connects.requestee_id为NULL')) q=q.where(“convas.sender\u id=:user\u id或convas.recipient\u id=:user\u id”,user\u id:user\u id)如果是user\u id Q
     sql = <<-SQL
       (select distinct user_id from 
    
         (select sender_id as user_id from convos where sender_id=#{user.id} or recipient_id=#{user.id}
          union
          select recipient_id as user_id from convos where sender_id=#{user.id} or recipient_id=#{user.id}
         )
       )
       except
       (
       (select distinct user_id from          
         (select requestor_id as user_id from connections where requestor_id=#{user.id} or requestee_id=#{user.id}
          union
          select requestee_id as user_id from convos where requestor_id=#{user.id} or requestee_id=#{user.id}
         )
       ) 
     SQL  
    
     result = Convo.connection.execute(sql)
     users_ids_in_convo_without_connection = result.to_a.map(&:values).flatten 
    
    current_user.convos.joins("
      LEFT JOIN connects ON
        (connects.requestor_id = convos.sender_id AND connects.requestee_id = convos.recipient_id)
        OR
        (connects.requestor_id = convos.recipient_id AND connects.requestee_id = convos.sender_id)
      ").where(connects: {id: nil})
    
    class UserPair < ApplicationRecord
      belongs_to :user_1, class_name: "User"
      belongs_to :user_2, class_name: "User"
    
      before_save :sort_users
    
      scope :between, -> (user_1_id,user_2_id) do
        # records are always saved in sorted id order, so sort before querying
        user_1_id, user_2_id = [user_1_id, user_2_id].sort
        where(user_1_id: user_1_id, user_2_id: user_2_id)
      end
    
      # always put lowest id first for easy lookup
      def sort_users
        if user_1.present? && user_2.present? && user_1.id > user_2.id
          self.user_1, self.user_2 = user_2, user_1
        end
      end
    end
    
    class Convo < ApplicationRecord
      belongs_to :sender, :foreign_key => :sender_id, class_name: 'User'
      belongs_to :recipient, :foreign_key => :recipient_id, class_name: 'User'
      belongs_to :user_pair
    
      before_validation :set_user_pair
    
      scope :involving, -> (user) do
        where("convos.sender_id =? OR convos.recipient_id =?",user.id,user.id)
      end
    
      # since UserPair records are always user_id sorted, we can just use
      # that model's scope here without need to repeat it, using `merge`
      scope :between, -> (sender_id,recipient_id) do
        joins(:user_pair).merge(UserPair.between(sender_id, recipient_id))
      end
    
      def set_user_pair
        self.user_pair = UserPair.find_or_initialize_by(user_1: sender, user_2: recipient)
      end
    end
    
    SELECT convos.*
    FROM convos
    WHERE (sender_id = :user_id
            AND NOT EXISTS (
             SELECT 1
             FROM connects
             WHERE (requestor_id = sender_id AND requestee_id = recipient_id) OR (requestor_id = recipient_id AND requestee_id = sender_id)
            ))
      OR        
        (recipient_id = :user_id
           AND NOT EXISTS (
             SELECT 1
             FROM connects
            WHERE (requestor_id = recipient_id AND requestee_id = sender_id) OR (requestor_id = sender_id AND requestee_id = recipient_id)
        ))
    
    
    
    class Convo < ApplicationRecord
      def self.no_connects(user_id = nil)
        q = joins('
          LEFT JOIN connects ON
            sender_id IN (connects.requestor_id, connects.requestee_id)
              OR
            recipient_id IN (connects.requestor_id, connects.requestee_id)
        ')
        q = q.where('connects.requestor_id IS NULL AND connects.requestee_id IS NULL')
        q = q.where("convos.sender_id = :user_id OR convos.recipient_id = :user_id", user_id: user_id) if user_id
        q
      end
    end