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