Rails:通过存储的GlobalID字符串查询记录

时间:2019-07-06 20:03:07

标签: ruby-on-rails

我有一个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号村庄,所以这不是一个好的解决方案。

更新

  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 :(得分:0)

U。这是一个面对面的时刻。

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之前进行的。我想我打算保存一些{-{1}}之类的键值散列。

现在要弄清楚如何反序列化列。

答案 1 :(得分:0)

为了关闭记录,我切换到多态关联。可能不如@MichaelChaney在上一个答案的注释中建议的Enum解决方案那么快,但对于我的内部应用程序并创建我的应用程序已知的关联而言,速度足够快。

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

在我所有的地理模型中,它都与以下对象建立了伙伴关系,例如:

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以及我将非常关心的关联,因此我不会尝试在整个dB上进行愚蠢的Regex搜索。

另一课:在代码库中保留更好的注释,因此当我更改策略时,可以正确地放松实现的内容。