Как мне подходить к многим сквозным отношениям с Single Table Inheritance (STI) в Rails 4.0
У меня есть родительский класс под названием 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
Кто-нибудь может объяснить, как мне следует подойти ко многим с ИППП?
Обновить
Вот выходной SQL-запрос от @country.districts
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 ответа
Это сложный случай для ActiveRecord. Он должен сделать вывод, что столбцы в самообъединении, необходимые для поиска districts
являются случаями ИППП. Видимо, он недостаточно умен, чтобы понять это правильно. Поскольку единственная таблица 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" = ?
Как видите, проверка типа должна завершиться сбоем, так как одна строка не может быть одновременно City
а также District
, Все будет работать, если первый пункт в 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. Это интересный случай.
Я думаю, вам нужно изменить наследование ваших моделей.
class Country < Place
class City < Country
class District < City
А затем удалите
has_one :country through: :city
линия.
Прокрутите вниз, чтобы найти информацию о ИППП http://api.rubyonrails.org/classes/ActiveRecord/Base.html