У меня есть приложение Rails (5.2.2) (база данных Postgres) с некоторыми моделями, относящимися к разным регионам:
- districts (have many sectors)
-- sectors (have many cells, have one district)
--- cells (have many villages, have one sector)
---- villages (have many facilities, have one cell)
----- facilities (have one village)
У меня также есть модель Report
, которая для контекста записывает количество конкретной технологии, распределенной в определенном месте.
#<Report id: nil, date: nil, technology_id: nil, user_id: nil, contract_id: nil, model_gid: nil, distributed: nil, checked: nil, created_at: nil, updated_at: nil, people: nil, households: nil>
Это местоположение может быть любой из моделей географии. Поэтому я использую GlobalID, хранящийся в виде строки в model_gid
в записи Report
.
например:.
#<Report id: 1, ... model_gid: "gid://liters-tracker/Village/64", ...>
Затем я написал несколько областей, которые работают нормально:
scope :only_districts, -> { where('model_gid ILIKE ?', '%/District/%') }
scope :only_sectors, -> { where('model_gid ILIKE ?', '%/Sector/%') }
scope :only_cells, -> { where('model_gid ILIKE ?', '%/Cell/%') }
scope :only_villages, -> { where('model_gid ILIKE ?', '%/Village/%') }
scope :only_facilities, -> { where('model_gid ILIKE ?', '%/Facility/%') }
Я подумал, что это хороший подход, потому что мой report.model
метод работает:
def model
GlobalID::Locator.locate model_gid
end
например:.
2.4.5 :001 > Report.first.model
Report Load (0.5ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
Village Load (0.4ms) SELECT "villages".* FROM "villages" WHERE "villages"."id" = $1 LIMIT $2 [["id", 64], ["LIMIT", 1]]
=> #<Village id: 64, name: "Ruhanga", cell_id: 11, gis_id: 13080406, latitude: -2.00828333333333, longitude: 30.1708, population: 518, households: 179, created_at: "2019-01-21 22:53:06", updated_at: "2019-01-21 22:53:06">
Я решил сделать это строковое поле вместо полиморфной ассоциации, потому что GlobalID::Locator
методы могут принимать строки и анализировать модель и ID из нее. Так почему хлопот с ассоциацией? Может быть, это фундаментальный недостаток моего мышления?
Поскольку поиск записей на основе model_gid
, кажется, не удается:
2.4.5 :045 > Report.all.where(model_gid: Report.first.model_gid)
Report Load (0.4ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
Report Load (0.5ms) SELECT "reports".* FROM "reports" WHERE "reports"."model_gid" = $1 LIMIT $2 [["model_gid", "--- gid://liters-tracker/Village/64\n"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
(честно говоря, не уверен, почему SQL преобразован в "--- gid://liters-tracker/Village/64\n"
и действительно ли это моя проблема)
2.4.5 :046 > Report.all.where("model_gid ILIKE ?", Report.first.model_gid)
Report Load (0.5ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
Report Load (3.2ms) SELECT "reports".* FROM "reports" WHERE (model_gid ILIKE 'gid://liters-tracker/Village/64') LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
2.4.5 :049 > Report.all.where("model_gid = ?", Report.first.model_gid)
Report Load (0.3ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
Report Load (0.6ms) SELECT "reports".* FROM "reports" WHERE (model_gid = 'gid://liters-tracker/Village/64') LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
Я пытаюсь заставить этот метод работать:
def self.related_to(record)
where(model_gid: record.to_global_id.to_s)
end
И я действительно не понимаю, почему это не работает:
2.4.5 :010 > Report.first.model_gid
Report Load (0.6ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> "gid://liters-tracker/Village/64"
2.4.5 :011 > Village.find(64).to_global_id.to_s
Village Load (0.5ms) SELECT "villages".* FROM "villages" WHERE "villages"."id" = $1 LIMIT $2 [["id", 64], ["LIMIT", 1]]
=> "gid://liters-tracker/Village/64"
2.4.5 :012 > Report.first.model_gid == Village.find(64).to_global_id.to_s
Report Load (0.4ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT $1 [["LIMIT", 1]]
Village Load (0.3ms) SELECT "villages".* FROM "villages" WHERE "villages"."id" = $1 LIMIT $2 [["id", 64], ["LIMIT", 1]]
=> true
2.4.5 :013 > Report.all.where(model_gid: Village.find(64).to_global_id.to_s)
Village Load (0.4ms) SELECT "villages".* FROM "villages" WHERE "villages"."id" = $1 LIMIT $2 [["id", 64], ["LIMIT", 1]]
Report Load (0.4ms) SELECT "reports".* FROM "reports" WHERE "reports"."model_gid" = $1 LIMIT $2 [["model_gid", "--- gid://liters-tracker/Village/64\n"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
Если я имитирую области видимости, это работает:
def self.related_to(record)
where('model_gid ILIKE ?', "%#{record.to_global_id.to_s}%")
end
Но в примерах записей, которые я показываю, это будет соответствовать Деревне № 64 и Деревне № 640, так что это не хорошее решение.
UPDATE
- Я подумал, может быть, специальные персонажи отбрасывают вещи. Но все работает, как и ожидалось, когда я использую другой строковый столбец в другой модели:
2.4.5 :052 > Village.first.update(name: "gid://liters-tracker/Village/64")
Village Load (0.5ms) SELECT "villages".* FROM "villages" ORDER BY "villages"."id" ASC LIMIT $1 [["LIMIT", 1]]
(0.2ms) BEGIN
Cell Load (0.2ms) SELECT "cells".* FROM "cells" WHERE "cells"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Village Exists (0.3ms) SELECT 1 AS one FROM "villages" WHERE "villages"."gis_id" = $1 AND "villages"."id" != $2 LIMIT $3 [["gis_id", 11070101], ["id", 1], ["LIMIT", 1]]
Village Update (0.3ms) UPDATE "villages" SET "name" = $1, "updated_at" = $2 WHERE "villages"."id" = $3 [["name", "gid://liters-tracker/Village/64"], ["updated_at", "2019-07-06 22:16:38.585563"], ["id", 1]]
(1.2ms) COMMIT
=> true
2.4.5 :053 > Village.where(name: "gid://liters-tracker/Village/64")
Village Load (0.3ms) SELECT "villages".* FROM "villages" WHERE "villages"."name" = $1 LIMIT $2 [["name", "gid://liters-tracker/Village/64"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Village id: 1, name: "gid://liters-tracker/Village/64", cell_id: 1, gis_id: 11070101, latitude: -2.054922, longitude: 30.0912883, population: 513, households: 110, created_at: "2019-01-21 22:53:04", updated_at: "2019-07-06 22:16:38">]>
- Я подумал, что, возможно, мне нужен индекс в поле
Report.model_gid
. Но это не имело значения.
class AddModelGidIndexToReports < ActiveRecord::Migration[5.2]
def change
add_index :reports, :model_gid
end
end
ОБНОВЛЕНИЕ 2
(это основано на моем собственном «ответе», но так как это вопрос, я поставил его здесь)
@ MichaelChaney:
Просто, чтобы я понял, вы предлагаете что-то вроде этого:
class Report < ApplicationRecord
belongs_to :technology, inverse_of: :reports
belongs_to :user, inverse_of: :reports
belongs_to :contract, inverse_of: :reports
enum geography: { district: 'district', sector: 'sector', cell: 'cell', village: 'village', facility: 'facility' }
На этом этапе мне нужно просто добавить целочисленный столбец geography_id
и прекратить использовать GlobalID?
А как же просто превратиться в полиморф?