19

I have a weird data model situation to start with, so maybe my whole approach is wrong. Here's what I'm doing:

I have a class called Bird and a simple class called Color. Conceptually, each bird has two to_many associations to Color, one for male colors and one for female colors. The way I've handled this is to use a join model called BirdColoration that belongs to a bird and a color and has an additional boolean field to tell if the coloration is for male or female. So each bird actually has a to_many relationship to BirdColoration as well as a to_many to Color :through BirdColoration. If this sounds reasonable, then continue reading. Otherwise, stop and tell me why it's wrong!

I need to be able to dump the birds table as json. Previously, when each bird only had one to_many association to colors, I could just use :include to include each bird's colors in the json dump. Now, I'm including the BirdColorations in the dump, but I still need to get at the color models themselves. I could separately include each bird's colors and colorations and then match them up while parsing, but I would much rather just include each coloration's color directly. Something like

      format.json  { render :json => @birds.to_json(:include => [{:bird_colorations => :color}, :seasons, :habitats, :image_holders]) }

The above doesn't work, however. I think that this should be possible. Can anyone point me in the right direction for how to handle this?

For now, I'll just include each bird's color and colorations separately and match them up in parsing. At least I know that will work.

Thanks!

CharlieMezak
  • 5,999
  • 1
  • 38
  • 54

2 Answers2

51

I found the answer here. The syntax for the :include option in to_xml and to_json is different than that for ActiveRecord's find method. To include nested resources in this way, you pass in a hash instead of an array. The correct method call for me looks like:

      format.json  { render :json => @birds.to_json(:include => {:bird_colorations => {:include => :color}, :seasons => {}, :habitats => {}, :image_holders => {}}) }

Compare to the one in my question to see the difference. For resources for which you don't want to include sub-resources, just pass an empty hash as the value for it's symbolized name.

Live and learn!

CharlieMezak
  • 5,999
  • 1
  • 38
  • 54
  • me too @brutuscat. Saved my bacon! – Rots Sep 27 '13 at 03:23
  • The code in your question looks so much cleaner, this is so counterintuitive. Especially since if you didn't have the bird_colorations nesting color, the questions example would work. Thanks for posting your solution. Rails should fix this... – pizza247 Oct 07 '13 at 22:27
2

If you have a complex JSON structure, it's better to override serializable_hash or as_json in your model than to try to do it all through render :json.

So something like

def serializable_hash(options = nil)
  options ||= {}
  bird = {:name => name, ...}
  bird[:seasons] = seasons.serilizable_hash
  bird[:colors] = ... # whatever complex logic you want to get to the colors
  bird
end

Both functions just need to return a hash.

icecream
  • 1,435
  • 12
  • 20
  • Thanks! I'll take a look at this. If I'm happy with the output I'm getting from render :json, is there any other reason to override those methods (performance, maybe)? All other things being equal, my instinct is to let the built-in functionality take care of things for me. – CharlieMezak Dec 16 '10 at 15:09
  • I don't think you will get a performance increase because render json calls as_json/serializable_hash in the background. It's just a good design philosophy called "fat models, thin controllers," most elegantly discussed here: http://stackoverflow.com/questions/2550376/rails-skinny-controller-vs-fat-model-or-should-i-make-my-controller-anorexic – icecream Dec 16 '10 at 22:15