#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
inUserDetail
).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. Наследование кода от кого-то, кто не знал, что он делает, рискованно.