Manuel
Manuel

Reputation: 11469

Create json document from ActiveRecord and avoid N+1

I'm trying to create a json array (string actually) based on my db structure. I have the following relationship:

Country > State > City

The way I'm doing it now is very innefficient (N+1):

data = "[" + Country.all.map{ |country|
  {
    name: country.name,
    states: country.states_data
  }.to_json
}.join(",") + "]"

Then on the Country model:

def states_data
  ret_states = []
  states.all.each do |state|
    ret_states.push name: state.name, cities: state.cities_data
  end
  ret_states
end

Then on the State model:

def cities_data
  ret_cities = []
  cities.all.each do |city|
    ret_cities.push name: city.name, population: city.population
  end
  ret_cities
end

How can I do this more efficiently?

Upvotes: 0

Views: 145

Answers (2)

Prasad Surase
Prasad Surase

Reputation: 6574

u can provide the model to be included when converting to json.

country.to_json(:include => {:states => {:include => :cities}})

check http://apidock.com/rails/ActiveRecord/Serialization/to_json for

Upvotes: 1

Wizard of Ogz
Wizard of Ogz

Reputation: 12643

Eager load the states and cities. Just be careful because this could take up a lot of memory for large datasets. See documentation here http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations. Whenever possible I like using joins in addition to includes to fetch all data at once.

#to_json will also serialize Arrays for you, so you don't need to manually add bits of JSON.

Your code from above could be altered like so:

data = Country.joins(:states => :cities).includes(:states => :cities).all.map{ |country|
  {
    name: country.name,
    states: country.states_data
  }
}.to_json

But you could also remove the need for the _data methods.

data = Country.joins(:states => :cities).includes(:states => :cities).to_json(
  :only => :name,
  :include => {
    :states => {
      :only => :name,
      :include => {
        :cities => {
          :only => [:name, :population]
        }
      }
    }
  }
)

That is pretty ugly, so you may want to look into overriding #as_json for each of your models. There is a lot of information about that available on the web.

Upvotes: 1

Related Questions