为啥 Ruby on Rails 不使用 .includes() 加载我的关联对象?

Posted

技术标签:

【中文标题】为啥 Ruby on Rails 不使用 .includes() 加载我的关联对象?【英文标题】:Why isn't Ruby on Rails loading my associated objects with .includes()?为什么 Ruby on Rails 不使用 .includes() 加载我的关联对象? 【发布时间】:2021-12-13 02:54:46 【问题描述】:

我的问题

我正在尝试通过我的 Ruby on Rails 应用程序中的外键关联检索数据。主表中的数据已正确加载,但关联对象未加载且始终为nil

背景信息(迁移、数据库表和模型类)

我目前正在处理两个表:

eval_forms user_details

这些表是通过 Rails 迁移创建的。 user_details 表是通过单次迁移创建的:

class CreateUserDetails < ActiveRecord::Migration[5.2]
  def change
    create_table :user_details do |t|
      t.string :eduPersonPrincipalName, unique: true
      t.string :DisplayName, default: 'NULL'
      t.string :Email, default: 'NULL'
      t.string :Role, default: 'Student'
      t.boolean :hasAppointment, default: '0'
      t.timestamps
    end
  end

  def self.down
    drop_table :user_details
  end
end

并且 eval_forms 表已经进行了一些迁移来创建和更新它:

class CreateEvalForms < ActiveRecord::Migration[6.1]
  def change
    create_table :eval_forms do |t|
      t.belongs_to :form_builder, foreign_key: 'form_builder_id'
      t.belongs_to :course, foreign_key: 'course_id'
      t.string :Description
      t.datetime :OpenDate
      t.datetime :CloseDate

      t.timestamps
    end
  end
end
class UpdateEvalForms < ActiveRecord::Migration[6.1]
  def change
    add_column :eval_forms, "Author_user_details_id", :bigint, null: false
    add_foreign_key :eval_forms, :user_details, column: "Author_user_details_id"

    add_column :eval_forms, "Year", :integer
    add_column :eval_forms, "Semester", :string
    add_column :eval_forms, "IsArchived", :boolean
  end
end

我知道外键设置正确,因为它在 mysql 中正确列出。以下是来自 MySQL 的 2 个表及其关系的参考:

另外,我已经在我的 Rails 应用程序中设置了模型类。

eval_form:

class EvalForm < ApplicationRecord
  has_many :eval_forms_roles
  has_many :roles, through: :eval_forms_roles
  has_many :eval_forms_courses
  has_many :courses, through: :eval_forms_courses
  has_many :eval_responses
  has_many :eval_reminders
  belongs_to :user_detail

  validates :formName, presence: true
  validates :formData, presence: true
end

用户详细信息:

class UserDetail < ApplicationRecord
  has_one :la_detail
  has_many :eval_responses
  has_many :eval_forms
end

那么怎么了?

最后,这里是从数据库中检索对象的代码以及出现错误的部分。

我的控制器操作:

 def index
    #  list *all* existing evaluation forms, with options to filter by OpenDate, CloseDate, etc (todo)
    @EvalForms = EvalForm.includes(:user_detail)
  end

我的看法:

<td><%= ef.user_detail.DisplayName %></td>

我的错误:

NoMethodError in Evaluations::EvalForms#index undefined method `DisplayName' for nil:NilClass

提取源位置:&lt;td&gt;&lt;%= ef.user_detail.DisplayName %&gt;&lt;/td&gt;

重述问题

总之,尽管我在控制器操作中使用了 .includes() 语句,但为什么没有检索到关联的 user_detail 对象,我真的很困惑。我对 Ruby 和 Rails 都很陌生,但是应用程序中的其他部分看起来与此类似并且可以正常工作,所以我看不出我的问题是什么。

【问题讨论】:

您需要告诉协会这种关系是非常规的,例如belongs_to :user_detail 应该是 belongs_to :user_detail, foreign_key: :another_user_details_id(同样需要在 UserDetail 中的 has_many 上应用) 你到底为什么要t.string :DisplayName, default: 'NULL'?这会将默认值设置为字符串 'NULL' 而不是实际的 null/nil,这将搞砸 Ruby 中的任何 WHERE IS NULL 查询和真值检查。 @engineersmnky 谢谢,就是这样! @max 不知道,我和我的团队继承了这个项目 【参考方案1】:

我将首先使用常规命名,在 Rails 中表示 snake_case everywhere:

class CreateUserDetails < ActiveRecord::Migration[5.2]
  def change
    create_table :user_details do |t|
      t.string  :edu_person_principal_name, unique: true
      t.string  :display_name 
      t.string  :email
      t.string  :role, default: 'Student'
      t.boolean :has_appointment, default: false # let the driver handle conversion
      t.timestamps
    end
  end
end
class UpdateEvalForms < ActiveRecord::Migration[6.1]
  def change
    change_table :eval_forms do |t|
      t.belongs_to :author_user_details, foreign_key:  to_table: :user_details 
      t.integer    :year # consider using `YEAR(4)` instead
      t.string     :semester
      t.boolean    :is_archived, default: false
    end
  end
end

如果您继续使用 camelCasePascalCase 的奇怪组合,您将需要显式配置所有关联,您将失去约定优于配置的所有优势。我根本不推荐这个,除非你坚持使用遗留数据库作为开发人员混淆和错误的万无一失的秘诀。

如果您在没有明确收件人(self)的情况下调用PascalCase 方法,也会收到缺少常量的错误:

class EvalForm < ApplicationRecord
  def short_description
    # uninitialized constant Description (NameError)
    Description.truncate(27, separator: ' ')
  end
end

虽然你可以用self.Description.truncate(27, separator: ' ') 解决这个问题,但它仍然很臭。

在这种情况下,如果您想调用列 author_user_details_id 而不是源自名称的 user_details_id,您需要将关联配置为使用非常规名称:

class EvalForm < ApplicationRecord
  belongs_to :user_detail, foreign_key: :author_user_details_id
end
class UserDetail < ApplicationRecord
  has_many :eval_forms, foreign_key: :author_user_details_id
end

如果您的架构的其余部分看起来像这样,您将不得不全面执行此操作。

【讨论】:

感谢您的提示,我和我的团队继承了这个项目,我们都没有使用 ruby​​ 或 rails 的经验。明确指定 foreign_key: :author_user_details_id 修复它! 在这种情况下,我真的会优先考虑用测试覆盖代码并准备好 Rubucop 之类的工具。从不知道自己在做什么的人那里继承代码是有风险的。

以上是关于为啥 Ruby on Rails 不使用 .includes() 加载我的关联对象?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的链接不适用于我的 ruby​​ on rails 网站?单击时链接保持在同一页面上

为啥 Ruby on Rails 的 URL Helper 在我的 URL 中加上句点?

为啥 Ruby on Rails 的 Enumerable 显示计数为 3 但“.each”仅打印出项目 1 次

2020ruby和ruby on rails想说再爱你不容易:安装rails失败解决办法

如何提高 Ruby On Rails 性能

Ruby On Rails 使用错误的版本执行