tl;博士:
所有表和列(包括外键)的数据库模糊处理导致在使用所有rails查询从postresql数据库急切加载关联模型的结果时出现问题。 includes
,joins
等返回错误而不是应该错误。
开始:
我有一个完全混淆的数据库,每个表和列都使用20个字符的拉丁字母随机字符串命名,包括foreignkeys。 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
相关联的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集。与SELECT
一样,这些查询会向数据库生成完全相同的.includes
查询。
查看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
的应用程序,这会产生完全相同的行为。
当然,我可以将正确的查询写入数据库,或者不急于加载关联的模型,但这对于开发人员来说似乎效率低下,因为解析手写JOIN
的结果也需要处理并且(更多)真实模型有多个列和多个关联。
答案 0 :(得分:0)
所以我制作了一个模块来解决这个问题,随意使用你可怕的混乱数据库......准备好hackiness:
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