我应该如何处理Rails 4.0中与单表继承(STI)的“有很多通过”关系

时间:2013-09-26 04:03:37

标签: ruby-on-rails activerecord ruby-on-rails-4 single-table-inheritance

我有一个名为Place的父类。

# Parent class for Country, City and District
class Place < ActiveRecord::Base
end

class Country < Place
  has_many :cities, foreign_key: "parent_id"
  has_many :districts, through: :cities
end

class City < Place
  belongs_to :country, foreign_key: "parent_id"
  has_many :districts, foreign_key: "parent_id"
end

class District < Place
  belongs_to :city, foreign_key: 'parent_id'
  has_one :country, through: :city
end

架构:

create_table "places", force: true do |t|
  t.string   "name"
  t.string   "type"
  t.integer  "parent_id"
  t.datetime "created_at"
  t.datetime "updated_at"
end

add_index "places", ["parent_id"], name: "index_places_on_parent_id"
add_index "places", ["type"], name: "index_places_on_type"

以下按预期方式工作:

@country.cities # => Returns all of the cities that belong to this country
@city.districts # => Returns all of the districts that belong to this city

但这并不像我想的那样有效:

@country.districts # => Does not return all of the districts belonging to cities in this country

任何人都可以通过STI来解释我应该如何处理吗?

更新

以下是来自@country.districts

的输出SQL查询
SELECT "places".* FROM "places" INNER JOIN "places" "cities_districts_join" ON "places"."parent_id" = "cities_districts_join"."id" WHERE "places"."type" IN ('City') AND "places"."type" IN ('District') AND "cities_districts_join"."parent_id" = ?  [["parent_id", 1]]

我认为问题在于它为两个关系使用相同的连接表,但我不确定是否有一个“Rails方式”来更改连接表的名称(优雅地)

2 个答案:

答案 0 :(得分:2)

这对ActiveRecord来说是一个具有挑战性的案例。它需要推断查找districts所需的自联接中的列是STI实例。显然,要做到这一点并不够聪明。由于唯一的表是places,因此生成此查询并不奇怪:

SELECT "places".* FROM "places" 
INNER JOIN "places" "cities_districts_join" 
ON "places"."parent_id" = "cities_districts_join"."id" 
WHERE "places"."type" IN ('City')   <<<<< ERROR HERE
AND "places"."type" IN ('District') 
AND "cities_districts_join"."parent_id" = ?  

正如您所看到的,类型检查必须失败,因为一个字符串不能同时是CityDistrict。如果WHERE中的第一个条款改为

,那么所有这些都会有效
WHERE "cities_districts_join"."type" IN ('City')  

我在关系上尝试了几个选项(思考:class_name可能会这样做),但没有快乐。

您可以使用SQL解决此限制。删除has_many ... through类中的Country并替换为

def districts
  District.find_by_sql(['SELECT * from places AS city 
                           INNER JOIN places AS district 
                         ON district.parent_id = city.id 
                         WHERE city.parent_id = ?', id])
end

或许其他人会看到更优雅的方式。如果没有,您可以考虑将此作为Rails开发中的问题发布。这是一个有趣的案例。

答案 1 :(得分:-1)

我认为您需要更改模型的继承。

class Country < Place

class City < Country

class District < City

然后删除

has_one :country through: :city 

线。

向下滚动以查找有关STI的信息 http://api.rubyonrails.org/classes/ActiveRecord/Base.html