Rails中复杂的多对多关系

时间:2015-06-21 17:05:55

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

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

并且地方可以彼此属于,例如一个地区可以有很多城市,一个国家可以有很多地区等。

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

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

如果它是多对多的,我如何在一个查询中获得一系列父级地方,以制作简单的字符串,例如" Place,Parent place 1,Parent place 2,Parent place 3&#34 ;,例如"美国加利福尼亚州旧金山"?

这是我的代码:

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

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

2 个答案:

答案 0 :(得分:2)

这使得在不干预该模型的情况下,将多个与多个关系直接关联到另一个模型。但是如果你想像Polymorphic Association那样你可以使用更高级的东西。

有关详细信息,请访问Rails指南:Polymorphic Association

答案 1 :(得分:2)

这是第一个出现在我脑海中的解决方案,可能有很多其他方法可以做到,但我相信这可能是最干净的。

您已经有了正确的总体思路,但您只需对联接表稍作修改即可。基本上,您将使用has_many... through关系,以便您可以附加某种时间帧鉴别符。

在我的示例中,我使用日期时间字段来指示关联的相关点。结合默认范围,通过时间鉴别器(在我的示例中称为effective_from)对结果进行排序,您可以轻松选择&#34;当前&#34;没有额外努力的地方的父母和孩子,或者在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

因此,如果我们创建了一些&#34;顶级&#34;国家地方:

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)

然后你会有一个城市(&#34;旧金山&#34;)属于州&#34;加利福尼亚&#34;,历史属于该国&#34; ;美国&#34;,后来&#34;新加利福尼亚共和国&#34;。

此外,如果您想构建一个包含该地点名称及其所有&#34;父母&#34;的字符串,您可以通过递归方式&#34;像这样:

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

对于我们的城市&#34;旧金山&#34;,考虑到"San Francisco, California, New California Republic"字段的排序,应该生成effective_from