Я использую Rails 3.2.8 и сталкиваюсь с точно такой же проблемой.
Похоже, что вы пытаетесь сделать (назначить / обновить существующую сохраненную запись дляbelongs_to
ассоциация (user
) новой несохраненной родительской модели (Blogger
) просто невозможна в Rails 3.2.8 (или Rails 2.3.8, если на то пошло, хотя I надеюсь вы уже обновились до 3.x) ... не без некоторых обходных путей.
Я нашел 2 обходных пути, которые, кажется, работают (в Rails 3.2.8). Чтобы понять почему они работают, вы должны сначала понять код, в котором это вызывает ошибку.
Понимание, почему ActiveRecord вызывает ошибку ...
В моей версии activerecord (3.2.8) код, который обрабатывает присвоение вложенных атрибутов для belongs_to
ассоциации, можно найти в lib/active_record/nested_attributes.rb:332
и выглядит следующим образом:
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, assignment_opts = {})
options = self.nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes)
elsif attributes['id'].present? && !assignment_opts[:without_protection]
raise_nested_attributes_record_not_found(association_name, attributes['id'])
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
if respond_to?(method)
send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
else
raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
end
end
end
в операторе if
, если он видит, что выпередал идентификатор пользователя (!attributes['id'].blank?
), он пытается получить существующую запись user
от блоггера user
association (record = send(association_name)
, где association_name - :user
).
Но так как это недавно построенный объект Blogger
, blogger.user изначально будет nil
, поэтому он не будетassign_to_or_mark_for_destruction
вызов в той ветви, которая обрабатывает обновление существующего record
.Это то, что нам нужно обойти (см. Следующий раздел).
Итак, он переходит к 1-й ветви else if
, которая снова проверяет наличие идентификатора пользователя (attributes['id'].present?
).Он присутствует, поэтому он проверяет следующее условие: !assignment_opts[:without_protection]
.
Поскольку вы инициализируете свой новый объект Blogger с помощью Blogger.new(params[:blogger])
(то есть без передачи as: :role
или without_protection: true
),по умолчанию используется assignment_opts
из {}
.!{}[:without_protection]
имеет значение true, поэтому он переходит к raise_nested_attributes_record_not_found
, что является ошибкой, которую вы видели.
Наконец, если ни один из двух других, если ветви были взяты, он проверяет, следует ли отклонить новую записьи (если нет) приступает к созданию новой записи.Это путь, который следует в случае «создать нового пользователя, если он еще не существует», о котором вы упомянули.
Обходной путь 1 (не рекомендуется): without_protection: true
Первый обходной путь, о котором я подумал - но не рекомендую, - это присвоить атрибуты объекту Blogger, используя without_protection: true
(Rails 3.2.8).
Blogger.new(params[:blogger], without_protection: true)
Таким образом, он пропускает1-й elsif
и переходит к последнему elsif
, который создает нового пользователя со всеми атрибутами из параметров, , включая :id
.На самом деле, я не знаю, приведет ли это к обновлению существующей пользовательской записи так, как вы хотели (вероятно, нет - не слишком много тестировал этот вариант), но, по крайней мере, это позволяет избежать ошибки ...:)
Обходной путь 2 (рекомендуется): установите self.user
в user_attributes=
Но обходной путь, который я бы порекомендовал более, заключается в том, чтобы фактически инициализировать / установить ассоциацию user
из параметра: idтак что используется первая ветка if
и она обновляет существующую запись в памяти так, как вы хотите ...
accepts_nested_attributes_for :user
def user_attributes=(attributes)
if attributes['id'].present?
self.user = User.find(attributes['id'])
end
super
end
Чтобы иметь возможность переопределить средство доступа к вложенным атрибутамвот так и позвоните super
, вам нужно будет либо использовать пограничные Rails, либо включить патч обезьяны, который я выложил на https://github.com/rails/rails/pull/2945. В качестве альтернативы, вы можете просто позвонить assign_nested_attributes_for_one_to_one_association(:user, attributes)
прямо из вашего установщика user_attributes=
вызова super
.
Если вы хотите сделать это всегда, создайте новую запись пользователя и не обновите существующего пользователя ...
В моем случаеЯ решил, что я диЯ не хочу, чтобы хотел, чтобы люди могли обновлять существующие записи пользователей из этой формы, поэтому я использовал небольшое изменение вышеупомянутого обходного пути:
accepts_nested_attributes_for :user
def user_attributes=(attributes)
if user.nil? && attributes['id'].present?
attributes.delete('id')
end
super
end
Этот подход также предотвращает возникновение ошибки., но делает это немного по-другому.
Если в параметрах передается идентификатор, вместо того, чтобы использовать его для инициализации ассоциации user
, я просто удаляю переданный идентификатор, чтобы он вернулся кпостроение пользователя new
из оставшихся параметров пользователя.