如何使用活动记录正确定义此关联?

How to define this association properly using active record?

我在一个应用中有以下两个模型

class City
end

class Suburb
end

一个城市恰好有北、东、南、西、中5个郊区。我想通过相应的方法引用每个郊区,这样, 通过 city.north 访问北方 通过 city.south

访问南

我可以为 cities table 中的每个郊区添加一个外键,并使用 belongs_tohas_one 来定义每个关联。但我发现它并不像它应该的那样直观。这是因为 Suburb belongs_to a City 而不是相反。所以下面的定义并不直观。

class City
  belongs_to :north, class_name: 'Suburb'
  belongs_to :east, class_name: 'Suburb'
  belongs_to :south, class_name: 'Suburb'
  belongs_to :west, class_name: 'Suburb'
  belongs_to :center, class_name: 'Suburb'
end

class Suburb
  has_one :city
end

这按预期工作。但是当你阅读它时,它恰恰相反。郊区 belongs_to 城市和城市 has_one :north, has_one :east, has_one :south, has_one :west and has_one :center .

我还尝试在 city 模型上定义一个 has_many :suburbs,并向郊区模型添加一个枚举 属性 direction 而不是定义一个方法,使用 define_method' 每个方向,但我发现它设计过度了。

有没有办法正确建模。

我会说你需要一个额外的模型,CitySuburb,这样 City 和 Suburb has_many CitySuburb,CitySuburb 同时属于 City 和 Suburb。

您可以在 CitySuburb 上放置北、南等范围,从而将 City 与 north_suburb、south_suburb 等相关联

您的架构本身没有任何问题,但为了便于讨论,让我提出一个满足您的建模问题的替代方案:

按照您的提议,让 Suburb 属于 City。为了加强郊区相对于城市的唯一性,我们在 suburbs table 中添加了一个 direction 列,以及一个结合了 city_iddirection。这样一个Suburb正好属于一个城市,一个城市在给定的direction.

中不能有超过一个Suburb

db/migrate/...create_suburbs.rb

class CreateDeviseUsers < ActiveRecord::Migration
  def self.change
    create_table(:suburbs) do |t|
      # no need for `index: true` here because of composite key below
      t.references :city, null: false
      t.text :direction, null: false

      t.index [:city_id, :direction], unique: true
    end
  end
end

app/models/city.rb

class City < ActiveRecord::Base
  has_many :suburbs
end

我们的 Suburb 模型现在有点复杂。我们需要 direction 的验证以及每个可能值的范围。我喜欢添加一个 getter 以确保 direction 也始终是一个符号。

app/models/suburb.rb

class Suburb < ActiveRecord::Base
  DIRECTIONS = [:north, :east, :south, :west]

  belongs_to :city

  validates :city, presence: true
  validates :direction, inclusion: { in: DIRECTIONS }

  # define a scope for each direction
  DIRECTIONS.each { |d| scope d, -> { where(direction: d) } }

  # convenience getter so that we can reason about direction using symbols
  def direction
    self[:direction].try(:to_sym)
  end
end

现在我们可以使用以下范围访问和深入城市郊区:

# all north suburbs
north_suburbs = Suburb.north

# northern suburbs of a city (there can only be one!)
north_suburbs = city.suburbs.north

# as a model
north_suburb = city.suburbs.north.first

如果你实在不喜欢first位,你可以定义方便访问器:

app/models/city.rb

class City < ActiveRecord::Base
  has_many :suburbs

  def north
    suburbs.north.first
  end

  # ...
end