presentability 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,57 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'presentability'
4
+
5
+ require 'roda/roda_plugins' unless defined?( Roda::RodaPlugins )
6
+
7
+
8
+ module Roda::RodaPlugins::Presenters
9
+
10
+ # Default options
11
+ OPTS = {}.freeze
12
+
13
+
14
+ ### Add presenter variables to the given +app+.
15
+ def self::configure( app, opts=OPTS, &block )
16
+ collection = opts[:collection] || Module.new
17
+ app.singleton_class.attr_accessor :presenter_collection
18
+ app.presenter_collection = collection
19
+ end
20
+
21
+
22
+ module ClassMethods
23
+
24
+ ### Inheritance hook -- give +subclass+es their own presenters ivar.
25
+ def inherited( subclass )
26
+ super
27
+ subclass.presenter_collection = self.presenter_collection.clone
28
+ end
29
+
30
+
31
+ end # module ClassMethods
32
+
33
+
34
+ module InstanceMethods
35
+
36
+ ### Find the presenter for the given +object+ and apply it with the given
37
+ ### +options+. Raises an exception if no presenter can be found.
38
+ def present( object, **options )
39
+ mod = self.class.presenter_collection
40
+ return mod.present( object, **options )
41
+ end
42
+
43
+
44
+ ### Find the presenter for the given +object+ and apply it with the given
45
+ ### +options+. Raises an exception if no presenter can be found.
46
+ def present_collection( object, **options )
47
+ mod = self.class.presenter_collection
48
+ return mod.present_collection( object, **options )
49
+ end
50
+
51
+ end
52
+
53
+
54
+ Roda::RodaPlugins.register_plugin( :presenters, self )
55
+
56
+ end # module Roda::RodaPlugins::Presenters
57
+
@@ -9,7 +9,18 @@ require 'presentability/presenter'
9
9
  RSpec.describe( Presentability::Presenter ) do
10
10
 
11
11
  let( :presenter_subject ) do
12
- OpenStruct.new( country: 'Philippines', export: 'Copper', flower: 'Sampaguita' )
12
+ OpenStruct.new(
13
+ country: 'Philippines',
14
+ export: 'Copper',
15
+ flower: 'Sampaguita',
16
+ cities: ['Quezon City', 'Cagayan de Oro', 'Roxas']
17
+ )
18
+ end
19
+
20
+ let( :presenters ) do
21
+ mod = Module.new
22
+ mod.extend( Presentability )
23
+ return mod
13
24
  end
14
25
 
15
26
 
@@ -27,7 +38,7 @@ RSpec.describe( Presentability::Presenter ) do
27
38
 
28
39
  it "can be created with just a subject" do
29
40
  presenter = subclass.new( presenter_subject )
30
- expect( presenter.apply ).to eq( {} )
41
+ expect( presenter.apply(presenters) ).to eq( {} )
31
42
  end
32
43
 
33
44
 
@@ -35,7 +46,7 @@ RSpec.describe( Presentability::Presenter ) do
35
46
  subclass.expose( :country )
36
47
  presenter = subclass.new( presenter_subject )
37
48
 
38
- expect( presenter.apply ).to eq({ country: 'Philippines' })
49
+ expect( presenter.apply(presenters) ).to eq({ country: 'Philippines' })
39
50
  end
40
51
 
41
52
 
@@ -47,9 +58,9 @@ RSpec.describe( Presentability::Presenter ) do
47
58
  financial_presenter = subclass.new( presenter_subject, financial: true )
48
59
  cultural_presenter = subclass.new( presenter_subject, financial: false )
49
60
 
50
- expect( financial_presenter.apply ).
61
+ expect( financial_presenter.apply(presenters) ).
51
62
  to eq({ country: 'Philippines', export: 'Copper' })
52
- expect( cultural_presenter.apply ).
63
+ expect( cultural_presenter.apply(presenters) ).
53
64
  to eq({ country: 'Philippines', flower: 'Sampaguita' })
54
65
  end
55
66
 
@@ -86,6 +97,14 @@ RSpec.describe( Presentability::Presenter ) do
86
97
  end
87
98
 
88
99
 
100
+ it "can expose an attribute as a collection" do
101
+ subclass.expose( :country )
102
+ subclass.expose_collection( :cities )
103
+
104
+ expect( subclass.exposures[:cities] ).to include( unless: :in_collection )
105
+ end
106
+
107
+
89
108
  it "has useful #inspect output" do
90
109
  presenter = subclass.new( presenter_subject )
91
110
  expect( presenter.inspect ).to match( /Presentability::Presenter\S+ for /i )
@@ -11,9 +11,7 @@ RSpec.describe Presentability do
11
11
 
12
12
  let( :entity_class ) do
13
13
  Class.new do
14
- def self::name
15
- return 'Acme::Entity'
16
- end
14
+ set_temporary_name 'Acme::Entity (Testing Class)'
17
15
 
18
16
  def initialize( foo: 1, bar: 'two', baz: :three )
19
17
  @foo = foo
@@ -27,9 +25,7 @@ RSpec.describe Presentability do
27
25
 
28
26
  let( :other_entity_class ) do
29
27
  Class.new do
30
- def self::name
31
- return 'Acme::User'
32
- end
28
+ set_temporary_name 'Acme::User (Testing Class)'
33
29
 
34
30
  def initialize( firstname, lastname, email, password )
35
31
  @firstname = firstname
@@ -43,6 +39,21 @@ RSpec.describe Presentability do
43
39
  end
44
40
  end
45
41
 
42
+ let( :complex_entity_class ) do
43
+ Class.new do
44
+ set_temporary_name 'Acme::Pair (Testing Class)'
45
+
46
+ def initialize( user:, entity:, overridden: false, locked: true )
47
+ @user = user
48
+ @entity = entity
49
+ @overridden = overridden
50
+ @locked = locked
51
+ end
52
+
53
+ attr_accessor :user, :entity, :overridden, :locked
54
+ end
55
+ end
56
+
46
57
  let( :entity_instance ) { entity_class.new }
47
58
  let( :other_entity_instance ) do
48
59
  other_entity_class.new(
@@ -52,6 +63,9 @@ RSpec.describe Presentability do
52
63
  Faker::Internet.password
53
64
  )
54
65
  end
66
+ let( :complex_entity_instance ) do
67
+ complex_entity_class.new( user: other_entity_instance, entity: entity_instance )
68
+ end
55
69
 
56
70
 
57
71
  describe "when used to extend a module" do
@@ -73,7 +87,7 @@ RSpec.describe Presentability do
73
87
 
74
88
 
75
89
  it "can define a presenter for a class name" do
76
- extended_module.presenter_for( 'Acme::Entity' ) do
90
+ extended_module.presenter_for( entity_class.name ) do
77
91
  expose :foo
78
92
  expose :bar
79
93
  end
@@ -162,10 +176,146 @@ RSpec.describe Presentability do
162
176
  end
163
177
 
164
178
 
179
+ it "doesn't try to present objects with no instance variables by default" do
180
+ object = 'a string'
181
+ expect( extended_module.present(object) ).to be( object )
182
+
183
+ object = 8
184
+ expect( extended_module.present(object) ).to be( object )
185
+
186
+ object = :a_symbol
187
+ expect( extended_module.present(object) ).to be( object )
188
+
189
+ object = Time.now
190
+ expect( extended_module.present(object) ).to be( object )
191
+
192
+ object = %[an array of strings]
193
+ expect( extended_module.present(object) ).to be( object )
194
+
195
+ object = Object.new
196
+ expect( extended_module.present(object) ).to be( object )
197
+ end
198
+
199
+
200
+ it "allows presenters to be defined for objects with no instance variables" do
201
+ extended_module.presenter_for( Time ) do
202
+ expose :sec
203
+ expose :usec
204
+ end
205
+
206
+ object = Time.at( 1699287281.336554 )
207
+
208
+ expect( extended_module.present(object) ).to eq({
209
+ sec: object.sec,
210
+ usec: object.usec
211
+ })
212
+ end
213
+
214
+
215
+ describe 'serialization' do
216
+
217
+ it "presents each element for Arrays by default" do
218
+ extended_module.presenter_for( entity_class ) do
219
+ expose :foo
220
+ end
221
+
222
+ array = 5.times.map { entity_class.new }
223
+
224
+ result = extended_module.present( array )
225
+
226
+ expect( result ).to eq( [{foo: 1}] * 5 )
227
+ end
228
+
229
+
230
+ it "presents each value for Hashes by default" do
231
+ extended_module.presenter_for( entity_class ) do
232
+ expose :foo
233
+ end
234
+
235
+ hash = { user1: entity_instance(), user2: entity_instance() }
236
+
237
+ result = extended_module.present( hash )
238
+
239
+ expect( result ).to eq({
240
+ user1: {foo: 1}, user2: {foo: 1}
241
+ })
242
+ end
243
+
244
+
245
+ it "presents each key for Hashes by default too" do
246
+ extended_module.presenter_for( entity_class ) do
247
+ expose :id do
248
+ self.subject.object_id
249
+ end
250
+ end
251
+
252
+ key1 = entity_instance()
253
+ key2 = entity_instance()
254
+ hash = { key1 => 'user1', key2 => 'user2' }
255
+
256
+ result = extended_module.present( hash )
257
+
258
+ expect( result ).to eq({
259
+ {id: key1.object_id} => 'user1',
260
+ {id: key2.object_id} => 'user2'
261
+ })
262
+ end
263
+
264
+
265
+ it "automatically sets :in_collection for sub-Arrays" do
266
+ extended_module.presenter_for( entity_class ) do
267
+ expose :foo
268
+ expose :bar, unless: :in_collection
269
+ expose :baz
270
+ end
271
+
272
+ results = extended_module.present( [entity_instance] )
273
+
274
+ expect( results.first ).to include( :foo, :baz )
275
+ expect( results.first ).not_to include( :bar )
276
+ end
277
+
278
+
279
+ it "automatically sets :in_collection for sub-Hashes" do
280
+ extended_module.presenter_for( entity_class ) do
281
+ expose :foo
282
+ expose :bar, unless: :in_collection
283
+ expose :baz
284
+ end
285
+
286
+ results = extended_module.present( {result: entity_instance} )
287
+
288
+ expect( results[:result] ).to include( :foo, :baz )
289
+ expect( results[:result] ).not_to include( :bar )
290
+ end
291
+
292
+
293
+ it "can be defined by class for objects that have a simple presentation" do
294
+ extended_module.serializer_for( IPAddr, :to_s )
295
+
296
+ object = IPAddr.new( '127.0.0.1/24' )
297
+
298
+ expect( extended_module.present(object) ).to eq( '127.0.0.0' )
299
+ end
300
+
301
+
302
+ it "can be defined by class name for objects that have a simple presentation" do
303
+ extended_module.serializer_for( 'IPAddr', :to_s )
304
+
305
+ object = IPAddr.new( '127.0.0.1/24' )
306
+
307
+ expect( extended_module.present(object) ).to eq( '127.0.0.0' )
308
+ end
309
+
310
+ end
311
+
312
+
165
313
  it "errors usefully if asked to present an object it knows nothing about" do
166
314
  expect {
167
315
  extended_module.present( entity_instance )
168
- }.to raise_error( NoMethodError, /no presenter found/i )
316
+ }.to raise_error( NoMethodError, /no presenter found/i ) do |err|
317
+ expect( err.backtrace.first ).to match( /#{Regexp.escape(__FILE__)}/ )
318
+ end
169
319
  end
170
320
 
171
321
 
@@ -230,7 +380,7 @@ RSpec.describe Presentability do
230
380
  end
231
381
 
232
382
 
233
- it "handles a hetergeneous collection" do
383
+ it "handles a heterogeneous collection" do
234
384
  extended_module.presenter_for( entity_class ) do
235
385
  expose :foo
236
386
  expose :bar
@@ -307,6 +457,43 @@ RSpec.describe Presentability do
307
457
 
308
458
  end
309
459
 
460
+
461
+ describe "and used to present a complex object" do
462
+
463
+ it "uses registered presenters for sub-objects" do
464
+ extended_module.presenter_for( entity_class ) do
465
+ expose :foo
466
+ expose :bar
467
+ end
468
+ extended_module.presenter_for( other_entity_class ) do
469
+ expose :firstname
470
+ expose :lastname
471
+ expose :email
472
+ end
473
+ extended_module.presenter_for( complex_entity_class ) do
474
+ expose :user
475
+ expose :entity
476
+ expose :locked
477
+ end
478
+
479
+ result = extended_module.present( complex_entity_instance )
480
+
481
+ expect( result ).to eq({
482
+ user: {
483
+ firstname: other_entity_instance.firstname,
484
+ lastname: other_entity_instance.lastname,
485
+ email: other_entity_instance.email,
486
+ },
487
+ entity: {
488
+ foo: entity_instance.foo,
489
+ bar: entity_instance.bar
490
+ },
491
+ locked: complex_entity_instance.locked,
492
+ })
493
+ end
494
+
495
+ end
496
+
310
497
  end
311
498
 
312
499
  end
@@ -0,0 +1,71 @@
1
+ # -*- ruby -*-
2
+
3
+ require_relative '../../spec_helper'
4
+
5
+ require 'roda'
6
+ require 'roda/plugins/presenters'
7
+
8
+
9
+ RSpec.describe( Roda::RodaPlugins::Presenters ) do
10
+
11
+ let( :app ) { Class.new(Roda) }
12
+
13
+ let( :entity_class ) do
14
+ Class.new do
15
+ def self::name
16
+ return 'Acme::Entity'
17
+ end
18
+
19
+ def initialize( foo: 1, bar: 'two', baz: :three )
20
+ @foo = foo
21
+ @bar = bar
22
+ @baz = baz
23
+ end
24
+
25
+ attr_accessor :foo, :bar, :baz
26
+ end
27
+ end
28
+
29
+
30
+ it "adds an anonymous presenter collection to including apps" do
31
+ app.plugin( described_class )
32
+
33
+ expect( app.presenter_collection ).to be_a( Module )
34
+ end
35
+
36
+
37
+ it "clones the presenter collection for subclasses" do
38
+ app.plugin( described_class )
39
+ subapp = Class.new( app )
40
+
41
+ expect( subapp.presenter_collection ).to be_a( Module )
42
+ expect( app.presenter_collection ).to_not be( subapp.presenter_collection )
43
+ end
44
+
45
+
46
+ it "allows an existing presenter collection module to be added to including apps" do
47
+ collection = Module.new
48
+ app.plugin( described_class, collection: collection )
49
+
50
+ expect( app.presenter_collection ).to be( collection )
51
+ end
52
+
53
+
54
+ it "allows the use of presenters in application routes" do
55
+ collection = Module.new
56
+ collection.extend( Presentability )
57
+ collection.presenter_for( entity_class ) do
58
+ expose :foo
59
+ expose :bar
60
+ end
61
+ app.plugin( described_class, collection: collection )
62
+
63
+ app_instance = app.new( {} )
64
+
65
+ result = app_instance.present( entity_class.new )
66
+ expect( result ).to be_a( Hash ).and( include(:foo, :bar) )
67
+ expect( result ).to_not include( :baz )
68
+ end
69
+
70
+ end
71
+
data/spec/spec_helper.rb CHANGED
@@ -18,6 +18,15 @@ I18n.reload!
18
18
 
19
19
  require 'loggability/spechelpers'
20
20
 
21
+ if !Module.respond_to?( :set_temporary_name )
22
+
23
+ Module.class_eval {
24
+ def set_temporary_name( tempname )
25
+ define_singleton_method( :name ) { tempname }
26
+ end
27
+ }
28
+
29
+ end
21
30
 
22
31
  ### Mock with RSpec
23
32
  RSpec.configure do |config|
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: presentability
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Granger
@@ -10,9 +10,9 @@ bindir: bin
10
10
  cert_chain:
11
11
  - |
12
12
  -----BEGIN CERTIFICATE-----
13
- MIID+DCCAmCgAwIBAgIBBTANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdnZWQv
14
- REM9RmFlcmllTVVEL0RDPW9yZzAeFw0yMzAxMTYxNzE2MDlaFw0yNDAxMTYxNzE2
15
- MDlaMCIxIDAeBgNVBAMMF2dlZC9EQz1GYWVyaWVNVUQvREM9b3JnMIIBojANBgkq
13
+ MIID+DCCAmCgAwIBAgIBBjANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdnZWQv
14
+ REM9RmFlcmllTVVEL0RDPW9yZzAeFw0yNDAxMjkyMzI5MjJaFw0yNTAxMjgyMzI5
15
+ MjJaMCIxIDAeBgNVBAMMF2dlZC9EQz1GYWVyaWVNVUQvREM9b3JnMIIBojANBgkq
16
16
  hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAvyVhkRzvlEs0fe7145BYLfN6njX9ih5H
17
17
  L60U0p0euIurpv84op9CNKF9tx+1WKwyQvQP7qFGuZxkSUuWcP/sFhDXL1lWUuIl
18
18
  M4uHbGCRmOshDrF4dgnBeOvkHr1fIhPlJm5FO+Vew8tSQmlDsosxLUx+VB7DrVFO
@@ -23,17 +23,17 @@ cert_chain:
23
23
  ozilJg4aar2okb/RA6VS87o+d7g6LpDDMMQjH4G9OPnJENLdhu8KnPw/ivSVvQw7
24
24
  N2I4L/ZOIe2DIVuYH7aLHfjZDQv/mNgpAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYD
25
25
  VR0PBAQDAgSwMB0GA1UdDgQWBBRyjf55EbrHagiRLqt5YAd3yb8k4DANBgkqhkiG
26
- 9w0BAQsFAAOCAYEARYCeUVBWARNKqF0cvNnLJvFf4hdW2+Rtc7NfC5jQvX9a1oom
27
- sfVvS96eER/9cbrphu+vc59EELw4zT+RY3/IesnoE7CaX6zIOFmSmG7K61OHsSLR
28
- KqMygcWwyuPXT2JG7JsGHuxbzgaRWe29HbSjBbLYxiMH8Zxh4tKutxzKF7jb0Ggq
29
- KAf9MH5LwG8IHVIfV5drT14PvgR3tcvmrn1timPyJl+eZ3LNnm9ofOCweuZCq1cy
30
- 4Q8LV3vP2Cofy9q+az3DHdaUGlmMiZZZqKixDr1KSS9nvh0ZrKMOUL1sWj/IaxrQ
31
- RV3y6td14q49t+xnbj00hPlbW7uE2nLJLt2NAoXiE1Nonndz1seB2c6HL79W9fps
32
- E/O12pQjCp/aPUZMt8/8tKW31RIy/KP8XO6OTJNWA8A/oNEI0g5p/LmmEtJKWYr1
33
- WmEdESlpWhzFECctefIF2lsN9vaOuof57RM77t2otrtcscDtNarIqjZsIyqtDvtL
34
- DttITiit0Vwz7bY0
26
+ 9w0BAQsFAAOCAYEATLX50LXGjumlF+AtWyDLRS5kKavyMRiyx2LbLBUYgJFBka2u
27
+ mQYapo5k4+oFgsChEGvAdukTjYJSj9GxLvgDT9mZTTtudOg1WSyUTuIuzlVVgps5
28
+ 4XJ6WBo8IuEn4IBJ2s5FTkvEx8GKpOIa6nwEOrk2Qujx1Tk/ChQxR0sMwCbur/c1
29
+ y6rI18potiGD3ZNf1tFTeyQ9q1wQWn9sn2It047X5jypuJcYrQ95W8Tq6eI4W5CG
30
+ NkW6ib/vmrgzNmNsZ0IALjOoeQK9lxQ/a9WpNokaZinmtvysGCVonH67BUJI7Udt
31
+ usImdLjDpkOVaJSrUsHgnI8iq2F5qYKj2K3No+fTHTPNF4c9KtmvBb0uFxI4JSoY
32
+ F/9VtnYr7sx/akZGuCfcG7WwTnx1iasrQIwjwDG9YRbPDNnffTN50agKxgxmP6JB
33
+ gHMt4zxJlML5nLwM/RX6nhR8LXJrm5eTdXMw9JG8ZbBnTHdipBOnYvUXMgAA904m
34
+ nO/om6m3bLHIgJaE
35
35
  -----END CERTIFICATE-----
36
- date: 2023-02-02 00:00:00.000000000 Z
36
+ date: 2024-04-29 00:00:00.000000000 Z
37
37
  dependencies:
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: loggability
@@ -63,6 +63,20 @@ dependencies:
63
63
  - - "~>"
64
64
  - !ruby/object:Gem::Version
65
65
  version: '3.0'
66
+ - !ruby/object:Gem::Dependency
67
+ name: roda
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - "~>"
71
+ - !ruby/object:Gem::Version
72
+ version: '3.79'
73
+ type: :development
74
+ prerelease: false
75
+ version_requirements: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '3.79'
66
80
  - !ruby/object:Gem::Dependency
67
81
  name: rake-deveiate
68
82
  requirement: !ruby/object:Gem::Requirement
@@ -100,15 +114,16 @@ executables: []
100
114
  extensions: []
101
115
  extra_rdoc_files: []
102
116
  files:
117
+ - GettingStarted.md
103
118
  - History.md
104
119
  - LICENSE.txt
105
- - Presentability.md
106
- - Presenter.md
107
120
  - README.md
108
121
  - lib/presentability.rb
109
122
  - lib/presentability/presenter.rb
123
+ - lib/roda/plugins/presenters.rb
110
124
  - spec/presentability/presenter_spec.rb
111
125
  - spec/presentability_spec.rb
126
+ - spec/roda/plugins/presenters_spec.rb
112
127
  - spec/spec_helper.rb
113
128
  homepage: https://hg.sr.ht/~ged/Presentability
114
129
  licenses:
@@ -134,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
149
  - !ruby/object:Gem::Version
135
150
  version: '0'
136
151
  requirements: []
137
- rubygems_version: 3.4.6
152
+ rubygems_version: 3.5.3
138
153
  signing_key:
139
154
  specification_version: 4
140
155
  summary: Facade-based presenters with minimal assumptions.
metadata.gz.sig CHANGED
Binary file
data/Presentability.md DELETED
@@ -1,105 +0,0 @@
1
-
2
- Facade-based presenter toolkit with minimal assumptions.
3
-
4
- ## Basic Usage
5
-
6
- Basic usage of Presentability requires two steps: declaring presenters and
7
- then using them.
8
-
9
- ### Declaring Presenters
10
-
11
- Presenters are just regular Ruby classes with some convenience methods for
12
- declaring exposures, but in a lot of cases you'll want to declare them all in
13
- one place. Presentability offers a mixin that implements a simple DSL for
14
- declaring presenters and their associations to entity classes, intended to be
15
- used in a container module:
16
-
17
- require 'presentability'
18
-
19
- module Acme::Presenters
20
- extend Presentability
21
-
22
- presenter_for( Acme::Widget ) do
23
- expose :sku
24
- expose :name
25
- expose :unit_price
26
- end
27
-
28
- end
29
-
30
- The block of `presenter_for` is evaluated in the context of a new Presenter
31
- class, so refer to that documentation for what's possible there.
32
-
33
- Sometimes you can't (or don't want to) have to load the entity class to
34
- declare a presenter for it, so you can also declare it using the class's name:
35
-
36
- presenter_for( 'Acme::Widget' ) do
37
- expose :sku
38
- expose :name
39
- expose :unit_price
40
- end
41
-
42
-
43
- ### Using Presenters
44
-
45
- You use presenters by instantiating them with the object they are a facade for
46
- (the "subject"), and then applying it:
47
-
48
- acme_widget = Acme::Widget.new(
49
- sku: "FF-2237H455",
50
- name: "Throbbing Frobnulator",
51
- unit_price: 299,
52
- inventory_count: 301,
53
- wholesale_cost: 39
54
- )
55
- presentation = Acme::Presenters.present( acme_widget )
56
- # => { :sku => "FF-2237H455", :name => "Throbbing Frobnulator", :unit_price => 299 }
57
-
58
- If you want to present a collection of objects as a collection, you can apply presenters to the collection instead:
59
-
60
- widgets_in_stock = Acme::Widget.where { inventory_count > 0 }
61
- collection_presentation = Acme::Presenters.present_collection( widgets_in_stock )
62
- # => [ {:sku => "FF-2237H455", [...]}, {:sku => "FF-2237H460", [...]}, [...] ]
63
-
64
- The collection can be anything that is `Enumerable`.
65
-
66
-
67
- ### Presentation Options
68
-
69
- 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`:
70
-
71
- presenter_for( 'Acme::Widget' ) do
72
- expose :sku
73
- expose :name
74
- expose :unit_price
75
-
76
- # Only expose the wholesale cost if presented via an internal API
77
- expose :wholesale_cost, if: :internal_api
78
- end
79
-
80
- acme_widget = Acme::Widget.new(
81
- sku: "FF-2237H455",
82
- name: "Throbbing Frobnulator",
83
- unit_price: 299,
84
- inventory_count: 301,
85
- wholesale_cost: 39
86
- )
87
-
88
- # External API remains unchanged:
89
- presentation = Acme::Presenters.present( acme_widget )
90
- # => { :sku => "FF-2237H455", :name => "Throbbing Frobnulator", :unit_price => 299 }
91
-
92
- # But when run from an internal service:
93
- internal_presentation = Acme::Presenters.present( acme_widget, internal_api: true )
94
- # => { :sku => "FF-2237H455", :name => "Throbbing Frobnulator", :unit_price => 299,
95
- # :wholesale_cost => 39 }
96
-
97
- There are some options that are set for you:
98
-
99
- <dl>
100
- <td><code>:in_collection</code></td>
101
- <dd>Set if the current object is being presented as part of a collection.</dd>
102
- </dl>
103
-
104
-
105
-