ruby-on-rails - Rails 中复杂的多对多关系

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

我有一个模型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

请随时给我一些想法!

最佳答案

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

您的总体思路是正确的,但您所需要的只是对连接表进行轻微修改。本质上,您将使用 has_many... through 关系来代替,以便您可以附加某种时间范围鉴别器。

在我的示例中,我使用日期时间字段来指示关联从哪个点开始相关。结合按时间鉴别器对结果进行排序的默认范围(在我的示例中称为 effective_from ),您可以轻松选择某个地点的“当前” parent 和 child ,而无需额外的努力,或者选择历史在 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 的顺序,结果应该是“旧金山,加利福尼亚州,新加利福尼亚共和国”字段。

关于ruby-on-rails - Rails 中复杂的多对多关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30967130/

相关文章:

ruby-on-rails - 使用 `assign_attributes` 立即保存 `has_many through:` 关联

mysql - has_and_belongs_to_many 权限过滤

ruby-on-rails - 如何验证 ActiveRecord 模型中的虚拟属性?

ruby-on-rails - 如何以编程方式订阅 messenger webhook

ruby-on-rails - rails rake : test are slow

ruby-on-rails - ruby 包含 vs 扩展

CakePHP 中使用 HABTM 的 find() 多模型条件

ruby-on-rails - HABTM rails 关系中的 Pundit 范围

ruby-on-rails - 从 HABTM 关系返回多个结果

ruby-on-rails - named_scope 至少有一个在 has_many 关联中