diff --git a/README.md b/README.md index 844a6591..da7d7f97 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,9 @@ RABL is intended to require little to no configuration to get working. This is t 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.include_json_root = true + # config.include_msgpack_root = true # config.include_xml_root = false # config.enable_json_callbacks = false # config.xml_options = { :dasherize => true, :skip_types => false } @@ -105,6 +107,32 @@ gem 'yajl-ruby', :require => "yajl" and RABL will automatically start using that engine for encoding your JSON responses! +### Message Pack ### + +Rabl also includes optional support for [Message Pack](http://www.msgpack.org/) serialization format using the [msgpack gem](https://rubygems.org/gems/msgpack). +To enable, include the msgpack gem in your project's Gemfile. Then use Rabl as normal with the `msgpack` format (akin to json and xml formats). + +```ruby +# Gemfile +gem 'msgpack', '~> 0.4.5' +``` + +One can additionally use a custom Message Pack implementation by setting the Rabl `msgpack_engine` configuration attribute. This custom message pack engine must conform to the MessagePack#pack method signature. + +```ruby +class CustomEncodeEngine + def self.pack string + # Custom Encoding by your own engine. + end +end + +Rabl.configure do |config| + config.msgpack_engine = CustomEncodeEngine +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. + ## Usage ## ### Object Assignment ### @@ -359,4 +387,4 @@ See the [examples](https://github.com/nesquena/rabl/tree/master/examples) direct ## Copyright -Copyright © 2011 Nathan Esquenazi. See [MIT-LICENSE](https://github.com/nesquena/rabl/blob/master/MIT-LICENSE) for details. \ No newline at end of file +Copyright © 2011 Nathan Esquenazi. See [MIT-LICENSE](https://github.com/nesquena/rabl/blob/master/MIT-LICENSE) for details. diff --git a/lib/rabl/configuration.rb b/lib/rabl/configuration.rb index 37a2988c..a6d42c55 100644 --- a/lib/rabl/configuration.rb +++ b/lib/rabl/configuration.rb @@ -1,19 +1,29 @@ +# We load the msgpack library if it is available. +begin + require 'msgpack' +rescue LoadError +end + module Rabl # Rabl.host class Configuration attr_accessor :include_json_root + attr_accessor :include_msgpack_root attr_accessor :include_xml_root attr_accessor :enable_json_callbacks attr_writer :json_engine + attr_writer :msgpack_engine attr_writer :xml_options DEFAULT_XML_OPTIONS = { :dasherize => true, :skip_types => false } def initialize @include_json_root = true + @include_msgpack_root = true @include_xml_root = false @enable_json_callbacks = false @json_engine = nil + @msgpack_engine = nil @xml_options = {} end @@ -22,6 +32,12 @@ def json_engine @json_engine || MultiJson.engine end + ## + # @return the MessagePack encoder/engine to use. + def msgpack_engine + @msgpack_engine || ::MessagePack + end + # Allows config options to be read like a hash # # @param [Symbol] option Key for a given attribute diff --git a/lib/rabl/engine.rb b/lib/rabl/engine.rb index 11944a02..f07d9f31 100644 --- a/lib/rabl/engine.rb +++ b/lib/rabl/engine.rb @@ -46,6 +46,15 @@ def to_json(options={}) format_json result end + # Returns a msgpack representation of the data object + # to_msgpack(:root => true) + def to_msgpack(options={}) + include_root = Rabl.configuration.include_msgpack_root + options = options.reverse_merge(:root => include_root, :child_root => include_root) + result = @_collection_name ? { @_collection_name => to_hash(options) } : to_hash(options) + Rabl.configuration.msgpack_engine.pack result + end + # Returns an xml representation of the data object # to_xml(:root => true) def to_xml(options={}) diff --git a/rabl.gemspec b/rabl.gemspec index d2a83be2..8e831e68 100644 --- a/rabl.gemspec +++ b/rabl.gemspec @@ -27,4 +27,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'tilt' s.add_development_dependency 'bson_ext' s.add_development_dependency 'yajl-ruby' + s.add_development_dependency 'msgpack', '~> 0.4.5' end diff --git a/test/msgpack_engine_test.rb b/test/msgpack_engine_test.rb new file mode 100644 index 00000000..d3d819d1 --- /dev/null +++ b/test/msgpack_engine_test.rb @@ -0,0 +1,333 @@ +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 => 'msgpack') { t } } + + context "with msgpack defaults" do + setup do + Rabl.configure do |config| + # Comment this line out because include_msgpack_root is default. + #config.include_msgpack_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 "\x81\xA4user\x80" + + 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 "\x81\xA6person\x80" + 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) + end.equals "\x92\x81\xA4user\x80\x81\xA4user\x80" + + 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) + end.equals "\x81\xA6person\x92\x81\xA6person\x80\x81\xA6person\x80" + + 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) + end.equals "\x81\xA4user\x81\xA4name\xA6irvine" + + 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) + end.equals "\x81\xA4user\x81\xA4city\xA6irvine" + + 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) + end.equals "\x81\xA4user\x81\xA4city\xA6irvine" + + end + + context "#code" do + + asserts "that it can create an arbitraty code node" do + template = rabl %{ + code(:foo) { 'bar' } + } + template.render(Object.new) + end.equals "\x81\xA3foo\xA3bar" + + asserts "that it can be passed conditionals" do + template = rabl %{ + code(:foo, :if => lambda { |i| false }) { 'bar' } + } + template.render(Object.new) + end.equals "\x80" + + 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) + end.equals "\x81\xA4user\x82\xA4name\xA3leo\xA4user\x81\xA4city\xA2LA" + + 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) + + end.equals "\x81\xA4user\x82\xA4name\xA3leo\xA6person\x81\xA4city\xA2LA" + 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) + end.equals "\x81\xA4user\x83\xA4name\xA3leo\xA4city\xA2LA\xA3age\f" + end + + teardown do + Rabl.reset_configuration! + end + end + + context "with msgpack_engine" do + setup do + class CustomEncodeEngine + def self.pack string + 42 + end + end + + Rabl.configure do |config| + config.msgpack_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 msgpack root" do + setup do + Rabl.configure do |config| + config.include_msgpack_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 "\x80" + + 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 "\x80" + 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) + end.equals "\x92\x80\x80" + + 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) + end.equals "\x81\xA6person\x92\x80\x80" + + 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) + end.equals "\x81\xA4name\xA6irvine" + + 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) + end.equals "\x81\xA4city\xA6irvine" + + 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) + end.equals "\x81\xA4city\xA6irvine" + + end + + context "#code" do + + asserts "that it can create an arbitraty code node" do + template = rabl %{ + code(:foo) { 'bar' } + } + template.render(Object.new) + end.equals "\x81\xA3foo\xA3bar" + + asserts "that it can be passed conditionals" do + template = rabl %{ + code(:foo, :if => lambda { |i| false }) { 'bar' } + } + template.render(Object.new) + end.equals "\x80" + + 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) + end.equals "\x82\xA4name\xA3leo\xA4user\x81\xA4city\xA2LA" + + 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) + + end.equals "\x82\xA4name\xA3leo\xA6person\x81\xA4city\xA2LA" + 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) + end.equals "\x83\xA4name\xA3leo\xA4city\xA2LA\xA3age\f" + end + + teardown do + Rabl.reset_configuration! + end + end +end