views:

380

answers:

1

I am a student in the process a building an Android app that can post a GPS track into a Rails application. I would like to do things the "Rails" way and take advantage of the REST. My rails application basically has 3 models at this point: users, tracks, and points. A user has_many tracks and a track has_many points. A track also has a total distance. Points have a latitude and longitude. I have successfully been able to create an empty track with:

curl -i -X POST -H 'Content-Type: application/xml' -d '<track><distance>100</distance></track>' http://localhost:3000/users/1/tracks

Whoo hoo! That is pretty cool. I am really impressed that rails do this. Just to see what would happen I tried the following:

curl -i -X POST -H 'Content-Type: application/xml -d '<track><distance>100</distance><points><point><lat>3</lat><lng>2</lng></point></points></track>' http://localhost:3000/users/1/tracks

Fail! The server spits back:

Processing TracksController#create (for 127.0.0.1 at 2010-04-14 00:03:25) [POST] Parameters: {"track"=>{"points"=>{"point"=>{"lng"=>"2", "lat"=>"3"}}, "distance"=>"100"}, "user_id"=>"1"} User Load (0.6ms) SELECT * FROM "users" WHERE ("users"."id" = 1)

ActiveRecord::AssociationTypeMismatch (Point(#-620976268) expected, got Array(#-607740138)): app/controllers/tracks_controller.rb:47:in `create'

It seems my tracks_controller doesn't like or understand what it's getting from the params object in my tracks_controller.rb:

def create
    @track = @user.tracks.build(params[:track])

My xml might be wrong, but at least Rails seems to be expecting a Point from it. Is there anyway I can fix TracksController.create so that it will be able to parse xml of a track with nested multiple points? Or is there another way I should be doing this entirely?

A: 

Track.points= is a magic method that expects any number of Point objects. It adds and removes objects from the collection. You get this behaviour when you define a has_many association. This is why you're seeing ActiveRecord::AssociationTypeMismatch.

What you actually want to do is accept nested attributes for the associated collection.

class Track < ActiveRecord::Base
  has_many :points

  accepts_nested_attributes_for :points
end

Sending accepts_nested_attributes_for will provide you with the Track.points_attributes magic method. It expects an array of hashes, where each hash is the attributes for building the nested object.

curl -i -X POST -H 'Content-Type: application/xml' -d '<track><distance>100</distance><points_attributes><lat>3</lat><lng>2</lng></points_attributes></track>' http://localhost:3000/users/1/tracks

The Rails log may look something like this.

Processing TracksController#create
  Parameters: {"track"=>{"distance"=>"100"}{"points_attributes"=>[{"lat"=>"3","lng"=>"2"}]}}

ActiveRecord::NestedAttributes::ClassMethods API documentation.

Tate Johnson
Wow, this seems like it might work! Unfortunately, I am now getting an error involving "with_indifferent_access" being called on the value passed. Any advice?Processing TracksController#create (for 127.0.0.1 at 2010-04-14 10:46:27) [POST] Parameters: {"track"=>{"points_attributes"=>{"lng"=>"2", "lat"=>"3"}, "distance"=>"100"}, "user_id"=>"1"} User Load (0.6ms) SELECT * FROM "users" WHERE ("users"."id" = 1) NoMethodError (undefined method `with_indifferent_access' for "2":String): app/controllers/tracks_controller.rb:47:in `create'
joecan
If I change your XML I can insert a single point: "curl -i -X POST -H 'Content-Type: application/xml' -d '<track><distance>1000</distance><points_attributes><point><lat>39</lat><lng>29</lng></point></points_attributes></track>' http://localhost:3000/users/1/tracks"Unfortunately, this doesn't work for 2: "curl -i -X POST -H 'Content-Type: application/xml' -d '<track><distance>1000</distance><points_attributes><point><lat>39</lat><lng>29</lng></point><point><lat>39</lat><lng>29</lng></point></points_attributes></track>' http://localhost:3000/users/1/tracks"How do I format the XML for 2?
joecan
Okay, I think I've go it now. The following XML seems to work. <track> <distance>1000</distance> <points_attributes> <lat>39</lat><lng>29</lng> </points_attributes> <points_attributes> <lat>2</lat><lng>5</lng> </points_attributes></track>YEAH! Thank you soo much for pointing me in the right direction.
joecan
Sorry, I realise now that I forgot to give an example with many points. Looks like you worked it out anyway! :-)
Tate Johnson