【问题标题】:Active Record: Remove element from PostgreSQL arrayActive Record:从 PostgreSQL 数组中删除元素
【发布时间】:2020-01-23 16:28:05
【问题描述】:

假设有一个名为Job 的活动记录模型,它有一个数组列follower_ids。我已经创建了一个允许获取用户后面的所有作业的范围:

# Returns all jobs followed by the specified user id
def self.followed_by(user_id)
  where(
    Arel::Nodes::InfixOperation.new(
      '@>',
      Job.arel_table[:follower_ids],
      Arel.sql("ARRAY[#{user_id}]::bigint[]")
    )
  )
end
# Retrieve all jobs followed by user with id=1
Job.followed_by(1)

有没有办法使用数据库从follower_ids 列中删除特定元素(即,不循环通过活动记录对象并为每个对象手动调用delete/save)?

例如,最好执行Job.followed_by(1).remove_follower(1) 之类的操作,只需一个查询即可从所有这些作业的follower_ids 中删除具有id=1 的用户。

【问题讨论】:

    标签: ruby-on-rails postgresql activerecord


    【解决方案1】:

    我结束了使用 PostgreSQL array_remove 函数,它允许从数组中删除一个值,如下所示:

    user_id = 1
    update_query = <<~SQL
      follower_ids = ARRAY_REMOVE(follower_ids, :user_id::bigint),
    SQL
    sql = ActiveRecord::Base.sanitize_sql([update_query, { user_id: user_id }])
    Job.followed_by(user_id).update_all(sql)
    

    【讨论】:

    • 感谢您提供此代码 sn-p,它可能会提供一些有限的即时帮助。 proper explanation 将通过展示为什么这是解决问题的好方法,并使其对有其他类似问题的未来读者更有用,从而大大提高其长期价值。请edit您的回答添加一些解释,包括您所做的假设。
    【解决方案2】:

    我认为这确实是一个 XY 问题,因为您使用的是数组列,而您应该使用连接表。

    您不想使用数组的主要原因是:

    • 如果用户被删除,您必须更新作业表中的每一行,而不是仅使用级联或删除回调删除联接表中的行。
    • 没有参照完整性。
    • 可怕的不可读查询。它实际上只是一个逗号分隔字符串的边际步骤。
    • 联接并不那么昂贵。 “过早的优化是万恶之源”。
    • 您不能将 ActiveRecord 关联与数组列一起使用。

    使用rails g model following user:references job:references 创建连接模型。然后设置关联:

    class Job < ApplicationRecord
      has_many :followings
      has_many :followers,
        through: :followings,
        source: :user
    end
    
    class User < ApplicationRecord
      has_many :followings
      has_many :followed_jobs,
        source: :job,
        through: :followings,
        class_name: 'Job'
    end
    

    要选择用户跟随的作业,您只需进行内部联接:

    user.followed_jobs
    

    要获得未被关注的工作,您需要对用户 ID 为 nil 或不等于 user_id 的关注进行外部联接。

    fui = Following.arel_table[:user_id]
    Job.left_joins(:followings)
       .where(fui.eq(nil).or(fui.not_eq(1)))
    

    如果您想取消关注某个工作,只需从 followings 中删除该行:

    Following.find_by(job: job, user: user).destroy
    # or 
    job.followings.find_by(user: user).destroy
    # or
    user.followings.find_by(job: job).destroy
    

    您可以使用 dependent: 选项在作业或用户被销毁时自动执行此操作。

    【讨论】:

      猜你喜欢
      • 2016-12-03
      • 1970-01-01
      • 2020-10-21
      • 2014-03-27
      • 1970-01-01
      • 2016-10-21
      • 2011-10-31
      • 1970-01-01
      相关资源
      最近更新 更多