Почему Ruby on Rails не загружает мои связанные объекты с помощью .includes()?

#mysql #ruby-on-rails #ruby

Вопрос:

моя проблема

Я пытаюсь извлечь данные с помощью ассоциации внешних ключей в своем приложении Ruby on Rails. Данные из основной таблицы загружены правильно, но связанные объекты не загружаются и загружаются всегда nil .

Справочная информация (Миграции, таблицы баз данных и классы моделей)

В настоящее время я работаю с двумя таблицами:

  • eval_формы
  • данные пользователя

Таблицы создаются с помощью миграции 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.

форма оценки:

 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

 

user_detail:

 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

Извлеченное местоположение источника: <td><%= ef.user_detail.DisplayName %></td>

Повторение проблемы

В заключение я действительно не понимаю, почему связанные объекты user_detail не извлекаются, несмотря на мое утверждение .includes() в действии контроллера. Я довольно новичок в Ruby, а также в Rails, но в приложении есть и другие разделы, которые выглядят аналогично этому и работают правильно, поэтому я не вижу, в чем моя проблема.

Комментарии:

1. Вам нужно сообщить ассоциации, что эта связь является нетрадиционной, например belongs_to :user_detail , должна быть belongs_to :user_detail, foreign_key: :another_user_details_id ( то же самое нужно будет применить к has_many in UserDetail ).

2. С какой стати ты это делаешь t.string :DisplayName, default: 'NULL' ? Это установит значение по умолчанию для строки 'NULL' вместо фактического null/nil, что приведет к ошибкам в любых запросах с нулевым значением и проверках истинности в Ruby.

3. @engineersmnky спасибо, это было все!

4. @макс понятия не имею, мы с моей командой унаследовали этот проект

Ответ №1:

Я бы начал с использования обычных имен, которые в Rails означают snake_case везде:

 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
 

Если вы продолжите использовать странное сочетание camelCase и PascalCase вам нужно будет явно настроить все ваши ассоциации, и вы потеряете все преимущества соглашения по сравнению с конфигурацией. Я бы вообще не рекомендовал это делать, если только вы не застряли с устаревшей базой данных, так как это верный рецепт для путаницы и ошибок разработчиков.

Вы также получите отсутствующую постоянную ошибку, если вызовете PascalCase методы без явного получателя (self):

 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
 

Если остальная часть вашей схемы выглядит так, вам придется делать это по всем направлениям.

Комментарии:

1. спасибо за совет, мы с моей командой унаследовали этот проект, и ни у кого из нас нет опыта работы с ruby или rails. явное указание foreign_key: :author_user_details_id исправлено!

2. В этом случае я бы действительно отдал приоритет покрытию кода тестами и внедрению таких инструментов, как Rubucop. Наследование кода от кого-то, кто не знал, что он делает, рискованно.