繁体   English   中英

Rails使用多态关联时对ActiveRecord关系的组合和排序

[英]Rails Combining and Sorting ActiveRecord Relations when using polymorphic associations

我正在研究一个小型收集跟踪器,我觉得 STI 可以真正简化这个问题,但似乎普遍的共识是尽可能避免 STI,所以我已经把我的模型分开了。 目前,它们都是相同的,但我确实有一些不同的元数据,我可以看到自己附加到它们上。

无论如何,根是一个Platform ,它有许多GamesSystemsPeripherals等,我试图在一个可过滤、可排序和可搜索的动态表中的视图上显示所有这些关系。

例如,查询可以是@platform.collectables.search(q).order(:name)

# Schema: platforms[ id, name ]
class Platform < ApplicationRecord
  has_many :games
  has_many :systems
  has_many :peripherals
end

# Schema: games[ id, platform_id, name ]
class Game < ApplicationRecord
  belongs_to :platform
end

# Schema: systems[ id, platform_id, name ]
class System < ApplicationRecord
  belongs_to :platform
end

# Schema: peripherals[ id, platform_id, name ]
class Peripheral < ApplicationRecord
  belongs_to :platform
end

在上面,当我将它们添加到Collection时,多态性开始发挥作用:

# Schema: collections[ id, user_id, collectable_type, collectable_id ]
class Collection < ApplicationRecord
  belongs_to :user
  belongs_to :collectable, polymorphic: true
end

现在,当我查看一个Platform时,我希望看到它的所有游戏、系统和外围设备,我称之为收藏品。 我将如何查询所有这些同时能够作为一个整体进行排序(即:“名称 ASC”)。 下面在理论上有效,但这会改变与 Array 的关系,这会阻止我在数据库级别进行进一步过滤、搜索或重新排序,因此我无法在另一个scopeorder上添加标签。

class Platform < ApplicationRecord
...

  def collectables
    games + systems + peripherals
  end
end

我偶然发现了委托类型,这听起来像是朝着我正在寻找的方向迈出的一步,但也许我错过了一些东西。

我很想尝试 STI 路线,我没有看到这些模型有太大的差异,并且不同的东西可以存储在 JSONB 列中,因为它主要只是用于填充视图的元数据,而不是真正的搜索。 基本上是这样的 model 但它似乎很不受欢迎,我觉得我一定错过了一些东西。

# Schema: collectables[ id, platform_id, type, name, data ]
class Collectable < ApplicationRecord
  belongs_to :platform
end

class Platform < ApplicationRecord
  has_many :collectables

  def games
    collectables.where(type: 'Game')
  end

  def systems
    collectables.where(type: 'System')
  end

  ...
end

这里的一个解决方案是委托类型(一个相对较新的 Rails 特性),它基本上可以通过多态性概括为多表 Inheritance。 所以你有一个包含共享属性的基表,但每个 class 也有自己的表 - 从而避免了 STI 的一些关键问题。

# app/models/concerns/collectable.rb
# This module defines shared behavior for the collectable "subtypes"
module Collectable
  TYPES = %w{ Game System Peripheral }
  extend ActiveSupport::Concern
  included do
    has_one :base_collectable, as: :collectable
    accepts_nested_attributes_for :base_collectable
  end 
end
# This model contains the base attributes shared by all the collectable types
# rails g model base_collectable name collectable_type collectable_id:bigint
class BaseCollectable < ApplicationRecord 
  # this sets up a polymorhic association
  delegated_type :collectable, types: Collectable::TYPES
end
class Game < ApplicationRecord
  include Collectable
end
class Peripheral < ApplicationRecord
  include Collectable
end
class System < ApplicationRecord
  include Collectable
end

然后,您可以通过连接 model 设置多对多关联:

class Collection < ApplicationRecord
  belongs_to :user 
  has_many :collection_items
  has_many :base_collectables, through: :collection_items

  has_many :games, 
    through: :base_collectables, 
    source_type: 'Game'
  has_many :peripherals, 
    through: :base_collectables, 
    source_type: 'Peripheral'
  has_many :systems, 
    through: :base_collectables, 
    source_type: 'Systems'
end
class CollectionItem < ApplicationRecord
  belongs_to :collection
  belongs_to :base_collectable
end
# This model contains the base attributes shared by all the collectable types
# rails g model base_collectable name collectable_type collectable_id:bigint
class BaseCollectable < ApplicationRecord 
  # this sets up a polymorhic association
  delegated_type :collectable, types: %w{ Game System Peripheral }
  has_many :collection_items
  has_many :collections, through: :collection_items
end

这使您可以将其视为同质集合并按base_collectables表上的列排序。

大但是 - 关系 model 仍然不喜欢作弊者

多态关联是围绕 object 关系阻抗不匹配的肮脏作弊方法,其中一个列具有主键引用,另一个列存储 class 名称。 它不是实际的外键,因为如果不先将记录从数据库中拉出,则无法解决关联。

这意味着您无法has_many:collectables, through: :base_collectables 而且您不能急切地加载所有委托类型或按游戏、外围设备或系统表上的列对整个集合进行排序。

它适用于特定类型,例如:

has_many :games, 
    through: :base_collectables, 
    source_type: 'Game'

由于可以事先知道该表。

这对于基于表且不面向 object 且外键形式的关系指向单个表的关系数据库而言,这简直是一个难以破解的难题。

VS STI + JSON

这里的关键问题是,您填充到 JSON 列中的所有数据基本上都是无模式的,并且您受到 JSON 支持的 6 种类型的限制(其中都不是日期或体面的数字类型)并且使用数据可以是极其困难的。

JSON 的主要特点是简单,它作为一种传输格式工作得很好。 作为一种数据存储格式,它并不是那么好。

这是从 EAV 表或将 YAML/JSON 序列化到 varchar 列中的一大进步,但它仍然有很大的警告。

其他潜在的解决方案

  • 性病。 修复了一个多态性问题并引入了更多。
  • 用三个表的联合填充的实体化视图可以被视为表,因此可以简单地附加一个 ActiveRecord 关系。
  • 联合查询。 基本上与上面的想法相同,但您只需使用原始查询结果或将它们输入到代表无意义表的 model 中。
  • 非关系型数据库。 MongoDB 等基于文档的数据库可让您通过设计创建灵活的文档和非同质 collections,同时具有更好的类型支持。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM