All Projects → amatsuda → Jb

amatsuda / Jb

Licence: mit
A simple and fast JSON API template engine for Ruby on Rails

Programming Languages

ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to Jb

Jsonapi Rails
Rails gem for fast jsonapi-compliant APIs.
Stars: ✭ 242 (-77.49%)
Mutual labels:  json, rails
Acts as api
makes creating API responses in Rails easy and fun
Stars: ✭ 506 (-52.93%)
Mutual labels:  json, rails
Oj
Optimized JSON
Stars: ✭ 2,824 (+162.7%)
Mutual labels:  json, rails
Jsonapi Utils
Build JSON API-compliant APIs on Rails with no (or less) learning curve.
Stars: ✭ 191 (-82.23%)
Mutual labels:  json, rails
Jbuilder
Generate JSON objects with a Builder-style DSL, inspired by jbuilder
Stars: ✭ 37 (-96.56%)
Mutual labels:  json, template-engine
Rabl Rails
Rails 4.2 & 5 templating system with JSON, XML and Plist support.
Stars: ✭ 200 (-81.4%)
Mutual labels:  json, rails
Store model
Work with JSON-backed attributes as ActiveRecord-ish models
Stars: ✭ 410 (-61.86%)
Mutual labels:  json, rails
Pager Api
Easy API pagination for Rails
Stars: ✭ 86 (-92%)
Mutual labels:  json, rails
Treefrog Framework
TreeFrog Framework : High-speed C++ MVC Framework for Web Application
Stars: ✭ 885 (-17.67%)
Mutual labels:  json, template-engine
Mobility
Pluggable Ruby translation framework
Stars: ✭ 644 (-40.09%)
Mutual labels:  json, rails
Flexirest
Flexirest - The really flexible REST API client for Ruby
Stars: ✭ 188 (-82.51%)
Mutual labels:  json, rails
Emrichen
A Template engine for YAML & JSON
Stars: ✭ 40 (-96.28%)
Mutual labels:  json, template-engine
Aeromock
Lightweight mock web application server
Stars: ✭ 152 (-85.86%)
Mutual labels:  json, template-engine
Datoji
A tiny JSON storage service. Create, Read, Update, Delete and Search JSON data.
Stars: ✭ 222 (-79.35%)
Mutual labels:  json, rails
Sabisu Rails
Simple and powerful engine for exploring your Rails api application
Stars: ✭ 129 (-88%)
Mutual labels:  json, rails
Http Rpc
Lightweight REST for Java
Stars: ✭ 298 (-72.28%)
Mutual labels:  json, template-engine
String template
A template engine for Rails, focusing on speed, using Ruby's String interpolation syntax
Stars: ✭ 122 (-88.65%)
Mutual labels:  rails, template-engine
Himl
HTML-based Indented Markup Language for Ruby
Stars: ✭ 236 (-78.05%)
Mutual labels:  rails, template-engine
Blueprinter
Simple, Fast, and Declarative Serialization Library for Ruby
Stars: ✭ 623 (-42.05%)
Mutual labels:  json, rails
Manifold
Manifold plugs into Java to supplement it with powerful features, from Type-safe Metaprogramming (direct access to GraphQL, JSON, XML, etc.), Extension Methods, Operator Overloading, and Unit Expressions to an integrated Template Engine and a Preprocessor. All fully supported in IntelliJ IDEA and Android Studio. Simply add Manifold to your project and begin taking advantage of it.
Stars: ✭ 993 (-7.63%)
Mutual labels:  json, template-engine

Jb

A simpler and faster Jbuilder alternative.

Installation

Add this line to your application's Gemfile:

gem 'jb'

And bundle.

Usage

Put a template file named *.jb in your Rails app's app/views/* directory, and render it.

Features

  • No ugly builder syntax
  • No method_missing calls
  • render_partial with :collection option actually renders the collection (unlike Jbuilder)

Syntax

A .jb template should contain Ruby code that returns any Ruby Object that responds_to to_json (generally Hash or Array). Then the return value will be to_jsoned to a JSON String.

Examples

# app/views/messages/show.json.jb

json = {
  content: format_content(@message.content),
  created_at: @message.created_at,
  updated_at: @message.updated_at,
  author: {
    name: @message.creator.name.familiar,
    email_address: @message.creator.email_address_with_name,
    url: url_for(@message.creator, format: :json)
  }
}

if current_user.admin?
  json[:visitors] = calculate_visitors(@message)
end

json[:comments] = @message.comments.map do |comment|
  {
    content: comment.content,
    created_at: comment.created_at
  }
end

json[:attachments] = @message.attachments.map do |attachment|
  {
    filename: attachment.filename,
    url: url_for(attachment)
  }
end

json

This will build the following structure:

{
  "content": "10x JSON",
  "created_at": "2016-06-29T20:45:28-05:00",
  "updated_at": "2016-06-29T20:45:28-05:00",

  "author": {
    "name": "Yukihiro Matz",
    "email_address": "[email protected]",
    "url": "http://example.com/users/1-matz.json"
  },

  "visitors": 1326,

  "comments": [
    { "content": "Hello, world!", "created_at": "2016-06-29T20:45:28-05:00" },
    { "content": "<script>alert('Hello, world!');</script>", "created_at": "2016-06-29T20:47:28-05:00" }
  ],

  "attachments": [
    { "filename": "sushi.png", "url": "http://example.com/downloads/sushi.png" },
    { "filename": "sake.jpg", "url": "http://example.com/downloads/sake.jpg" }
  ]
}

To define attribute and structure names dynamically, just use Ruby Hash. Note that modern Ruby Hash syntax pretty much looks alike JSON syntax. It's super-straight forward. Who needs a DSL to do this?

{author: {name: 'Matz'}}

# => {"author": {"name": "Matz"}}

Top level arrays can be handled directly. Useful for index and other collection actions. And you know, Ruby is such a powerful language for manipulating collections:

# @comments = @post.comments

@comments.reject {|c| c.marked_as_spam_by?(current_user) }.map do |comment|
  {
    body: comment.body,
    author: {
      first_name: comment.author.first_name,
      last_name: comment.author.last_name
    }
  }
end

# => [{"body": "🍣 is omakase...", "author": {"first_name": "Yukihiro", "last_name": "Matz"}}]

Jb has no special DSL method for extracting attributes from array directly, but you can do that with Ruby.

# @people = People.all

@people.map {|p| {id: p.id, name: p.name}}

# => [{"id": 1, "name": "Matz"}, {"id": 2, "name": "Nobu"}]

You can use Jb directly as an Action View template language. When required in Rails, you can create views ala show.json.jb. You'll notice in the following example that the .jb template doesn't have to be one big Ruby Hash literal as a whole but it can be any Ruby code that finally returns a Hash instance.

# Any helpers available to views are available in the template
json = {
  content: format_content(@message.content),
  created_at: @message.created_at,
  updated_at: @message.updated_at,

  author: {
    name: @message.creator.name.familiar,
    email_address: @message.creator.email_address_with_name,
    url: url_for(@message.creator, format: :json)
  }
}

if current_user.admin?
  json[:visitors] = calculate_visitors(@message)
end

json

You can use partials as well. The following will render the file views/comments/_comments.json.jb, and set a local variable comments with all this message's comments, which you can use inside the partial.

render 'comments/comments', comments: @message.comments

It's also possible to render collections of partials:

render partial: 'posts/post', collection: @posts, as: :post

NOTE: Don't use render @post.comments because if the collection is empty, render will return nil instead of an empty array.

You can pass any objects into partial templates with or without :locals option.

render 'sub_template', locals: {user: user}

# or

render 'sub_template', user: user

You can of course include Ruby nil as a Hash value if you want. That would become null in the JSON.

You can use Hash#compact/! method to prevent including null values in the output:

{foo: nil, bar: 'bar'}.compact

# => {"bar": "bar"}

If you want to cache a template fragment, just directly call Rails.cache.fetch:

Rails.cache.fetch ['v1', @person], expires_in: 10.minutes do
  {name: @person.name, age: @person.age}
end

The Generator

Jb extends the default Rails scaffold generator and adds some .jb templates. If you don't need them, please configure like so.

Rails.application.config.generators.jb false

Why is Jb fast?

Jbuilder's partial + :collection internally calls array! method inside which _render_partial is called per each element of the given collection, and then it falls back to the view_context's render method.

So, for example if the collection has 100 elements, Jbuilder's render partial: performs render method 100 times, and so it calls find_template method (which is known as one of the heaviest parts of Action View) 100 times.

OTOH, Jb simply calls ActionView::PartialRenderer's render which is cleverly implemented to find_template only once beforehand, then pass each element to that template.

Benchmarks

Here're the results of a benchmark (which you can find here in this repo) rendering a collection to JSON.

RAILS_ENV=development

% ./bin/benchmark.sh
* Rendering 10 partials via render_partial
Warming up --------------------------------------
                  jb    15.000  i/100ms
            jbuilder     8.000  i/100ms
Calculating -------------------------------------
                  jb    156.375  (± 7.0%) i/s -    780.000  in   5.016581s
            jbuilder     87.890  (± 6.8%) i/s -    440.000  in   5.037225s

Comparison:
                  jb:      156.4 i/s
            jbuilder:       87.9 i/s - 1.78x slower


* Rendering 100 partials via render_partial
Warming up --------------------------------------
                  jb    13.000  i/100ms
            jbuilder     1.000  i/100ms
Calculating -------------------------------------
                  jb    121.187  (±14.0%) i/s -    598.000  in   5.049667s
            jbuilder     11.478  (±26.1%) i/s -     54.000  in   5.061996s

Comparison:
                  jb:      121.2 i/s
            jbuilder:       11.5 i/s - 10.56x slower


* Rendering 1000 partials via render_partial
Warming up --------------------------------------
                  jb     4.000  i/100ms
            jbuilder     1.000  i/100ms
Calculating -------------------------------------
                  jb     51.472  (± 7.8%) i/s -    256.000  in   5.006584s
            jbuilder      1.510  (± 0.0%) i/s -      8.000  in   5.383548s

Comparison:
                  jb:       51.5 i/s
            jbuilder:        1.5 i/s - 34.08x slower

RAILS_ENV=production

% RAILS_ENV=production ./bin/benchmark.sh
* Rendering 10 partials via render_partial
Warming up --------------------------------------
                  jb   123.000  i/100ms
            jbuilder    41.000  i/100ms
Calculating -------------------------------------
                  jb      1.406k (± 4.2%) i/s -      7.134k in   5.084030s
            jbuilder    418.360  (± 9.8%) i/s -      2.091k in   5.043381s

Comparison:
                  jb:     1405.8 i/s
            jbuilder:      418.4 i/s - 3.36x slower


* Rendering 100 partials via render_partial
Warming up --------------------------------------
                  jb    37.000  i/100ms
            jbuilder     5.000  i/100ms
Calculating -------------------------------------
                  jb    383.082  (± 8.4%) i/s -      1.924k in   5.061973s
            jbuilder     49.914  (± 8.0%) i/s -    250.000  in   5.040364s

Comparison:
                  jb:      383.1 i/s
            jbuilder:       49.9 i/s - 7.67x slower


* Rendering 1000 partials via render_partial
Warming up --------------------------------------
                  jb     4.000  i/100ms
            jbuilder     1.000  i/100ms
Calculating -------------------------------------
                  jb     43.017  (± 9.3%) i/s -    216.000  in   5.080482s
            jbuilder      4.604  (±21.7%) i/s -     23.000  in   5.082100s

Comparison:
                  jb:       43.0 i/s
            jbuilder:        4.6 i/s - 9.34x slower

Summary

According to the benchmark results, you can expect 2-30x performance improvement in development env, and 3-10x performance improvement in production env.

Contributing

Pull requests are welcome on GitHub at https://github.com/amatsuda/jb.

License

The gem is available as open source under the terms of the MIT License.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].