chic 0.2.0 → 0.3.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
- data/README.md +300 -32
- data/chic.gemspec +1 -1
- data/lib/chic/configuration.rb +7 -2
- data/lib/chic/errors.rb +3 -2
- data/lib/chic/presentable.rb +8 -0
- data/lib/chic/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d229620e4cecbd12bf3c5b6c179977c4547684796855055190f816461994613d
|
4
|
+
data.tar.gz: 9fce4389d3ae6d75ec20329ba923094fdec7f27c34c9adc1f2292f78e2f7269c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8ccc7f570009c3ddaceb44b7c017835ee188a712a65c6f9ac0fe02bae404742ad32de302a214eaae115ef019887a7d7f2f4c387b10e75822fe4e08fb95e9ef3
|
7
|
+
data.tar.gz: efb4967023205dd1f4322679f79e3609eaa5aecacd80a45f40f46556fdb2d8c8651764096faafcf31ba8f12f47d1d6678f43db90f0e64da7579c6b8a0bb72f42
|
data/README.md
CHANGED
@@ -1,86 +1,354 @@
|
|
1
1
|
# Chic
|
2
2
|
|
3
|
-
|
3
|
+
An opinionated presentation layer comprising presenters and formatters.
|
4
|
+
|
5
|
+
Chic was borne out of a need for a simple PORO-style presentation layer for Rails applications to help DRY up formatting logic in views and view helpers.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
7
|
-
|
9
|
+
To get started, add this line to your Gemfile and install it using Bundler:
|
8
10
|
|
9
|
-
|
11
|
+
```ruby
|
12
|
+
gem 'chic'
|
13
|
+
```
|
10
14
|
|
11
|
-
|
15
|
+
## Usage
|
12
16
|
|
13
|
-
|
17
|
+
Though it is not a requirement, for ease of reference it is assumed that you will be using this library in a Rails application where you will be creating presenters for your models.
|
14
18
|
|
15
|
-
|
19
|
+
### Creating Presenters
|
16
20
|
|
17
|
-
|
21
|
+
Create a presenter by deriving from `Chic::Presenter`, and then declare which attributes should return formatters or presenters.
|
18
22
|
|
19
|
-
|
23
|
+
```ruby
|
24
|
+
# app/presenters/foo_presenter.rb
|
20
25
|
|
21
|
-
|
26
|
+
class FooPresenter < Chic::Presenter
|
27
|
+
formats :baz
|
28
|
+
|
29
|
+
presents bar: 'BarPresenter'
|
30
|
+
end
|
31
|
+
```
|
22
32
|
|
23
|
-
|
33
|
+
### Using Presenters
|
34
|
+
|
35
|
+
Include `Chic::Presentable` to make your objects presentable:
|
24
36
|
|
25
37
|
```ruby
|
26
|
-
|
38
|
+
# app/models/foo.rb
|
39
|
+
|
40
|
+
class Foo < ApplicationRecord
|
27
41
|
include Chic::Presentable
|
42
|
+
|
43
|
+
belongs_to :bar
|
28
44
|
end
|
29
45
|
```
|
30
46
|
|
31
|
-
|
47
|
+
Instantiate a presenter by calling `.present` on the presenter class, for example in a Rails view:
|
32
48
|
|
33
|
-
|
49
|
+
```erb
|
50
|
+
<% FooPresenter.present @foo do |foo_presenter| %>
|
51
|
+
<!-- ... -->
|
52
|
+
<% end %>
|
53
|
+
```
|
54
|
+
|
55
|
+
Collections can be presented using `.present_each`:
|
56
|
+
|
57
|
+
```erb
|
58
|
+
<% FooPresenter.present_each @foos do |foo_presenter, foo| %>
|
59
|
+
<!-- ... -->
|
60
|
+
<% end %>
|
61
|
+
```
|
62
|
+
|
63
|
+
You can also include the view helpers:
|
34
64
|
|
35
65
|
```ruby
|
36
|
-
|
37
|
-
|
66
|
+
module ApplicationHelper
|
67
|
+
include Chic::Helpers::View
|
38
68
|
end
|
39
69
|
```
|
40
70
|
|
41
|
-
|
71
|
+
Which will allow you to instantiate presenters without having to use the class name:
|
72
|
+
|
73
|
+
```erb
|
74
|
+
<% present @foo do |foo_presenter| %>
|
75
|
+
<!-- ... -->
|
76
|
+
<% end %>
|
77
|
+
|
78
|
+
<% present_each @foos do |foo_presenter, foo| %>
|
79
|
+
<!-- ... -->
|
80
|
+
<% end %>
|
81
|
+
```
|
82
|
+
|
83
|
+
See the [Conventions](#conventions) section below for more on using presenters.
|
84
|
+
|
85
|
+
### Creating Formatters
|
86
|
+
|
87
|
+
Formatters format values by deriving from `Chic::Formatters::Nil` and overriding the private `value` method:
|
42
88
|
|
43
89
|
```ruby
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
90
|
+
# app/formatters/date_time_formatter.rb
|
91
|
+
|
92
|
+
class DateTimeFormatter < Chic::Formatters::Nil
|
93
|
+
private
|
94
|
+
|
95
|
+
def value
|
96
|
+
return if object.blank?
|
97
|
+
|
98
|
+
object.strftime('%-d %b %Y %H:%M')
|
99
|
+
end
|
48
100
|
end
|
49
101
|
```
|
50
102
|
|
51
|
-
**Note:** You
|
103
|
+
**Note:** You should always return `nil` if the object being formatted is blank so that the `Nil` formatter behaves correctly.
|
52
104
|
|
53
|
-
|
105
|
+
Provide additional formatter options as chainable methods:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
# app/formatters/date_time_formatter.rb
|
109
|
+
|
110
|
+
class DateTimeFormatter < Chic::Formatters::Nil
|
111
|
+
def format=(value)
|
112
|
+
@format = value
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def value
|
119
|
+
return if object.blank?
|
120
|
+
|
121
|
+
object.strftime(@format || '%-d %b %Y %H:%M')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
54
125
|
|
55
|
-
|
126
|
+
### Using Formatters
|
127
|
+
|
128
|
+
Declare formatted values in presenters using `formats`:
|
56
129
|
|
57
130
|
```ruby
|
131
|
+
# app/presenters/foo_presenter.rb
|
132
|
+
|
133
|
+
class FooPresenter < Chic::Presenter
|
134
|
+
formats :created_at,
|
135
|
+
with: 'DateTimeFormatter'
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
Render formatted values by calling `#to_s` on the formatter returned, which happens implicitly in Rails views for example:
|
140
|
+
|
141
|
+
```erb
|
58
142
|
<% FooPresenter.present @foo do |foo_presenter, _foo| %>
|
59
|
-
|
143
|
+
<%= foo_presenter.created_at %>
|
60
144
|
<% end %>
|
61
145
|
```
|
62
146
|
|
63
|
-
|
147
|
+
#### Configurable options
|
148
|
+
|
149
|
+
If the formatter derives from `Chic::Formatters::Nil`, then configure a blank value to be used:
|
64
150
|
|
65
151
|
```ruby
|
66
|
-
|
67
|
-
|
152
|
+
# app/presenters/foo_presenter.rb
|
153
|
+
|
154
|
+
class FooPresenter < Chic::Presenter
|
155
|
+
formats :created_at,
|
156
|
+
with: 'DateTimeFormatter',
|
157
|
+
options: {
|
158
|
+
blank_value: '(Not yet created)'
|
159
|
+
}
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
If the formatter supports additional options using chainable methods as described above, configure those options in the same way:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
# app/presenters/foo_presenter.rb
|
167
|
+
|
168
|
+
class FooPresenter < Chic::Presenter
|
169
|
+
formats :created_at,
|
170
|
+
with: 'DateTimeFormatter',
|
171
|
+
options: {
|
172
|
+
format: '%-d %B %Y at %H:%M'
|
173
|
+
}
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
If needed, override those options where the formatted value is being rendered:
|
178
|
+
|
179
|
+
```erb
|
180
|
+
<% FooPresenter.present @foo do |foo_presenter, _foo| %>
|
181
|
+
<%= foo_presenter.created_at.format('%c').blank_value('–') %>
|
68
182
|
<% end %>
|
69
183
|
```
|
70
184
|
|
71
|
-
|
185
|
+
#### Named formatters
|
186
|
+
|
187
|
+
Optionally configure formatters with a name, for example in a Rails initializer:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
# config/initializers/chic.rb
|
191
|
+
|
192
|
+
require 'chic'
|
193
|
+
|
194
|
+
Chic.configure do |config|
|
195
|
+
config.formatters.merge!(
|
196
|
+
date_time: 'DateTimeFormatter'
|
197
|
+
)
|
198
|
+
end
|
199
|
+
```
|
200
|
+
|
201
|
+
Allowing you to refer to those formatters by name instead of by class:
|
72
202
|
|
73
203
|
```ruby
|
74
|
-
|
204
|
+
# app/presenters/foo_presenter.rb
|
205
|
+
|
206
|
+
class FooPresenter < Chic::Presenter
|
207
|
+
formats :created_at,
|
208
|
+
with: :date_time
|
209
|
+
end
|
210
|
+
```
|
211
|
+
|
212
|
+
## Logging
|
213
|
+
|
214
|
+
If a presenter class for an object you're trying to present can't be found, an entry at debug level will be made to the configured logger.
|
215
|
+
|
216
|
+
You can configure the logger to be used:
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
# config/initializers/chic.rb
|
220
|
+
|
221
|
+
require 'chic'
|
222
|
+
|
223
|
+
Chic.configure do |config|
|
224
|
+
config.logger = Logger.new($stdout)
|
225
|
+
end
|
226
|
+
```
|
227
|
+
|
228
|
+
It may be beneficial to know about missing presenter classes sooner than later, in which case you can enable exceptions when it makes sense to do so – for example, a Rails application in any environment other than production:
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
# config/initializers/chic.rb
|
232
|
+
|
233
|
+
require 'chic'
|
234
|
+
|
235
|
+
Chic.configure do |config|
|
236
|
+
config.raise_exceptions = Rails.env.production? == false
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
## Conventions
|
241
|
+
|
242
|
+
A few helpful conventions that have gone a long way to keep things maintainable.
|
243
|
+
|
244
|
+
### Naming presenter classes
|
245
|
+
|
246
|
+
Presenter class names are derived by appending `Presenter` to the `#model_name` or the class name of the object being presented. It is strongly recommended that you stick to this convention, but if you need to change it – for example you might have overridden `#model_name` – you can do so by defining a `#presenter_class` method:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
# app/forms/user/sign_up_form.rb
|
250
|
+
|
251
|
+
class User::SignUpForm < User
|
252
|
+
include Chic::Presentable
|
253
|
+
|
254
|
+
def self.model_name
|
255
|
+
ActiveModel::Name.new(self, nil, 'User')
|
256
|
+
end
|
257
|
+
|
258
|
+
def presenter_class
|
259
|
+
User::SignUpFormPresenter
|
260
|
+
end
|
261
|
+
end
|
262
|
+
```
|
263
|
+
|
264
|
+
### Instantiate presenters in views only
|
265
|
+
|
266
|
+
Try not instantiate presenters outside of the view layer if possible.
|
267
|
+
|
268
|
+
**Don't**
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
# app/controllers/foo_controller.rb
|
272
|
+
|
273
|
+
class FoosController < ApplicationController
|
274
|
+
def show
|
275
|
+
@foo = Foo.find(params[:id])
|
276
|
+
@foo_presenter = FooPresenter.new(@foo)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
```
|
280
|
+
|
281
|
+
```erb
|
282
|
+
<!-- app/views/foos/_show.html.erb -->
|
283
|
+
|
284
|
+
<%= link_to @foo_presenter.created_at, foo_path(@foo) %>
|
285
|
+
```
|
286
|
+
|
287
|
+
**Do**
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
# app/controllers/foo_controller.rb
|
291
|
+
|
292
|
+
class FoosController < ApplicationController
|
293
|
+
def show
|
294
|
+
@foo = Foo.find(params[:id])
|
295
|
+
end
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
```erb
|
300
|
+
<!-- app/views/foos/_show.html.erb -->
|
301
|
+
|
302
|
+
<% present @foo do |foo_presenter| %>
|
303
|
+
<%= link_to foo_presenter.created_at, foo_path(@foo) %>
|
304
|
+
<% end %>
|
305
|
+
```
|
306
|
+
|
307
|
+
### Keep presenter instances scoped to only the view in which they're being used
|
308
|
+
|
309
|
+
It can get messy if you pass presenter instances to other views, try avoid that if possible.
|
310
|
+
|
311
|
+
**Don't**
|
312
|
+
|
313
|
+
```erb
|
314
|
+
<% present @foo do |foo_presenter| %>
|
75
315
|
<!-- ... -->
|
316
|
+
<%= render partial: 'path/to/partial', locals: { foo: foo_presenter } %>
|
76
317
|
<% end %>
|
77
318
|
```
|
78
319
|
|
79
|
-
|
320
|
+
**Do**
|
80
321
|
|
81
|
-
```
|
82
|
-
<%
|
322
|
+
```erb
|
323
|
+
<% present @foo do |foo_presenter| %>
|
83
324
|
<!-- ... -->
|
325
|
+
<%= render partial: 'path/to/partial', locals: { foo: @foo } %>
|
326
|
+
<% end %>
|
327
|
+
```
|
328
|
+
|
329
|
+
### Use presenters and formatters for presentation only
|
330
|
+
|
331
|
+
It may be tempting to use presenters for conditional logic, but it's far better to use the original object for anything other than presentation.
|
332
|
+
|
333
|
+
**Don't**
|
334
|
+
|
335
|
+
```erb
|
336
|
+
<% present_each @foos do |foo_presenter, foo| %>
|
337
|
+
<% if foo_presenter.created_at %>
|
338
|
+
<%= link_to foo_presenter.created_at, foo_path(foo_presenter) %>
|
339
|
+
<% end
|
340
|
+
<% end %>
|
341
|
+
```
|
342
|
+
|
343
|
+
**Note:** Passing a presenter instance to a route helper as shown would only work if the presenter declared `id` as a formatted attribute. While this may work, it is not predictable behaviour.
|
344
|
+
|
345
|
+
**Do**
|
346
|
+
|
347
|
+
```erb
|
348
|
+
<% present_each @foos do |foo_presenter, foo| %>
|
349
|
+
<% if foo.created_at %>
|
350
|
+
<%= link_to foo_presenter.created_at, foo_path(foo) %>
|
351
|
+
<% end
|
84
352
|
<% end %>
|
85
353
|
```
|
86
354
|
|
data/chic.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
|
26
26
|
spec.required_ruby_version = '>= 2.7'
|
27
27
|
|
28
|
-
spec.add_development_dependency 'bundler', '~> 2.
|
28
|
+
spec.add_development_dependency 'bundler', '~> 2.3.10'
|
29
29
|
spec.add_development_dependency 'minitest', '~> 5.0'
|
30
30
|
spec.add_development_dependency 'rake', '~> 13.0'
|
31
31
|
spec.add_development_dependency 'rubocop', '~> 0.71'
|
data/lib/chic/configuration.rb
CHANGED
@@ -11,8 +11,9 @@ module Chic
|
|
11
11
|
nil: Formatters::Nil
|
12
12
|
}.freeze
|
13
13
|
|
14
|
-
attr_writer :
|
15
|
-
:
|
14
|
+
attr_writer :formatters,
|
15
|
+
:logger,
|
16
|
+
:raise_exceptions
|
16
17
|
|
17
18
|
def formatters
|
18
19
|
@formatters ||= FORMATTERS.dup
|
@@ -21,5 +22,9 @@ module Chic
|
|
21
22
|
def logger
|
22
23
|
@logger ||= Logger.new($stdout)
|
23
24
|
end
|
25
|
+
|
26
|
+
def raise_exceptions
|
27
|
+
@raise_exceptions == true
|
28
|
+
end
|
24
29
|
end
|
25
30
|
end
|
data/lib/chic/errors.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Chic
|
4
|
-
class PresentsOptionsNotValid < StandardError; end
|
5
|
-
class FormatsOptionsNotValid < StandardError; end
|
6
4
|
class ConfigFormatterNotValid < StandardError; end
|
5
|
+
class FormatsOptionsNotValid < StandardError; end
|
6
|
+
class PresenterClassNotDefined < StandardError; end
|
7
|
+
class PresentsOptionsNotValid < StandardError; end
|
7
8
|
end
|
data/lib/chic/presentable.rb
CHANGED
@@ -12,6 +12,8 @@ module Chic
|
|
12
12
|
|
13
13
|
module_function
|
14
14
|
|
15
|
+
# rubocop: disable Metrics/AbcSize
|
16
|
+
# rubocop: disable Metrics/MethodLength
|
15
17
|
def presenter_for(object)
|
16
18
|
if object.respond_to?(:presenter_class)
|
17
19
|
object.presenter_class
|
@@ -19,8 +21,14 @@ module Chic
|
|
19
21
|
"#{object&.model_name || object.class.name}Presenter".constantize
|
20
22
|
end
|
21
23
|
rescue NameError, LoadError
|
24
|
+
if Chic.configuration.raise_exceptions
|
25
|
+
raise PresenterClassNotDefined, "Couldn't find a presenter for '#{object.class.name}'"
|
26
|
+
end
|
27
|
+
|
22
28
|
Chic.configuration.logger&.debug "Couldn't find a presenter for '#{object.class.name}'"
|
23
29
|
nil
|
24
30
|
end
|
31
|
+
# rubocop: enable Metrics/AbcSize
|
32
|
+
# rubocop: enable Metrics/MethodLength
|
25
33
|
end
|
26
34
|
end
|
data/lib/chic/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lawrance Shepstone
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.3.10
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 2.3.10
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -119,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
119
|
- !ruby/object:Gem::Version
|
120
120
|
version: '0'
|
121
121
|
requirements: []
|
122
|
-
rubygems_version: 3.
|
122
|
+
rubygems_version: 3.3.7
|
123
123
|
signing_key:
|
124
124
|
specification_version: 4
|
125
125
|
summary: Opinionated presentation layer comprised of presenters and formatters.
|