1

The two models involved here are the Item and Inventory models, having a "has_many :through => :categorizations" relationship to each other. Namely, each item has and belongs to inventory(s) and vise versa. The problem is that the "inventories" attribute is not saved to database when updating the item, while other non-nested attributes can be properly updated. Your help is much appreciated 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 (the angular controller for all templates)

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>

Server Response when editing the inventories attributes of an item

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.1ms) BEGIN (0.1ms) COMMIT Completed 204 No Content in 17ms (ActiveRecord: 0.5ms)

1 Answer 1

1

Just to make your params easier to read I reformatted them:

{"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}}

So now you can see that "inventories" is at the top level of your params (so in your controller that array would be at params[:inventories]) and it's called "inventories"

For accepts_nested_attributes_for, what Rails expects is for that array to be in params[:item][:inventories_attributes]

Now, there happened to be a question about this very thing on the rails-core mailing list this week, and an answer pointing to AngularJS Rails Resource which, amongst other goodies, has a nestedAttribute method for doing the correct serialization of these parameters.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the quick response. Is there any way I can get it serialized properly without installing the angularjs-rails-resource gem?
You're new to StackOverflow, so let me point you to What should I do when someone answers my question?. Now to your followup question, yes of course, you could write your own before_filter in your controller to alter your params, you could add the logic to your items_params method, you could add the logic to your model. I recommend the gem, hence my answer.
Sorry for not voting up your answer.. it's just that StackOverflow requires 15 reputations to vote and I've got only 6 :-( (I don't understand why they set threshold for voting! ) I am not only new to StackOverflow, but also rails as well... would you please tell me how to set the before_action or alter my params in details? I have struggled a whole day on this and my head kind of stopped spinning right now.. much thanks T.T
You should ask a new question for your new problem, although I'd recommend checking out the Guides and the API docs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.