presentability 0.1.0.pre.20220808082516 → 0.2.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: 804aef5ae0367b75c4182c98315ad2791419fde213551ea9a5d9e860849b3e44
4
- data.tar.gz: be7e218aafbbeb7f743d612da6498ab0b8a841594ca704fe342c2df2751bf1ec
3
+ metadata.gz: f5b0aae4df8ff0ef130bbcc9704f2f44a9b109dee48cc530db6abdfd0733cbd4
4
+ data.tar.gz: d149966c13d352f89c2f437c8ed251d63520ec33287c920e8ff7c1cd43eeb0e4
5
5
  SHA512:
6
- metadata.gz: e461283d2b5c43af73319f41277b870b57372329a3194c2b37609a4a537493dfe12bf59be44d9da83a491957eb7f965430e0c670697d1058abb43cd30d4c2564
7
- data.tar.gz: ba92018b64fe673aa858d1fc47381d43c11a0f5fe27bb640a9a44bf5f9f194fc2791f0faad3358fb44b9334dfe587bb9a49f461ad8fed6c11eb8d4cf96920635
6
+ metadata.gz: 906172259081cb4c871975c6e8096bfcd7d8a2434903cc8bef9562b9b18c11af889ef1733372d46c94420008baf5821499d19e6708c75224abd962febabc0515
7
+ data.tar.gz: 5d2c5691ee59ba2526755a3a2eab49547d093fa1d2fbedad4163a78ea35b8acc4dc74d20f0e16a90d7b9af3cbedd9a043c68f3b402f8197ea40031984fc4773b
checksums.yaml.gz.sig ADDED
Binary file
data/History.md CHANGED
@@ -1,4 +1,15 @@
1
- ## v0.0.1 [YYYY-MM-DD] Michael Granger <ged@FaerieMUD.org>
1
+ # Release History for presentability
2
+
3
+ ---
4
+
5
+ ## v0.2.0 [2022-12-06] Michael Granger <ged@faeriemud.org>
6
+
7
+ Improvements:
8
+
9
+ - Add collection presentation.
10
+
11
+
12
+ ## v0.1.0 [2022-08-11] Michael Granger <ged@faeriemud.org>
2
13
 
3
14
  Initial release.
4
15
 
data/README.md CHANGED
@@ -22,14 +22,9 @@ services, logging output, etc.
22
22
  It is intended to be dead-simple by default, returning a Hash containing
23
23
  only the attributes you have intentionally exposed from the subject.
24
24
 
25
- # lib/acme/widget.rb
26
- class Acme::Widget
27
- attr_accessor :sku,
28
- :name,
29
- :unit_price,
30
- :internal_cost,
31
- :inventory_count
32
- end
25
+ The most basic usage looks something like this:
26
+
27
+ require 'presentatbility'
33
28
 
34
29
  # lib/acme/presenters.rb
35
30
  module Acme::Presenters
@@ -45,7 +40,7 @@ only the attributes you have intentionally exposed from the subject.
45
40
  # lib/acme/service.rb
46
41
  class Acme::Service < Some::Webservice::Framework
47
42
 
48
- on '/api/widgets/<sku>' do |sku|
43
+ get '/api/widgets/<sku>' do |sku|
49
44
  widget = Acme::Widget.lookup( sku )
50
45
  content_type 'application/json'
51
46
  representation = Acme::Presenters.present( widget )
@@ -5,7 +5,39 @@ require 'loggability'
5
5
 
6
6
  require 'presentability' unless defined?( Presentability )
7
7
 
8
-
8
+ #
9
+ # A presenter (facade) base class.
10
+ #
11
+ # When you declare a presenter in a Presentability collection, the result is a
12
+ # subclass of Presentability::Presenter. The main way of defining a Presenter's
13
+ # functionality is via the ::expose method, which marks an attribute of the underlying
14
+ # entity object (the "subject") for exposure.
15
+ #
16
+ # ```ruby
17
+ # class MyPresenter < Presentability::Presenter
18
+ # expose :name
19
+ # end
20
+ #
21
+ # # Assuming `entity_object' has a "name" attribute...
22
+ # presenter = MyPresenter.new( entity_object )
23
+ # presenter.apply
24
+ # # => { :name => "entity name" }
25
+ # ```
26
+ #
27
+ # Setting up classes like this manually is one option, but Presentability also lets you
28
+ # set them up as a collection, which is what further examples will assume for brevity:
29
+ #
30
+ # ```ruby
31
+ # module MyPresenters
32
+ # extend Presentability
33
+ #
34
+ # presenter_for( EntityObject ) do
35
+ # expose :name
36
+ # end
37
+ #
38
+ # end
39
+ # ```
40
+ #
9
41
  class Presentability::Presenter
10
42
  extend Loggability
11
43
 
@@ -66,13 +98,6 @@ class Presentability::Presenter
66
98
  attr_reader :options
67
99
 
68
100
 
69
- ### Return a new instance of whatever object type will be used to represent the
70
- ### subject.
71
- def empty_representation
72
- return {}
73
- end
74
-
75
-
76
101
  ### Apply the exposures to the subject and return the result.
77
102
  def apply
78
103
  result = self.empty_representation
@@ -101,6 +126,23 @@ class Presentability::Presenter
101
126
  end
102
127
 
103
128
 
129
+ ### Return a human-readable representation of the object suitable for debugging.
130
+ def inspect
131
+ return "#<Presentability::Presenter:%#0x for %p>" % [ self.object_id / 2, self.subject ]
132
+ end
133
+
134
+
135
+ #########
136
+ protected
137
+ #########
138
+
139
+ ### Return a new instance of whatever object type will be used to represent the
140
+ ### subject.
141
+ def empty_representation
142
+ return {}
143
+ end
144
+
145
+
104
146
  ### Attempt to expose the attribute with the given +name+ via delegation to the
105
147
  ### subject's method of the same name. Returns +nil+ if no such method exists.
106
148
  def expose_via_delegation( name, options={} )
@@ -123,11 +165,5 @@ class Presentability::Presenter
123
165
  return meth.call
124
166
  end
125
167
 
126
-
127
- ### Return a human-readable representation of the object suitable for debugging.
128
- def inspect
129
- return "#<Presentability::Presenter:%#0x for %p>" % [ self.object_id / 2, self.subject ]
130
- end
131
-
132
168
  end # class Presentability::Presenter
133
169
 
@@ -4,13 +4,75 @@
4
4
  require 'loggability'
5
5
 
6
6
 
7
- # Facade-based presenters with minimal assumptions.
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
+ # ```ruby
23
+ # require 'presentability'
24
+ #
25
+ # module Acme::Presenters
26
+ # extend Presentability
27
+ #
28
+ # presenter_for( Acme::Widget ) do
29
+ # expose :sku
30
+ # expose :name
31
+ # expose :unit_price
32
+ # end
33
+ #
34
+ # end
35
+ # ```
36
+ #
37
+ # The block of `presenter_for` is evaluated in the context of a new Presenter
38
+ # class, so refer to that documentation for what's possible there.
39
+ #
40
+ # Sometimes you can't (or don't want to) have to load the entity class to
41
+ # declare a presenter for it, so you can also declare it using the class's name:
42
+ #
43
+ # ```ruby
44
+ # presenter_for( 'Acme::Widget' ) do
45
+ # expose :sku
46
+ # expose :name
47
+ # expose :unit_price
48
+ # end
49
+ # ```
50
+ #
51
+ # ### Using Presenters
52
+ #
53
+ # You use presenters by instantiating them with the object they are a facade for
54
+ # (the "subject"), and then applying it:
55
+ #
56
+ # ```ruby
57
+ # acme_widget = Acme::Widget.new(
58
+ # sku: "FF-2237H455",
59
+ # name: "Throbbing Frobnulator",
60
+ # unit_price: 299,
61
+ # inventory_count: 301,
62
+ # wholesale_cost: 39
63
+ # )
64
+ # presenter = Acme::Presenters.present( acme_widget )
65
+ # presenter.apply
66
+ # # => { :sku => "FF-2237H455", :name => "Throbbing Frobnulator", :unit_price => 299 }
67
+ # ```
68
+ #
69
+ #
8
70
  module Presentability
9
71
  extend Loggability
10
72
 
11
73
 
12
74
  # Package version
13
- VERSION = '0.0.1'
75
+ VERSION = '0.2.0'
14
76
 
15
77
 
16
78
  # Automatically load subordinate components
@@ -49,6 +111,17 @@ module Presentability
49
111
  end
50
112
 
51
113
 
114
+ ### Return an Array of all representations of the members of the
115
+ ### +collection+ by applying a declared presentation.
116
+ def present_collection( collection, **options )
117
+ return collection.map {|object| self.present(object, **options) }
118
+ end
119
+
120
+
121
+ #########
122
+ protected
123
+ #########
124
+
52
125
  ### Return a representation of the +object+ by applying a presenter declared for its
53
126
  ### class. Returns +nil+ if no such presenter exists.
54
127
  def present_by_class( object, **presentation_options )
@@ -3,6 +3,7 @@
3
3
 
4
4
  require_relative 'spec_helper'
5
5
 
6
+ require 'faker'
6
7
  require 'rspec'
7
8
  require 'presentability'
8
9
 
@@ -15,17 +16,35 @@ RSpec.describe Presentability do
15
16
  return 'Acme::Entity'
16
17
  end
17
18
 
18
- def initialize
19
- @foo = 1
20
- @bar = 'two'
21
- @baz = :three
19
+ def initialize( foo: 1, bar: 'two', baz: :three )
20
+ @foo = foo
21
+ @bar = bar
22
+ @baz = baz
22
23
  end
23
24
 
24
25
  attr_accessor :foo, :bar, :baz
25
26
  end
26
27
  end
27
28
 
29
+ let( :other_entity_class ) do
30
+ Class.new do
31
+ def self::name
32
+ return 'Acme::User'
33
+ end
34
+
35
+ def initialize( firstname, lastname, email, password )
36
+ @firstname = firstname
37
+ @lastname = lastname
38
+ @email = email
39
+ @password = password
40
+ end
41
+
42
+ attr_accessor :firstname, :lastname, :email, :password
43
+ end
44
+ end
45
+
28
46
  let( :entity_instance ) { entity_class.new }
47
+ let( :other_entity_instance ) { entity_class.new }
29
48
 
30
49
 
31
50
  describe "an extended module" do
@@ -103,6 +122,87 @@ RSpec.describe Presentability do
103
122
  }.to raise_error( NoMethodError, /can't expose :id -- no such attribute exists/i )
104
123
  end
105
124
 
125
+
126
+ describe "collection handling" do
127
+
128
+ it "can present a collection" do
129
+ extended_module.presenter_for( entity_class ) do
130
+ expose :foo
131
+ expose :bar
132
+ end
133
+ collection = 5.times.map do
134
+ entity_class.new( foo: rand(100) )
135
+ end
136
+
137
+ results = extended_module.present_collection( collection )
138
+
139
+ expect( results ).to have_attributes( length: 5 )
140
+ results.each do |representation|
141
+ expect( representation ).to include( :foo, :bar )
142
+ expect( representation ).to_not include( :baz )
143
+ end
144
+ end
145
+
146
+
147
+ it "can present a mixed collection" do
148
+ extended_module.presenter_for( entity_class ) do
149
+ expose :foo
150
+ expose :bar
151
+ end
152
+ extended_module.presenter_for( other_entity_class ) do
153
+ expose :firstname
154
+ expose :lastname
155
+ expose :email
156
+ end
157
+
158
+ collection = 5.times.flat_map do
159
+ [
160
+ entity_class.new( foo: rand(100) ),
161
+ other_entity_class.new(
162
+ Faker::Name.first_name,
163
+ Faker::Name.last_name,
164
+ Faker::Internet.email,
165
+ Faker::Internet.password
166
+ )
167
+ ]
168
+ end
169
+
170
+ results = extended_module.present_collection( collection )
171
+
172
+ expect( results ).to have_attributes( length: 10 )
173
+ results.each do |representation|
174
+ expect( representation ).to include( :foo, :bar ).
175
+ or( include(:firstname, :lastname, :email) )
176
+ expect( representation ).to_not include( :baz )
177
+ expect( representation ).to_not include( :password )
178
+ end
179
+ end
180
+
181
+
182
+ it "passes options to the individual presenters" do
183
+ extended_module.presenter_for( entity_class ) do
184
+ expose :foo
185
+ expose :bar, if: :include_bar
186
+ end
187
+ collection = 5.times.map do
188
+ entity_class.new( foo: rand(100) )
189
+ end
190
+
191
+ results = extended_module.present_collection( collection )
192
+ results.each do |representation|
193
+ expect( representation ).to include( :foo )
194
+ expect( representation ).to_not include( :bar, :baz )
195
+ end
196
+
197
+ results = extended_module.present_collection( collection, include_bar: true )
198
+ results.each do |representation|
199
+ expect( representation ).to include( :foo, :bar )
200
+ expect( representation ).to_not include( :baz )
201
+ end
202
+ end
203
+
204
+ end
205
+
106
206
  end
107
207
 
108
208
  end
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,11 @@
4
4
  require 'simplecov' if ENV['COVERAGE']
5
5
 
6
6
  require 'rspec'
7
+ require 'i18n'
8
+ require 'faker'
9
+
10
+ Faker::Config.locale = 'en'
11
+ I18n.reload!
7
12
 
8
13
  require 'loggability/spechelpers'
9
14
 
data.tar.gz.sig ADDED
Binary file
metadata CHANGED
@@ -1,14 +1,39 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: presentability
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre.20220808082516
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Granger
8
8
  autorequire:
9
9
  bindir: bin
10
- cert_chain: []
11
- date: 2022-08-08 00:00:00.000000000 Z
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIID+DCCAmCgAwIBAgIBBDANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdnZWQv
14
+ REM9RmFlcmllTVVEL0RDPW9yZzAeFw0yMjAxMDcyMzU4MTRaFw0yMzAxMDcyMzU4
15
+ MTRaMCIxIDAeBgNVBAMMF2dlZC9EQz1GYWVyaWVNVUQvREM9b3JnMIIBojANBgkq
16
+ hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAvyVhkRzvlEs0fe7145BYLfN6njX9ih5H
17
+ L60U0p0euIurpv84op9CNKF9tx+1WKwyQvQP7qFGuZxkSUuWcP/sFhDXL1lWUuIl
18
+ M4uHbGCRmOshDrF4dgnBeOvkHr1fIhPlJm5FO+Vew8tSQmlDsosxLUx+VB7DrVFO
19
+ 5PU2AEbf04GGSrmqADGWXeaslaoRdb1fu/0M5qfPTRn5V39sWD9umuDAF9qqil/x
20
+ Sl6phTvgBrG8GExHbNZpLARd3xrBYLEFsX7RvBn2UPfgsrtvpdXjsHGfpT3IPN+B
21
+ vQ66lts4alKC69TE5cuKasWBm+16A4aEe3XdZBRNmtOu/g81gvwA7fkJHKllJuaI
22
+ dXzdHqq+zbGZVSQ7pRYHYomD0IiDe1DbIouFnPWmagaBnGHwXkDT2bKKP+s2v21m
23
+ ozilJg4aar2okb/RA6VS87o+d7g6LpDDMMQjH4G9OPnJENLdhu8KnPw/ivSVvQw7
24
+ N2I4L/ZOIe2DIVuYH7aLHfjZDQv/mNgpAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYD
25
+ VR0PBAQDAgSwMB0GA1UdDgQWBBRyjf55EbrHagiRLqt5YAd3yb8k4DANBgkqhkiG
26
+ 9w0BAQsFAAOCAYEASrm1AbEoxACZ9WXJH3R5axV3U0CA4xaETlL2YT+2nOfVBMQ9
27
+ 0ZlkPx6j4ghKJgAIi1TMfDM2JyPJsppQh8tiNccDjWc62UZRY/dq26cMqf/lcI+a
28
+ 6YBuEYvzZfearwVs8tHnXtwYV3WSCoCOQaB+nq2lA1O+nkKNl41WOsVbNama5jx3
29
+ 8cQtVSEEmZy6jIDJ8c5TmBJ7BQUDEUEWA/A3V42Xyctoj7DvUXWE0lP+X6ypAVSr
30
+ lFh3TS64D7NTvxkmg7natUoCvobl6kGl4yMaqE4YRTlfuzhpf91TSOntClqrAOsS
31
+ K1s56WndQj3IoBocdY9mQhDZLtLHofSkymoP8btBlj5SsN24TiF0VMSZlctSCYZg
32
+ GKyHim/MMlIfGOWsgfioq5jzwmql7W4CDubbb8Lkg70v+hN2E/MnNVAcNE3gyaGc
33
+ P5YP5BAbNW+gvd3QHRiWTTuhgHrdDnGdXg93N2M5KHn1ug8BtPLQwlcFwEpKnlLn
34
+ btEP+7EplFuoiMfd
35
+ -----END CERTIFICATE-----
36
+ date: 2022-12-06 00:00:00.000000000 Z
12
37
  dependencies:
13
38
  - !ruby/object:Gem::Dependency
14
39
  name: loggability
@@ -24,6 +49,20 @@ dependencies:
24
49
  - - "~>"
25
50
  - !ruby/object:Gem::Version
26
51
  version: '0.18'
52
+ - !ruby/object:Gem::Dependency
53
+ name: faker
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - "~>"
57
+ - !ruby/object:Gem::Version
58
+ version: '3.0'
59
+ type: :development
60
+ prerelease: false
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - "~>"
64
+ - !ruby/object:Gem::Version
65
+ version: '3.0'
27
66
  - !ruby/object:Gem::Dependency
28
67
  name: rake-deveiate
29
68
  requirement: !ruby/object:Gem::Requirement
@@ -73,11 +112,11 @@ homepage: https://hg.sr.ht/~ged/Presentability
73
112
  licenses:
74
113
  - BSD-3-Clause
75
114
  metadata:
76
- bug_tracker_uri: https://todo.sr.ht/~ged/Presentability
77
- changelog_uri: https://deveiate.org/code/presentability/History_md.html
78
- documentation_uri: https://deveiate.org/code/presentability
79
115
  homepage_uri: https://hg.sr.ht/~ged/Presentability
116
+ documentation_uri: https://deveiate.org/code/presentability
117
+ changelog_uri: https://deveiate.org/code/presentability/History_md.html
80
118
  source_uri: https://hg.sr.ht/~ged/Presentability
119
+ bug_tracker_uri: https://todo.sr.ht/~ged/Presentability
81
120
  post_install_message:
82
121
  rdoc_options: []
83
122
  require_paths:
@@ -89,9 +128,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
89
128
  version: '0'
90
129
  required_rubygems_version: !ruby/object:Gem::Requirement
91
130
  requirements:
92
- - - ">"
131
+ - - ">="
93
132
  - !ruby/object:Gem::Version
94
- version: 1.3.1
133
+ version: '0'
95
134
  requirements: []
96
135
  rubygems_version: 3.3.7
97
136
  signing_key:
metadata.gz.sig ADDED
Binary file