I'm developing an API that deals with localities. I have a table that has been build as a self-referential and contains a parent_id
column. I have so far 144 records in that table.
Our client app needs to receive nested localities. Since I'm using Rails, I was trying at first to use the power of serializers to do it for me:
class LocalitySerializer < ActiveModel::Serializer
attributes :id, :name, :postcode, :kind, :latitude, :longitude, :parent_id
has_many :childrens
end
While it does works, it does not seems to be efficient. First, the log shows massive SQL queries to the database. Of course I've tried to eager load data using something like Locality.includes(:childrens).all
but it did not works and has no effect (maybe I did something wrong?)
I ended up writing a recursive function that takes the full list of localities (Locality.all
) and check each element's parent_id to nest them. So I removed the association in LocalitySerializer:
class LocalitySerializer < ActiveModel::Serializer
attributes :id, :name, :postcode, :kind, :latitude, :longitude, :parent_id
end
and build it separately within it's own serializer:
class DictionarySerializer < ActiveModel::Serializer
def attributes
hash = super
hash[:localities] = localities
hash
end
private
def localities
Rails.cache.fetch('localities.json', expires_in: 7.days) do
localities = ActiveModel::ArraySerializer.new(Locality.all, each_serializer: LocalitySerializer).as_json
build_hierarchy(localities, [], nil)
end
end
def build_hierarchy(source, target_array, n)
source.select { |h| h[:parent_id] == n }.each do |h|
h.except!(:parent_id)
target_array << h.clone.merge(childrens: build_hierarchy(source, [], h[:id]))
end
target_array
end
end
While this is a bit faster, it is still very slow IMHO. Note that I set a low-level cache here to make it faster on subsequent request. I haven't actually made any benchmark (just base my word as end user perception), but I'll be happy to do it if it could help anyone to figure out a faster way to generate it.