1

I'm currently having a hard time finding a solution for a problem I have in a Ruby on Rails project.

I have the following model structure:

class Room < ActiveRecord::Base
  has_many :photos
end

class Photo < ActiveRecord::Base
  belongs_to :room
  has_many :points
end

class Point < ActiveRecord::Base
  belongs_to :photo
end

I want to query the database so that I get something that rendered as JSON would have this structure:

{
        "rooms" :
            [
                //room 1
                {
                    "id": 1,
                    "param1": "dummy",
                    "photos":
                        [
                            {
                                "id": 3,
                                "title": "test photo",
                                "points":
                                    [
                                        {"id": 1, "text": "test point"},
                                        {"id": 2, "text": "test point"}
                                    ]
                            }
                        ]

                },
                //room 2
                {
                    "id": 2,
                    // ...
                }
            ]
    }
    

Basically, I want to make a query that returns all the info, in all the models, structured according to the models relations.

I searched for a couple of hours and can't find any simple solution for this... (not even complicated one). I don't know if I'm not using the correct search keywords for my problem, but I really couldn't find anything that works...

For a partial solution (to get only rooms and photos), I tried this:

Room.all.includes(:photos)

From what I found, I thought it could be the solution, but it just returns the information about the rooms, not the photos, so probably this is a totally different approach to the one I need.

norim_13
  • 303
  • 2
  • 11

3 Answers3

3

@dankohn's answer is correct for eager loading nested associations, but I believe you need a bit more for it to work with to_json. The issue i believe is to_json just looks at the original set of objects and doesn't know about the eager loaded associations. But luckily to_json is a method call that can take the includes as a set hash of options. (Had an issue at in the original code that @norim_13 helped correct. Thanks!!)

Room.all.to_json({:include => { :photos => {:include => { :points => {}} }}})

https://www.tigraine.at/2011/11/17/rails-to_json-nested-includes-and-methods Rails: Eager loading as_json includes

Here is the documentation for as_json http://apidock.com/rails/ActiveModel/Serializers/JSON/as_json

and here in the source for to_json you can see it passes its hash of options to as_json http://apidock.com/rails/Hash/to_json

Community
  • 1
  • 1
Alex Bezek
  • 467
  • 2
  • 7
1

Revised:

What do you get with:

Room.all.includes(photos: :points).to_json

For more info, see Rails - Nested includes on Active Records?

Community
  • 1
  • 1
Dan Kohn
  • 33,811
  • 9
  • 84
  • 100
  • "Association named 'points' was not found on Room; perhaps you misspelled it?" It is associating the table "points" with "rooms" and raising an error because the are not directly connected... – norim_13 May 16 '16 at 00:04
  • I also tried just: Room.all.includes(:photos).to_json , but although it doesn't raise any errors, it returns only info on the Rooms, not the Photos inside it. – norim_13 May 16 '16 at 00:08
  • I've seen your revised answered and checked the link, and it seems that your suggestion is what I need, but it doesn't work... The result is, again, only the Rooms info... (same as Room.all ) – norim_13 May 16 '16 at 00:51
  • Yes, of course. I don't have it in my model, I just had forgotten to delete it when I was writing the question as I was copy-pasting from the previous classes. – norim_13 May 16 '16 at 11:31
0

If you get to breaking point, it might be easier just writing a series of .each loops and then just building up the json string manually around them..

Room.all.each do |room|
  room.photos.each do |photo|
    photo.points.each do |point|
      // inner loop
    end
  end
end
Jamie G
  • 156
  • 1
  • 8
  • Be careful of this. This would produce some crazy N + 1 queries if you don't eager load. – Alex Bezek May 16 '16 at 03:33
  • Thanks for the suggestion. Even though someone already gave me the most "correct" approach, I probably will be using something like this so that I can give custom keys to some of the objects. – norim_13 May 16 '16 at 12:32