【问题标题】:Complex many-to-many relation in RailsRails 中复杂的多对多关系
【发布时间】:2015-09-07 03:32:51
【问题描述】:

我有一个模特Place。 例如,地点可以是城市、地区、地区或国家,但我认为一个模型就足够了,因为就我的目的而言,城市和地区之间没有太大区别。

而且地点可以相互归属,例如一个地区可以有很多城市,一个国家可以有很多地区等等。

但我的问题是,这是一个历史项目,一个城市可以属于许多地区。例如,从 1800 年到 1900 年,一座城市属于一个历史区域(现在不存在),而从 1900 年到现在,属于另一个历史区域。重要的是,将这些地区存储为不同的地方,尽管它们可以具有相似的地理边界,但只有名称不同。

我猜是多对多,但也许有人可以给出更好的主意?

如果它是多对多的,我怎样才能在一个查询中获取一系列父位置以生成简单的字符串,例如“位置,父位置 1,父位置 2,父位置 3”,例如“美国加利福尼亚州旧金山”?

这是我的代码:

create_table :places do |t|
  t.string :name

  t.timestamps null: false
end

create_table :place_relations, id: false do |t|
  t.integer :sub_place_id
  t.integer :parent_place_id

  t.timestamps null: false
end

class Place < ActiveRecord::Base
  has_and_belongs_to_many :parent_places,
    class_name: "Place",
    join_table: "place_relations",
    association_foreign_key: "parent_place_id"

  has_and_belongs_to_many :sub_places,
    class_name: "Place",
    join_table: "place_relations",
    association_foreign_key: 'sub_place_id'
end

请不要犹豫,给我一些想法!

【问题讨论】:

  • 这是一个相当复杂的关系。我对 Rails 还是很陌生,但似乎您在每个时间段内为每个地方/区域都有一个 Place 模型。具有诸如 contains_region_id 之类的属性,它可以告诉您 Place 是什么的一部分,而 previous_region_id 可以告诉您该 Place 是什么或其中的一部分。不过,我不确定拥有/属于关系将如何运作。
  • @AaronWashburn,不完全是......例如,某个地方(城市)可能以前是 5 或 10 个不同国家的一部分......而现在有些国家甚至可能不存在。所以我无法创建一个字段previous_region_id。还是我误会了你?
  • 明白了。看起来下面 Paul Richter 的关系表是要走的路。

标签: ruby-on-rails activerecord has-and-belongs-to-many


【解决方案1】:

这是我想到的第一个解决方案,可能还有很多其他方法可以做到,但我相信这可能是最干净的。

您的总体思路是正确的,但您只需要对连接表稍作修改即可。本质上,您将改用has_many... through 关系,以便您可以附加某种时间范围鉴别器。

在我的示例中,我使用日期时间字段来指示关联从什么时候开始是相关的。结合按时间鉴别器(在我的示例中称为effective_from)对结果进行排序的默认范围,您可以轻松选择一个地方的“当前”父母和孩子,而无需额外的努力,或者使用单个日期选择历史数据where 子句中的比较。 请注意,您不需要像我那样处理时间范围歧视,这只是为了演示这个概念。根据需要进行修改。

class PlaceRelation < ActiveRecord::Base
  default_scope { order "effective_from DESC" }

  belongs_to :parent, class_name: "Place"
  belongs_to :child, class_name: "Place"
end

class Place < ActiveRecord::Base
  has_many :parent_places, class_name: "PlaceRelation", foreign_key: "child_id"
  has_many :child_places, class_name: "PlaceRelation", foreign_key: "parent_id"

  has_many :parents, through: :parent_places, source: :parent
  has_many :children, through: :child_places, source: :child
end

place_relations 表的迁移应如下所示:

class CreatePlaceRelations < ActiveRecord::Migration
  def change
    create_table :place_relations do |t|
      t.integer :parent_id
      t.integer :child_id
      t.datetime :effective_from
      t.timestamps
    end
  end
end

因此,如果我们创建几个“顶级”国家/地区:

country1 = Place.create(name: "USA")
country2 = Place.create(name: "New California Republic")

还有一个州

state = Place.create("California")

一个城市

city = Place.create("San Francisco")

最后把它们绑在一起:

state.parent_places.create(parent_id: country1.id, effective_from: DateTime.now - 1.year)
state.parent_places.create(parent_id: country2.id, effective_from: DateTime.now)

city.parent_places(parent_id: state.id, effective_from: DateTime.now)

然后您将拥有一个属于“加利福尼亚”州的城市(“旧金山”),历史上属于“美国”国家,后来属于“新加利福尼亚共和国”。

此外,如果您想构建一个包含地点名称及其所有“父母”的字符串,您可以像这样“递归”地进行:

def full_name(name_string = [])
  name_string << self.name
  parent = self.parents.first
  if parent.present?
    return parent.full_name name_string
  else
    return name_string.join(", ")
  end
end

对于我们的城市“旧金山”,根据 effective_from 字段的顺序,这应该导致 "San Francisco, California, New California Republic"

【讨论】:

  • 太棒了!谢谢!是否有可能在一次查询中获取某个特定位置的所有父位置链?根据您的示例,我想获得类似 2 个字符串的内容:“旧金山,加利福尼亚,美国”和“旧金山,加利福尼亚,新加利福尼亚共和国”。如何以最少的查询次数做到这一点?
  • @ktaras 坦率地说,在不知道该地方有多少父母的情况下,我不确定如何在单个或几个查询中执行此操作 - 取决于您的 dbms,您可能需要查看 hierarchical queries,其中在 mysql 中不支持。如果您使用的是 mysql,您可能需要查看some workarounds。父子查询herehere可能有一些有趣的事情
  • 太棒了,感谢您的提示!我使用的是 PostgreSQL,所以我会仔细研究分层查询。
【解决方案2】:

这使得直接多对多关系与另一个模型建立关联,而无需干预该模型。但是如果你想像 Polymorphic Association 一样,你可以使用更高级的东西。

欲了解更多信息,请访问 Rails 指南:Polymorphic Association

【讨论】:

  • 谢谢,但我猜多态关联不太适合这种情况,因为我没有一些预定义的地方类型——除了区域和地区之外,可能还有很多类型,例如州、地区、王国、省等。所以我只需要能够创建地点并指定彼此的归属。
  • 那么我的解决方案很好吗?如果是,是否有可能在一个查询中获得某个特定地点的所有父地点的链?
猜你喜欢
  • 1970-01-01
  • 2022-01-06
  • 1970-01-01
  • 2015-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多