Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

BSON support provided through bson gem. #163

Merged
merged 1 commit into from

4 participants

Andrey Voronkov Andrey Ognevsky Oleg Balbekov Nathan Esquenazi
Andrey Voronkov

Features:

  • no tests :trollface:
  • collection should always be wrapped into hash (implemented into to_bson) because of BSON.serialize accepts only hash.
Andrey Ognevsky

I've tried add bson support to Rabl couple months ago, but something gone wrong.
Great work, thanks!

Nathan Esquenazi
Owner

Sweet, great to have BSON support. If we can add something akin to https://github.com/nesquena/rabl/blob/master/test/msgpack_engine_test.rb for BSON, I will merge this and release a new gem version.

Andrey Voronkov

I think I'll provide some tests soon - lack of time for today.

Nathan Esquenazi
Owner

Awesome thanks for working on this.

Andrey Voronkov Antiarchitect BSON format support provided. Fixed pluralization (without requiring …
…active_support/inflector it doesn't work in Rabl::Helpers). Covered by tests, results were compared with msgpack results and matches except for those that returns array - BSON.serialize can't accept arrays so it wraps it into a hash with first object name pluralization as a key.
b9ec72b
Andrey Voronkov

I've just corrected my previous commit. Not it is covered by tests, bugs are fixed and it works as needed.

Nathan Esquenazi nesquena merged commit f4aed40 into from
Nathan Esquenazi
Owner

OK, added you to the readme, updated the changelog and pushed out 0.5.5.e. Let me know if everything looks OK.

Ismael Abreu ismaelga referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 12, 2012
  1. Andrey Voronkov

    BSON format support provided. Fixed pluralization (without requiring …

    Antiarchitect authored
    …active_support/inflector it doesn't work in Rabl::Helpers). Covered by tests, results were compared with msgpack results and matches except for those that returns array - BSON.serialize can't accept arrays so it wraps it into a hash with first object name pluralization as a key.
This page is out of date. Refresh to see the latest.
35 README.md
View
@@ -1,6 +1,6 @@
# RABL #
-RABL (Ruby API Builder Language) is a Rails and [Padrino](http://padrinorb.com) ruby templating system for generating JSON and XML. When using the ActiveRecord 'to_json' method, I tend to quickly find myself wanting a more expressive and powerful solution for generating APIs.
+RABL (Ruby API Builder Language) is a Rails and [Padrino](http://padrinorb.com) ruby templating system for generating JSON, XML, MessagePack and BSON. When using the ActiveRecord 'to_json' method, I tend to quickly find myself wanting a more expressive and powerful solution for generating APIs.
This is especially frustrating when the JSON representation is complex or doesn't match the exact schema defined in the database.
I wanted a simple and flexible system for generating my APIs. In particular, I wanted to easily:
@@ -100,8 +100,10 @@ Rabl.configure do |config|
# Commented as these are the defaults
# config.json_engine = nil # Any multi\_json engines
# config.msgpack_engine = nil # Defaults to ::MessagePack
+ # config.bson_engine = nil # Defaults to ::BSON
# config.include_json_root = true
# config.include_msgpack_root = true
+ # config.include_bson_root = true
# config.include_xml_root = false
# config.enable_json_callbacks = false
# config.xml_options = { :dasherize => true, :skip_types => false }
@@ -145,6 +147,37 @@ end
*NOTE*: Attempting to render the msgpack format without either including the msgpack gem or setting a `msgpack_engine` will cause an exception to be raised.
+### BSON ###
+
+Rabl also includes optional support for [BSON](http://bsonspec.org/) serialization format using the [bson gem](https://rubygems.org/gems/bson).
+To enable, include the bson gem in your project's Gemfile. Then use Rabl as normal with the `bson` format (akin to json and xml formats).
+
+```ruby
+# Gemfile
+gem 'bson', '~> 1.5.2'
+```
+To use it with Rails just register bson mime type format.
+```ruby
+# config/initializers/mime_types.rb
+Mime::Type.register "application/bson", :bson
+```
+
+One can additionally use a custom BSON implementation by setting the Rabl `bson_engine` configuration attribute. This custom BSON engine must conform to the BSON#serialize method signature.
+
+```ruby
+class CustomEncodeEngine
+ def self.serialize string
+ # Custom Encoding by your own engine.
+ end
+end
+
+Rabl.configure do |config|
+ config.bson_engine = CustomEncodeEngine
+end
+```
+
+*NOTE*: Attempting to render the bson format without either including the bson gem or setting a `bson_engine` will cause an exception to be raised.
+
## Usage ##
### Object Assignment ###
20 lib/rabl/configuration.rb
View
@@ -4,6 +4,12 @@
rescue LoadError
end
+# We load the bson library if it is available.
+begin
+ require 'bson'
+ rescue LoadError
+end
+
# Load MultiJSON
require 'multi_json'
@@ -13,8 +19,12 @@ class Configuration
attr_accessor :include_json_root
attr_accessor :include_msgpack_root
attr_accessor :include_xml_root
+ attr_accessor :include_bson_root
attr_accessor :enable_json_callbacks
+ attr_accessor :bson_check_keys
+ attr_accessor :bson_move_id
attr_writer :msgpack_engine
+ attr_writer :bson_engine
attr_writer :xml_options
DEFAULT_XML_OPTIONS = { :dasherize => true, :skip_types => false }
@@ -23,9 +33,13 @@ def initialize
@include_json_root = true
@include_msgpack_root = true
@include_xml_root = false
+ @include_bson_root = true
@enable_json_callbacks = false
+ @bson_check_keys = false
+ @bson_move_id = false
@json_engine = nil
@msgpack_engine = nil
+ @bson_engine = nil
@xml_options = {}
end
@@ -47,6 +61,12 @@ def msgpack_engine
@msgpack_engine || ::MessagePack
end
+ ##
+ # @return the Bson encoder/engine to use.
+ def bson_engine
+ @bson_engine || ::BSON
+ end
+
# Allows config options to be read like a hash
#
# @param [Symbol] option Key for a given attribute
16 lib/rabl/engine.rb
View
@@ -72,6 +72,21 @@ def to_xml(options={})
to_hash(options).to_xml(xml_options)
end
+ # Returns a bson representation of the data object
+ # to_bson(:root => true)
+ def to_bson(options={})
+ include_root = Rabl.configuration.include_bson_root
+ options = options.reverse_merge(:root => include_root, :child_root => include_root)
+ result = if defined?(@_collection_name)
+ { @_collection_name => to_hash(options) }
+ elsif defined?(@_bson_collection_name)
+ { @_bson_collection_name => to_hash(options) }
+ else
+ to_hash(options)
+ end
+ Rabl.configuration.bson_engine.serialize(result).to_s
+ end
+
# Sets the object to be used as the data source for this template
# object(@user)
# object @user => :person
@@ -85,6 +100,7 @@ def object(data)
# collection @users => :people
def collection(data)
@_collection_name = data.values.first if data.respond_to?(:each_pair)
+ @_bson_collection_name = data_name(data) if is_collection?(data)
self.object(data_object(data).to_a) if data
end
4 lib/rabl/helpers.rb
View
@@ -1,3 +1,5 @@
+require 'active_support/inflector' # for the sake of pluralizing
+
module Rabl
module Helpers
# data_object(data) => <AR Object>
@@ -18,7 +20,7 @@ def data_name(data)
return data.values.first if data.is_a?(Hash) # @user => :user
data = @_object.send(data) if data.is_a?(Symbol) && @_object # :address
if data.respond_to?(:first)
- data_name(data.first).pluralize if data.first.present?
+ data_name(data.first).to_s.pluralize if data.first.present?
else # actual data object
object_name = @_collection_name.to_s.singularize if defined? @_collection_name
object_name ||= data.class.respond_to?(:model_name) ? data.class.model_name.element : data.class.to_s.downcase
5 rabl.gemspec
View
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
s.authors = ["Nathan Esquenazi"]
s.email = ["nesquena@gmail.com"]
s.homepage = "https://github.com/nesquena/rabl"
- s.summary = %q{General ruby templating for json or xml}
- s.description = %q{General ruby templating for json or xml}
+ s.summary = %q{General ruby templating with json, bson, xml and msgpack support}
+ s.description = %q{General ruby templating with json, bson, xml and msgpack support}
s.rubyforge_project = "rabl"
@@ -28,4 +28,5 @@ Gem::Specification.new do |s|
s.add_development_dependency 'tilt'
s.add_development_dependency 'yajl-ruby'
s.add_development_dependency 'msgpack', '~> 0.4.5'
+ s.add_development_dependency 'bson', '~> 1.5.2'
end
331 test/bson_engine_test.rb
View
@@ -0,0 +1,331 @@
+require File.expand_path('../teststrap', __FILE__)
+require File.expand_path('../../lib/rabl', __FILE__)
+require File.expand_path('../../lib/rabl/template', __FILE__)
+require File.expand_path('../models/user', __FILE__)
+
+context "Rabl::Engine" do
+
+ helper(:rabl) { |t| RablTemplate.new("code", :format => 'bson') { t } }
+
+ context "with bson defaults" do
+ setup do
+ Rabl.configure do |config|
+ # Comment this line out because include_bson_root is default.
+ #config.include_bson_root = true
+ end
+ end
+
+ context "#object" do
+
+ asserts "that it sets data source" do
+ template = rabl %q{
+ object @user
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new
+ template.render(scope)
+ end.matches "\x10\x00\x00\x00\x03user\x00\x05\x00\x00\x00\x00\x00"
+
+ asserts "that it can set root node" do
+ template = rabl %q{
+ object @user => :person
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new
+ template.render(scope).split("").sort
+ end.equals "\x12\x00\x00\x00\x03person\x00\x05\x00\x00\x00\x00\x00".split("").sort
+ end
+
+ context "#collection" do
+
+ asserts "that it sets object to be casted as a simple array" do
+ template = rabl %{
+ collection @users
+ }
+ scope = Object.new
+ scope.instance_variable_set :@users, [User.new, User.new]
+ template.render(scope).split("").sort
+ end.equals "7\x00\x00\x00\x04users\x00+\x00\x00\x00\x030\x00\x10\x00\x00\x00\x03user\x00\x05\x00\x00\x00\x00\x00\x031\x00\x10\x00\x00\x00\x03user\x00\x05\x00\x00\x00\x00\x00\x00\x00".split("").sort
+
+ asserts "that it sets root node for objects" do
+ template = rabl %{
+ collection @users => :people
+ }
+ scope = Object.new
+ scope.instance_variable_set :@users, [User.new, User.new]
+ template.render(scope).split("").sort
+ end.equals "<\x00\x00\x00\x04people\x00/\x00\x00\x00\x030\x00\x12\x00\x00\x00\x03person\x00\x05\x00\x00\x00\x00\x00\x031\x00\x12\x00\x00\x00\x03person\x00\x05\x00\x00\x00\x00\x00\x00\x00".split("").sort
+
+ end
+
+ context "#attribute" do
+
+ asserts "that it adds an attribute or method to be included in output" do
+ template = rabl %{
+ object @user
+ attribute :name
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'irvine')
+ template.render(scope).split("").sort
+ end.equals "!\x00\x00\x00\x03user\x00\x16\x00\x00\x00\x02name\x00\a\x00\x00\x00irvine\x00\x00\x00".split("").sort
+
+ asserts "that it can add attribute under a different key name through :as" do
+ template = rabl %{
+ object @user
+ attribute :name, :as => 'city'
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'irvine')
+ template.render(scope).split("").sort
+ end.equals "!\x00\x00\x00\x03user\x00\x16\x00\x00\x00\x02city\x00\a\x00\x00\x00irvine\x00\x00\x00".split("").sort
+
+ asserts "that it can add attribute under a different key name through hash" do
+ template = rabl %{
+ object @user
+ attribute :name => :city
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'irvine')
+ template.render(scope).split("").sort
+ end.equals "!\x00\x00\x00\x03user\x00\x16\x00\x00\x00\x02city\x00\a\x00\x00\x00irvine\x00\x00\x00".split("").sort
+
+ end
+
+ context "#code" do
+
+ asserts "that it can create an arbitraty code node" do
+ template = rabl %{
+ code(:foo) { 'bar' }
+ }
+ template.render(Object.new).split("").sort
+ end.equals "\x12\x00\x00\x00\x02foo\x00\x04\x00\x00\x00bar\x00\x00".split("").sort
+
+ asserts "that it can be passed conditionals" do
+ template = rabl %{
+ code(:foo, :if => lambda { |i| false }) { 'bar' }
+ }
+ template.render(Object.new).split("").sort
+ end.equals "\x05\x00\x00\x00\x00".split("").sort
+
+ end
+
+ context "#child" do
+
+ asserts "that it can create a child node" do
+ template = rabl %{
+ object @user
+ attribute :name
+ child(@user) { attribute :city }
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'leo', :city => 'LA')
+ template.render(scope).split("").sort
+ end.equals "6\x00\x00\x00\x03user\x00+\x00\x00\x00\x02name\x00\x04\x00\x00\x00leo\x00\x03user\x00\x12\x00\x00\x00\x02city\x00\x03\x00\x00\x00LA\x00\x00\x00\x00".split("").sort
+
+ asserts "that it can create a child node with different key" do
+ template = rabl %{
+ object @user
+ attribute :name
+ child(@user => :person) { attribute :city }
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'leo', :city => 'LA')
+ template.render(scope).split("").sort
+ end.equals "8\x00\x00\x00\x03user\x00-\x00\x00\x00\x02name\x00\x04\x00\x00\x00leo\x00\x03person\x00\x12\x00\x00\x00\x02city\x00\x03\x00\x00\x00LA\x00\x00\x00\x00".split("").sort
+ end
+
+ context "#glue" do
+
+ asserts "that it glues data from a child node" do
+ template = rabl %{
+ object @user
+ attribute :name
+ glue(@user) { attribute :city }
+ glue(@user) { attribute :age }
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'leo', :city => 'LA', :age => 12)
+ template.render(scope).split("").sort
+ end.equals "4\x00\x00\x00\x03user\x00)\x00\x00\x00\x02name\x00\x04\x00\x00\x00leo\x00\x02city\x00\x03\x00\x00\x00LA\x00\x10age\x00\f\x00\x00\x00\x00\x00".split("").sort
+ end
+
+ teardown do
+ Rabl.reset_configuration!
+ end
+ end
+
+ context "with bson_engine" do
+ setup do
+ class CustomEncodeEngine
+ def self.serialize string
+ 42
+ end
+ end
+
+ Rabl.configure do |config|
+ config.bson_engine = CustomEncodeEngine
+ end
+ end
+
+ asserts 'that it returns process by custom to_json' do
+ template = rabl %q{
+ object @user
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new
+ template.render(scope)
+ end.equals "42"
+
+ teardown do
+ Rabl.reset_configuration!
+ end
+ end
+
+ context "without bson root" do
+ setup do
+ Rabl.configure do |config|
+ config.include_bson_root = false
+ end
+ end
+
+ context "#object" do
+
+ asserts "that it sets data source" do
+ template = rabl %q{
+ object @user
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new
+ template.render(scope)
+ end.matches "\x05\x00\x00\x00\x00"
+
+ asserts "that it can set root node" do
+ template = rabl %q{
+ object @user => :person
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new
+ template.render(scope)
+ end.equals "\x05\x00\x00\x00\x00"
+ end
+
+ context "#collection" do
+
+ asserts "that it sets object to be casted as a simple array" do
+ template = rabl %{
+ collection @users
+ }
+ scope = Object.new
+ scope.instance_variable_set :@users, [User.new, User.new]
+ template.render(scope).split("").sort
+ end.equals "!\x00\x00\x00\x04users\x00\x15\x00\x00\x00\x030\x00\x05\x00\x00\x00\x00\x031\x00\x05\x00\x00\x00\x00\x00\x00".split("").sort
+
+ asserts "that it sets root node for objects" do
+ template = rabl %{
+ collection @users => :person
+ }
+ scope = Object.new
+ scope.instance_variable_set :@users, [User.new, User.new]
+ template.render(scope).split("").sort
+ end.equals "\"\x00\x00\x00\x04person\x00\x15\x00\x00\x00\x030\x00\x05\x00\x00\x00\x00\x031\x00\x05\x00\x00\x00\x00\x00\x00".split("").sort
+
+ end
+
+ context "#attribute" do
+
+ asserts "that it adds an attribute or method to be included in output" do
+ template = rabl %{
+ object @user
+ attribute :name
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'irvine')
+ template.render(scope).split("").sort
+ end.equals "\x16\x00\x00\x00\x02name\x00\a\x00\x00\x00irvine\x00\x00".split("").sort
+
+ asserts "that it can add attribute under a different key name through :as" do
+ template = rabl %{
+ object @user
+ attribute :name, :as => 'city'
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'irvine')
+ template.render(scope).split("").sort
+ end.equals "\x16\x00\x00\x00\x02city\x00\a\x00\x00\x00irvine\x00\x00".split("").sort
+
+ asserts "that it can add attribute under a different key name through hash" do
+ template = rabl %{
+ object @user
+ attribute :name => :city
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'irvine')
+ template.render(scope).split("").sort
+ end.equals "\x16\x00\x00\x00\x02city\x00\a\x00\x00\x00irvine\x00\x00".split("").sort
+
+ end
+
+ context "#code" do
+
+ asserts "that it can create an arbitraty code node" do
+ template = rabl %{
+ code(:foo) { 'bar' }
+ }
+ template.render(Object.new).split("").sort
+ end.equals "\x12\x00\x00\x00\x02foo\x00\x04\x00\x00\x00bar\x00\x00".split("").sort
+
+ asserts "that it can be passed conditionals" do
+ template = rabl %{
+ code(:foo, :if => lambda { |i| false }) { 'bar' }
+ }
+ template.render(Object.new).split("").sort
+ end.equals "\x05\x00\x00\x00\x00".split("").sort
+
+ end
+
+ context "#child" do
+
+ asserts "that it can create a child node" do
+ template = rabl %{
+ object @user
+ attribute :name
+ child(@user) { attribute :city }
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'leo', :city => 'LA')
+ template.render(scope).split("").sort
+ end.equals "+\x00\x00\x00\x02name\x00\x04\x00\x00\x00leo\x00\x03user\x00\x12\x00\x00\x00\x02city\x00\x03\x00\x00\x00LA\x00\x00\x00".split("").sort
+
+ asserts "that it can create a child node with different key" do
+ template = rabl %{
+ object @user
+ attribute :name
+ child(@user => :person) { attribute :city }
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'leo', :city => 'LA')
+ template.render(scope).split("").sort
+ end.equals "-\x00\x00\x00\x02name\x00\x04\x00\x00\x00leo\x00\x03person\x00\x12\x00\x00\x00\x02city\x00\x03\x00\x00\x00LA\x00\x00\x00".split("").sort
+ end
+
+ context "#glue" do
+
+ asserts "that it glues data from a child node" do
+ template = rabl %{
+ object @user
+ attribute :name
+ glue(@user) { attribute :city }
+ glue(@user) { attribute :age }
+ }
+ scope = Object.new
+ scope.instance_variable_set :@user, User.new(:name => 'leo', :city => 'LA', :age => 12)
+ template.render(scope).split("").sort
+ end.equals ")\x00\x00\x00\x02name\x00\x04\x00\x00\x00leo\x00\x02city\x00\x03\x00\x00\x00LA\x00\x10age\x00\f\x00\x00\x00\x00".split("").sort
+ end
+
+ teardown do
+ Rabl.reset_configuration!
+ end
+ end
+end
Something went wrong with that request. Please try again.