Невозможно массово назначить защищенные атрибуты для создания вложенной модели has_many с помощью Devise
Я смотрел RailsCast, еще одно вложенное видео атрибутов, много сообщений SO, и некоторое время боролся с этим, но я все еще не могу понять. Надеюсь, это что-то крошечное.
У меня есть две модели: User
(созданная Devise) и Locker
(aka, список желаний), и я пытаюсь создать Locker
для User
при регистрации. В моей регистрационной форме есть поле для имени их нового Locker
(aptly called :name
), которое я пытаюсь назначить шкафчику, который создается при новой регистрации пользователя. Все, с чем я когда-либо встречался, это:
WARNING: Can't mass-assign protected attributes: locker
Я пробовал каждую комбинацию accepts_nested_attributes
и attr_accesible
в обеих моделях, но пока ничего не работает. Я вижу из журналов, что он обрабатывается методом Devise # create, и я знаю, что Devise недостаточно умен, чтобы создавать мои модели, как я хочу:)
Здесь соответствующие биты двух моих моделей:
# user.rb
class User < ActiveRecord::Base
attr_accessible :username, :email, :password, :password_confirmation, :remember_me, :locker_attributes
# Associations
has_many :lockers
has_many :lockups, :through => :lockers
# Model nesting access
accepts_nested_attributes_for :lockers
end
и
# locker.rb
class Locker < ActiveRecord::Base
belongs_to :user
has_many :lockups
has_many :products, :through => :lockups
attr_accessible :name, :description
end
# lockers_controller.rb (create)
@locker = current_user.lockers.build(params[:locker])
@locker.save
Я предполагаю, что мне нужно переопределить метод Devise create
, чтобы как-то заставить это работать, но я совершенно новичок в рельсах и привык к черной "магической" природе всего этого.
Если кто-нибудь может мне помочь, я был бы невероятно благодарен. Уже потрачено слишком много времени на это, как есть:)
EDIT: Я понял, что я пропустил что-то в своей проблеме. Моя модель Locker
имеет три атрибута - name
, description
(не обязательно) и user_id
, чтобы связать ее с User
. Для моей регистрационной формы требуется только name
, поэтому я не перебираю все атрибуты в моей вложенной форме. Может ли это иметь отношение к моей проблеме?
EDIT 2: Я также выяснил, как переопределить метод Devise RegistrationsController#create
, я просто не знаю, что туда положить. Разработать целую resource
вещь для меня не имеет смысла, и просмотр исходного кода для RegistrationsController
тоже не помог мне.
И для бонусных очков: когда пользователь отправляет форму входа с недопустимыми данными, поле Locker
всегда возвращается в исходное состояние, в то время как заполняются поля, имя пользователя и адрес электронной почты. Может ли это также быть легко исправлено? Если да, то как?
Ответы
Ответ 1
Кто-то помог мне разобраться с решением более "Rails 4'y" с strong attributes
и как переопределить Devise sign_up_params
(чтобы поймать все данные, поступающие из моей регистрационной формы).
def sign_up_params
params.require(:user).permit(:username, :email, :password, :lockers_attributes)
end
Добавление Gemfile: gem 'strong_parameters'
Комментируя оператор attr_accessible
в моем файле user.rb
, поскольку явно сильные параметры устраняют необходимость в объявлениях attr_accessible
.
# attr_accessible :username, :email, :password, :password_confirmation, :lockers
И//правильный способ построения Locker перед отправкой формы: в начале вложенной формы:
<%= l.input :name, :required => true, label: "Locker name", :placeholder => "Name your first locker" %>
Еще раз спасибо за вашу помощь. Я знаю, что такой вопрос трудно ответить, не видя всей кодовой базы.
Ответ 2
во-первых, у вас есть опечатка:
attr_accessible :locker_attributes
должно быть множественным:
attr_accessible :lockers_attributes
тогда стандартный способ использования nested_attributes
:
<%= form_for @user do |f| %>
<%# fields_for will iterate over all user.lockers and
build fields for each one of them using the block below,
with html name attributes like user[lockers_attributes][0][name].
it will also generate a hidden field user[lockers_attributes][0][id]
if the locker is already persisted, which allows nested_attributes
to know if the locker already exists of if it must create a new one
%>
<% f.fields_for :lockers do |locker_fields| %>
<%= locker_fields.label :name %>
<%= locker_fields.text_field :name %>
<% end %>
<% end %>
и в вашем контроллере:
def new
@user = User.new
@user.lockers.build
end
def create
# no need to use build here : params[:user] contains a
# :lockers_attributes key, which has an array of lockers attributes as value ;
# it gets assigned to the user using user.lockers_attributes=,
# a method created by nested_attributes
@user = User.new( params[:user] )
end
в качестве побочного примечания, вы можете не создавать новые блокировки для новых пользователей в контроллере по-разному:
-
создайте метод factory на User
или переопределите new
или используйте обратный вызов after_initialize
, чтобы каждый новый экземпляр пользователя автоматически создавал блокиратор
-
передать конкретный объект fields_for
:
<% f.fields_for :lockers, f.object.lockers.new do |new_locker_fields| %>