Description
Depict is a presentation library for Ruby which will let you define multiple presentations for any Ruby class using a (hopefully) pleasant DSL. It also includes the vital missing component from other presentation libraries which will be instantiating a new instance of a class from a presentation you specify - a common use case for this would be, for example, having multiple presentations of an object to support aging versions of a Web API.
DSL
This will be how you define a presentation inline in your class:
class User
include Depict
# presentation for unprivileged users
define_presentation :user do
maps :id
maps :email
end
# presentation for superusers
define_presentation :admin, :extends => :user do
maps :role
end
endYou will also be able to define presentations outside of your class if you want to break it up over several files
class User
include Depict
end
User.define_presentation :user do
maps :id
maps :email
end
User.define_presentation :admin, :extends => :user do
maps :role
endYou'll be able to convert into a presentation using a standard parameterized method
>> person = User.new(:id => 1, :email => "foo@example.com", :role => "user")
=> #<User:0xb73df4c0>
>> person.to_presentation(:user)
=> {:id => 1, :email => "foo@example.com"}
>> person.to_presentation(:admin)
=> {:id => 1, :email => "foo@example.com", :role => "user"}There will also be some method_missing magic
>> person = User.new(:id => 1, :email => "foo@example.com", :role => "user")
=> #<User:0xb73df4c0>
>> person.to_user_presentation
=> {:id => 1, :email => "foo@example.com"} And you will be able to construct new objects from a given presentation
>> person = User.new_from_presentation(:user, {:email => "foo@example.com"})
=> #<User:0xb73df4c0>
>> person.id
=> nil
>> person.email
=> "foo@example.com"Which also has some method_missing magic
>> person = User.new_from_user_presentation(:email => "foo@example.com")
=> #<User:0xb73df4c0>
>> person.id
=> nil
>> person.email
=> "foo@example.com"And will inherently give you some help with against mass assignment protection
>> User.new_from_user_presentation(:role => "admin").role
=> nil
>> User.new_from_admin_presentation(:role => "admin").role
=> "admin"Back to the DSL - you'll be able to specify your own custom serializers, so the mapped value is more appropriate
class User
include Depict
# UNIX timestamp representations for javascript friendly presentations
define_presentation :javascript do
maps :created_at, :serializes_with => lambda { |x| x.utc.to_i * 1000 }
end
# ISO 8601 formatted timestamps for XML friendly presentations
define_presentation :xml do
maps :created_at, :serializes_with => lambda { |x| x.strftime('%Y-%m-%dT%H:%M:%S%z') }
end
endWhich has deserializer counterparts as well
class User
include Depict
# UNIX timestamp representations for javascript friendly presentations
define_presentation :javascript do
maps :created_at, :serializes_with => lambda { |x| x.utc.to_i * 1000 },
:deserializes_with => lambda { |x| Time.at((x / 1000).to_i).utc }
end
# ISO 8601 formatted timestamps for XML friendly presentations
define_presentation :xml do
maps :created_at, :serializes_with => lambda { |x| x.strftime('%Y-%m-%dT%H:%M:%S%z') },
:deserializes_with => lambda { |x| Date.iso8601(x) }
end
endBoth of which will be able to be DRYed up with a serializer/deserializer class of some kind
class UnixTimestampConverter
def serialize(value)
value.utc.to_i * 1000
end
def deserialize(value)
Time.at((value / 1000).to_i).utc
end
end
class IsoTimestampConverter
def serialize(value)
value.strftime('%Y-%m%dT%H:%M:%S%z')
end
def deserialize(value)
Date.iso8601(value)
end
end
class User
include Depict
# UNIX timestamp representations for javascript friendly presentations
define_presentation :javascript do
maps :created_at, :with => UnixTimestampConverter.new
end
# ISO 8601 formatted timestamps for XML friendly presentations
define_presentation :xml do
maps :created_at, :with => IsoTimestampConverter.new
end
endInternally all of this syntactic sugar is managed by the Depict::Presenter class which can be used without
associating it directly with any model:
UserPresenter = Depict::Presenter.define do
maps :id
maps :name
maps :role
endWhich can be used for duck-typing and to promote DRYness:
UserPresenter.new(User.first).to_hash
UserPresenter.new(Customer.first).to_hash