I'm optimizing some slow transactions in our Rails application and a I see significant time spent rendering JSON views:
Rendered welcome/index.json.rabl (490.5ms)
Completed 200 OK in 1174ms (Views: 479.6ms | ActiveRecord: 27.8ms)
Assuming that the API call is returning exactly the data it needs to return, What is the fastest way to render JSON in rails?
We are using Rabl because of the ability to share code easily, but we aren't tied to it.
Currently oj seems to be the fastest renderer - beating yajl (according to the oj author's comparison).
Oj is used by default in the latest multi_json (and rails uses mutli_json by default), so swapping to oj should be as simple as adding the following to your Gemfile:
# Gemfile
gem "oj"
Then each time you call render, it will now use oj.
render :json => { ... } # uses multi_json which uses oj
Oj also provides additional specific interfaces, if you want even more performance, but sticking to multi_json makes it easier to swap out gems in the future.
Note that if you have any { ... }.to_json calls - these will not be upgraded to use oj unless you call Oj.mimic_JSON in an initializer.
Rails 3 uses multi_json, but it only uses it for json decoding, not encoding. Json encoding/rendering/generation uses ActiveSupport JSON library's to_json method, therefore is always slow (even if you uses Oj gem).
You can explicitly rendering using multi_json by doing:
render :json => MultiJson.dump(#posts)
Or you can try rails-patch-json-encode gem (by me) which will use multi_json by default. It will affect all build-in to_json methods, so make sure all the tests passes.
Rabl uses multi_json for compatibility across platforms and doesn't use the quite fast Yajl library by default. Rabl's config documentation explains the solution:
# Gemfile
gem 'yajl-ruby', :require => "yajl"
In the event that still isn't performant enough, you might want to explore a different JSON serializer like oj. You could also instrument your render and see where the bottleneck exists.
Netflix recently released a new JSON rendering library which is supposedly 25-40 times faster than the default library. Announcement. Code. You'll need to create a new Serializer to take advantage of it, but for people who are impacted, that doesn't seem to be a big hurdle.
Related
So the coffeescript code shown in the attached view runs without a problem in my Rails 4.2.0 view, but is breaking in the Rails 5.1.2 upgrade with the messages shown: I have upgraded the gems shown below:
coffee-rails from 4.1.0 to 4.2.2
coffee-script-source from 1.9.0 to 1.12.2
And the haml_coffee_assets has always pointed to the master branch as shown below so no changes there:
gem 'haml_coffee_assets', git: "https://github.com/netzpirat/haml_coffee_assets"
Before I start to make crazy changes, I want to see if anyone else has run into similar problems. I am moving away from coffeescript in favor of the plain javascript in my newer Rails applications and have no problems with embedded javascript code. However, this is an older application that is not used heavily and I want to invest minimum time in maintaining it. There is too much Coffeescript code in it for me to convert it easily. If there is a site that will let me convert the coffeescript to Javascript without too many problems, then I would like to. The best solution would be to just keep going forward with what I have.
Has anyone run into this problem? Any ideas?
Here is the code:
- if #well.has_sense_graph?
#sensitivity.tab
= render :partial => "shared_wells/show_sensitivity", :locals => {:sensitivity => sense_hash[:sensitivity], :offset => sense_hash[:offset] }
%br
#discount_chart
:coffeescript
$ ->
model = new Backbone.Model
forecastTickInterval: #{graph_hash[:forecast_tick_interval]}
typeWell: #{#well.to_json}
x_labels: #{graph_hash[:price_array]}
disc_rate_array: #{graph_hash[:disc_rate_array]}
disc_pv10_array: #{graph_hash[:disc_pv10_array]}
ngl_array: #{ngl_vol_array}
view = new VGStream.Views.TypeWells.Show(
model: model
).render()
VGStream.App.router = new VGStream.Routers.Tabs()
VGStream.App.currentView = view
Backbone.history.start()
_.defer ->
$(document).scrollTop(0)
So I solved this problem in a practical way. Given the goal that I have to get rid of CoffeeScript code in my application and replace it with equivalent Javascript code, I did the following:
All pages where I have embedded CoffeeScript code in the haml pages as shown above in the code that I have provided, I replaced it with equivalent Javascript code as shown below:
:javascript
$(function() {
...
});
Note that I left the 'pure' coffeescript files, i.e., with extension '.coffee' stored in assets/javascript/... ' folders alone, since my more immediate goal is to get the Rails 5.1.2 upgrade done as quickly as possible.
For some reason that I do not know (nor do I care to know), the embedded coffeescript code no longer works for me as it did in the Rails 4.2.0 version. But since I do not care about CoffeeScript anymore, this hybrid solution is acceptable to me.
I'd like to use datatables in my rails application but I'd like to avoid prepraring JSON data by myself, so I'm looking for a gem that does it. Ideally I'd pass an ActiveRecored Relation and the gem generated JSON that could be consumed by datatables, for example:
class ItemsController < ApplicationController
def index
# I fetch data I need (taking into account authorization, search etc.)
#items = Item.find_relevant_items
respond_to do |format|
# gem prepares JSON for datatables
format.json { ItemDatatable.new(#items) }
# ...
end
end
end
I'm aware that there are several gems availabe. However, they don't suit me: jquery-datatables-rails is just a wrapper for the JS and the others seem to be outdated or not maintained (ajax-datatables-rails, rails_datatables, simple_datatables).
Do you know about any gem that would serve data for datatables?
Ruby's JSON module is built-in, just require 'json' at the top of a Ruby script to make it available. See "How do I parse JSON with Ruby on Rails?" for more information.
Rails also includes JSON capability too. See "Understanding Ruby and Rails: Serializing Ruby objects with JSON" for info using JSON and Rails. Rails is a fast-moving platform so that might be a bit out of date, but it should get you started. "Lightning JSON in Rails" is a good read for things to pay attention to as you generate JSON.
"JSON implementation for Ruby" is a great reference for Ruby and JSON also.
The best way I have found to using datatables in a rails app is this:
jquery-datatables-rails gem to get all the assets datatables needs
ajax-datatables-rails gem to get the the JSON datatables expects when using server side pagination and searching.
There are other gems that make it easier to generate the JSON datatables need If you don't want to go through the trouble of making a new class like in the RailsCast. You can find them here
RABL and JBuilder both spring to mind.
They are json template handlers and awesomely simple to use and both are equally powerfull enabling complete customisation of your json output.
There are railscasts on them both
http://railscasts.com/episodes/320-jbuilder
http://railscasts.com/episodes/322-rabl
and the sites
https://github.com/rails/jbuilder
https://github.com/nesquena/rabl
Both are well maintained.
Otherwise the datatables-rails gem is really the best option for you
http://railscasts.com/episodes/340-datatables?view=asciicast
UPDATE 2
It's the gem you have already looked at
As per railscast the gem is defined in the Gemfile
group :assets do
gem 'jquery-datatables-rails', github: 'rweng/jquery-datatables-rails'
gem 'jquery-ui-rails'
end
I strongly urge you to watch that railscast (http://railscasts.com/episodes/340-datatables?view=asciicast) as there are other configurations needed.
Does anyone have an idea on how to implement this (http://railscasts.com/episodes/256-i18n-backends) with MongoDB/Mongoid? My question is primarily about the initializer.rb file.
The docs of Mongo-I18n on github suggests the following using its 'MongoI18n::Store.new' method:
collection = Mongo::Connection.new['my_app_related_db'].collection('i18n')
I18n.backend = I18n::Backend::KeyValue.new(MongoI18n::Store.new(collection)
But how to do this if you don't want to use their plugin? Is there something like a Mongo::Store method?
I just did this exact same thing, except that I had trouble installing Mongo-I18n, because it has a dependency on a very old version of MongoDB.
To get around this, I copied the code from here into lib/mongo_i18n.rb.
You were on the right track with your initializer though, if you're using Mongoid - the best way forward is to do this:
require 'mongo_i18n'
collection = Mongoid.database.collection('i18n')
I18n.backend = I18n::Backend::KeyValue.new(MongoI18n::Store.new(collection))
Which tells the I18n backend to use a new collection (called i18n), but in the same database as the rest of your application.
Make sure you delete the Mongo_I18n gem out of your gemfile and run bundle before starting your server again.
You can access your store directly using:
I18n.backend.store
But to make it a little cleaner, I added this method to my I18n library:
# mongo_i18n.rb
def self.store
collection = Mongoid.database.collection('i18n')
MongoI18n::Store.new
end
So that I can access the store directly with:
MongoI18n.store
I did exactly like theTRON said, except that instead of require 'mongo_i18n' I added whole class MongoI18n::Store definition from Mongo_i18n gem directly to mongo initializer. It not such a big deal, because whole MongoI18n::Store is 41 lines long. Look here, why make dependancy from 41 lines gem ?
Rails 2.3.6 started using the fast new json library, yajl-ruby, "if available".
In the "JSON gem Compatibility API" section of the yajl-ruby readme it outlines a method to just drop in yajl-ruby inclusion and have the rest of the app seamlessly pick it up.
So, ideally, I'd like
Rails to use it
My gems to use it
My application code to use it
What's the easiest way to achieve this? My guess:
config.gem 'yajl-ruby', :lib => 'yajl/json_gem'
As the very first gem in environment.rb. Doing this doesn't result in any errors, but I'm not sure how to know if rails is picking it up for its own use.
Thanks!
John
I'd recommend using yajl-ruby's API directly instead of the JSON gem compatibility API mainly for the reason that the JSON gem's to_json method conflict with ActiveSupport and has had long-standing issues making them work together.
If you just do config.gem 'yajl-ruby', :lib => 'yajl' instead, you'll need to use Yajl::Parser and Yajl::Encoder directly to parse/encode objects. The advantage of this is you'll be certain there won't be any conflicts with method overrides and as such, be guaranteed your JSON encoding/parsing code will work as expected.
The disadvantage is if you're using any gems that use the JSON gem, they'll continue to do so but you're own code will use yajl-ruby.
If you wanted to, you could use your config.gem line, then in an initializer require 'yajl' so you'd have both API's loaded. The yajl/json_gem include will override anything that's using the JSON gem with yajl - to ensure this overrides those methods try to make sure require 'yajl/json_gem' happens last.
If you're using Rails 3, you can add this to an initializer:
ActionController::Renderers.add :json do |json, options|
json = Yajl.dump(json) unless json.respond_to?(:to_str)
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
self.content_type ||= Mime::JSON
self.response_body = json
end
To make sure render :json => ... calls use yajl-ruby as well.
Sorry if this isn't really answering your question but I wanted to at least give the suggestion of using yajl-ruby's API directly :)
I've just started using HTTParty, and i've encountered a problem in the way it builds the Hash from the XML replied by the server.
If i setup the following Builder template on the server:
xml.thunt :sendSubscriptionResult, :"xmlns:thunt" => "http://example.com/thunt", :status => #status
everything works well, i.e. the Hash built by HTTParty matches the XML generated by Builder, (the latter can be observed by making the same request via curl):
curl Request
curl -s -H "Accept: text/xml" -d "xml=`cat vendor/testxml/requests/sendsubscription.xml`" $SERVER/${name}
Reply as seen by curl
'<thunt:sendSubscriptionResult xmlns:thunt="http://example.com/thunt" status="alreadySubscribed" />'
HTTParty request
TreasureHunt.post('/sendsubscription', :query => { :xml => sub } )
Reply in HTTParty
{"thunt:sendSubscriptionResult"=>{"status"=>"alreadySubscribed", "xmlns:thunt"=>"http://example.com/thunt"}}
But, if in the Builder i specify that i want the sendSubscriptionResult element to have a text node:
xml.thunt :sendSubscriptionResult, "Hello, World", :"xmlns:thunt" => "http://example.com/thunt", :status => #status
(note the "Hello, World" addition) the two tools suddenly disagree.
curl
'<thunt:sendSubscriptionResult xmlns:thunt="http://example.com/thunt" status="alreadySubscribed">Hello, World</thunt:sendSubscriptionResult>'
HTTParty
{"thunt:sendSubscriptionResult"=>"Hello, World"}
As you can see, HTTParty has stripped all of the element's attributes, and has put only the text node in the resulting Hash
Is this a bug in HTTParty or am I doing something wrong?
Thanks!
Check out my post on the github issue for a resolution to your problem. http://github.com/jnunemaker/httparty/issues/#issue/14
I would go ahead and post your problems on the issues page for their Github project.
http://github.com/jnunemaker/httparty/issues
It already looks like there are some issues surrounding some XML issues. But it's definitely the best way to communicate directly with the developers and to provide them feedback.
Under the hood, httparty currently uses multi_xml for XML parsing. multi_xml will use, based on the speed of the available parsing gem: Ox, LibXML, Nokogiri, REXML. That is, it will choose Ox first, if you have it installed. You can also specify which parser to use.
There were some bugs recently resolved in multi_xml, particularly with regard to arrays.
I suggest you point bundler to the GitHub repo to get the very latest version of multi_xml, in your Gemfile like this:
gem 'multi_xml', :git => 'https://github.com/sferik/multi_xml'
gem 'ox'
gem 'httparty'
Then where you are going to use httparty (for example, in your Sinatra server) you would do this:
require 'bundler/setup'
Note that with this setup, multi_xml will not show up in your "gem list" output, but it will work.