Невозможно массово назначить защищенные атрибуты для создания вложенной модели 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| %>