tl; dr:
所有表和列的数据库混淆(包括外键(正在引起问题,即使用所有Rails查询从PoststresQL数据库中急切地加载相关模型的结果。 includes
, joins
等返回错误,而不是应有的。
开始:
我有一个完全混淆的数据库,每个表和列都使用20个字符的拉丁字符串命名,包括外国人。has_many关系的典型迁移是:
class CreateModelOne < ActiveRecord::Migration[5.0]
def change
create_table :abcdfeghijklmnopqrst do |t|
t.boolean :bcdfeghijklmnopqrstu, null: true
t.float :cdfeghijklmnopqrstuv, null: true, default: 0
t.timestamps
end
end
end
class CreateModelTwo < ActiveRecord::Migration[5.0]
def change
create_table :dfeghijklmnopqrstuvx do |t|
t.date :feghijklmnopqrstuvxy, null: true
t.time :eghijklmnopqrstuvxyz, null: true
t.integer :ghijklmnopqrstuvxyza
t.timestamps
end
add_foreign_key :dfeghijklmnopqrstuvx, :abcdfeghijklmnopqrst, column: :ghijklmnopqrstuvxyza
end
end
在模型文件中,我已将关系列为:
class ModelTwo < ApplicationRecord
alias_attribute :model_one_id, :ghijklmnopqrstuvxyza
end
当我想急切地加载 ModelTwo
S与ModelOne
关联时,我正在尝试使用标准includes
方法:
@model_one = ModelOne.includes(:model_two).find(particular_model_one_id)
这产生了错误:
NoMethodError: undefined method `association' for nil:NilClass
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.2/lib/active_record/associations/preloader/association.rb:67:in `block in associated_records_by_owner'
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.2/lib/active_record/core.rb:349:in `init_with'
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.2/lib/active_record/persistence.rb:69:in `instantiate'
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.2/lib/active_record/querying.rb:50:in `block (2 levels) in find_by_sql'
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.2/lib/active_record/result.rb:52:in `block in each'
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.2/lib/active_record/result.rb:52:in `each'
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.2/lib/active_record/result.rb:52:in `each'
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.2/lib/active_record/querying.rb:50:in `map'
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.2/lib/active_record/querying.rb:50:in `block in find_by_sql'
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activesupport-5.0.2/lib/active_support/notifications/instrumenter.rb:21:in `instrument'
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.2/lib/active_record/querying.rb:49:in `find_by_sql'
from /home/mark/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/bullet-5.5.1/lib/bullet/active_record5.rb:25:in `find_by_sql'
from
对我来说这似乎很奇怪,因为单独运行查询正常工作,如:
ModelOne.find(particular_model_one_id)
返回预期的ModelOne
,
ModelTwo.where(model_one_id: particular_model_one_id)
返回ModelTwo
s的预期集。这些产生与.includes
完全相同的SELECT
查询。
查看ModelOne.includes(:model_two)
返回的对象返回我期望的查询,以及消息:
ModelOne Load (2.3ms) SELECT "abcdfeghijklmnopqrst".* FROM "abcdfeghijklmnopqrst"
ModelTwo Load (2.6ms) SELECT "dfeghijklmnopqrstuvx".* FROM "dfeghijklmnopqrstuvx" WHERE "dfeghijklmnopqrstuvx"."ghijklmnopqrstuvxyza" IN (4, 1, 2, 3, 8, 5, 7, 6, 9, 10, 11, 14, 12, 13, 18, 15, 17, 16, 19, 20, 21, 22, 28, 23, 25, 24, 27, 26, 29, 30, 31, 34, 32, 33, 38, 35, 37, 36, 39, 40, 41, 42, 48, 43, 45, 44, 47, 46, 49, 50, 51, 52, 57, 53, 55, 54, 58, 56)
(Object doesn't support #inspect)
我认为这也意味着(Object doesn't support #association)
。
免责声明:
我已经阅读了相关的Stackoverflow条目和GitHub上的Rails问题,它们都没有出现在此特定问题上,至少尝试实施工作中提供的建议的尝试。
我已经更改了将foreign_key
添加到迁移中的直接references
的应用,该迁移产生了完全相同的行为。
我当然可以将适当的查询写入数据库或不急切的加载相关模型,但是这似乎效率低下的WRT开发人员时间是通过手写的JOIN
来解析结果,还需要处理(更多((更多(真实模型具有多个列和一个以上的关联。
,所以我做了一个模块来围绕该问题,随时可以在您想要的可怕混淆的DBS中使用...准备黑客:
module AliasedAttrsHelper
def find_by_includes_aliased(id_col, id_match, *associations)
# get foreign key column name
class_name = self.to_s
fk_column = class_name[0]
class_name.split('').each_with_index do |char,i|
next if i == 0
fk_column += char.match(/[A-Z]/) ? "_#{char}" : char
end
fk_column += '_id'
fk_column.downcase!
# make id string and table name
id_column = self.attribute_aliases[id_col.to_s]
id_table = self.table_name
# make the set of associations to query on
associations.map! do |ass|
name = ass.to_s.singularize.capitalize.constantize
{ class: name, table: name.table_name, fk: name.new.attribute_aliases[fk_column] }
end
# perform the composite query transaction
klass = ""
ActiveRecord::Base.transaction do
klass = self.find_by_sql("SELECT c.* FROM #{id_table} AS c WHERE c.#{id_column} = '#{id_match}'")[0]
associations.each do |ass|
ass[:class].find_by_sql("SELECT b.* FROM #{ass[:table]} AS b JOIN #{id_table} as c ON c.id = b.#{ass[:fk]} WHERE c.#{id_column} = '#{id_match}'")
end
end
klass
end
end