Rails 4 Не обновлять вложенные атрибуты через JSON
Я просмотрел связанные вопросы и по-прежнему имею проблему с обновлением вложенных атрибутов в рельсах 4 через JSON, возвращенными с моего front-end AngularJS.
Вопрос: В приведенном ниже коде JSON передается от AngularJS к модели кандидата в моем приложении Rails4. Модель кандидата имеет много Works, и я пытаюсь обновить модель Works через модель кандидата. По какой-то причине модель Works не обновляется, и я надеюсь, что кто-то может указать, что мне не хватает. Благодарим за помощь.
Здесь json в front-end AngularJS для кандидата:
{"id"=>"13", "nickname"=>"New Candidate", "works_attributes"=>[
{"title"=>"Financial Analyst", "description"=>"I did things"},
{"title"=>"Accountant", "description"=>"I did more things"}]}
Rails затем переводит этот JSON в следующий, добавляя заголовок кандидата, но не включает вложенные атрибуты под заголовком кандидата, а не обновляет свойства works_attributes через модель-кандидат:
{"id"=>"13", "nickname"=>"New Candidate", "works_attributes"=>[
{"title"=>"Financial Analyst", "description"=>"I did things"},
{"title"=>"Accountant", "description"=>"I did more things"}],
"candidate"=>{"id"=>"13", "nickname"=>"New Candidate"}}
В кандидате_controller.rb содержится простое обновление:
class CandidatesController < ApplicationController
before_filter :authenticate_user!
respond_to :json
def update
respond_with Candidate.update(params[:id], candidate_params)
end
private
def candidate_params
params.require(:candidate).permit(:nickname,
works_attributes: [:id, :title, :description])
end
end
Модель кандидата .rb включает следующий код, определяющий отношение has_many с моделью работ:
class Candidate < ActiveRecord::Base
## Model Relationships
belongs_to :users
has_many :works, :dependent => :destroy
## Nested model attributes
accepts_nested_attributes_for :works, allow_destroy: true
## Validations
validates_presence_of :nickname
validates_uniqueness_of :user_id
end
И, наконец, модель works.rb определяет другую сторону отношения has_many:
class Work < ActiveRecord::Base
belongs_to :candidate
end
Я ценю любую помощь, которую вы можете предоставить, поскольку я уверен, что мне не хватает чего-то довольно простого.
Спасибо!
Ответы
Ответ 1
Я также работал с JSON API между Rails и AngularJS. Я использовал такое же решение, как RTPnomad, но нашел способ не иметь жесткого кода атрибутов include:
class CandidatesController < ApplicationController
respond_to :json
nested_attributes_names = Candidate.nested_attributes_options.keys.map do |key|
key.to_s.concat('_attributes').to_sym
end
wrap_parameters include: Candidate.attribute_names + nested_attributes_names,
format: :json
# ...
end
Обратитесь к этому issue в Rails, чтобы узнать, исправлена ли эта проблема.
Обновление 10/17
В ожидании слияния PR здесь: rails/rails # 19254.
Ответ 2
Я выяснил один из способов решения моей проблемы на основе документации по рельсам: http://edgeapi.rubyonrails.org/classes/ActionController/ParamsWrapper.html
В принципе, Rails ParamsWrapper включен по умолчанию для переноса JSON из front-end с корневым элементом для потребления в Rails, поскольку AngularJS не возвращает данные в корневом обернутом элементе. В приведенной выше документации содержится следующее:
"В моделях ActiveRecord с параметром no: include или: exclude он будет только обертывать параметры, возвращаемые методом класса attribute_names."
Это означает, что я должен явно включать вложенные атрибуты со следующим утверждением, чтобы гарантировать, что Rails включает в себя все элементы:
class CandidatesController < ApplicationController
before_filter :authenticate_user!
respond_to :json
wrap_parameters include: [:id, :nickname, :works_attributes]
...
Пожалуйста, добавьте еще один ответ на этот вопрос, если есть лучший способ передать данные JSON между AngularJS и Rails
Ответ 3
Вы можете также обернуть параметр паттерна обезьяны, чтобы всегда включать вложенные_трибуты, помещая это, например, в wrap_parameters.rb
initializer:
module ActionController
module ParamsWrapper
Options.class_eval do
def include
return super if @include_set
m = model
synchronize do
return super if @include_set
@include_set = true
unless super || exclude
if m.respond_to?(:attribute_names) && m.attribute_names.any?
self.include = m.attribute_names + nested_attributes_names_array_of(m)
end
end
end
end
private
# added method. by default code was equivalent to this equaling to []
def nested_attributes_names_array_of model
model.nested_attributes_options.keys.map { |nested_attribute_name|
nested_attribute_name.to_s + '_attributes'
}
end
end
end
end