Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

BSON support provided through bson gem. #163

Merged
merged 1 commit into from

4 participants

@Antiarchitect

Features:

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

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

@nesquena
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.

@Antiarchitect

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

@nesquena
Owner

Awesome thanks for working on this.

@Antiarchitect 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
@Antiarchitect

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

@nesquena nesquena merged commit f4aed40 into nesquena:master
@nesquena
Owner

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

@ismaelga 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. @Antiarchitect

    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.
View
35 README.md
@@ -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 ###
View
20 lib/rabl/configuration.rb
@@ -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
View
16 lib/rabl/engine.rb
@@ -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
View
4 lib/rabl/helpers.rb
@@ -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
View
5 rabl.gemspec
@@ -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
View
331 test/bson_engine_test.rb
@@ -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.