Giter Site home page Giter Site logo

nesquena / rabl Goto Github PK

View Code? Open in Web Editor NEW
3.6K 77.0 336.0 1.88 MB

General ruby templating with json, bson, xml, plist and msgpack support

Home Page: http://blog.codepath.com/2011/06/27/building-a-platform-api-on-rails/

License: MIT License

Ruby 88.95% HTML 8.87% JavaScript 1.45% CoffeeScript 0.10% CSS 0.55% SCSS 0.08%

rabl's Introduction

RABL

Continuous Integration status Code Climate

RABL (Ruby API Builder Language) is a Rails and Padrino ruby templating system for generating JSON, XML, MessagePack, PList and BSON. When using the ActiveRecord 'to_json' method, I find myself wanting a more expressive and powerful solution for generating APIs. This is especially true when the JSON representation is complex or doesn't match the exact schema defined within the database.

In particular, I want to easily:

  • Create arbitrary nodes named based on combining data in an object
  • Pass arguments to methods and store the result as a child node
  • Render partial templates and inherit to reduce code duplication
  • Rename or alias attributes to change the name from the model
  • Append attributes from a child into a parent node
  • Include nodes only if a certain condition has been met

Anyone who has tried the 'to_json' method used in ActiveRecord for generating a JSON response has felt the pain of this restrictive approach. RABL is a general templating system created to solve these problems by approaching API response generation in an entirely new way.

RABL at the core is all about adhering to MVC principles by deferring API data representations to the view layer of your application. For a breakdown of common misconceptions about RABL, please check out our guide to understanding RABL which can help clear up any confusion about this project.

Breaking Changes

  • v0.9.0 (released Oct 14, 2013) changes the default node name for certain associations especially around STI models. You might want to verify for any breakages as a result and be more explicit by specifying an alias i.e @users => :users

  • v0.8.0 (released Feb 14, 2013) removes multi_json dependency and relies on Oj (or JSON) as the json parser. Simplifies code, removes a dependency but you might want to remove any references to MultiJson.

  • v0.6.14 (released June 28, 2012) requires the use of render_views with RSpec to test templates. Otherwise, the controller will simply pass through the render command as it does with ERB templates.

Installation

Install RABL as a gem:

gem install rabl

or add to your Gemfile:

# Gemfile
gem 'rabl'
# Also add either `oj` or `yajl-ruby` as the JSON parser
# If using `oj`, Rabl will set the mode to :compat
gem 'oj'

and run bundle install to install the dependency.

If you are using Rails 2.3.8 (and up), Rails 3.X or Padrino, RABL works without configuration.

Important: With Padrino, be sure that the rabl gem is listed after the padrino gem in your Gemfile, otherwise Rabl will not register properly as a template engine.

With Sinatra, or any other tilt-based framework, simply register:

Rabl.register!

and RABL will be initialized and ready for use. For usage with Sinatra, check out the Sinatra Usage guide.

Overview

You can use RABL to generate JSON and XML based APIs from any ruby object. With RABL, the data typically is derived primarily from models (ORM-agnostic) and the representation of the API output is described within a view template using a simple ruby DSL. This allows you to keep your data separated from the JSON or XML you wish to output.

Once you have installed RABL (explained above), you can construct a RABL view template and then render the template from your Sinatra, Padrino or Rails applications from the controller (or route) very easily. Using Padrino as an example, assuming you have a Post model filled with blog posts, you can render an API representation (both JSON and XML) by creating a route:

# app/app.rb
get "/posts", :provides => [:json, :xml] do
  @user = current_user
  @posts = Post.order("id DESC")
  render "posts/index"
end

Then we can create the following RABL template to express the API output of @posts:

# app/views/posts/index.rabl
collection @posts
attributes :id, :title, :subject
child(:user) { attributes :full_name }
node(:read) { |post| post.read_by?(@user) }

Which would output the following JSON or XML when visiting http://localhost:3000/posts.json

[{  "post" :
  {
    "id" : 5, "title": "...", "subject": "...",
    "user" : { "full_name" : "..." },
    "read" : true
  }
}]

That's a basic overview but there is a lot more to see such as partials, inheritance, custom nodes, etc. Read the full details of RABL below.

Configuration

RABL is intended to require little to no configuration to get working. This is the case in most scenarios, but depending on your needs you may want to set the following global configurations in your application (this block is completely optional):

# config/initializers/rabl_init.rb
require 'rabl'
Rabl.configure do |config|
  # Commented as these are defaults
  # config.cache_all_output = false
  # config.cache_sources = Rails.env != 'development' # Defaults to false
  # config.cache_engine = Rabl::CacheEngine.new # Defaults to Rails cache
  # config.perform_caching = false
  # config.escape_all_output = false
  # config.json_engine = nil # Class with #dump class method (defaults JSON)
  # config.msgpack_engine = nil # Defaults to ::MessagePack
  # config.bson_engine = nil # Defaults to ::BSON
  # config.plist_engine = nil # Defaults to ::Plist::Emit
  # config.include_json_root = true
  # config.include_msgpack_root = true
  # config.include_bson_root = true
  # config.include_plist_root = true
  # config.include_xml_root  = false
  # config.include_child_root = true
  # config.enable_json_callbacks = false
  # config.xml_options = { :dasherize  => true, :skip_types => false }
  # config.view_paths = []
  # config.raise_on_missing_attribute = true # Defaults to false
  # config.replace_nil_values_with_empty_strings = true # Defaults to false
  # config.replace_empty_string_values_with_nil_values = true # Defaults to false
  # config.exclude_nil_values = true # Defaults to false
  # config.exclude_empty_values_in_collections = true # Defaults to false
  # config.camelize_keys = :upper # Defaults to false
end

Each option specifies behavior related to RABL's output.

If include_json_root is disabled that removes the root node for each root object in the output, and enable_json_callbacks enables support for 'jsonp' style callback output if the incoming request has a 'callback' parameter.

If include_child_root is set to false then child objects in the response will not include a root node by default. This allows you to further fine-tune your desired response structure.

If cache_engine is set, you should assign it to a class with a fetch method. See the default engine for an example.

If perform_caching is set to true then it will perform caching. You can ignore this option if you are using Rails, it's same to Rails config.action_controller.perform_caching

If cache_sources is set to true, template lookups will be cached for improved performance. The cache can be reset manually by running Rabl.reset_source_cache! within your application.

If cache_all_output is set to true, every template including each individual template used as part of a collection will be cached separately. Additionally, anything within child, glue and partial will also be cached separately. To cache just a single template, see the section titled 'Caching' below.

If escape_all_output is set to true and ActiveSupport is available, attribute output will be escaped using ERB::Util.html_escape.

If view_paths is set to a path, this view path will be checked for every rabl template within your application. Add to this path especially when including Rabl in an engine and using view paths within a another Rails app.

If raise_on_missing_attribute is set to true, a RuntimeError will be raised whenever Rabl attempts to render an attribute that does not exist. Otherwise, the attribute will simply be omitted. Setting this to true during development may help increase the robustness of your code, but using true in production code is not recommended.

If replace_nil_values_with_empty_strings is set to true, all values that are nil and would normally be displayed as null in the response are converted to empty strings.

If exclude_nil_values is set to true, all values that are nil and would normally be displayed as null in the response are not included in the response.

if exclude_empty_values_in_collections is set to true, all values in a collection that are {} and would normally be displayed as {} in the response are not included in the response.

If camelize_keys is set to true, all object keys will be converted to camel case. By default the first character will be lower case. The value can be set to :upper to set the first character to upper case.

If you wish to use oj as the primary JSON encoding engine simply add that to your Gemfile:

# Gemfile
gem 'oj'

and RABL will use that engine automatically for encoding your JSON responses. Set your own custom json_engine which define a dump or encode method for converting to JSON from ruby data:

config.json_engine = ActiveSupport::JSON

Format Configuration

RABL supports configuration for MessagePack, BSON, and Plist. Check the Format Configuration page for more details.

Usage

Object Assignment

To declare the data object for use in the template:

# app/views/users/show.json.rabl
object @user

or specify an alias for the object:

object @user => :person
# => { "person" : { ... } }

or pass a collection of objects:

collection @users
# => [ { "user" : { ... } } ]

or specify a root node label for the collection:

collection @users => :people
# => { "people" : [ { "person" : { ... } } ] }

or even specify both the child and root labels for a collection:

collection @users, :root => "people", :object_root => "user"
# => { "people" : [ { "user" : { ... } } ] }

and this will be used as the default data for the rendering, or disable the object root explicitly:

collection @users, :root => "people", :object_root => false
# => { "people" : [ { ... }, { ... } ] }

There can also be odd cases where the root-level of the response doesn't map directly to any object:

object false
node(:some_count) { |m| @user.posts.count }
child(@user) { attribute :name }

In those cases, object can be assigned to 'false' and nodes can be constructed free-form.

Attributes

Basic usage of the templater to define a few simple attributes for the response:

# app/views/users/show.json.rabl
attributes :id, :foo, :bar

or use with aliased attributes:

# Take the value of model attribute `foo` and name the node `bar`
attribute :foo => :bar
# => { bar : 5 }

or even multiple aliased attributes:

attributes :bar => :baz, :dog => :animal
# => # { baz : <bar value>, animal : <dog value> }

or show attributes only if a condition is true:

# m is the object being rendered, also supports :unless
attributes :foo, :bar, :if => lambda { |m| m.condition? }

Named and aliased attributes can not be combined on the same line. This currently does not work:

attributes :foo, :bar => :baz # throws exception

in such cases, you need to specify attributes twice.

attributes :foo
attributes :bar => :baz

Child Nodes

Often a response requires including nested information from data associated with the parent model:

child :address do
  attributes :street, :city, :zip, :state
end

You can also disable object root for child node:

child :posts, :object_root => false do
  attributes :id, :title
end

You can also add child nodes from an arbitrary data source:

child @posts => :foobar do
  attributes :id, :title
end

or use model associations with an alias:

# Renders all the 'posts' association
# from the model into a node called 'foobar'
child :posts => :foobar do
  attributes :id, :title
end

You can also pass in the current object:

object @user
child :posts do |user|
  attribute :title unless user.suspended?
end

Gluing Attributes

You can also append child attributes back to the root node:

# Appends post_id and post_name to parent json object
glue @post do
  attributes :id => :post_id, :name => :post_name
end

Use glue to add additional attributes to the parent object.

You can also pass in the current object:

object @user
glue(@post) {|user| attribute :title if user.active? }

Custom Nodes

This will generate a json response based on the result of the node block:

# app/views/users/show.json.rabl
node :full_name do |u|
  u.first_name + " " + u.last_name
end

or don't pass a name and have the node block merged into the response:

node do |u|
  { :full_name => u.first_name + " " + u.last_name }
  # => { full_name : "Bob Johnson" }
end

You can use custom nodes like these to create flexible representations of a value utilizing all the data from the model.

Partials

Often you need to access other data objects in order to construct custom nodes in more complex associations. You can get access to the rabl representation of another data object by rendering a RABL partial:

node :location do
  { :city => @city, :address => partial("users/address", :object => @address) }
end

or even access an object associated with the parent model:

node :location do |m|
  { :city => m.city, :address => partial("users/address", :object => m.address) }
end

You can use this method to construct arbitrarily complex nodes for your APIs. Note that you need to have RABL templates defined for each of the objects you wish to construct representations for in this manner.

Inheritance

Another common issue of many template builders is unnecessary code redundancy. Typically many representations of an object across multiple endpoints share common attributes or nodes. The nodes for a 'post' object are probably the same or similar in most references throughout the various endpoints.

RABL has the ability to extend other "base" rabl templates and additional attributes:

# app/views/users/advanced.json.rabl
extends "users/base" # another RABL template in "app/views/users/base.json.rabl"

node :can_drink do |m|
  m.age > 21
end

You can also extend other rabl templates while constructing child nodes to reduce duplication:

# app/views/users/show.json.rabl
child @address do
  extends "address/item"
end

Using partials and inheritance can significantly reduce code duplication in your templates.

You can see more examples on the Reusing Templates wiki page.

Passing Locals in Partials

You can pass an arbitrary set of locals when rendering partials or extending templates. For example, if we want to show on posts/:id.json any information regarding particular post and associated comments but in other cases we want to hide those comments. We can use locals to do this:

# app/views/posts/index.json.rabl
collection @posts

extends('posts/show', :locals => { :hide_comments => true })
# or using partial instead of extends
# node(false) { |post| partial('posts/show', :object => :post, :locals => { :hide_comments => true })}

and then access locals in the sub-template:

# app/views/posts/show.json.rabl
object @post

attributes :id, :title, :body, :created_at
node(:comments) { |post| post.comments } unless locals[:hide_comments]

This can be useful as an advanced tool when extending or rendering partials.

Conditions

You can provide conditions to all kinds of nodes, attributes, extends, etc. which includes a given element only if the specified condition is true.

collection @posts
# m is the object being rendered, also supports :unless
node(:coolness, :if => lambda { |m| m.coolness > 5 }) do |m|
  m.coolness
end

Because attributes take conditional options as well, we could simplify the example with:

collection @posts
# m is the object being rendered, also supports :unless
attribute(:coolness, :if => lambda { |m| m.coolness > 5 })

The value for the :if and :unless options may be a simple Boolean, Proc or a Symbol. If it is a Symbol and the specific @object responds to its, the method will be called. Thus the example above can be rewritten as:

class Post
  def cool?
    coolness > 5
  end
end

and then:

collection @posts
attribute :coolness, if: :cool?

Using conditions allows for easy control over when certain elements render.

Template Scope

In RABL, you have access to everything you need to build an API response. Each RABL template has full access to the controllers instance variables as well as all view helpers and routing urls.

# app/some/template.rabl
object @post
# Access instance variables
child(@user => :user) { ... }
# or Rails helpers
node(:formatted_body) { |post| simple_format(post.body) }

There should be no problem fetching the appropriate data to construct a response.

Deep Nesting

In APIs, you can often need to construct 2nd or 3rd level nodes. Let's suppose we have a 'quiz' model that has many 'questions' and then each question has many 'answers'. We can display this hierarchy in RABL quite easily:

# app/views/quizzes/show.json.rabl
object @quiz
attribute :title
child :questions do
  attribute :caption
  child :answers do
    # Use inheritance to reduce duplication
    extends "answers/item"
  end
end

This will display the quiz object with nested questions and answers as you would expect with a quiz node, and embedded questions and answers. Note that RABL can be nested arbitrarily deep within child nodes to allow for these representations to be defined.

Caching

RABL has built-in caching support for templates leveraging fragment caching strategies. Note that caching is currently only available for Rails but support for other frameworks is planned in a future release. Simplest caching usage is:

# app/views/users/show.json.rabl
object @quiz
cache @quiz # key = rabl/quiz/[cache_key]
attribute :title

Caching can significantly speed up the rendering of RABL templates in production and is strongly recommended when possible. For more a more detailed look at caching, check out the Caching guide on the wiki.

Rendering Templates Directly

There are situations where an application requires RABL templates to be rendered outside a traditional view context. For instance, to render RABL within a Rake task or to create message queue payloads. For this case, Rabl.render can be used as show below:

Rabl.render(object, template, :view_path => 'app/views', :format => :json) #=> "{...json...}"

You can use convenience methods on Rabl::Renderer to render the objects as well:

Rabl::Renderer.json(@post, 'posts/show')
Rabl::Renderer.xml(@post, 'posts/show')

These methods allow RABL to be used for arbitrary conversions of an object into a desired format.

Rabl::Renderer.new('posts/show', @post, :view_path => 'app/views', :format => 'hash').render

You can also pass in other instance variables to be used in your template as:

Rabl::Renderer.new('posts/show', @post, :locals => { :custom_title => "Hello world!" })

Then, in your template, you can use locals[:custom_title] as:

attribute :content
node(:title) { locals[:custom_title] }

Content Type Headers

Currently in RABL, the content-type of your response is not set automatically. This is because RABL is intended to work for any Rack-based framework and as agnostic to format as possible. Check this issue for more details, and if you have any ideas or patches please let me know.

In the meantime, be sure to set the proper content-types if needed. This is usually pretty simple in both Rails and Padrino. I recommend a before_filter on that controller or directly specified in an action.

Resources

There are many resources available relating to RABL including the RABL Wiki, and many tutorials and guides detailed below. You can check out the RABL Site as well.

Advanced Usage

Links to resources for advanced usage:

Please add your own usages and let me know so we can add them here! Also be sure to check out the RABL Wiki for other usages.

Tutorials

Tutorials can always be helpful when first getting started:

Let me know if there's any other useful resources not listed here.

Related Libraries

There are other libraries that can either complement or extend the functionality of RABL:

  • versioncake - Excellent library for easily versioning your RABL APIs
  • gon - Exposes your Rails variables in JS with RABL support integrated.
  • rabl-rails - Reimplementation for RABL and Rails focused on speed.

Let me know if there's any other related libraries not listed here.

Troubleshooting

Examples

See the examples directory.

Issues

Check out the Issues tab for a full list:

  • Rigorous benchmarking and performance optimizations

Authors and Contributors

Active Maintainers:

Thanks to Miso for allowing me to create this for our applications and release this project!

and many more contributors listed in the CHANGELOG.

Want to contribute support for another format? Check out the patches for msgpack support, plist support and BSON support for reference.

Please fork and contribute, any help in making this project better is appreciated!

This project is a member of the OSS Manifesto.

Inspirations

There are a few excellent libraries that helped inspire RABL and they are listed below:

Thanks again for all of these great projects.

Copyright

Copyright ยฉ 2011-2013 Nathan Esquenazi. See MIT-LICENSE for details.

rabl's People

Contributors

achiurizo avatar ahlatimer avatar bhicks avatar cj avatar databyte avatar dgynn avatar domcleal avatar douwem avatar ghiculescu avatar ivanvanderbyl avatar jpawlyn avatar kossnocorp avatar le0pard avatar leoc avatar lte avatar mrinterweb avatar mrthe avatar nesquena avatar nguyenmighty avatar nik avatar oharsta avatar olleolleolle avatar plukevdh avatar s01ipsist avatar sanemat avatar siong1987 avatar swalkinshaw avatar tagliala avatar vimutter avatar zenspider avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rabl's Issues

Sinatra example

I can't seem to get rabl to work with sinatra, can you provide an example, perhaps in the wiki?

collections work very strangely

when I do:

object @users
attributes :id, :name

I get a collection similar to

 [
  {
    "user": {
      "id": "...",
      "name": "...."
    }
  },
  {
    "user": {
      "id": "...",
      "name": "...."
    }
  },
  {
    "user": {
      "id": "...",
      "name": "...."
    }
  }
]

Is there a way to return the results in the more standard format:

"users": [
  {
    "id": "...",
    "name": "..."
  },
  {
    "id": "...",
    "name": "..."
  },
  {
    "id": "...",
    "name": "..."
  }
]

The latter format is understood better by ExtJS data store.

Empty children are displayed as "array[]"

Hey I'm back.

I have something that looks like this

class Location < ActiveRecord::Base

  #Associations
  has_many :photos

end

class Photo < ActiveRecord::Base

  # Assoications
  belongs_to :location

end

Now a location does not have to have a photo, but can have many. In the situation where the location doesn't have any photo objects created, the json output looks like this.

 "array": []

Now if I do have a photo created it shows up like this

 "location": {
                        "name": "grand canyon store",
                        "description": "grand canyon store is a way awesome place",
                        "location_type": "museum",
                        "lat": 39,
                        "lng": -111,
                        "address": null,
                        "city": null,
                        "state": null,
                        "postal_code": null,
                        "phone": null,
                        "email": null,
                        "url": null,
                        "fee_scale": 1,
                        "fee_description": null,
                        "availability_description": null,
                        "created_at": "2011-05-20T22:12:11Z",
                        "updated_at": "2011-05-20T22:12:11Z",
                        "photos": [
                            {
                                "name": "sweet canyon photo",
                                "small_image_url": "http://tour_builder.dev/uploads/photo/image/2/small_test.gif",
                                "large_image_url": "http://tour_builder.dev/uploads/photo/image/2/large_test.gif"
                            }
                        ]

My rabl code looks like this

    child :photos do 
      attribute :name
      node :small_image_url do |photo|
        @root_url + photo.small_image_url
      end

      node :large_image_url do |photo|
        @root_url + photo.large_image_url
      end
    end
  end

If there aren't any photos, the photos array should just be empty. Something like photos[] vs array[]

Questions & Ideas

Don't know if there's a Google Group or anything, so I'm just posting here.
I've been working on a REST API for a Rails app for the past 2 months and just discovered rabl.
It might be something I've been looking for.
Converted a few resources from as_json to .json.rabl and it seems cool so far.

I've got a few questions/ideas.


1. What's the proper way to extend a @object template from a @collection template?

I've got a posts resource and want to re-use the show template in the index template vibratim.
Here's what I've come up with:

posts/show.json.rabl:

object @post
attributes :id, :body

posts/index.json.rabl:

collection @posts
extends 'posts/show'

It seems clumsy, but it works. I was kind of expecting a partial somewhere here. What would be the proper way to do that?


2. Using regular ifs within a template

Instead of the :if => lambda { ... } pattern, I tried using regular ifs within a template:

posts/show.json.rabl

object @post
attributes :id, :body

if @post.published?
  code(:publisher) { |p| p.publisher.username }
end

This works also, but presents a problem when using extends as presented above. The controller for an index action only provides the @posts collection, so the @post is nil and the if always fails when rendering the index action.
I think it would be cleaner to use regular ifs. Especially when adding multiple custom fields - this would just be a single if block instead of multiple code(:field, :if => lambda { ... repeating condition ... }) blocks.

Maybe the way to do it could be - instead of relying on instance variables, provide an object or collection helper, which can always access the current object? Kind of like RSpec utilises subject.


3. Conditional child elements

I needed to include an entire subresource based on a condition. This didn't work:

child(:user, :if => lambda { ... }) do
  ...
end

So I eventually went with:

code(:user, :if => lambda { ... }) do |p|
  {
    :id => p.user.id,
    :username => p.user.username
  }
end

This works, but breaks DRY. I have a users/show.json.rabl with properly defined attributes, and would like to re-use it here. How would I go about it?


All in all, thanks for the great work.
These are just some of my thoughts, Markdown-formatted.
I'd be glad to dig into the code if you see any point in my mumbling ;)
Cheers!

cannot encode ActiveSupport::TimeWithZone

After update rabl gem version from 0.2.8 to 0.3.0 have the Error Message when i try to print date

The stack trace

gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:416:in `valenc'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:422:in `objenc'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:422:in `each'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:422:in `map'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:422:in `objenc'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:408:in `valenc'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:422:in `objenc'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:422:in `each'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:422:in `map'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:422:in `objenc'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:408:in `valenc'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:427:in `arrenc'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:427:in `map'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:427:in `arrenc'
gems/multi_json-1.0.3/lib/multi_json/vendor/ok_json.rb:409:in `valenc'
gems/multi_json-1.0.3/lib/multi_json/engines/ok_json.rb:15:in `encode'
gems/multi_json-1.0.3/lib/multi_json.rb:72:in `encode'
rabl (0.3.0) lib/rabl/engine.rb:50:in `to_json'
rabl (0.3.0) lib/rabl/engine.rb:28:in `send'
rabl (0.3.0) lib/rabl/engine.rb:28:in `render'

Allow object node name to be configurable

If I have:

object @user

Allow the root node to be configurable:

object @user => :person

or:

object @users => :people

Also support a collection syntax:

collection @users => :people

Not rendering rabl when using Rails 2.3.x

Hi,

I'm unsure if this is an application-specific problem, or what, but with my app I'm trying to use rabl and running into a snag.

So i have the following controller:

class PeopleController < ApplicationController
  def show
    @person = Person.find(params[:id])  
    respond_to do |format|
      format.html
      format.json
    end
  end
end

My show.json.rabl file looks like this:

object @person
attribute :id

My app/views/layouts/application.rhtml file looks like this:

<html>
  <body>
    <%= yield :main if @content_for_main -%>
  </body>
</html>

Now, when I attempt to get /people/1.json, I do not see the json. I see html!

GET /people/1.json

<html>
  <body>
          </body>
</html>

The rails log looks like this:

Processing PeopleController#show to json (for 127.0.0.1 at 2011-07-01 11:03:00) [GET]
  Parameters: {"id"=>"1"}
  Person Columns (44.9ms)   SHOW FIELDS FROM `people`
  Person Load (0.7ms)   SELECT * FROM `people` WHERE (`people`.`id` = 1) 
Rendering template within layouts/application
Rendering people/show
  Lead Columns (14.4ms)   SHOW FIELDS FROM `people`
Completed in 173ms (View: 30, DB: 77) | 200 OK [http://localhost/people/1.json]

so it looks to me like rails is attempting to show json, but ends up rendering html. I'm confused.

If I explicitly tell the controller to not render a layout, then it shows the json no problem:

class PeopleController < ApplicationController
  def show
    @person = Person.find(params[:id])  
    respond_to do |format|
      format.html
      format.json { render :layout => false }
    end
  end
end

That works. Unfortunately I do not know rails views that well, otherwise I'd have submitted a patch with my issue. But can you guys think why my app is doing this?

Thanks in advance!

Support for arbitrary ruby objects

Great gem thanks. Is there a way to use an arbitrary ruby object (as opposed to an active model) as the root object? I hacked round it for the time being by adding a 'valid?' method to my class.

Cheers

Modify attributes

Hey I have something that looks like of like

collection @tours
attributes :created_at, :days_required, :description, :fee_description, :fee_scale, :id, :name, :published, :updated_at, :lat, :lng

attribute :small_image_url do |url|
  root_url + url
end
attribute :large_image_url do |url|
  root_url + url
end

I'm trying to modify the :large_image_url and :small_image_url to add the root_url to it. I'm working in rails 3. I think I might be confused on what functionality is available or how to do this.

Any way to use association methods in rabl templates?

Example:

From app/models/offer.rb:

has_many :offer_images do
  def history_image(size = :history)
    history.first.try(:image).try(:url, size)
  end
end

From app/views/offers/show.json.rabl:

child :offer_images do
  node(:thumb)  { |i| i.history_image(:small_thumb) }
  node(:normal) { |i| i.history_image(:small_promo) }
end

Empty { } for single object render to show

For this example

class PostsController < ApplicationController
  def index
    @posts = Post.all
    respond_to do |format|
      format.json
    end
  end
  def show
    @post = Post.find(params[:id])
    respond_to do |format|
      format.json
    end
  end
end

With collection template index.json.rabl it's not necessary declare the collection

attributes :id, :title, :content

But in object template show.json.rabl the declaration it's necessary.

object @post
attributes :id, :title, :content

otherwise the { } it's empty

It's normal way ?

XML Generation Support

This should be easy. I already have a hash compiled, might be as easy as invoking to_xml and making sure it looks OK.

Disabling the child root node

I see that you can set include_json_root = false in an initializer so you don't get the root node at every level, but is is possible to only disable the root node for a particular child node and in a particular template? So instead of rendering a collection of objects as follows :-

{ "myObjects" :
  [
    { "myObject" : { "keyOne" : "valueOne", "keyTwo" : "valueTwo" } },
    { "myObject" : { "keyOne" : "anotherValueOne", "keyTwo" : "anotherValueTwo" } }
  ]
}

I want something like this :-

{ "myObjects" :
  [
    { "keyOne" : "valueOne", "keyTwo" : "valueTwo" },
    { "keyOne" : "anotherValueOne", "keyTwo" : "anotherValueTwo" }
  ]
}

Assertion "Rabl::Engine with defaults #glue asserts that it glues data from a child node" fails on REE

But 1.9.2 is fine, because ree Hashes is not ordered.

rabl : rake test 
(in /Users/koss/Code/rabl)
/Users/koss/.rvm/rubies/ree-1.8.7-2011.03/bin/ruby -I"lib:lib:test" -rubygems "/Users/koss/.rvm/gems/ree-1.8.7-2011.03@global/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/builder_test.rb" "test/configuration_test.rb" "test/engine_test.rb" "test/template_test.rb" 
.............................................F......................
FAILURE
 Rabl::Engine with defaults #glue asserts that it glues data from a child node => expected "{\"user\":{\"name\":\"leo\",\"city\":\"LA\",\"age\":12}}", not "{\"user\":{\"city\":\"LA\",\"name\":\"leo\",\"age\":12}}"
 (on line 162 in ./test/engine_test.rb)

67 passes, 1 failures, 0 errors in 0.039941 seconds
rake aborted!
Command failed with status (1): [/Users/koss/.rvm/rubies/ree-1.8.7-2011.03/...]

(See full trace by running task with --trace)

My favorite solution is hash hash.to_s.split('').sort:

asserts "that it glues data from a child node" do
  ...
end.split('').sort.equals "{\"user\":{\"name\":\"leo\",\"city\":\"LA\",\"age\":12}}".split('').sort

Add Tilt Engine

Add Tilt support if Tilt is found (for Padrino/Sinatra support)

Partial/extends question

Hi, I'm having a little trouble wrapping my head around how to use partials and extends correctly. I have a list of people, and I'd like to include pagination info with the JSON, so my frontend can tell what page I'm on, and get to the next/previous page of data.

my json.rabl file looks like this:

object false
child(@people => :entries) do 
  attributes :id, :first_name
end
node(:pagination) do
  {
    :url => @pagination_url,
    :current_page => @people.current_page,
    :per_page => @people.per_page,
    :total_entries => @people.total_entries
  }
end

I'd like to push the pagination section into a shared partial, and possibly call it like this:

object false
child(@people => :entries) do 
  attributes :id, :first_name
end
partial "shared/pagination", :object => @people, :url => @pagination_url

That doesn't work. I've been struggling to land at the correct syntax to do this.

..and then have the shared/pagination.json.rabl look like this:

node(:pagination) do
  {
    :url => url,
    :current_page => object.current_page,
    :per_page => object.per_page,
    :total_entries => object.total_entries
  }
end

I guess my question is, how do I get where I want to be using rabl? I'd be happy to add a wiki entry to further explain partials and extends.. that is, after I understand how they work. Thanks!

template source is evaluated as empty

I set a debug point on line 55 of lib/rabl/template.rb. When I template.inspect I get "app/views/attendee/show.rabl" which is correct, but template.source returns and empty string.

If I then do File.open(Rails.root + template.inspect, 'r') { |f| puts f.read } that produces:

extends("attendee/base")
object @attendee

app/views/attendee/base.rabl

attributes :oid, :first_name, :last_name

I am setting @Attendee in my show action and I can not figure out why the rendered result is:

{"attendee": {}}

respond_with(:error) renders view still

I have a tour controller that has this in it:

  respond_to :json
  def show
    @tour = Tour.find_by_id_and_published(params[:id], true)
    if @tour.nil?
      respond_with(:error)
    else
    end
  end

(these isn't finished yet... so ya know...)

When I try to access a tour that doesn't exist, it still tries to render the rabl view, throwing a error. I don't know if my logic is wrong here but shouldn't it respond back with a 404 error with empty json or something like that?

Stub views in controller specs

I am using RSpec and in my controller specs I noticed that rabl views are still being rendered - usually rendering views with a template are stubbed in such examples ("By default, controller specs stub views with a template that renders an empty string instead of the views in the app." [1]).

I hacked that back into my specs manually with

ActionView::Template::Handlers::Rabl.stub(:call).and_return('')

Just wondering if there is a better way? Could this also be a bug on RSpec side, i.e. shouldn't it hook into the template renderers itself automatically, given it is possible?

Thanks again!

[1] http://relishapp.com/rspec/rspec-rails/v/2-6/dir/controller-specs/views-are-stubbed-by-default

use array instead of hash

How can I get an array as my root object (instead of {})?

I figured out a way to do it for my API using collections, but it is a bit of a hackโ€ฆ

How to remove the root node?

I have

[{"person":{"id":1,"firstname":"Zaphod","lastname":"BEEBLEBROX","gender":"Mr"}},{"person":{"id":2,"firstname":"Ford","lastname":"PREFECT","gender":"Mr"}}]

But i would like

[{"id":1,"firstname":"Zaphod","lastname":"BEEBLEBROX","gender":"Mr"},{"id":2,"firstname":"Ford","lastname":"PREFECT","gender":"Mr"}]

it's possible ?

I have builder template index.json.rabl for People#index controller with

attributes :id, :firstname, :lastname, :gender

and the collection of objects => @people

collection @variable produces array with empty hashes

I'm rather confused what's going on. I'm trying to do the example and I get this for a result

[{},{},{}]

Here's my controller

def index
    @courses = Course.all
    respond_to do |format|
      format.json
    end
end

Here's my index.rabl template

collection @courses
attributes :id, :title

However, my show action works great.

My controller:

  def show
    @course = Course.find(params[:id])
    respond_to do |format|
      format.json
    end
  end

My show.rabl template:

object @course 
attributes :title, :id

I'm using Rails 3.0.7 and Ruby 1.9.2-p180-patched. How would I test this to see what's going on?

Thanks!

`.each` called twice

sort of a low priority bug, however I noticed that object @users will iterate over @users twice.

This is noticeable in ORMs that return criteria objects that execute the query on iteration (such as mongoid). So the query actually gets executed twice.

Probably something to watch for and maybe add a unit test for it at some point. Not a huge deal since I can do to_a on the object before rendering it to rabl.

"partial" and "extends" support in Sinatra

partial() and extends() don't work in Sinatra because the internal method fetch_source() relies on either Rails or Padrino to resolve the path for the views directory. I had to monkey-patch that method in my Sinatra app:

module Rabl
  module Helpers
    def fetch_source(file, options={})
      file_path = Dir[File.join(APP_ROOT, 'views', file + "*.rabl")].first
      File.read(file_path) if file_path
    end
  end
end

.. Where APP_ROOT is a constant I defined in my app.

Is there a better way?

Registering in Padrino

Hello, when tried to use rabl in padrino just to add it to Gemfile it does not register as templating engine. It is solved when put gem 'rabl' after gem 'padrino' (which is last by default) in Gemfile. When I put breakpoint in rabl.rb:14 Padrino is not yet defined there. Do you think it is a problem?

Pepe

Some way to add global params?

I am using pagination, so I need to render all my users like an array, and then add to the results some pagination params (like :per_page, :current_page and :pages_count). Any suggestions how to do this?

Thanks.

Using a hash as the data object does not seem to work.

Hi,

I am probably doing this wrong, but I have a method that wants to return a collection and a status, so I am making my data object sort of like this:

@thing = {:foo => "bar", :pop => 123}

So, my rabl template is

    attributes :foo, :pop

Hoping to get
{ "foo" : "bar", "pop" :123}

But was getting this under last night's version:
{ "pop":"bar", "pop":123}

Today I get an error:
undefined method `to_html' for #<Rabl::Engine:0x1075c0730>

Perhaps I should be using the "object false" route...

Thanks,
Chris

PS Tried to add tests/fix it - seems that the problem is the attribute method can be passed a hash of options and its confusing the object hash as options:

https://github.com/kimptoc/rabl/commit/dc3be6eba6389998614f7cb846240aebcc2062ef

Child might not be named correctly

Hey,

I'm not sure if this is the expected behavior but it threw me off a little bit when I first ran into it so I thought I would post it here.

I have a tour_stop that has two tour_routes. This is what my models look like.

class TourRoute < ActiveRecord::Base

  #Associations
  belongs_to :end, :class_name => "TourStop"
  belongs_to :start, :class_name => "TourStop"

end

class TourStop < ActiveRecord::Base

  #Associations
  belongs_to :tour
  belongs_to :location
  has_one :route_starting_here, :class_name => "TourRoute", :foreign_key => "start_id"
  has_one :route_ending_here, :class_name => "TourRoute", :foreign_key => "end_id"

end

Ok cool. Now in my awesome api::tour view I have something like this

object @tour
attributes :created_at, :days_required, :description, :fee_description, :fee_scale, :name, :published, :updated_at, :lat, :lng

node :small_image_url do |tour|
 @root_url + tour.small_image_url
end

node :large_image_url do |tour|
 @root_url + tour.large_image_url
end


child :tour_stops do
  attributes :position, :updated_at, :created_at
  child :location do 
    attributes :name, :description, :location_type, :lat, :lng, :address, :city, :state, :postal_code, :phone, :email, :url, :fee_scale, :fee_description, :availability_description, :created_at, :updated_at
  end

  child :route_starting_here  do 
    attribute :route
  end

  child :route_ending_here do 
    attribute :route
  end
end

The part to look at here, is the child :route_starting_here portion. If I leave it as is, the json produced looks like this

{
    "tour": {
        "created_at": "2011-05-20T22:01:37Z",
        "days_required": null,
        "description": "This is the description for the grand canyon tour",
        "fee_description": null,
        "fee_scale": 1,
        "name": "Grand Canyon Tour",
        "published": true,
        "updated_at": "2011-05-20T22:01:37Z",
        "lat": 39,
        "lng": -111,
        "small_image_url": "http://tour_builder.dev/uploads/tour/image/1/small_test.gif",
        "large_image_url": "http://tour_builder.dev/uploads/tour/image/1/large_test.gif",
        "tour_stops": [
            {
                "tour_stop": {
                    "position": 1,
                    "updated_at": "2011-05-20T22:12:39Z",
                    "created_at": "2011-05-20T22:12:39Z",
                    "location": {
                        "name": "grand canyon store",
                        "description": "grand canyon store is a way awesome place",
                        "location_type": "museum",
                        "lat": 39,
                        "lng": -111,
                        "address": null,
                        "city": null,
                        "state": null,
                        "postal_code": null,
                        "phone": null,
                        "email": null,
                        "url": null,
                        "fee_scale": 1,
                        "fee_description": null,
                        "availability_description": null,
                        "created_at": "2011-05-20T22:12:11Z",
                        "updated_at": "2011-05-20T22:12:11Z"
                    },
                    "tour_route": {
                        "route": {
                            "type": "LineString",
                            "coordinates": [
                                [
                                    0,
                                    0
                                ],
                                [
                                    1,
                                    1
                                ],
                                [
                                    2,
                                    1
                                ],
                                [
                                    2,
                                    2
                                ]
                            ]
                        }
                    }
                }
            }
        ]
    }
}

Notice that tour_route is not being named :tour_starting_here.. it's being named "tour_route". In order for me to get it named correctly to :tour_starting_here I need to set it's alias like ...

  child :route_starting_here => :route_starting_here do 
    attribute :route
  end

Which will correctly produce

"route_starting_here": {
                        "route": {.......... other stuff blah blah blah

Shouldn't this all ready know that I named this route_starting_here? It seems like it's pulling from the class name to set the name vs the association name which imo would make more since. Anyway, just a thought. Thanks for any input.

Performance optimization ...

Hey guys,

Has anyone else noticed that some rabl views can take very long to render? I have API endpoints with 1800ms time in the view and just 226ms with ActiveRecord. I'm using Rails 3.1.0.rc4 by the way, but I also tried this with Rails 3.0.9 with similar results.

Peter

How to change the order of the nodes?

Here is my rabl view.

collection @lines => :lines
attributes :title, :description
code :id do |line|
  line.account.name+"-"+line.title
end

and the response is:

{"lines"=>
  [{"title"=>"private-line", "description"=>"Et pariatur eos eaquptas enim.", "id"=>"wliTa-private-line"}, 
   {"title"=>"public-line", "description"=>"This line can be viewed publicly", "id"=>"wliTa-public-line"}]
}

I want the id attribute to be at first attribute like below:

{"lines"=>
  [{"id"=>"wliTa-private-line", "title"=>"private-line", "description"=>"Et pariatur quae voluptas enim."}, 
  {"id"=>"wliTa-public-line", "title"=>"public-line", "description"=>"This line can be viewed publicly"}]
}

Is this doable or any other solution to get this order of response?

Shared attributes in similar rabl templates

Anyway to get instead:

index.json.rabl:

collection @offers

attributes :id, :title, :price, :permalink

child :metro_station do
  attributes :name
end

show.json.rabl:

object @offer

attributes :id, :title, :price, :permalink

child :metro_station do
  attributes :name
end

Something like that:

index.json.rabl:

collection @offers

render 'show'

show.json.rabl:

object @offer

attributes :id, :title, :price, :permalink

child :metro_station do
  attributes :name
end

I don't sure about syntax, but I hope you are got idea. Thanks.

Parent access is needed.

Hi all.

I'm working in rails 3 with rabl (obviously) and I'm trying to create some json to reflect the following database structure.

class Tour < ActiveRecord::Base

  #Associations
  belongs_to :provider
  has_many :tour_stops
end

class TourStop < ActiveRecord::Base

  #Associations
  belongs_to :tour
  belongs_to :location
  has_one :route_starting_here, :class_name => "TourRoute", :foreign_key => "start_id"
  has_one :route_ending_here, :class_name => "TourRoute", :foreign_key => "end_id"
end

class Location < ActiveRecord::Base

  #Associations
  has_many :photos
  has_many :tour_stops
  has_one :video
  belongs_to :provider
end

class TourRoute < ActiveRecord::Base

  #Associations
  belongs_to :end, :class_name => "TourStop"
  belongs_to :start, :class_name => "TourStop"
end

Note: I left out validations etc cause they aren't relevant for this ticket.

What I have created is something like looks kind of like this:

*show.json.rabl

object @tour
attributes :created_at, :days_required, :description, :fee_description, :fee_scale, :name, :published, :updated_at, :lat, :lng

node :small_image_url do |tour|
 @root_url + tour.small_image_url
end

node :large_image_url do |tour|
 @root_url + tour.large_image_url
end


child :tour_stops do
  attributes :position, :updated_at, :created_at
  child :location do 
    attributes :name, :description, :location_type, :coords, :address, :city, :state, :postal_code, :phone, :email, :url, :fee_scale, :fee_description, :availability_description, :created_at, :updated_at
  end
end

Note: This isn't 100% complete but it demonstrates the issue i'm running into.

When I try to grab json output I get this error:
"stack level too deep"

I believe this is because a :location is not the child of tour_stop but the parent of tour_stop. However not sure. Should there be a "parent" method as well as a "child" method?

Perhaps there is a way to do this with the current rabl, but I'm unsure as of now.

Thanks for the help.

Wiki

Hi
It would be nice if there was a wiki to post how tos etc.

Unique child count question

Hello,

maybe it is not Rabl question, but I have no more place to ask this.

I have three models: user, user_item, and item. User can buy items, user's items store in user_item model. If user buys some item 20 times, there would be 20 similar records in user_items table.

code(:num) do |e|
  current_user.items.include?(e) ? current_user.items.all.count(e) : 0
end

So, I render only unique items and field :num with quantity on items, like:

items: [
  {item1: {title: "xxx", num: 20}}
  {item1: {title: "yyy", num: 10}}
]

This is hacky a bit, but it works.

But one more problem: I have Block (like a category), Mission (it's a little game) and every mission have one or more required items. And I should render only unique required items per mission, and I do not know, how to do this. This is how I do this:

# controller
def blocks
  @blocks ||= Block.includes(:missions => [ :requirement_items ])
end


# view
object false

child blocks do
  attributes :id, :title

  child :missions do
    attributes :id, :title

    child :requirement_items => :requirements do
      attributes :id, :title
      # What will be here?
    end
  end
end

I wish you will help me, because I've already spent some days to complete this ;(

Thank you again for your awesome gem!

nil values - option to default to exclude them?

Hi,

I dont suppose there is a way to exclude values if they are nil/null?

Guess I can add an if condition in the template, but wondering if there is a global option to do this...

Thanks,
Chris

Change Log Please!

I see that you've bumped the gem to 0.3.0. I'd love to see a change log in the root directory to see if what's changed and if there are any breaking changes I need to know about before upgrading.

Thanks!

Add Tilt Engine Format Detection

Right now Tilt Engine always renders to JSON. Check the 'scope' format and use that to determine the format to render (xml or json)

Another noob question - how to access the flash message object

Hi,

Sorry, I should be able to do this, I think - the flash object is a collection of arrays - each of which are tuples, [name_symbol, message_string] - I tried this, but nothing is coming out - is there a debug mode that logs errors? Again I am using it in the context of "object false"

child @flash do |n,m|
attributes n, m
end

and this:

child @flash do
attributes :msg, :name
end

Thanks,
Chris

Configurations for json output

On a project and template basis, there needs to be configuration. Namely,

  • Include a 'root' node at the base of the collection users : { ... }
  • Include a 'root' node at the base of the object: { 'user' : { ... } }

And probably others I can't think of right now.

Root names for STI collections

I've been converting an API that used to_json over to RABL and one quirk that I've run into is the way in which the root names of items in a collection are determined. For instance, consider the following RABL template:

collection @fields

attributes :name, :id, :value

Where @fields is a collection of entities which use single table inheritance:

class Field < ActiveRecord::Base; end

class AddressField < Field; end

class EmailField < Field; end

When serializing to JSON using #to_json, I get something like:

[
  {"email_field": {"name":"Work Email", "id":1234, "value":"[email protected]"}}, 
  {"address_field": {"name":"Home Address","id":12345,"value":"123 Fake St."}}
] 

But when serializing using the RABL template, the root node names are always determined by the first item in the collection:

[
  {"email_field": {"name":"Work Email", "id":1234, "value":"[email protected]"}}, 
  {"email_field": {"name":"Home Address","id":12345,"value":"123 Fake St."}}
] 

I think the simplest fix here would be to change https://github.com/nesquena/rabl/blob/master/lib/rabl/engine.rb#L34 so that the object_name is computed for each item in the collection. I'm going to give that a try locally, but wanted to see if anyone had a better approach.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.