#ruby-on-rails #postgresql #nested-attributes #has-many #rails-console
#ruby-on-rails #postgresql #вложенные атрибуты #имеет-много #rails-консоль
Вопрос:
Я работал над внедрением новой модели, которая принадлежит одной из существующих моделей наших веб-приложений. В конце концов, я хочу создать вложенную форму. Я понимаю, что форма и сильные параметры могут иметь свой собственный набор проблем, но в настоящее время я изо всех сил пытаюсь заставить модели вести себя так, как я ожидал бы в консоли rails.
Rails 4.2.7, Postgres DB
ОБНОВЛЕНИЕ — 3.10.16 — Все еще пытаюсь найти правильное решение, но внесли некоторые изменения
Мы работаем со школами и округами, и в данном конкретном случае речь идет об опросах и о том, как опрос присваивается школе и округу. До сих пор опрос назначался округу с помощью модели SurveyAssignment, и некоторая логика предполагала, что все школы в округе также были «назначены» опросу. Теперь мы хотим иметь возможность добавить больше детализации в SurveyAssignment и разрешить некоторую специфику на уровне школы.
Итак, я создал модель SchoolSurveyAssignment и начал устанавливать биты на место.
Вот соответствующая информация о модели:
class District < ActiveRecord::Base
...
has_many :schools, dependent: :destroy
has_many :survey_assignments, dependent: :destroy
...
end
class School
...
belongs_to :district
has_many :school_survey_assignments
has_many :survey_assignments, :through => :school_survey_assignments
...
end
class SurveyAssignment
belongs_to :district
belongs_to :survey
has_one :survey_version, through: :survey
has_many :school_survey_assignments, inverse_of: survey_assignment
has_many :schools, :through => :school_survey_assignments
accepts_nested_attributes_for :school_survey_assignments
attr_accessor :survey_group, :survey_version_type, :survey_version_id, :school_survey_assignments_attributes
validates :survey_id, presence: true
end
class SchoolSurveyAssignment
belongs_to :survey_assignment, inverse_of: :school_survey_assignments
belongs_to :school
attr_accessor :school_id, :survey_assignment_id, :grades_affected, :ulc_affected
validates_presence_of :survey_assignment
validates :school_id, presence: true, uniqueness: {scope: :survey_assignment_id}
end
Соответствующий код контроллера:
class SurveyAssignmentsController < ApplicationController
before_action :set_district
before_action :set_survey_assignment, only: [:show, :edit, :update, :destroy]
respond_to :html, :json, :js
def new
@new_survey_assignment = SurveyAssignment.new()
@district.schools.each do |school|
@new_survey_assignment.school_survey_assignments.build(school_id: school.id)
end
end
def create
@survey_assignment = SurveyAssignment.new(survey_assignment_params)
if @survey_assignment.save
flash[:notice] = "Survey successfully assigned to #{@district.name}"
else
flash[:alert] = "There was a problem assigning this survey to #{@district.name}"
end
redirect_to district_survey_assignments_path(@district)
end
def survey_assignment_params
params.require(:survey_assignment).permit(:survey_id, :status, :survey_version_id, school_survey_assignments_attributes: [:id, :survey_assignment_id, :school_id, grades_affected: [], ulc_affected: []]).tap do |p|
p[:district_id] = @district.id
p[:school_year] = session[:selected_year]
end
end
def set_district
@district = District.find(params[:district_id])
end
Here is the relevant schema info:
create_table "school_survey_assignments", force: :cascade do |t|
t.integer "survey_assignment_id"
t.integer "school_id"
t.integer "grades_affected", default: [], array: true
t.string "ulc_affected", default: [], array: true
end
add_index "school_survey_assignments", ["school_id"], name: "index_school_survey_assignments_on_school_id", using: :btree
add_index "school_survey_assignments", ["survey_assignment_id"], name: "index_school_survey_assignments_on_survey_assignment_id", using: :btree
create_table "survey_assignments", force: :cascade do |t|
t.integer "district_id"
t.integer "survey_id"
t.integer "status"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "school_year"
t.integer "last_response_status_id"
end
add_index "survey_assignments", ["district_id"], name: "index_survey_assignments_on_district_id", using: :btree
Once these were in place, I stepped into my rails console and attempted the following:
2.3.1 :002 > sa1 = SurveyAssignment.create(district_id: 3, survey_id: 508, school_year: 2017)
(0.2ms) BEGIN
SQL (0.7ms) INSERT INTO "survey_assignments" ("district_id", "survey_id", "school_year", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["district_id", 3], ["survey_id", 508], ["school_year", 2017], ["created_at", "2016-09-30 21:30:20.205144"], ["updated_at", "2016-09-30 21:30:20.205144"]]
(7.2ms) COMMIT
=> #<SurveyAssignment id: 369, district_id: 3, survey_id: 508, status: nil, created_at: "2016-09-30 21:30:20", updated_at: "2016-09-30 21:30:20", school_year: 2017, last_response_status_id: nil>
2.3.1 :003 > sa2 = SurveyAssignment.create(district_id: 3, survey_id: 508, school_year: 2017)
(0.3ms) BEGIN
SQL (0.4ms) INSERT INTO "survey_assignments" ("district_id", "survey_id", "school_year", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["district_id", 3], ["survey_id", 508], ["school_year", 2017], ["created_at", "2016-09-30 21:30:30.701197"], ["updated_at", "2016-09-30 21:30:30.701197"]]
(0.5ms) COMMIT
=> #<SurveyAssignment id: 370, district_id: 3, survey_id: 508, status: nil, created_at: "2016-09-30 21:30:30", updated_at: "2016-09-30 21:30:30", school_year: 2017, last_response_status_id: nil>
So now, I’ve successfully created two Survey Assignments. I’m now going to create two School Survey Assignments off of sa1:
2.3.1 :004 > [{school_id: 5}, {school_id: 6}].each do |ssa|
2.3.1 :005 > sa1.school_survey_assignments.create(ssa)
2.3.1 :006?> end
(0.2ms) BEGIN
SchoolSurveyAssignment Exists (2.4ms) SELECT 1 AS one FROM "school_survey_assignments" WHERE ("school_survey_assignments"."school_id" = 5 AND "school_survey_assignments"."survey_assignment_id" = 369) LIMIT 1
SQL (0.4ms) INSERT INTO "school_survey_assignments" ("survey_assignment_id") VALUES ($1) RETURNING "id" [["survey_assignment_id", 369]]
(6.4ms) COMMIT
(0.6ms) BEGIN
SchoolSurveyAssignment Exists (0.4ms) SELECT 1 AS one FROM "school_survey_assignments" WHERE ("school_survey_assignments"."school_id" = 6 AND "school_survey_assignments"."survey_assignment_id" = 369) LIMIT 1
SQL (0.3ms) INSERT INTO "school_survey_assignments" ("survey_assignment_id") VALUES ($1) RETURNING "id" [["survey_assignment_id", 369]]
(0.4ms) COMMIT
=> [{:school_id=>5}, {:school_id=>6}]
2.3.1 :007 > sa1.save
(0.3ms) BEGIN
(0.4ms) COMMIT
=> true
Теперь, похоже, я успешно создал два SchoolSurveyAssignments с survey_assignment_id = 369 и school_ids = 5 и 6
2.3.1 :008 > sa1.school_survey_assignments
SchoolSurveyAssignment Load (0.3ms) SELECT "school_survey_assignments".* FROM "school_survey_assignments" WHERE "school_survey_assignments"."survey_assignment_id" = $1 [["survey_assignment_id", 369]]
=> #<ActiveRecord::Associations::CollectionProxy [#<SchoolSurveyAssignment id: 5, survey_assignment_id: 369, school_id: nil, grades_affected: [], ulc_affected: []>, #<SchoolSurveyAssignment id: 6, survey_assignment_id: 369, school_id: nil, grades_affected: [], ulc_affected: []>]>
Как вы можете видеть из ActivRecord ::Associations::CollectionProxy, оба SchoolSurveyAssignments были созданы с survey_assignment_id: 369, но с нулевым school_id . Это вызывает беспокойство, как кажется
- Игнорирование параметров, передаваемых в функцию create, и
- игнорирование проверки school_id
Еще один элемент, который я не понимаю, заключается в следующем:
2.3.1 :009 > SchoolSurveyAssignment.find(5).survey_assignment_id
SchoolSurveyAssignment Load (0.6ms) SELECT "school_survey_assignments".* FROM "school_survey_assignments" WHERE "school_survey_assignments"."id" = $1 LIMIT 1 [["id", 5]]
=> nil
2.3.1 :011 > SchoolSurveyAssignment.find(5).survey_assignment.id
SchoolSurveyAssignment Load (0.3ms) SELECT "school_survey_assignments".* FROM "school_survey_assignments" WHERE "school_survey_assignments"."id" = $1 LIMIT 1 [["id", 5]]
SurveyAssignment Load (0.4ms) SELECT "survey_assignments".* FROM "survey_assignments" WHERE "survey_assignments"."id" = $1 LIMIT 1 [["id", 369]]
=> 369
Вызов .survey_assignment_id должен вернуть атрибут в SchoolSurveyAssignment и дать 369 . .survey_assignment.id просто просто захватывает идентификатор родительского объекта. Я бы ожидал, что оба вернут одно и то же значение, но один возвращает nil .
Конечный вариант использования — создание формы назначения опроса, которая позволяет пользователю устанавливать атрибуты для нового назначения опроса, а также устанавливать атрибуты для X количества назначений школ (на основе количества школ в округе; варьируется от 2 до 15). Как только я получу лучшее представление о том, как взаимодействуют эти модели, я чувствую уверенность в выполнении этой цели, но поведение, которое я вижу, не имеет для меня смысла, и я надеялся найти некоторую ясность в реализации этих связанных моделей. Я чувствую, что я уклоняюсь от ответа, но мне не хватает ключевой детали.
Спасибо,
Алекс
Комментарии:
1. можете ли вы опубликовать код контроллера? Я предполагаю, что вы используете строгие параметры?
2. Я использую сильные параметры. Я обновлю код контроллера. Это может разоблачить мое невежество, но у меня создалось впечатление, что методы контроллера попадают только с истинными HTTP-запросами / маршрутизацией, и что запуск команд с консоли не повлияет на эти методы.
Ответ №1:
Попробуйте удалить свои attr_accessor
строки кода. attr_accessor
не следует использовать для атрибутов, которые сохраняются в базе данных, и это, вероятно, портит методы, которые ActiveRecord уже предоставляет по умолчанию, в результате чего эти атрибуты не сохраняются должным образом
class SurveyAssignment
belongs_to :district
belongs_to :survey
has_one :survey_version, through: :survey
has_many :school_survey_assignments, inverse_of: survey_assignment
has_many :schools, :through => :school_survey_assignments
accepts_nested_attributes_for :school_survey_assignments
validates :survey_id, presence: true
end
class SchoolSurveyAssignment
belongs_to :survey_assignment, inverse_of: :school_survey_assignments
belongs_to :school
validates_presence_of :survey_assignment
validates :school_id, presence: true, uniqueness: {scope: :survey_assignment_id}
end
Комментарии:
1. Привет, спасибо за помощь! Я попытался создать по вашему предложению, и он создает SurveyAssignment, но не school_survey_assignments. Я также пробовал, что api.rubyonrails.org документы говорят для примера «один ко многим» api.rubyonrails.org/v4.2/classes/ActiveRecord/NestedAttributes/… Это также привело к созданию назначения опроса, но не к созданию SchoolSurveyAssignments.
2. Основываясь на некоторых советах коллеги, я перешел к использованию пользовательского класса объектов формы для обработки создания назначения опроса и SchoolSurveyAssignments. Он соответствующим образом обрабатывает параметры, и я знаю, что получаю все необходимые параметры через SurveyAssignmentController и в этот объект. Тем не менее, он по-прежнему не сохраняет атрибуты в дочерней модели (school_survey_assignment). Похоже, у меня какая-то другая проблема, а не какая-либо проблема с вложенными формами / объектами формы
3. внес правку в исходное сообщение. попробуйте закомментировать / удалить
attr_accessor
строки4. Да, так оно и было. Я удалил строки attr_accessor в моей модели SchoolSurveyAssignment, и теперь мой объект form работает отлично. Я полагаю, я мог бы вернуться и заставить работать подход nested_attributes, но этот объект form кажется достаточно элегантным.
Ответ №2:
Для первого вопроса School и SurveyAssignment не знают друг друга, school_id становится нулевым. В вашем приложении эти модели косвенно связаны с m-to-n, так как насчет использования has_many через ассоциацию между моделями?
В модели School добавьте ниже:
has_many :survey_assignments, :through => :school_survey_assignments
В модели SurveyAssignments добавьте ниже:
has_many :schools, :through => :school_survey_assignments
Для последнего вопроса оба кода кажутся одинаковыми..
Комментарии:
1. спасибо, что указали на необходимую связь. Я добавил это, и я все еще сталкиваюсь с некоторыми проблемами. Я обновил код, чтобы отразить изменения. Что касается последнего вопроса, разница в .id против _id
2. Извините, я упустил это из виду, но оба .id и _id работают на моем компьютере. Ваша проблема все еще .id против _id? Кстати, в классе SurveyAssignment, inverse_of: survey_assignment отсутствует ‘:’ перед ‘survey_assignment’, я полагаю.
3. Спасибо за продолжение. Это отсутствует: была опечатка при переходе от моего редактора к Stackoverflow, но хороший улов.