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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd0f4b4ac3b5b6097ea4670bad650de8b39f011c59a73101f3b82552f9f14bac
4
- data.tar.gz: c199afc9dd72faa4f8f9fc8a466cb6fce82df91d926053044cfd1b8a95a428c1
3
+ metadata.gz: d229620e4cecbd12bf3c5b6c179977c4547684796855055190f816461994613d
4
+ data.tar.gz: 9fce4389d3ae6d75ec20329ba923094fdec7f27c34c9adc1f2292f78e2f7269c
5
5
  SHA512:
6
- metadata.gz: d3d6312d529f55e22af639948e8116ce0940741594a0414a023d67bfc31078b683e24b7cb9945413546cb65f582688a69422b3bb273147a9de81e86a766f42db
7
- data.tar.gz: 1e6bf540de26573830f280d8688b6c032595ca7526d9eb0742ae6cd637457ef2a9453628f7c4a48580c91666e55895d08d3a5a65b6420d6fcbabd8198fded850
6
+ metadata.gz: a8ccc7f570009c3ddaceb44b7c017835ee188a712a65c6f9ac0fe02bae404742ad32de302a214eaae115ef019887a7d7f2f4c387b10e75822fe4e08fb95e9ef3
7
+ data.tar.gz: efb4967023205dd1f4322679f79e3609eaa5aecacd80a45f40f46556fdb2d8c8651764096faafcf31ba8f12f47d1d6678f43db90f0e64da7579c6b8a0bb72f42
data/README.md CHANGED
@@ -1,86 +1,354 @@
1
1
  # Chic
2
2
 
3
- Opinionated presentation layer comprised of presenters and formatters.
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
- Add this line to your application's Gemfile:
9
+ To get started, add this line to your Gemfile and install it using Bundler:
8
10
 
9
- gem 'chic'
11
+ ```ruby
12
+ gem 'chic'
13
+ ```
10
14
 
11
- And then execute:
15
+ ## Usage
12
16
 
13
- $ bundle
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
- Or install it yourself as:
19
+ ### Creating Presenters
16
20
 
17
- $ gem install chic
21
+ Create a presenter by deriving from `Chic::Presenter`, and then declare which attributes should return formatters or presenters.
18
22
 
19
- ## Usage
23
+ ```ruby
24
+ # app/presenters/foo_presenter.rb
20
25
 
21
- ### Being Presentable
26
+ class FooPresenter < Chic::Presenter
27
+ formats :baz
28
+
29
+ presents bar: 'BarPresenter'
30
+ end
31
+ ```
22
32
 
23
- Make objects easily presentable by:
33
+ ### Using Presenters
34
+
35
+ Include `Chic::Presentable` to make your objects presentable:
24
36
 
25
37
  ```ruby
26
- class Foo
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
- ### Creating Presenters
47
+ Instantiate a presenter by calling `.present` on the presenter class, for example in a Rails view:
32
48
 
33
- Present presentables with a presenter by inheriting from `Chic::Presenter`:
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
- class FooPresenter < Chic::Presenter
37
- # ...
66
+ module ApplicationHelper
67
+ include Chic::Helpers::View
38
68
  end
39
69
  ```
40
70
 
41
- You can also include `Chic::Presents` and `Chic::Formats` as needed:
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
- class FooPresenter
45
- include Chic::Formats
46
- include Chic::Presents
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 need to make sure that the object being presented and the context in which it is being presented, for example the view, are accessible through `object` and `context` on the presenter instance respectively.
103
+ **Note:** You should always return `nil` if the object being formatted is blank so that the `Nil` formatter behaves correctly.
52
104
 
53
- ### Using Presenters
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
- Presenters should be instantiated from views using `.present`:
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
- Collections can be presented using `.present_each`:
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
- <% FooPresenter.present_each @foos do |foo_presenter, _foo| %>
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
- If you've made use of the view helpers, you can drop the class name:
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
- <% present @foo do |foo_presenter, _foo| %>
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
- And:
320
+ **Do**
80
321
 
81
- ```ruby
82
- <% present_each @foo do |foo_presenter, _foo| %>
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.0'
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'
@@ -11,8 +11,9 @@ module Chic
11
11
  nil: Formatters::Nil
12
12
  }.freeze
13
13
 
14
- attr_writer :logger,
15
- :formatters
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chic
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
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.2.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: 2020-10-09 00:00:00.000000000 Z
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: '2.0'
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: '2.0'
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.1.4
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.