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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/GettingStarted.md +234 -0
- data/History.md +18 -0
- data/README.md +3 -1
- data/lib/presentability/presenter.rb +124 -2
- data/lib/presentability.rb +175 -7
- data/lib/roda/plugins/presenters.rb +57 -0
- data/spec/presentability/presenter_spec.rb +24 -5
- data/spec/presentability_spec.rb +196 -9
- data/spec/roda/plugins/presenters_spec.rb +71 -0
- data/spec/spec_helper.rb +9 -0
- data.tar.gz.sig +0 -0
- metadata +32 -17
- metadata.gz.sig +0 -0
- data/Presentability.md +0 -105
- data/Presenter.md +0 -109
@@ -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(
|
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 )
|
data/spec/presentability_spec.rb
CHANGED
@@ -11,9 +11,7 @@ RSpec.describe Presentability do
|
|
11
11
|
|
12
12
|
let( :entity_class ) do
|
13
13
|
Class.new do
|
14
|
-
|
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
|
-
|
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(
|
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
|
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
|
+
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+
|
14
|
-
|
15
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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:
|
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.
|
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
|
-
|