diff --git a/README.md b/README.md index a9ccf758..fe37bc6e 100644 --- a/README.md +++ b/README.md @@ -102,9 +102,11 @@ Rabl.configure do |config| # config.json_engine = nil # Any multi\_json engines # 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.enable_json_callbacks = false # config.xml_options = { :dasherize => true, :skip_types => false } @@ -191,6 +193,32 @@ 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. +### Plist ### + +Rabl also includes optional support for [Plist](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/Introduction/Introduction.html]) serialization format using the [plist gem](http://plist.rubyforge.org/). +To enable, include the plist gem in your project's Gemfile. Then use Rabl as normal with the `plist` format (akin to other formats). + +```ruby +# Gemfile +gem 'plist' +``` + +There is also an option for a custom Plist implementation by setting the Rabl `plist_engine` configuration attribute. + +```ruby +class CustomEncodeEngine + def self.dump string + # Custom Encoding by your own engine. + end +end + +Rabl.configure do |config| + config.plist_engine = CustomEncodeEngine +end +``` + +*NOTE*: Attempting to render the plist format without either including the plist gem or setting a `plist_engine` will cause an exception to be raised. + ## Usage ## ### Object Assignment ### @@ -482,6 +510,7 @@ Thanks to [Miso](http://gomiso.com) for allowing me to create this for our appli * [Matthew Schulkind](https://github.com/mschulkind) - Cleanup of configuration and tests * [Luke van der Hoeven](https://github.com/plukevdh) - Support non-ORM objects in templates * [Andrey Voronkov](https://github.com/Antiarchitect) - Added BSON format support +* [Alli Witheford](https://github.com/alzeih) - Added Plist format support and many more contributors listed in the [CHANGELOG](https://github.com/nesquena/rabl/blob/master/CHANGELOG.md). diff --git a/lib/rabl/configuration.rb b/lib/rabl/configuration.rb index fd5ec1ab..26dcfe57 100644 --- a/lib/rabl/configuration.rb +++ b/lib/rabl/configuration.rb @@ -10,6 +10,12 @@ rescue LoadError end +# We load the plist library if it is available. +begin + require 'plist' +rescue LoadError +end + # Load MultiJSON require 'multi_json' @@ -18,6 +24,7 @@ module Rabl class Configuration attr_accessor :include_json_root attr_accessor :include_msgpack_root + attr_accessor :include_plist_root attr_accessor :include_xml_root attr_accessor :include_bson_root attr_accessor :enable_json_callbacks @@ -25,6 +32,7 @@ class Configuration attr_accessor :bson_move_id attr_writer :msgpack_engine attr_writer :bson_engine + attr_writer :plist_engine attr_writer :xml_options attr_accessor :cache_sources @@ -33,6 +41,7 @@ class Configuration def initialize @include_json_root = true @include_msgpack_root = true + @include_plist_root = true @include_xml_root = false @include_bson_root = true @enable_json_callbacks = false @@ -41,6 +50,7 @@ def initialize @json_engine = nil @msgpack_engine = nil @bson_engine = nil + @plist_engine = nil @xml_options = {} @cache_sources = false end @@ -67,6 +77,11 @@ def msgpack_engine # @return the Bson encoder/engine to use. def bson_engine @bson_engine || ::BSON + + ## + # @return the Plist encoder/engine to use. + def plist_engine + @plist_engine || ::Plist::Emit end # Allows config options to be read like a hash diff --git a/lib/rabl/engine.rb b/lib/rabl/engine.rb index a9f6596d..2854c25a 100644 --- a/lib/rabl/engine.rb +++ b/lib/rabl/engine.rb @@ -62,6 +62,15 @@ def to_msgpack(options={}) end alias_method :to_mpac, :to_msgpack + # Returns a plist representation of the data object + # to_plist(:root => true) + def to_plist(options={}) + include_root = Rabl.configuration.include_plist_root + options = options.reverse_merge(:root => include_root, :child_root => include_root) + result = defined?(@_collection_name) ? { @_collection_name => to_hash(options) } : to_hash(options) + Rabl.configuration.plist_engine.dump(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 730a43b2..932806ee 100644 --- a/rabl.gemspec +++ b/rabl.gemspec @@ -29,4 +29,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'yajl-ruby' s.add_development_dependency 'msgpack', '~> 0.4.5' s.add_development_dependency 'bson', '~> 1.5.2' + s.add_development_dependency 'plist' end diff --git a/test/plist_engine_test.rb b/test/plist_engine_test.rb new file mode 100644 index 00000000..e045e688 --- /dev/null +++ b/test/plist_engine_test.rb @@ -0,0 +1,332 @@ +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 => 'plist') { t } } + + context "with plist defaults" do + setup do + Rabl.configure do |config| + # Comment this line out because include_plist_root is default. + #config.include_plist_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 "\n\n\n\n\tuser\n\t\n\n\n" + + 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 "\n\n\n\n\tperson\n\t\n\n\n" + 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 "\n\n\n\n\t\n\t\tuser\n\t\t\n\t\n\t\n\t\tuser\n\t\t\n\t\n\n\n" + + 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 "\n\n\n\n\tperson\n\t\n\t\t\n\t\t\tperson\n\t\t\t\n\t\t\n\t\t\n\t\t\tperson\n\t\t\t\n\t\t\n\t\n\n\n" + + 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 "\n\n\n\n\tuser\n\t\n\t\tname\n\t\tirvine\n\t\n\n\n" + + 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 "\n\n\n\n\tuser\n\t\n\t\tcity\n\t\tirvine\n\t\n\n\n" + + 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 "\n\n\n\n\tuser\n\t\n\t\tcity\n\t\tirvine\n\t\n\n\n" + + 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 "\n\n\n\n\tfoo\n\tbar\n\n\n" + + asserts "that it can be passed conditionals" do + template = rabl %{ + code(:foo, :if => lambda { |i| false }) { 'bar' } + } + template.render(Object.new) + end.equals "\n\n\n\n\n" + + 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 "\n\n\n\n\tuser\n\t\n\t\tname\n\t\tleo\n\t\tuser\n\t\t\n\t\t\tcity\n\t\t\tLA\n\t\t\n\t\n\n\n" + + 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 "\n\n\n\n\tuser\n\t\n\t\tname\n\t\tleo\n\t\tperson\n\t\t\n\t\t\tcity\n\t\t\tLA\n\t\t\n\t\n\n\n" + 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 "\n\n\n\n\tuser\n\t\n\t\tage\n\t\t12\n\t\tcity\n\t\tLA\n\t\tname\n\t\tleo\n\t\n\n\n" + end + + teardown do + Rabl.reset_configuration! + end + end + + context "with plist_engine" do + setup do + class CustomPlistEncodeEngine + def self.dump string + 42 + end + end + + Rabl.configure do |config| + config.plist_engine = CustomPlistEncodeEngine + 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 plist root" do + setup do + Rabl.configure do |config| + config.include_plist_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 "\n\n\n\n\n" + + 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 "\n\n\n\n\n" + 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 "\n\n\n\n\t\n\t\n\n\n" + + 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 "\n\n\n\n\tperson\n\t\n\t\t\n\t\t\n\t\n\n\n" + + 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 "\n\n\n\n\tname\n\tirvine\n\n\n" + + 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 "\n\n\n\n\tcity\n\tirvine\n\n\n" + + 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 "\n\n\n\n\tcity\n\tirvine\n\n\n" + + 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 "\n\n\n\n\tfoo\n\tbar\n\n\n" + + asserts "that it can be passed conditionals" do + template = rabl %{ + code(:foo, :if => lambda { |i| false }) { 'bar' } + } + template.render(Object.new) + end.equals "\n\n\n\n\n" + + 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 "\n\n\n\n\tname\n\tleo\n\tuser\n\t\n\t\tcity\n\t\tLA\n\t\n\n\n" + + 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 "\n\n\n\n\tname\n\tleo\n\tperson\n\t\n\t\tcity\n\t\tLA\n\t\n\n\n" + 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 "\n\n\n\n\tage\n\t12\n\tcity\n\tLA\n\tname\n\tleo\n\n\n" + end + + teardown do + Rabl.reset_configuration! + end + end +end