mysql - 有没有办法在 Rails 中预加载任意数量的父关联?

标签 mysql ruby-on-rails ruby belongs-to

TL;DR: I have a model that belongs_to :group, where group is another instance of the same model. That "parent" group can also have a parent, and so on up the chain. Is there a way to includes this structure as far up as it goes?

我有一个 Location 模型,如下所示(删节版):

create_table "locations", force: :cascade do |t|
  t.string "name"
  t.decimal "lat", precision: 20, scale: 15
  t.decimal "long", precision: 20, scale: 15
  t.bigint "group_id"
  t.string "type"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["group_id"], name: "index_locations_on_group_id"
end
class Location < ApplicationRecord
  belongs_to :group, class_name: 'Location', required: false
  has_many :locations, foreign_key: 'group_id', dependent: :destroy
end

换句话说,它可以选择属于其自身的“父”实例,引用为group

该父实例也可以属于另一级别的父实例,其父实例也可以属于此类,等等。大象,一直向下。

我想要做的是将位置及其所有父实例的名称串在一起,因此我最终会得到类似“顶级组>中级组>最低组>位置”的内容。这很好,我已经在模型中实现了它:

def parent_chain
  Enumerator.new do |enum|
    parent_group = group
    while parent_group != nil
      enum.yield parent_group
      parent_group = parent_group.group
    end
  end
end

def name_chain
  (parent_chain.map(&:name).reverse + [name]).join(" \u00BB ")
end

然而,唯一的问题是,当每个父实例到达那里时,它将单独查询(N+1 问题)。一旦我在单个页面中包含多个位置,就会出现大量查询,从而减慢加载速度。我想预加载(通过 includes)这个结构,就像预加载正常的 belongs_to 关联一样,但我不知道是否有办法包含任意数字这样的 parent 。

有吗?我该怎么做?

最佳答案

使用包含?不。递归预加载可以通过这种方式实现:

解决方案#1:真正的递归

class Location
  belongs_to :group

  # Impure method that preloads the :group association on an array of group instances.
  def self.preload_group(records)
    preloader = ActiveRecord::Associations::Preloader.new
    preloader.preload(records, :group)
  end

  # Impure method that recursively preloads the :group association 
  # until there are no more parents.
  # Will trigger an infinite loop if the hierarchy has cycles.
  def self.deep_preload_group(records)
    return if records.empty?
    preload_group(records)
    deep_preload_group(records.select(&:group).map(&:group))
  end
end

locations = Location.all
Location.deep_preload_group(locations)

查询的数量将是组层次结构的深度。

解决方案#2:接受层次结构深度限制

class Location
  # depth needs to be greather than 1
  def self.deep_preload_group(records, depth=10)
    to_preload = :group
    (depth - 1).times { to_preload = {group: to_preload} }
    preloader = ActiveRecord::Associations::Preloader.new
    preloader.preload(records, to_preload)
  end
end

查询数量将是深度与层次结构的实际深度中的最小值

关于mysql - 有没有办法在 Rails 中预加载任意数量的父关联?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53133500/

相关文章:

ruby-on-rails - 在两个 Rails 项目之间共享模型 - 使用 git 子模块?

ruby - 无法使用 Watir 单击相同的元素类

mysql - 如何为mysql中的特定表自动递增2

mysql - 使用 IAM 角色允许访问 RDS 上的 MySQL 数据库

javascript - 如何使用下划线 POST JSON 属性?

ruby - 如何创建临时文件并在一定时间后将其删除

ruby - 除了 ClientIP 之外,还有其他方法可以为 kubernetes 中的 sessionAffinity 提供自定义值吗?

mysql - 根据相关表中的最大值更新记录

php - 如何在 PHP/MYSQL 中限制条件结果查询?

ruby-on-rails - 什么是ArticlesController#show中的NoMethodError?什么是nil :NilClass?的未定义方法 `category_id'