Rails 4 + Angular 项目中的嵌套属性未保存到数据库

Nested attributes not saved to database in Rails 4 + Angular project

这里涉及的两个模型是 Item 和 Inventory 模型,它们之间有 "has_many :through => :categorizations" 关系。即,每个项目都有并属于库存,反之亦然。问题是 "inventories" 属性在更新项目时没有保存到数据库,而其他非嵌套属性可以正确更新。 非常感谢您的帮助 T.T

item.rb

class Item < ActiveRecord::Base
  has_many :checkouts, :dependent => :destroy
  has_many :categorizations
  has_many :inventories, :through => :categorizations
  accepts_nested_attributes_for :categorizations, :allow_destroy => true,     :reject_if => :all_blank
  accepts_nested_attributes_for :inventories

end

inventory.rb

class Inventory < ActiveRecord::Base
    has_many :categorizations
    has_many :items, :through => :categorizations
    accepts_nested_attributes_for :categorizations, :allow_destroy => true, :reject_if => :all_blank
end

items_controller.rb

  # PUT /items/1
  # PUT /items/1.json
  def update
    respond_to do |format|
      if @item.update(item_params)
        format.html { redirect_to @item, :notice => 'Item was successfully updated.' }
        format.json { respond_with(@item) }
      else
        format.html { render action: 'edit' }
        #format.json { render json: @item.errors, status: :unprocessable_entity }
        respond_with(@item)
      end
    end
  end
......
  def item_params
    params.require(:item).permit! 
  end

ItemsController.coffee(所有模板的 angular 控制器)

controllers = angular.module('controllers')
controllers.controller("ItemsController", [ '$scope', '$routeParams',     '$location','$resource','Flash','ngDialog'
  ($scope,$routeParams,$location,$resource,Flash,ngDialog)->
    #Item querying
    Item = $resource('/api/items/:itemId', { itemId: "@id", format: 'json' },
      {
        'get': { method: 'GET'},
        'save':   { method:'PUT'},
        'create': { method:'POST'},
        'update': { method:'PUT'},
        'duplicate': { url: 'api/items/:itemId/duplicate', method:'PUT'},
        'checkin': { url: '/api/items/:itemId/checkin', method:'PUT'}
        'checkin': { url: '/api/items/:itemId/checkin', method:'GET'},
        'checkout': { url: '/api/items/:itemId/checkout', method:'GET'},
        'checkout': { url: '/api/items/:itemId/checkout', method:'POST'}
      }
    )

Item.query().$promise.then ((items) ->
  $scope.items = items
), (errResponse) ->

#Inventory querying
Inventory = $resource('/api/inventories/:inventoryId', { inventoryId: "@id", format: 'json' },
  {
    'get': { method: 'GET'},
    'save':  {method:'PUT'},
    'create': {method:'POST'}
  }
)

Inventory.query().$promise.then ((inventories) ->
  $scope.inventories = inventories
), (errResponse) ->

$scope.saveItem = ->
  onError = (_httpResponse)-> flash.error = "Something went wrong"
  if $scope.item.id
    $scope.$apply ->
    $scope.item.$update({itemId: $scope.item.id}
      ( ()->
        console.log $scope.item.inventories
        ngDialog.close()
        $scope.openItemView($scope.item)
        $scope.warningUpdateItem() ),
      onError)
  else
    Item.create($scope.item,
      ( (newItem)->
        ngDialog.close()
        $scope.openItemView(newItem)
        $scope.warningCreateItem() ),
      onError
    )

edit_item.html.erb

  <div class="form-group" ng-class="{'has-warning has-feedback':errors.name}" ng-repeat="inventory in item.inventories">
    <label for="inventories">
      Inventory
    </label>
    <input type="text" name="inventories" class="form-control" ng-model='inventory.name' placeholder="Item inventories"></input>    
  </div>

编辑物品的库存属性时的服务器响应

Started PUT "/api/items/15?format=json" for ::1 at 2015-03-26 16:14:54 -0400
Processing by ItemsController#update as JSON
  Parameters: {"id"=>"15", "name"=>"arashi", "quantity"=>1, "notes"=>"",     "tags"=>"岚", "price"=>23, "purchase_date"=>nil, "needs_repair"=>false,     "repair_notes"=>"", "is_disposed"=>false, "disposed_on"=>nil, "is_out"=>nil, "created_at"=>"2015-03-26T13:17:36.000Z", "updated_at"=>"2015-03-26T19:48:21.000Z", "warranty_expires_on"=>nil, "inventories"=>[{"id"=>1007, "name"=>"Groups", "created_at"=>"2015-03-26T13:17:36.000Z", "updated_at"=>"2015-03-26T13:17:36.000Z"}], "item"=>{"id"=>"15", "name"=>"arashi", "quantity"=>1, "notes"=>"", "tags"=>"岚", "price"=>23, "purchase_date"=>nil, "needs_repair"=>false, "repair_notes"=>"", "is_disposed"=>false, "disposed_on"=>nil, "is_out"=>nil, "created_at"=>"2015-03-26T13:17:36.000Z",   "updated_at"=>"2015-03-26T19:48:21.000Z", "warranty_expires_on"=>nil}}
Can't verify CSRF token authenticity
  Item Load (0.3ms)  SELECT  `items`.* FROM `items` WHERE `items`.`id` = 15     LIMIT 1

(0.1 毫秒)开始 (0.1 毫秒)提交 17 毫秒内完成 204 无内容(ActiveRecord:0.5 毫秒)

为了让您的参数更易于阅读,我重新格式化了它们:

{"id"=>"15",
 "name"=>"arashi",
 "quantity"=>1,
 "notes"=>"",
 "tags"=>"岚",
 "price"=>23,
 "purchase_date"=>nil,
 "needs_repair"=>false,
 "repair_notes"=>"",
 "is_disposed"=>false,
 "disposed_on"=>nil,
 "is_out"=>nil,
 "created_at"=>"2015-03-26T13:17:36.000Z",
 "updated_at"=>"2015-03-26T19:48:21.000Z",
 "warranty_expires_on"=>nil,
 "inventories"=>
  [{"id"=>1007,
    "name"=>"Groups",
    "created_at"=>"2015-03-26T13:17:36.000Z",
    "updated_at"=>"2015-03-26T13:17:36.000Z"}],
 "item"=>
  {"id"=>"15",
   "name"=>"arashi",
   "quantity"=>1,
   "notes"=>"",
   "tags"=>"岚",
   "price"=>23,
   "purchase_date"=>nil,
   "needs_repair"=>false,
   "repair_notes"=>"",
   "is_disposed"=>false,
   "disposed_on"=>nil,
   "is_out"=>nil,
   "created_at"=>"2015-03-26T13:17:36.000Z",
   "updated_at"=>"2015-03-26T19:48:21.000Z",
   "warranty_expires_on"=>nil}}

所以现在您可以看到 "inventories" 位于您的参数的顶层(因此在您的控制器中该数组将位于 params[:inventories])并且它被称为 "inventories"

对于 accepts_nested_attributes_for,Rails 期望该数组位于 params[:item][:inventories_attributes]

现在,碰巧 a question about this very thing on the rails-core mailing list this week, and an answer pointing to AngularJS Rails Resource 有一个 nestedAttribute 方法来正确序列化这些参数。