presentability 0.4.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67ea66c2c975ecb5064c9ddb9c0be1a203b0e9e9944dfc3f3821f07e360fc726
4
- data.tar.gz: c13fdaccf960dc87d3feaed73e2a22a96ad805aed83cadbadeeee9cc60e80e49
3
+ metadata.gz: c1802a45773fa3f0e4662926ae1067ae729e81b88201437f7e4bf9705ceac5ec
4
+ data.tar.gz: c0dedfb32ced019e2105b4434816610598f5f7b560ffab7353144955b9f4a9f3
5
5
  SHA512:
6
- metadata.gz: 550a710c7ae589dfc6ebb1ed801508c8e2d347dbd37e1cdd19e3f3699b5b203ad38b7f2dc6a917e71d3e3dd3711e4eefe8c01594a1f4d92f10b120c4fe1e0a82
7
- data.tar.gz: 07d9674d3ad188b6b19923153accddf6175bf61d238d79867b7237a34a10595e066e10f4d37cc702bbcca8b7dcf65fd7df75ad0eb2fa0c4557716be3a370090b
6
+ metadata.gz: c3c3eb84c0c800517b515defb1b8717f1f940d4c582cb63baf5f7689df9aeb47d63a956f997ba64238301f1db88447cffff972a23faa3fad0026b37b3a0b872b
7
+ data.tar.gz: 9305b383f0f617186e27411fd153a0aa65b430ae96264c70bc3cc6b9ba99e4d9da77a4e817a9cb2e0870be2c057ed17249a6fad5c1fdd4985edb94b3b4232d3e
checksums.yaml.gz.sig CHANGED
Binary file
data/History.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # Release History for presentability
2
2
 
3
3
  ---
4
+ ## v0.5.0 [2023-11-06] Michael Granger <ged@faeriemud.org>
5
+
6
+ Improvements:
7
+
8
+ - Add recursive presentation
9
+ - Allow presenters to be defined for objects with no instance variables
10
+
11
+
4
12
  ## v0.4.0 [2023-02-02] Michael Granger <ged@faeriemud.org>
5
13
 
6
14
  Improvements:
@@ -57,6 +57,14 @@ class Presentability::Presenter
57
57
  end
58
58
 
59
59
 
60
+ ### Set up an exposure of a collection with the given +name+. This means it will
61
+ ### have the :in_collection option set by default.
62
+ def self::expose_collection( name, **options, &block )
63
+ options = options.merge( unless: :in_collection )
64
+ self.expose( name, **options, &block )
65
+ end
66
+
67
+
60
68
  ### Generate the body an exposure method that delegates to a method with the
61
69
  ### same +name+ on its subject.
62
70
  def self::generate_expose_method( name, **options )
@@ -107,12 +115,14 @@ class Presentability::Presenter
107
115
 
108
116
 
109
117
  ### Apply the exposures to the subject and return the result.
110
- def apply
118
+ def apply( presenters )
111
119
  result = self.empty_representation
112
120
 
113
121
  self.class.exposures.each do |name, exposure_options|
114
122
  next if self.skip_exposure?( name )
115
123
  value = self.method( name ).call
124
+ value = presenters.present( value, **exposure_options ) unless
125
+ value.is_a?( result.class )
116
126
  key = exposure_options.key?( :as ) ? exposure_options[:as] : name
117
127
  result[ key.to_sym ] = value
118
128
  end
@@ -9,7 +9,7 @@ module Presentability
9
9
 
10
10
 
11
11
  # Package version
12
- VERSION = '0.4.0'
12
+ VERSION = '0.5.0'
13
13
 
14
14
 
15
15
  # Automatically load subordinate components
@@ -41,8 +41,15 @@ module Presentability
41
41
  ### Return a representation of the +object+ by applying a declared presentation.
42
42
  def present( object, **options )
43
43
  representation = self.present_by_class( object, **options ) ||
44
- self.present_by_classname( object, **options ) or
45
- raise NoMethodError, "no presenter found for %p" % [ object ]
44
+ self.present_by_classname( object, **options )
45
+
46
+ unless representation
47
+ if object.instance_variables.empty?
48
+ return object
49
+ else
50
+ raise NoMethodError, "no presenter found for %p" % [ object ]
51
+ end
52
+ end
46
53
 
47
54
  return representation
48
55
  end
@@ -66,7 +73,7 @@ module Presentability
66
73
  presenter_class = self.presenters[ object.class ] or return nil
67
74
  presenter = presenter_class.new( object, **presentation_options )
68
75
 
69
- return presenter.apply
76
+ return presenter.apply( self )
70
77
  end
71
78
 
72
79
 
@@ -77,7 +84,7 @@ module Presentability
77
84
  presenter_class = self.presenters[ classname ] or return nil
78
85
  presenter = presenter_class.new( object, **presentation_options )
79
86
 
80
- return presenter.apply
87
+ return presenter.apply( self )
81
88
  end
82
89
 
83
90
  end # module Presentability
@@ -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 )
@@ -43,6 +43,23 @@ RSpec.describe Presentability do
43
43
  end
44
44
  end
45
45
 
46
+ let( :complex_entity_class ) do
47
+ Class.new do
48
+ def self::name
49
+ return 'Acme::Pair'
50
+ end
51
+
52
+ def initialize( user:, entity:, overridden: false, locked: true )
53
+ @user = user
54
+ @entity = entity
55
+ @overridden = overridden
56
+ @locked = locked
57
+ end
58
+
59
+ attr_accessor :user, :entity, :overridden, :locked
60
+ end
61
+ end
62
+
46
63
  let( :entity_instance ) { entity_class.new }
47
64
  let( :other_entity_instance ) do
48
65
  other_entity_class.new(
@@ -52,6 +69,9 @@ RSpec.describe Presentability do
52
69
  Faker::Internet.password
53
70
  )
54
71
  end
72
+ let( :complex_entity_instance ) do
73
+ complex_entity_class.new( user: other_entity_instance, entity: entity_instance )
74
+ end
55
75
 
56
76
 
57
77
  describe "when used to extend a module" do
@@ -162,6 +182,42 @@ RSpec.describe Presentability do
162
182
  end
163
183
 
164
184
 
185
+ it "doesn't try to present objects with no instance variables by default" do
186
+ object = 'a string'
187
+ expect( extended_module.present(object) ).to be( object )
188
+
189
+ object = 8
190
+ expect( extended_module.present(object) ).to be( object )
191
+
192
+ object = :a_symbol
193
+ expect( extended_module.present(object) ).to be( object )
194
+
195
+ object = Time.now
196
+ expect( extended_module.present(object) ).to be( object )
197
+
198
+ object = %[an array of strings]
199
+ expect( extended_module.present(object) ).to be( object )
200
+
201
+ object = Object.new
202
+ expect( extended_module.present(object) ).to be( object )
203
+ end
204
+
205
+
206
+ it "allows presenters to be defined for objects with no instance variables" do
207
+ extended_module.presenter_for( Time ) do
208
+ expose :sec
209
+ expose :usec
210
+ end
211
+
212
+ object = Time.at( 1699287281.336554 )
213
+
214
+ expect( extended_module.present(object) ).to eq({
215
+ sec: object.sec,
216
+ usec: object.usec
217
+ })
218
+ end
219
+
220
+
165
221
  it "errors usefully if asked to present an object it knows nothing about" do
166
222
  expect {
167
223
  extended_module.present( entity_instance )
@@ -230,7 +286,7 @@ RSpec.describe Presentability do
230
286
  end
231
287
 
232
288
 
233
- it "handles a hetergeneous collection" do
289
+ it "handles a heterogeneous collection" do
234
290
  extended_module.presenter_for( entity_class ) do
235
291
  expose :foo
236
292
  expose :bar
@@ -307,6 +363,43 @@ RSpec.describe Presentability do
307
363
 
308
364
  end
309
365
 
366
+
367
+ describe "and used to present a complex object" do
368
+
369
+ it "uses registered presenters for sub-objects" do
370
+ extended_module.presenter_for( entity_class ) do
371
+ expose :foo
372
+ expose :bar
373
+ end
374
+ extended_module.presenter_for( other_entity_class ) do
375
+ expose :firstname
376
+ expose :lastname
377
+ expose :email
378
+ end
379
+ extended_module.presenter_for( complex_entity_class ) do
380
+ expose :user
381
+ expose :entity
382
+ expose :locked
383
+ end
384
+
385
+ result = extended_module.present( complex_entity_instance )
386
+
387
+ expect( result ).to eq({
388
+ user: {
389
+ firstname: other_entity_instance.firstname,
390
+ lastname: other_entity_instance.lastname,
391
+ email: other_entity_instance.email,
392
+ },
393
+ entity: {
394
+ foo: entity_instance.foo,
395
+ bar: entity_instance.bar
396
+ },
397
+ locked: complex_entity_instance.locked,
398
+ })
399
+ end
400
+
401
+ end
402
+
310
403
  end
311
404
 
312
405
  end
data.tar.gz.sig CHANGED
@@ -1,3 +1 @@
1
- "�Y�i��$ua_ZV�������j�KoVp
2
- j{�����:g�#�O��f��?�;:TK��dF�J�ՂaS�] ��_;����[�Ս��Z�-B����
3
- շ�`���%`�_�Ԃ8lqM�|Y� ����3��b? 3���Dixŗ����B�{�n��+��9��vas�� :�0��1�O�sM7�9�*+N��A�����!�T�#C�џ5nd�a�g�]:����j��QV��*�_�#Q�d��}o5���a;iIO���!�j�8�
1
+ �B6s��m�{����Μ��'�*'7�tD�!�&CI&��57|�Me�aU®��|!��@~9�"z-��Xz4[(�M������^Z��������s &�ˀ��|�]��C��fʰ�%_�ߦ� "rz4ER��l�݃G�T�<"��E���滌6ڠ��#J]';kh�9e���V�R=.�`:�d�T��>z�k5 m�/�h��ak�FV3�!ʢ�`�w�rued-���~��ן��(=��H*���8j�AU��_���Tw�i�v���G�.� �dDW����˵����E���L-�7���_�Ӷ���-��𹐪�}���_�}�Hb"��4d%WR�.,д����S��vt�������J���KR�r��
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.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Granger
@@ -33,7 +33,7 @@ cert_chain:
33
33
  WmEdESlpWhzFECctefIF2lsN9vaOuof57RM77t2otrtcscDtNarIqjZsIyqtDvtL
34
34
  DttITiit0Vwz7bY0
35
35
  -----END CERTIFICATE-----
36
- date: 2023-02-02 00:00:00.000000000 Z
36
+ date: 2023-11-06 00:00:00.000000000 Z
37
37
  dependencies:
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: loggability
@@ -134,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
134
  - !ruby/object:Gem::Version
135
135
  version: '0'
136
136
  requirements: []
137
- rubygems_version: 3.4.6
137
+ rubygems_version: 3.4.21
138
138
  signing_key:
139
139
  specification_version: 4
140
140
  summary: Facade-based presenters with minimal assumptions.
metadata.gz.sig CHANGED
Binary file