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