Rails: запрос записей по сохраненной строке GlobalID - PullRequest
1 голос
/ 06 июля 2019

У меня есть приложение 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

  1. Я подумал, может быть, специальные персонажи отбрасывают вещи. Но все работает, как и ожидалось, когда я использую другой строковый столбец в другой модели:
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">]>
  1. Я подумал, что, возможно, мне нужен индекс в поле 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?

А как же просто превратиться в полиморф?

Ответы [ 2 ]

0 голосов
/ 11 июля 2019

Ради закрытия записи я переключился на полиморфную ассоциацию. Возможно, не так быстро, как решение Enum, предложенное @MichaelChaney в комментариях к предыдущему ответу, но достаточно быстро для моего собственного приложения и создает ассоциацию, известную моему приложению.

class Report < ApplicationRecord
  belongs_to :technology, inverse_of: :reports
  belongs_to :user,       inverse_of: :reports
  belongs_to :contract,   inverse_of: :reports
  # serialize :model_gid  #<-- this was real bad as @MichaelChaney points out
  # enum geography: { district: 'district', sector: 'sector', cell: 'cell', village: 'village', facility: 'facility' }  #<-- this is probably the fastest option
  belongs_to :reportable, polymorphic: true  #<-- this is probably the middle ground, as the :reportable_id and :reportable_type columns are indexed together

И это было связано со следующим на всех моих географических моделях, например ::1004

class Facility < ApplicationRecord
  belongs_to :village, inverse_of: :facilities
  has_one :cell, through: :village, inverse_of: :facilities
  has_one :sector, through: :cell, inverse_of: :facilities
  has_one :district, through: :sector, inverse_of: :facilities
  has_many :reports, as: :reportable, inverse_of: :reportable  #<-- tadaa

Так что теперь мне даже не нужен мой первоначальный метод, поскольку я могу сравнить результаты reports.reportable с записью, которую я должен увидеть, связаны ли они.

Урок, который я усвоил: на ранних этапах мне нужно больше думать о RdBMS и о каких ассоциациях, которые меня очень волнуют, поэтому я не пытаюсь выполнять глупый поиск по регулярному выражению в моем дБ.

Другой урок: лучше хранить заметки в моей базе кода, поэтому, когда я меняю стратегии, я могу корректно развернуть вещи, которые я реализовал.

0 голосов
/ 07 июля 2019

Тьфу. Это момент ладони лица.

class Report < ApplicationRecord
  belongs_to :technology, inverse_of: :reports
  belongs_to :user,       inverse_of: :reports
  belongs_to :contract,   inverse_of: :reports
  serialize :model_gid

  scope :only_districts,  -> { where('model_gid ILIKE ?', '%/District/%') }
  ...

Report.model_gid сериализуется, что я и сделал до того, как открыл GlobalID. Я думаю, что планировал сохранить некоторый хеш-ключ-значение, например {model: 'Village', id: '64'}.

Теперь, чтобы выяснить, как не сериализовать столбец.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...