Module: Presentability

Extended by:
Loggability
Defined in:
lib/presentability.rb

Overview

Facade-based presenter toolkit with minimal assumptions.

## Basic Usage

Basic usage of Presentability requires two steps: declaring presenters and then using them.

### Declaring Presenters

Presenters are just regular Ruby classes with some convenience methods for declaring exposures, but in a lot of cases you’ll want to declare them all in one place. Presentability offers a mixin that implements a simple DSL for declaring presenters and their associations to entity classes, intended to be used in a container module:

require 'presentability'

module Acme::Presenters
  extend Presentability

  presenter_for( Acme::Widget ) do
      expose :sku
      expose :name
      expose :unit_price
  end

end

The block of ‘presenter_for` is evaluated in the context of a new Presenter class, so refer to that documentation for what’s possible there.

Sometimes you can’t (or don’t want to) have to load the entity class to declare a presenter for it, so you can also declare it using the class’s name:

presenter_for( 'Acme::Widget' ) do
    expose :sku
    expose :name
    expose :unit_price
end

### Using Presenters

You use presenters by instantiating them with the object they are a facade for (the “subject”), and then applying it:

acme_widget = Acme::Widget.new(
    sku: "FF-2237H455",
    name: "Throbbing Frobnulator",
    unit_price: 299,
    inventory_count: 301,
    wholesale_cost: 39
)
presentation = Acme::Presenters.present( acme_widget )
# => { :sku => "FF-2237H455", :name => "Throbbing Frobnulator", :unit_price => 299 }

If you want to present a collection of objects as a collection, you can apply presenters to the collection instead:

widgets_in_stock = Acme::Widget.where { inventory_count > 0 }
collection_presentation = Acme::Presenters.present_collection( widgets_in_stock )
# => [ {:sku => "FF-2237H455", [...]}, {:sku => "FF-2237H460", [...]}, [...] ]

The collection can be anything that is ‘Enumerable`.

### Presentation Options

Sometimes you want a bit more flexibility in what you present, allowing a single uniform presenter to be used in multiple use cases. To facilitate this, you can pass an options keyword hash to ‘#present`:

presenter_for( 'Acme::Widget' ) do
    expose :sku
    expose :name
    expose :unit_price

    # Only expose the wholesale cost if presented via an internal API
    expose :wholesale_cost, if: :internal_api
end

acme_widget = Acme::Widget.new(
    sku: "FF-2237H455",
    name: "Throbbing Frobnulator",
    unit_price: 299,
    inventory_count: 301,
    wholesale_cost: 39
)

# External API remains unchanged:
presentation = Acme::Presenters.present( acme_widget )
# => { :sku => "FF-2237H455", :name => "Throbbing Frobnulator", :unit_price => 299 }

# But when run from an internal service:
internal_presentation = Acme::Presenters.present( acme_widget, internal_api: true )
# => { :sku => "FF-2237H455", :name => "Throbbing Frobnulator", :unit_price => 299,
#      :wholesale_cost => 39 }

There are some options that are set for you:

<dl> <td>:in_collection</td> <dd>Set if the current object is being presented as part of a collection.</dd> </dl>

Defined Under Namespace

Classes: Presenter

Constant Summary collapse

VERSION =

Package version

'0.6.0'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(mod) ⇒ Object

Extension hook – decorate the including mod.



132
133
134
135
136
137
138
139
140
141
142
# File 'lib/presentability.rb', line 132

def self::extended( mod )
	super
	mod.singleton_class.attr_accessor :presenters
	mod.singleton_class.attr_accessor :serializers

	mod.presenters = {}
	mod.serializers = {
		Array => mod.method( :serialize_array ),
		Hash => mod.method( :serialize_hash ),
	}
end

Instance Method Details

#present(object, **options) ⇒ Object

Return a representation of the object by applying a declared presentation.



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/presentability.rb', line 171

def present( object, **options )
	representation = self.present_by_class( object, **options ) ||
		self.present_by_classname( object, **options ) ||
		self.serialize( object, **options )

	unless representation
		if object.instance_variables.empty?
			return object
		else
			raise NoMethodError, "no presenter found for %p" % [ object ], caller( 1 )
		end
	end

	return representation
end

#present_collection(collection, **options) ⇒ Object

Return an Array of all representations of the members of the collection by applying a declared presentation.



190
191
192
193
# File 'lib/presentability.rb', line 190

def present_collection( collection, **options )
	options = options.merge( in_collection: true )
	return collection.map {|object| self.present(object, **options) }
end

#presenter_for(entity_class, &block) ⇒ Object

Set up a presentation for the given entity_class.



151
152
153
154
155
156
# File 'lib/presentability.rb', line 151

def presenter_for( entity_class, &block )
	presenter_class = Class.new( Presentability::Presenter )
	presenter_class.module_eval( &block )

	self.presenters[ entity_class ] = presenter_class
end

#serialize(object) ⇒ Object

Serialize the specified object if a serializer has been declared for it and return the scalar result.



198
199
200
201
202
203
204
205
# File 'lib/presentability.rb', line 198

def serialize( object, ** )
	serializer = self.serializers[ object.class ] ||
		self.serializers[ object.class.name ] or
		return nil
	serializer_proc = serializer.to_proc

	return serializer_proc.call( object )
end

#serialize_array(object) ⇒ Object

Default serializer for an Array; returns a new array of presented objects.



209
210
211
212
213
# File 'lib/presentability.rb', line 209

def serialize_array( object )
	return object.map do |member|
		self.present( member, in_collection: true )
	end
end

#serialize_hash(object) ⇒ Object

Default serializer for a Hash; returns a new Hash of presented keys and values.



217
218
219
220
221
222
223
224
# File 'lib/presentability.rb', line 217

def serialize_hash( object )
	return object.each_with_object( {} ) do |(key, val), newhash|
		p_key = self.present( key, in_collection: true )
		p_val = self.present( val, in_collection: true )

		newhash[ p_key ] = p_val
	end
end

#serializer_for(type, method) ⇒ Object

Set up a rule for how to serialize objects of the given type if there is no presenter declared for it.



161
162
163
# File 'lib/presentability.rb', line 161

def serializer_for( type, method )
	self.serializers[ type ] = method
end