presentability 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67ea66c2c975ecb5064c9ddb9c0be1a203b0e9e9944dfc3f3821f07e360fc726
4
- data.tar.gz: c13fdaccf960dc87d3feaed73e2a22a96ad805aed83cadbadeeee9cc60e80e49
3
+ metadata.gz: a47d99629c4294cef8b308ac17e98f5be3967d9c2663caab2ab633c81d512d2e
4
+ data.tar.gz: 0c325f17f38543a9a7420a9e1ecf727140ecfdc449853ea434f7643343b710e3
5
5
  SHA512:
6
- metadata.gz: 550a710c7ae589dfc6ebb1ed801508c8e2d347dbd37e1cdd19e3f3699b5b203ad38b7f2dc6a917e71d3e3dd3711e4eefe8c01594a1f4d92f10b120c4fe1e0a82
7
- data.tar.gz: 07d9674d3ad188b6b19923153accddf6175bf61d238d79867b7237a34a10595e066e10f4d37cc702bbcca8b7dcf65fd7df75ad0eb2fa0c4557716be3a370090b
6
+ metadata.gz: e5a49d7d49f0b9f0bc780097620394ecd8d96d62722e746bd34bb9834ba080b767d01527f2417bfb8b8e85c8cda6522621c6b9e0184b0791361343c2fb7b6461
7
+ data.tar.gz: dbc38700dccecb3e48c18525a836a6b5f2115e6fae5c662e2df7f325e54cd68b82d1940178ab98be41a89fda98a97dc502a18c6038cbd39c2c2e86a5d45890d5
checksums.yaml.gz.sig CHANGED
Binary file
data/GettingStarted.md ADDED
@@ -0,0 +1,234 @@
1
+ # Presentability - Getting Started
2
+
3
+ To set up your own presentation layer, there are three steps:
4
+
5
+ - Set up a Module as a presenter collection.
6
+ - Declare one or more _presenters_ within the collection.
7
+ - Use the collection to present entities from a service or other limited interface.
8
+
9
+ For the purposes of this document, we'll pretend we're responsible for creating a JSON web service for Acme Widgets, Inc. We've declared all of our company's code inside the `Acme` namespace. We're using a generic Sinatra-like web framework that lets you declare endpoints like so:
10
+
11
+ ```ruby
12
+ get '/status_check' do |parameters|
13
+ return { status: 'success' }.to_json
14
+ end
15
+ ```
16
+
17
+
18
+ ## Create a Presenter Collection
19
+
20
+ A _presenter collection_ is just a Module somewhere within your namespace that one can use to access the declared presenters. You designate it as a presenter collection simply by `extend`ing `Presentability`.
21
+
22
+ We'll declare ours under `Acme` and call it `Presenters`:
23
+
24
+ ```ruby
25
+ require 'presentability'
26
+
27
+ require 'acme'
28
+
29
+ module Acme::Presenters
30
+ extend Presentability
31
+
32
+ # Presenters will be declared here
33
+
34
+ end # module Acme::Presenters
35
+ ```
36
+
37
+ Since we haven't declared any presenters, the collection isn't really all that useful yet, so let's declare some.
38
+
39
+
40
+ ## Declare Presenters
41
+
42
+ A _presenter_ is an object that is responsible for constructing a _representation_ of another object. There are a number of reasons to use a presenter:
43
+
44
+ - Security: avoid exposing sensitive data from your domain objects to the public, e.g., passwords, internal prices, numeric IDs, etc.
45
+ - Transform: normalize and flatten complex objects to some standard form, e.g., convert `Time` object timestamps to RFC 2822 form.
46
+ - Consistency: change your model layer independently of your service's entities, e.g., adding a new column to a model doesn't automatically expose it in the service layer.
47
+
48
+ The _representation_ is just a simple object that serves as an intermediate form for the transformed object until it is ultimately encoded. The default _representation_ is an empty `Hash`, but you can customize it to suit your needs.
49
+
50
+ To declare a presenter, we'll call the `presenter_for` method on the presenter collection module, and then call `expose` or `expose_collection` for each attribute that should be exposed.
51
+
52
+ The first argument to `presenter_for` is the type of object the presenter is for, which can be specified in a couple of different ways. The easiest is to just pass in the class itself. The domain class the Acme service is built around is the Widget, so let's declare a presenter for it:
53
+
54
+ ```ruby
55
+ require 'acme/widget'
56
+
57
+ module Acme::Presenters
58
+ extend Presentability
59
+
60
+ presenter_for Acme::Widget do
61
+ expose :name
62
+ end
63
+
64
+ end # module Acme::Presenters
65
+ ```
66
+
67
+ To present an object, call `.present` on the collection module with the object to be presented, and it will return a representation that is a `Hash` with a single `:name` key-value pair:
68
+
69
+ ```ruby
70
+ widget = Acme::Widget.where( name: 'The Red One' )
71
+ Acme::Presenters.present( widget )
72
+ # => { name: "The Red One" }
73
+ ```
74
+
75
+ If we want to add a `sku` field to all widgets served by our service, we just add another exposure:
76
+
77
+ ```ruby
78
+ expose :sku
79
+ ```
80
+
81
+ ```ruby
82
+ widget = Acme::Widget.where( name: 'The Red One' )
83
+ Acme::Presenters.present( widget )
84
+ # => { name: "The Red One", sku: 'DGG-17044-0822' }
85
+ ```
86
+
87
+ ### Overriding Exposures
88
+
89
+ Sometime you want to alter the value that appears for a particular field. Say, for example, that the SKU that we exposed in our Widget presenter has an internal-only suffix in the form: `-xxxx` that we'd like to avoid exposing in a public-facing service. We can accomplish this by adding a block to it that alters the field from the model. Inside this block, the original object can be accessed via the `subject` method, so we can call the original `#sku` method and truncate it:
90
+
91
+ ```ruby
92
+ expose :sku do
93
+ original = self.subject.sku
94
+ return original.sub(/-\d{4}/, '')
95
+ end
96
+ ```
97
+
98
+ Now the last part of the SKU will be removed in the representation:
99
+
100
+ ```ruby
101
+ widget = Acme::Widget.where( name: 'The Red One' )
102
+ Acme::Presenters.present( widget )
103
+ # => { name: "The Red One", sku: 'DGG-17044' }
104
+ ```
105
+
106
+ ### Exposure Options
107
+
108
+ You can also pass zero or more options as a keyword Hash when presenting:
109
+
110
+ ```ruby
111
+ Acme::Presenters.present( widget, internal: true )
112
+ ```
113
+
114
+ There are a few ways options can be used out of the box:
115
+
116
+ #### Exposure Aliases
117
+
118
+ Sometimes you want the field in the representation to have a different name than the method on the model object:
119
+
120
+ ```ruby
121
+ presenter_for Acme::Company do
122
+ expose :id
123
+ expose :legal_entity, as: :name
124
+ expose :icon_url, as: :icon
125
+ end
126
+ ```
127
+
128
+ In the representation, the `#legal_entity` method will be called on the `Company` being presented and the return value associated with the `:name` key, and the same for `#icon_url` and `:icon`:
129
+
130
+ ```ruby
131
+ { id: 4, name: "John's Small Grapes", icon: "grapes-100.png" }
132
+ ```
133
+
134
+ #### Conditional Exposure
135
+
136
+ You can make an exposure conditional on an option being passed or not:
137
+
138
+ ```ruby
139
+ # Don't include the price if presented with `public: true` option set
140
+ expose :price, unless: :public
141
+
142
+ # Only include the settings if presented with `detailed: true` option set
143
+ expose :settings, if: :detailed
144
+ ```
145
+
146
+ #### Collection Exposure
147
+
148
+ A common use-case for conditional presentations is when you want an entity in a collection to be a less-detailed version. E.g.,
149
+
150
+ ```ruby
151
+ presenter_for Acme::User do
152
+ expose :id
153
+ expose :username
154
+ expose :email
155
+
156
+ expose :settings, unless: :in_collection
157
+ end
158
+ ```
159
+
160
+ You can pass `in_collection: true` when you're presenting, but you can also use the `present_collection` convenience method which sets it for you:
161
+
162
+ ```ruby
163
+ users = Acme::User.where( :activated ).limit( 20 )
164
+ Acme::Presenters.present_collection( users )
165
+ # => [{ id: 1, username: 'fran', email: 'fran@example.com'}, ...]
166
+ ```
167
+
168
+ #### Custom Options
169
+
170
+ You also have access to the presenter options (via the `#options` method) in a overridden exposure block. With this you can build your own presentation logic:
171
+
172
+ ```ruby
173
+ presenter_for Acme::Widget do
174
+ expose :name
175
+ expose :sku
176
+
177
+ expose :scancode do
178
+ self.subject.make_scancode( self.options[:scantype] )
179
+ end
180
+ end
181
+
182
+ # In your service:
183
+ widget = Acme::Widget[5]
184
+ Acme::Presenters.present( widget, scantype: :qr )
185
+ # { name: "Duck Quackers", sku: 'HBG-121-0424', scancode: '<qrcode data>'}
186
+ ```
187
+
188
+
189
+ ## Declare Serializers
190
+
191
+ Oftentimes your model objects include values which are themselves not inherently serializable to your representation format. To help with this, you can also declare a "serializer" for one or more classes in your collection using the `.serializer_for` method:
192
+
193
+ ```ruby
194
+ require 'time' # for Time#rfc2822
195
+
196
+ module Acme::Presenters
197
+ extend Presentability
198
+
199
+ serializer_for :IPAddr, :to_s
200
+ serializer_for Time, :rfc2822
201
+ serializer_for Set, :to_a
202
+
203
+ end # module Acme::Presenters
204
+ ```
205
+
206
+ Now when one of your models includes any of the given types, the corresponding method will be called on it and the result used as the value instead.
207
+
208
+ ## Use the Roda Plugin
209
+
210
+ If you're using the excellent [Roda](https://roda.jeremyevans.net/) web framework, `Presentability` includes a plugin for using it in your Roda application. To enable it, in your app just `require` your collection and enable the plugin. That will enable you to use `#present` and `#present_collection` in your routes:
211
+
212
+ ```ruby
213
+ require 'roda'
214
+ require 'acme/presenters'
215
+
216
+ class Acme::WebService < Roda
217
+
218
+ plugin :presenters, collection: Acme::Presenters
219
+ plugin :json
220
+
221
+ route do |r|
222
+ r.on "users" do
223
+ r.is do
224
+ # GET /users
225
+ r.get do
226
+ users = Acme::User.where( :activated )
227
+ present_collection( users.all )
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end # class Acme::WebService
233
+ ```
234
+
data/History.md CHANGED
@@ -1,6 +1,24 @@
1
1
  # Release History for presentability
2
2
 
3
3
  ---
4
+
5
+ ## v0.6.0 [2024-04-29] Michael Granger <ged@faeriemud.org>
6
+
7
+ Improvements:
8
+
9
+ - Added custom serialization, and better collection handling
10
+ - Added a Roda plugin
11
+ - Improved documentation
12
+
13
+
14
+ ## v0.5.0 [2023-11-06] Michael Granger <ged@faeriemud.org>
15
+
16
+ Improvements:
17
+
18
+ - Add recursive presentation
19
+ - Allow presenters to be defined for objects with no instance variables
20
+
21
+
4
22
  ## v0.4.0 [2023-02-02] Michael Granger <ged@faeriemud.org>
5
23
 
6
24
  Improvements:
data/README.md CHANGED
@@ -56,6 +56,8 @@ your data objects that return a limited representation of their subjects.
56
56
  More details can be found in the docs for the Presentability module, and in
57
57
  Presentability::Presenter.
58
58
 
59
+ See GettingStarted to get started with your own presenters.
60
+
59
61
 
60
62
  ## Prerequisites
61
63
 
@@ -88,7 +90,7 @@ This will install dependencies, and do any other necessary setup for development
88
90
 
89
91
  ## License
90
92
 
91
- Copyright (c) 2022, Michael Granger
93
+ Copyright (c) 2022-2024, Michael Granger
92
94
  All rights reserved.
93
95
 
94
96
  Redistribution and use in source and binary forms, with or without
@@ -4,7 +4,119 @@ require 'loggability'
4
4
 
5
5
  require 'presentability' unless defined?( Presentability )
6
6
 
7
- # :include: Presenter.md
7
+ #
8
+ # A presenter (facade) base class.
9
+ #
10
+ #
11
+ # ### Declaring Presenters
12
+ #
13
+ # When you declare a presenter in a Presentability collection, the result is a
14
+ # subclass of Presentability::Presenter. The main way of defining a Presenter's
15
+ # functionality is via the ::expose method, which marks an attribute of the underlying
16
+ # entity object (the "subject") for exposure.
17
+ #
18
+ # class MyPresenter < Presentability::Presenter
19
+ # expose :name
20
+ # end
21
+ #
22
+ # # Assuming `entity_object' has a "name" attribute...
23
+ # presenter = MyPresenter.new( entity_object )
24
+ # presenter.apply
25
+ # # => { :name => "entity name" }
26
+ #
27
+ #
28
+ # ### Presenter Collections
29
+ #
30
+ # Setting up classes manually like this is one option, but Presentability also lets you
31
+ # set them up as a collection, which is what further examples will assume for brevity:
32
+ #
33
+ # module MyPresenters
34
+ # extend Presentability
35
+ #
36
+ # presenter_for( EntityObject ) do
37
+ # expose :name
38
+ # end
39
+ #
40
+ # end
41
+ #
42
+ #
43
+ # ### Complex Exposures
44
+ #
45
+ # Sometimes you want to do more than just use the presented entity's values as-is.
46
+ # There are a number of ways to do this.
47
+ #
48
+ # The first of these is to provide a block when exposing an attribute. The subject
49
+ # of the presenter is available to the block via the `subject` method:
50
+ #
51
+ # require 'time'
52
+ #
53
+ # presenter_for( LogEvent ) do
54
+ # # Turn Time objects into RFC2822-formatted time strings
55
+ # expose :timestamp do
56
+ # self.subject.timestamp.rfc2822
57
+ # end
58
+ #
59
+ # end
60
+ #
61
+ # You can also declare the exposure using a regular method with the same name:
62
+ #
63
+ # require 'time'
64
+ #
65
+ # presenter_for( LogEvent ) do
66
+ # # Turn Time objects into RFC2822-formatted time strings
67
+ # expose :timestamp
68
+ #
69
+ # def timestamp
70
+ # return self.subject.timestamp.rfc2822
71
+ # end
72
+ #
73
+ # end
74
+ #
75
+ # This can be used to add presence checks:
76
+ #
77
+ # require 'time'
78
+ #
79
+ # presenter_for( LogEvent ) do
80
+ # # Require that presented entities have an `id` attribute
81
+ # expose :id do
82
+ # id = self.subject.id or raise "no `id' for %p" % [ self.subject ]
83
+ # raise "`id' for %p is blank!" % [ self.subject ] if id.blank?
84
+ #
85
+ # return id
86
+ # end
87
+ # end
88
+ #
89
+ # or conditional exposures:
90
+ #
91
+ # presenter_for( Acme::Product ) do
92
+ #
93
+ # # Truncate the long description if presented as part of a collection
94
+ # expose :detailed_description do
95
+ # desc = self.subject.detailed_description
96
+ # if self.options[:in_collection]
97
+ # return desc[0..15] + '...'
98
+ # else
99
+ # return desc
100
+ # end
101
+ # end
102
+ #
103
+ # end
104
+ #
105
+ #
106
+ # ### Exposure Aliases
107
+ #
108
+ # If you want to expose a field but use a different name in the resulting data
109
+ # structure, you can use the `:as` option in the exposure declaration:
110
+ #
111
+ # presenter_for( LogEvent ) do
112
+ # expose :timestamp, as: :created_at
113
+ # end
114
+ #
115
+ # presenter = MyPresenter.new( log_event )
116
+ # presenter.apply
117
+ # # => { :created_at => '2023-02-01 12:34:02.155365 -0800' }
118
+ #
119
+ #
8
120
  class Presentability::Presenter
9
121
  extend Loggability
10
122
 
@@ -57,6 +169,14 @@ class Presentability::Presenter
57
169
  end
58
170
 
59
171
 
172
+ ### Set up an exposure of a collection with the given +name+. This means it will
173
+ ### have the :in_collection option set by default.
174
+ def self::expose_collection( name, **options, &block )
175
+ options = options.merge( unless: :in_collection )
176
+ self.expose( name, **options, &block )
177
+ end
178
+
179
+
60
180
  ### Generate the body an exposure method that delegates to a method with the
61
181
  ### same +name+ on its subject.
62
182
  def self::generate_expose_method( name, **options )
@@ -107,12 +227,14 @@ class Presentability::Presenter
107
227
 
108
228
 
109
229
  ### Apply the exposures to the subject and return the result.
110
- def apply
230
+ def apply( presenters )
111
231
  result = self.empty_representation
112
232
 
113
233
  self.class.exposures.each do |name, exposure_options|
114
234
  next if self.skip_exposure?( name )
235
+ self.log.debug "Presenting %p" % [ name ]
115
236
  value = self.method( name ).call
237
+ value = presenters.present( value, **exposure_options )
116
238
  key = exposure_options.key?( :as ) ? exposure_options[:as] : name
117
239
  result[ key.to_sym ] = value
118
240
  end
@@ -3,14 +3,118 @@
3
3
  require 'loggability'
4
4
 
5
5
 
6
- # :include: Presentability.md
6
+ #
7
+ # Facade-based presenter toolkit with minimal assumptions.
8
+ #
9
+ # ## Basic Usage
10
+ #
11
+ # Basic usage of Presentability requires two steps: declaring presenters and
12
+ # then using them.
13
+ #
14
+ # ### Declaring Presenters
15
+ #
16
+ # Presenters are just regular Ruby classes with some convenience methods for
17
+ # declaring exposures, but in a lot of cases you'll want to declare them all in
18
+ # one place. Presentability offers a mixin that implements a simple DSL for
19
+ # declaring presenters and their associations to entity classes, intended to be
20
+ # used in a container module:
21
+ #
22
+ # require 'presentability'
23
+ #
24
+ # module Acme::Presenters
25
+ # extend Presentability
26
+ #
27
+ # presenter_for( Acme::Widget ) do
28
+ # expose :sku
29
+ # expose :name
30
+ # expose :unit_price
31
+ # end
32
+ #
33
+ # end
34
+ #
35
+ # The block of `presenter_for` is evaluated in the context of a new Presenter
36
+ # class, so refer to that documentation for what's possible there.
37
+ #
38
+ # Sometimes you can't (or don't want to) have to load the entity class to
39
+ # declare a presenter for it, so you can also declare it using the class's name:
40
+ #
41
+ # presenter_for( 'Acme::Widget' ) do
42
+ # expose :sku
43
+ # expose :name
44
+ # expose :unit_price
45
+ # end
46
+ #
47
+ #
48
+ # ### Using Presenters
49
+ #
50
+ # You use presenters by instantiating them with the object they are a facade for
51
+ # (the "subject"), and then applying it:
52
+ #
53
+ # acme_widget = Acme::Widget.new(
54
+ # sku: "FF-2237H455",
55
+ # name: "Throbbing Frobnulator",
56
+ # unit_price: 299,
57
+ # inventory_count: 301,
58
+ # wholesale_cost: 39
59
+ # )
60
+ # presentation = Acme::Presenters.present( acme_widget )
61
+ # # => { :sku => "FF-2237H455", :name => "Throbbing Frobnulator", :unit_price => 299 }
62
+ #
63
+ # If you want to present a collection of objects as a collection, you can apply
64
+ # presenters to the collection instead:
65
+ #
66
+ # widgets_in_stock = Acme::Widget.where { inventory_count > 0 }
67
+ # collection_presentation = Acme::Presenters.present_collection( widgets_in_stock )
68
+ # # => [ {:sku => "FF-2237H455", [...]}, {:sku => "FF-2237H460", [...]}, [...] ]
69
+ #
70
+ # The collection can be anything that is `Enumerable`.
71
+ #
72
+ #
73
+ # ### Presentation Options
74
+ #
75
+ # Sometimes you want a bit more flexibility in what you present, allowing a single
76
+ # uniform presenter to be used in multiple use cases. To facilitate this, you can pass
77
+ # an options keyword hash to `#present`:
78
+ #
79
+ # presenter_for( 'Acme::Widget' ) do
80
+ # expose :sku
81
+ # expose :name
82
+ # expose :unit_price
83
+ #
84
+ # # Only expose the wholesale cost if presented via an internal API
85
+ # expose :wholesale_cost, if: :internal_api
86
+ # end
87
+ #
88
+ # acme_widget = Acme::Widget.new(
89
+ # sku: "FF-2237H455",
90
+ # name: "Throbbing Frobnulator",
91
+ # unit_price: 299,
92
+ # inventory_count: 301,
93
+ # wholesale_cost: 39
94
+ # )
95
+ #
96
+ # # External API remains unchanged:
97
+ # presentation = Acme::Presenters.present( acme_widget )
98
+ # # => { :sku => "FF-2237H455", :name => "Throbbing Frobnulator", :unit_price => 299 }
99
+ #
100
+ # # But when run from an internal service:
101
+ # internal_presentation = Acme::Presenters.present( acme_widget, internal_api: true )
102
+ # # => { :sku => "FF-2237H455", :name => "Throbbing Frobnulator", :unit_price => 299,
103
+ # # :wholesale_cost => 39 }
104
+ #
105
+ # There are some options that are set for you:
106
+ #
107
+ # <dl>
108
+ # <td><code>:in_collection</code></td>
109
+ # <dd>Set if the current object is being presented as part of a collection.</dd>
110
+ # </dl>
111
+ #
7
112
  module Presentability
8
113
  extend Loggability
9
114
 
10
115
 
11
116
  # Package version
12
- VERSION = '0.4.0'
13
-
117
+ VERSION = '0.6.0'
14
118
 
15
119
  # Automatically load subordinate components
16
120
  autoload :Presenter, 'presentability/presenter'
@@ -20,15 +124,29 @@ module Presentability
20
124
  log_as :presentability
21
125
 
22
126
 
127
+ #
128
+ # Hooks
129
+ #
130
+
23
131
  ### Extension hook -- decorate the including +mod+.
24
132
  def self::extended( mod )
25
133
  super
26
134
  mod.singleton_class.attr_accessor :presenters
135
+ mod.singleton_class.attr_accessor :serializers
136
+
27
137
  mod.presenters = {}
138
+ mod.serializers = {
139
+ Array => mod.method( :serialize_array ),
140
+ Hash => mod.method( :serialize_hash ),
141
+ }
28
142
  end
29
143
 
30
144
 
31
145
 
146
+ #
147
+ # DSL Methods
148
+ #
149
+
32
150
  ### Set up a presentation for the given +entity_class+.
33
151
  def presenter_for( entity_class, &block )
34
152
  presenter_class = Class.new( Presentability::Presenter )
@@ -38,11 +156,30 @@ module Presentability
38
156
  end
39
157
 
40
158
 
159
+ ### Set up a rule for how to serialize objects of the given +type+ if there is
160
+ ### no presenter declared for it.
161
+ def serializer_for( type, method )
162
+ self.serializers[ type ] = method
163
+ end
164
+
165
+
166
+ #
167
+ # Presentation Methods
168
+ #
169
+
41
170
  ### Return a representation of the +object+ by applying a declared presentation.
42
171
  def present( object, **options )
43
172
  representation = self.present_by_class( object, **options ) ||
44
- self.present_by_classname( object, **options ) or
45
- raise NoMethodError, "no presenter found for %p" % [ object ]
173
+ self.present_by_classname( object, **options ) ||
174
+ self.serialize( object, **options )
175
+
176
+ unless representation
177
+ if object.instance_variables.empty?
178
+ return object
179
+ else
180
+ raise NoMethodError, "no presenter found for %p" % [ object ], caller( 1 )
181
+ end
182
+ end
46
183
 
47
184
  return representation
48
185
  end
@@ -56,6 +193,37 @@ module Presentability
56
193
  end
57
194
 
58
195
 
196
+ ### Serialize the specified +object+ if a serializer has been declared for it
197
+ ### and return the scalar result.
198
+ def serialize( object, ** )
199
+ serializer = self.serializers[ object.class ] ||
200
+ self.serializers[ object.class.name ] or
201
+ return nil
202
+ serializer_proc = serializer.to_proc
203
+
204
+ return serializer_proc.call( object )
205
+ end
206
+
207
+
208
+ ### Default serializer for an Array; returns a new array of presented objects.
209
+ def serialize_array( object )
210
+ return object.map do |member|
211
+ self.present( member, in_collection: true )
212
+ end
213
+ end
214
+
215
+
216
+ ### Default serializer for a Hash; returns a new Hash of presented keys and values.
217
+ def serialize_hash( object )
218
+ return object.each_with_object( {} ) do |(key, val), newhash|
219
+ p_key = self.present( key, in_collection: true )
220
+ p_val = self.present( val, in_collection: true )
221
+
222
+ newhash[ p_key ] = p_val
223
+ end
224
+ end
225
+
226
+
59
227
  #########
60
228
  protected
61
229
  #########
@@ -66,7 +234,7 @@ module Presentability
66
234
  presenter_class = self.presenters[ object.class ] or return nil
67
235
  presenter = presenter_class.new( object, **presentation_options )
68
236
 
69
- return presenter.apply
237
+ return presenter.apply( self )
70
238
  end
71
239
 
72
240
 
@@ -77,7 +245,7 @@ module Presentability
77
245
  presenter_class = self.presenters[ classname ] or return nil
78
246
  presenter = presenter_class.new( object, **presentation_options )
79
247
 
80
- return presenter.apply
248
+ return presenter.apply( self )
81
249
  end
82
250
 
83
251
  end # module Presentability