Rendering large database record set as JSON in rails - ruby-on-rails

Is there a way to present a large record set in JSON format without consuming large amount of memory? For example I have the following query:
...
records = Records.where(query)
respond_to do |format|
format.html
format.json { render :json => records.to_json }
end
There are times that records will contain thousands of entries and JSON is strictly used for getting the data without using pagination and such data must fit inside the memory for it to be returned. A Record entry will also contain a lot of fields (I am using MongoDB/Mongoid) and it is necessary to include such fields.

It's almost always a bad idea to return every resource in a database.
You can respond with limited set of results and provide total number of records.
For example:
{
total: 503
records: [
{ id: 1 },
{ id: 2 }
]
}
And add possibility to use limit and offset parameters to iterate through all pages.
There is chapter named Pagination and partial response in free e-book Web API Design describes it.
I would recommend you to read that book. It contains only 30 pages.
upd:
In your case you are able to paginate results using limit and skip mongoid's methods.
records = Records.where(query).limit(params[:limit]).skip(params[:offset])
respond_to do |format|
format.html
format.json { render :json => { total: records.count, records: records }.to_json }
end

Related

Rails API get params to a custom method

Am trying to filter some data from database but its not getting params. This is my method:
def user_orders
orders = Order.select { | item | item[:user_id] == params[:id] }
if orders
render json: orders, status: :ok
else
render json: {error: "No orders available"}
end
end
This is the custom routing
get "/orders/user/:id", to: "orders#user_orders"
and the response is an empty array. However if I pass in a number in the method like so:
orders = Order.select { | item | item[:user_id] == 27 }
I get back the filtered array as expected. How can I pass in a dynamic ID from the routing?
EDIT: if your parameters look like this:
Parameters: { … "order"=>{…"user_id"=>27}}
… then you need to access the user_id as params[:order][:user_id].
Otherwise your conditional is comparing nil to 27, which will return false, and so nothing is ever selected.
If you’re querying an activerecord model, I also recommend that you use Order.where(item: {user_id: params[:id]}) to find your list of orders, so that and rails can cast to the right type as well.
What does the line in the rails log say? It should start with GET /user/orders and it should list what parameters have actually been received. Alternatively you can use puts to check the content of params.

postgres_ext-serializer each_serializer Rails 5 adding custom JSON meta data

I'm using Rails 5.2.3, postgres_ext (3.0.0), active_model_serializers (0.8.4), I am trying to render JSON data in the format of
{
total: 500,
totalNotFiltered: 500,
rows: [
...
]
}
I am able to get close but I am not able to get the additional custom fields added.
So far I have
respond_to do |format|
format.json { render json: #people, each_serializer: PersonSerializer, root: :rows, meta{total: 500, totalNotFiltered: 500}}
end
This gives the correct root :rows, but the meta data isnt being added.
The postgres_ext gem does great in getting the JSON rows correctly, down from 190ms av to 25ms av, so I want to use this. I have tried FastJSON (Netflix) but that is far slower and again not easy to format the output generated.
It might be something simple here but I can fathom how to add the 2 elements missing. Ideas or suggestions please
in array_serializer.rb of the postgres_ext-serializers-0.0.3 gem, I can hack this JSON string by adding the options I need, then added it as a patch for a temp solution. If someone has a better idea or way, please advise.
in the controller (this is for bootstraptable btw)
render json: #people, each_serializer: PersonSerializer, root: :rows, total: #people_count, totalNotFiltered: Person.all.count}
then serializer.rb
module IncludeMethods
def to_json(*)
root = #options.fetch(:root, self.class.root)
total = #options[:total]
totalNotFiltered = #options[:totalNotFiltered]
if ActiveRecord::Relation === object && root != false
total = "{\"total\":\"#{total}\",\"totalNotFiltered\":\"#{totalNotFiltered}\","
_postgres_serializable_array.sub('{', total)
else
super
end
end
end
Its not the best but it works for now

Cancancan Select Field

I am trying to customize a field on a cancancan object:
projectsAPI = Project.accessible_by(current_ability).select('projects.name, projects.price * 5 as new_price')
respond_to do |format|
format.json { render :json => {
:project_data => projectsAPI
}
}
The problem is when I check the API results, it just returns all the project fields, without any of the stuff in the .select statement.
When I check the log, the query has the custom fields in it, but also has the rest of the fields from projects.
How can I customize the fields in the json API return of the cancancan objects?
How does the .to_sql look like?
Have you tried something like this with a sub query? That is how I often solve CanCan issues in regards to the joins it makes.
projectsAPI = Project.where("projects.id IN (?)", Project.accessible_by(current_ability).select(:id).to_sql).select('projects.name, projects.price * 5 as new_price')

Returning JSON from Ruby on Rails api

I inherit a RoR api, that provides that to a angular app. I am new to RoR and I am having difficulties with the way the api returns data in json.
def getLocations
location = params[:name]
query = "select * from view_locations where lower(location_name) like lower('#{location}%') limit 4 "
propAll = Property.connection.select_all(query)
respond_to do |format|
if !propAll.blank?
format.json { render json: {status:"ok", data:propAll}}
else
format.json { render json: {status:"error"}}
end
end
end
This code returns this array:
[{"location_name":"Nuevo León","id":"19","quantity":"5988","type":"State"},{"location_name":"Naucalpan de Juárez, México","id":"09","quantity":"131","type":"City"},{"location_name":"Nayarit","id":"18","quantity":"91","type":"State"},{"location_name":"Nacajuca, Tabasco","id":"013","quantity":"20","type":"City"}]
Is there any way that I could return the JSON instead of an array?
If its not possible, how should I use the returned array in Angularjs, right now we are going through every array element and pushing it in another array .
Thanks, for any help provided
Alberto

Autocomplete action parses a large JSON file at each ajax call (make it faster)

I am trying to make an autocomplete model for cities and zipcode using Rails 4 and Jquery ui.
I have a large JSON file (about 500,000 lines) that contains an array (cp_autocomplete) that contains zipcode (CP) and cities (CITY).
In my current model, on every ajax query the autocomplete action reads the JSON file, parses it and then search for a zipcode or city, makes a list of nine zipcode+cities (if found) and render the list as json.
This works but it makes more than 2 seconds to render a list.
Any ideas to make it runs faster?
- How could I parse the json file once and not on every ajax query?
- I thought of caching the parsed json value and then delete it after 1 minute for example.
Here is my autocomplete action:
def autocomplete
require 'auto_compeletion_cp_city' #a class that has PostalCode and City attributes
postal_file = File.read("#{Rails.root}/public/postal_code_fr.json")
postal_parse = JSON.parse(postal_file)
#list = []
if !(ActionController::Base.helpers.sanitize(params[:Postalcode]).blank?)
search_term = ActionController::Base.helpers.sanitize(params[:Postalcode])
search = /\A#{search_term}/
postal_parse["cp_autocomplete"].each do |f|
if search.match("#{f['CP']}",0) && (#list.length < 9)
selected = AutoCompletionCpCity.new
selected.PostalCode = f['CP']
selected.City = f['CITY']
#list << selected
end
end
else
if !(ActionController::Base.helpers.sanitize(params[:city]).blank?)
search_term = ActionController::Base.helpers.sanitize(params[:city])
search = /\A#{Regexp.escape(search_term)}/i
postal_parse["cp_autocomplete"].each do |f|
if search.match("#{f['CITY']}",0) && (#list.length < 9)
selected = AutoCompletionCpCity.new
selected.PostalCode = f['CP']
selected.City = f['CITY']
#list << selected
end
end
end
end
respond_to do |format|
format.json {
render json: #list
}
end
end
Create a small module to house the postal code data. It can parse the file once when the process starts and hold onto it forever. Since postal codes don't change often (and this file is in your repo, so changing it requires a deploy), there's no reason to cache for just 1 minute.
module PostalCodes
DATA = JSON.parse(File.read("#{Rails.root}/public/postal_code_fr.json"))
end
This module can also be responsible for searching the codes and other behavior you need.
Before you get too deep in this, as with all performance optimization, you should verify the part of your code that's causing search to take 2 seconds. If it's the JSON parsing, caching the data will help, but if it's running a regular expression over ever postal code, you need a different solution.

Resources