我有一个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号村庄,所以这不是一个好的解决方案。
更新
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吗?
那只是去多态呢?
答案 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搜索。
另一课:在代码库中保留更好的注释,因此当我更改策略时,可以正确地放松实现的内容。