Ruby ActiveRecord关联在访问子级时生成错误

时间:2018-06-27 18:24:36

标签: sql ruby-on-rails sql-server ruby activerecord

简介:我正在创建一个使用ActiveRecord和ActiveRecord SQL Server适配器访问商业应用程序数据库的独立(非Rails)应用程序。我无法更改数据库架构或数据库服务器。我已尝试更改以下名称以保护罪名。

基本模型类:

class AppRecord < ActiveRecord::Base
  after_initialize :parent_init
  attr_reader :_downcase_field_values
  attr_accessor :downcase_field_values

  self.primary_key = 'IRN'

  def parent_init
    set_downcase_field_values
  end

  def set_downcase_field_values
    @downcase_field_values ||= []
    @_downcase_field_values = self.attributes.keys.select { |att| att if (att.match(/IRN/) || att == "Id") }
    downcase_fields! self
  end

  def self.table_name
    "app.#{self}"
  end

  def to_h
    self.attributes.to_options
  end
end

模型类:

class ReportIndex < AppRecord
  after_find :init

  has_many :ReportIndexParameters, class_name: "ReportIndexParameters", foreign_key: "ReportIndexIRN", dependent: :destroy
  has_many :ReportProperties, class_name: "ReportProperties", foreign_key: "ReportIndexIRN", dependent: :destroy
  has_many :ReportLayouts, class_name: "ReportLayouts", foreign_key: "ReportIndexIRN", dependent: :destroy

  has_many :ReportIndexSeries, class_name: "ReportIndexSeries", foreign_key: "ReportIndexesIRN", dependent: :destroy
  has_many :ReportUserDefinedViews, class_name: "ReportUserDefinedViews", foreign_key: "BaseClassID", primary_key: "ClassId"

  def init
    self.downcase_field_values = %w(BaseClassID)
  end
end

class ReportProperties < AppRecord
  belongs_to :ReportIndex
  after_find :init
  has_many :ReportPropertySeriesFilters, class_name: "ReportPropertySeriesFilters", foreign_key: "ReportPropertiesIRN", dependent: :destroy
  has_many :ReportPropertyParameters, class_name: "ReportPropertyParameters", foreign_key: "ReportPropertiesIRN", dependent: :destroy
  def init
    self.downcase_field_values = %w(ClassID)
  end
end

ActiveRecord查询:

pp ReportIndex.includes(:ReportProperties).find_by(ReportName: report_name)

错误:

/usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader/association.rb:63:in `block in associated_records_by_owner': undefined method `association' for nil:NilClass (NoMethodError)
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/core.rb:367:in `init_with'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/persistence.rb:69:in `instantiate'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/querying.rb:50:in `block (2 levels) in find_by_sql'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/result.rb:55:in `block in each'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/result.rb:55:in `each'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/result.rb:55:in `each'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/querying.rb:50:in `map'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/querying.rb:50:in `block in find_by_sql'
    from /usr/local/bundle/gems/activesupport-5.1.6/lib/active_support/notifications/instrumenter.rb:21:in `instrument'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/querying.rb:49:in `find_by_sql'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/relation.rb:678:in `exec_queries'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/relation.rb:546:in `load'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader/association.rb:122:in `block in load_records'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader/association.rb:121:in `each'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader/association.rb:121:in `each_slice'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader/association.rb:121:in `each'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader/association.rb:121:in `flat_map'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader/association.rb:121:in `load_records'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader/association.rb:61:in `associated_records_by_owner'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader/collection_association.rb:8:in `preload'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader/association.rb:19:in `run'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:151:in `block (2 levels) in preloaders_for_one'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:149:in `each'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:149:in `map'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:149:in `block in preloaders_for_one'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:148:in `each'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:148:in `flat_map'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:148:in `preloaders_for_one'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:115:in `preloaders_on'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:102:in `block in preload'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:101:in `each'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:101:in `flat_map'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/associations/preloader.rb:101:in `preload'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/relation.rb:686:in `block in exec_queries'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/relation.rb:684:in `each'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/relation.rb:684:in `exec_queries'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/relation.rb:546:in `load'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/relation.rb:255:in `records'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/relation/finder_methods.rb:508:in `find_take'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/relation/finder_methods.rb:100:in `take'
    from /usr/local/bundle/gems/activerecord-5.1.6/lib/active_record/relation/finder_methods.rb:78:in `find_by'
    from /src/artest.rb:13:in `<main>'

但是,ActiveRecord生成的SQL似乎是正确的:

D, [2018-06-27T17:42:49.256232 #1] DEBUG -- :   SQL (33.3ms)  USE [APP]
D, [2018-06-27T17:42:50.017505 #1] DEBUG -- :   ReportIndex Load (40.5ms)  EXEC sp_executesql N'SELECT  [app].[ReportIndex].* FROM [app].[ReportIndex] WHERE [app].[ReportIndex].[ReportName] = @0  ORDER BY [app].[ReportIndex].[IRN] ASC OFFSET 0 ROWS FETCH NEXT @1 ROWS ONLY', N'@0 nvarchar(80), @1 int', @0 = N'Master Condemnation', @1 = 1  [["ReportName", nil], ["LIMIT", nil]]
D, [2018-06-27T17:42:51.043389 #1] DEBUG -- :   ReportProperties Load (133.9ms)  SELECT [app].[ReportProperties].* FROM [app].[ReportProperties] WHERE [app].[ReportProperties].[ReportIndexIRN] = '3cad6165-221e-4607-b5ad-01bc32f29157'

我怀疑这些违规行为是由于我在体系结构上设法弄错了一些原因。现有的模型代码可用于DELETEUPDATE操作,但不适用于SELECT。我欢迎蜂巢的头脑给我两美分。

1 个答案:

答案 0 :(得分:0)

我学到的东西:

  • ActiveRecord::Base子类化通常是一个坏主意,除非您真的知道自己在做什么,而我没有。在模型中的类之间共享代码的最佳方法是使用模块。子类化ActiveRecord::Base可能会带来意想不到的副作用,除非我非常误解,否则无论如何它不会使您共享回调代码。
  • 据我所知,这些选项对activerecord-sqlserver-adapter gem 5.1.6版本无效,与docs相反:

    ActiveRecord::ConnectionAdapters::SQLServerAdapter.lowercase_schema_reflection = true ActiveRecord::Base.table_name_prefix = prefix

    我通过在模块的included块中设置self.table_name来解决这两个问题。也许我做错了事,但是我无法使用这两个选项中的任何一个,并且实际上无法分辨所生成的SQL中的区别。结果,我仍然不得不处理CamelCase列名。
  • MSSQL的uniqueidentifier列不区分大小写。我正在编写此代码的应用程序的每个GUID均使用小写字母变体,因此给我的印象是我必须模仿这种行为,但是数据库确实 不在乎我花了很多时间试图使ActiveRecord自动downcase这些字段,并且我确实使它起作用,但是效果不佳。请参阅下一项。
  • 如果您尝试通过after_initafter_find等来更改主键的值,则会破坏关联。就我而言,我正在尝试 将主键从uniqueidentifier数据类型更改为字符串 带有downcase d个十六进制值的版本。这就是产生原始物的原因 导致我创建此帖子的错误。

代码:

注意:需要ActiveSupport::Inflector部分,以确保不需要复数的事物不会复数。有关更多信息,请参见ActiveSupport::Inflector文档。
ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.irregular 'index', 'index'
  inflect.irregular 'layoutml', 'layoutml'
  inflect.acronym 'ML'
end

module AppRecord
  extend ActiveSupport::Concern

  included do
    self.primary_key = 'IRN'
    self.table_name = "app.#{self.to_s.pluralize}"
  end

  def to_h
    self.attributes.to_options
  end
end

class ReportIndex < ActiveRecord::Base
  include AppRecord
  has_many :report_index_parameters, foreign_key: "ReportIndexIRN", dependent: :destroy
  has_many :report_properties, foreign_key: "ReportIndexIRN", dependent: :destroy
  has_many :report_layouts, foreign_key: "ReportIndexIRN", dependent: :destroy

  has_many :report_index_series, foreign_key: "ReportIndexesIRN", dependent: :destroy
  has_many :report_user_defined_views, foreign_key: "ClassId", primary_key: "BaseClassID"

  has_many :report_index_param_series_filters, through: :report_index_series
  has_many :report_property_series_filters, through: :report_index_series
  has_many :report_layout_images, through: :report_layouts
  has_many :report_layout_ml, through: :report_layouts
  has_many :report_property_parameters, through: :report_properties
end

class ReportProperty < ActiveRecord::Base
  include AppRecord
  has_many :report_property_series_filters, foreign_key: "ReportPropertiesIRN", dependent: :destroy
  has_many :report_property_parameters, foreign_key: "ReportPropertiesIRN", dependent: :destroy
end

未解决的问题:

  • 是否有办法更改ActiveRecord猜测外键的方案?此应用程序中的外键不符合"#{class.to_s.underscore}_id"约定,而是符合"#{class.to_s}IRN"约定。如何干燥我的代码,并避免命名关联中的每个常规外键?
  • 是否可以声明我所有的has_many关联都应设置dependent: :destroy?再次,这将帮助我干燥代码。

感谢到目前为止发表评论的所有人。