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
方法来正确序列化这些参数。
这里涉及的两个模型是 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
方法来正确序列化这些参数。