chic 0.2.0 → 0.3.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: 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.