Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backbone and Rails associations: Avoiding JSON HashWithIndifferentAccess errors

I'm trying to get my backbone associations working inside a rails app , and I'm having difficulty when trying to update existing models. Specifically, Rails throws the following error:

Started PUT "/posts/2" for 127.0.0.1 at 2012-01-04 02:36:14 +1000
Processing by PostsController#update as JSON Parameters: {"post"=>{"content"=>"Seconderona", "created_at"=>"2012-01-03T10:51:09Z", "id"=>2, "title"=>"Second test post", "updated_at"=>"2012-01-03T10:51:09Z", "comments"=>[{}]}, "id"=>"2"} Post Load (0.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT 1 [["id", "2"]] WARNING: Can't mass-assign protected attributes: id Completed 500 Internal Server Error in 15ms

ActiveRecord::AssociationTypeMismatch (Comment(#70104367824560) expected, got ActiveSupport::HashWithIndifferentAccess(#70104367278120)):
app/controllers/posts_controller.rb:62:in block in update'
app/controllers/posts_controller.rb:61:in
update'

A few things:

This is triggered on (for example):

c = window.router.comments.models[0]
c.save({content: 'Changed content'})

Also, yes, 'accepts_nested_attributes_for' is present in the model.

The (offending) code below is taken pretty much verbatim from thougtbot's "backbone on rails" ebook, and I've also tried following the documentation for the backbone-relational gem. Both raise this error. Any ideas appreciated, code below

RAILS 'POST' MODEL

class Post < ActiveRecord::Base
  has_many :comments

  accepts_nested_attributes_for :comments

  def as_json(options = nil)
    super((options || {}).merge(include: { comments: { only: [content] } } ))
  end
end

RAILS 'COMMENT' MODEL

class Comment < ActiveRecord::Base
  belongs_to :post

  accepts_nested_attributes_for :post

  def as_json(options = nil)
    super((options || {}).merge(include: { post: { only: [:title, :content]}}))
  end
end

BACKBONE POST CONTROLLER

class Backbonerelationaldemo.Models.Post extends Backbone.Model
  paramRoot: 'post'

  initialize: () ->
    comments = new Backbonerelationaldemo.Collections.CommentsCollection
    comments.reset(@get('comments'))
    @setComments(comments)

  setComments: (comments) ->
    @comments = comments


class Backbonerelationaldemo.Collections.PostsCollection extends Backbone.Collection
  model: Backbonerelationaldemo.Models.Post
  url: '/posts'

BACKBONE COMMENTS CONTROLLER

class Backbonerelationaldemo.Models.Comment extends Backbone.Model
  paramRoot: 'comment'

  initialize: () ->
    if (@has('post')) 
      @setPost(new Backbonerelationaldemo.Models.Post(@get('post')))

  setPost: (post) ->
    @post = post

class Backbonerelationaldemo.Collections.CommentsCollection extends Backbone.Collection
  model: Backbonerelationaldemo.Models.Comment
  url: '/comments'
like image 396
PlankTon Avatar asked Jan 03 '12 16:01

PlankTon


1 Answers

I dealt with the same issue recently. It's actually not a HashWithIndifferentAccess error: it has to do with how accepts_nested_attributes_for expects params.

When you declare accepts_nested_attributes_for :comments, Rails looks for a parameter call comments_attributes on the incoming params.

The problem is that your JSON representation coming from Backbone has a "comments" property instead of a "comments_attributes" property.

You could fix it on the Backbone side by adding a toJSON function to your Post model:

# in your Post model
toJSON: ->
  attrs = _.clone(@attributes)
  attrs.comments_attributes = _.clone(@attributes.comments)
  delete attrs.comments
  attrs

Or you could handle it in your Rails controller:

# in your Posts controller
def update
  params[:comments_attributes] = params.delete(:comments) if params.has_key? :comments
  # call to update_attributes and whatever else you need to do
 end

Hopefully this helps.

like image 82
satchmorun Avatar answered Oct 06 '22 01:10

satchmorun