garden_variety 1.2 → 2.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
  SHA1:
3
- metadata.gz: c633defda33a2f8bcd26d96f58c1bfea52c2fb9c
4
- data.tar.gz: 988988d42d10f00c3f55d5690b39ec46710ed68a
3
+ metadata.gz: b80f333fab73447d743d99ef4cd170a997011a8e
4
+ data.tar.gz: ab23ee38fd1a658f8fa7a82eeac8708110346bb4
5
5
  SHA512:
6
- metadata.gz: 8eee6bea8231197d118f405c320a58af2a4d73c08f2ce65ad3373142afdf895585ce528397be97640e4cabb3631c9652c1dd58c84e1dec162f732bf558aa96c1
7
- data.tar.gz: 643ca6a75d537943723a1ee4fb27af838bd666dd787c08e7dea53898282201fa735be86de7acebf4f4f9a5791482a508b83919573ec3943068c30291802dad87
6
+ metadata.gz: 1a36d05543168c2739e1b2677aa4b870c534ca4a5fc340a80ae80f18e920b238c59017179cd16e5824f2bce62ac36db3ca73a4c80f98a5e8f61381de597ef336
7
+ data.tar.gz: a93377a1e7c252f6ad547febe70bdaedc83a62383819e04766bda20b604f5db055571d17fa7d56b5a4680eccb0d124bf54d5171787a5a1858cdb472b80cbc58f
data/README.md CHANGED
@@ -48,8 +48,10 @@ class PostsController < ApplicationController
48
48
  def create
49
49
  self.resource = vest(new_resource)
50
50
  if resource.save
51
+ flash[:success] = flash_message(:success)
51
52
  redirect_to resource
52
53
  else
54
+ flash.now[:error] = flash_message(:error)
53
55
  render :new
54
56
  end
55
57
  end
@@ -62,8 +64,10 @@ class PostsController < ApplicationController
62
64
  def update
63
65
  self.resource = vest(find_resource)
64
66
  if resource.save
67
+ flash[:success] = flash_message(:success)
65
68
  redirect_to resource
66
69
  else
70
+ flash.now[:error] = flash_message(:error)
67
71
  render :edit
68
72
  end
69
73
  end
@@ -71,8 +75,13 @@ class PostsController < ApplicationController
71
75
  def destroy
72
76
  self.resource = find_resource
73
77
  authorize(resource)
74
- resource.destroy!
75
- redirect_to action: :index
78
+ if resource.destroy
79
+ flash[:success] = flash_message(:success)
80
+ redirect_to action: :index
81
+ else
82
+ flash.now[:error] = flash_message(:error)
83
+ render :show
84
+ end
76
85
  end
77
86
 
78
87
  private
@@ -115,6 +124,22 @@ class PostsController < ApplicationController
115
124
  model
116
125
  end
117
126
 
127
+ def flash_options
128
+ { resource_name: "post", resource_capitalized: "Post" }
129
+ end
130
+
131
+ def flash_message(status)
132
+ keys = [
133
+ :"posts.#{action_name}.#{status}",
134
+ :"posts.#{action_name}.#{status}_html",
135
+ :"#{action_name}.#{status}",
136
+ :"#{action_name}.#{status}_html",
137
+ :"#{status}",
138
+ :"#{status}_html",
139
+ ]
140
+ helpers.translate(keys.first, default: keys.drop(1), **flash_options)
141
+ end
142
+
118
143
  end
119
144
  ```
120
145
 
@@ -182,17 +207,60 @@ generator in a few small ways:
182
207
  * `rails generate pundit:policy` is invoked for the specified model.
183
208
 
184
209
 
210
+ ## Flash messages
211
+
212
+ Flash messages are defined using I18n. The *garden_variety* installer
213
+ (`rails generate garden:install`) will create a
214
+ "config/locales/garden_variety.en.yml" file containing default "success"
215
+ and "error" messages. You can edit this file to customize those
216
+ messages, or add your own translation files to support other languages.
217
+
218
+ As seen in the `PostsController#flash_message` method in the example
219
+ above, a prioritized list of keys are tried when retrieving a flash
220
+ message. Keys specific to the controller are tried first, followed by
221
+ keys specific to the action, and then finally generic status keys. For
222
+ each level of specificity, `*_html` key variants are supported, which
223
+ allow raw HTML to be included in flash messages and not be escaped when
224
+ rendered. (See the [Safe HTML Translations] section of the Rails
225
+ Internationalization guide for more information.)
226
+
227
+ Interpolation in flash messages is also supported (as described by
228
+ [Passing Variables to Translations]), with interpolation values provided
229
+ by the `flash_options` method. By default, `flash_options` provides
230
+ `resource_name` and `resource_capitalized` values, but you can override
231
+ it to provide your own values.
232
+
233
+ [Safe HTML Translations]: http://guides.rubyonrails.org/i18n.html#using-safe-html-translations
234
+ [Passing Variables to Translations]: http://guides.rubyonrails.org/i18n.html#passing-variables-to-translations
235
+
236
+
185
237
  ## Beyond garden variety behavior
186
238
 
187
239
  *garden_variety* is designed to reduce the amount of custom code
188
240
  written, including in situations where custom code is unavoidable.
189
241
 
190
242
 
243
+ ### Integrating with pagination
244
+
245
+ You can integrate your your favorite pagination gem (*may I suggest
246
+ [foliate](https://rubygems.org/gems/foliate)?*) by overriding the
247
+ `list_resources` method:
248
+
249
+ ```ruby
250
+ class PostsController < ApplicationController
251
+ garden_variety
252
+
253
+ def list_resources
254
+ paginate(super)
255
+ end
256
+ end
257
+ ```
258
+
259
+
191
260
  ### Integrating with search
192
261
 
193
- It is possible to integrate searching functionality by overriding the
194
- `index` action. However, it can be simpler to override the
195
- `list_resources` method instead:
262
+ You can also integrate search functionality by overriding the
263
+ `list_resources` method:
196
264
 
197
265
  ```ruby
198
266
  class PostsController < ApplicationController
@@ -205,22 +273,55 @@ end
205
273
  ```
206
274
 
207
275
 
208
- ### Integrating with pagination
276
+ ### Integrating with SJR
209
277
 
210
- Your favorite pagination gem (*may I suggest
211
- [foliate](https://rubygems.org/gems/foliate)?*) can also be integrated
212
- by overriding the `list_resources` action:
278
+ Server-generated JavaScript Response (SJR) controller actions are
279
+ generally the same as conventional controller actions, with one
280
+ difference: non-GET SJR actions render instead of redirect on success.
281
+ *garden_variety* provides a concise syntax for overriding only
282
+ on-success behavior of non-GET actions:
213
283
 
214
284
  ```ruby
215
285
  class PostsController < ApplicationController
216
286
  garden_variety
217
287
 
218
- def list_resources
219
- paginate(super)
288
+ def create
289
+ super{ render plain: "text" } # renders text on success instead of redirect
290
+ end
291
+ end
292
+ ```
293
+
294
+ Thus, combining this syntax with Rails' default rendering convention:
295
+
296
+ ```ruby
297
+ class PostsController < ApplicationController
298
+ garden_variety
299
+
300
+ def create
301
+ respond_to do |format|
302
+ format.js{ super{} } # renders "app/views/posts/create.js.erb" on success
303
+ end
220
304
  end
305
+
306
+ def update
307
+ respond_to do |format|
308
+ format.js{ super{} } # renders "app/views/posts/update.js.erb" on success
309
+ end
310
+ end
311
+
312
+ def destroy
313
+ respond_to do |format|
314
+ format.js{ super{} } # renders "app/views/posts/destroy.js.erb" on success
315
+ end
316
+ end
317
+
221
318
  end
222
319
  ```
223
320
 
321
+ **Sidenote:** *garden_variety* automatically manages the life of flash
322
+ messages. If a redirect is replaced with a render, the flash message
323
+ will be set such that it does not affect the next request.
324
+
224
325
 
225
326
  ### Integrating with authentication
226
327
 
@@ -287,58 +388,32 @@ end
287
388
 
288
389
 
289
390
  class RegistrationFormsController < ApplicationController
290
- garden_variety :new
391
+ garden_variety :new, :create # only generate #new and #create actions
291
392
 
292
393
  def create
293
- self.resource = vest(new_resource)
294
- if resource.save
295
- redirect_to root_path # redirect to front page instead of show
296
- else
297
- render :new
298
- end
394
+ super{ redirect_to root_path } # redirect to home page on success
299
395
  end
300
396
  end
301
397
  ```
302
398
 
303
- Only the `new` controller action is generated by the `garden_variety`
304
- macro. The `create` action is implemented directly in order to redirect
305
- to `root_path` rather than the resource itself, as would be
306
- conventional. The *garden_variety* helper methods all work as expected
399
+ In this example, only the `new` and `create` actions are generated by
400
+ the `garden_variety` macro. The on-success behavior of `create` is
401
+ overridden to redirect to `root_path`, using the concise syntax
402
+ discussed in the [Integrating with SJR](#integrating-with-sjr) example.
403
+ The *garden_variety* controller helper methods all work as expected
307
404
  because `RegistrationForm` responds to `assign_attributes` and `save`,
308
405
  and has a default (nullary) constructor.
309
406
 
310
- This pattern of overriding a controller action merely to respond
311
- differently upon success is common enough that *garden_variety* provides
312
- a concise syntax for it:
313
-
314
- ```ruby
315
- class RegistrationFormsController < ApplicationController
316
- garden_variety :new, :create
317
-
318
- def create
319
- super{ redirect_to root_path }
320
- end
321
- end
322
- ```
323
-
324
- In the above example, the `garden_variety` macro generates a
325
- conventional `create` action, which is then invoked via `super` in the
326
- `create` override. Here, when a block is passed to `super`, it is
327
- treated as an on-success callback which replaces the default redirect.
328
- This callback behavior is also available for the `update` and `destroy`
329
- actions.
330
-
331
407
 
332
408
  ### Non-REST actions
333
409
 
334
410
  You may also define any non-REST controller actions you wish (i.e.
335
411
  actions other than: `index`, `show`, `new`, `create`, `edit`, `update`,
336
- and `destroy`). The helper methods *garden_variety* provides may be
337
- useful when doing so.
338
-
339
- However, before implementing a non-REST controller action, consider if
340
- the behavior might be better implemented as a REST action in a new
341
- controller. For example, instead of the following `published` action...
412
+ and `destroy`). The controller helper methods *garden_variety* provides
413
+ may be useful when doing so. However, before implementing a non-REST
414
+ controller action, consider if the behavior might be better implemented
415
+ as a REST action in a new controller. For example, instead of the
416
+ following `published` action...
342
417
 
343
418
  ```ruby
344
419
  class PostsController < ApplicationController
@@ -388,13 +463,14 @@ Then execute:
388
463
  $ bundle install
389
464
  ```
390
465
 
391
- And finally, if you haven't already used and installed Pundit, run the
392
- Pundit installation generator:
466
+ And finally, run the *garden_variety* install generator:
393
467
 
394
468
  ```bash
395
- $ rails generate pundit:install
469
+ $ rails generate garden:install
396
470
  ```
397
471
 
472
+ This will also run the Pundit install generator, if necessary.
473
+
398
474
 
399
475
  ## Contributing
400
476
 
@@ -1,5 +1,7 @@
1
1
  module GardenVariety
2
2
 
3
+ REDIRECT_CODES = [301, 302, 303, 307, 308]
4
+
3
5
  module IndexAction
4
6
  # Garden variety controller +index+ action.
5
7
  # @return [void]
@@ -40,8 +42,11 @@ module GardenVariety
40
42
  def create
41
43
  self.resource = vest(new_resource)
42
44
  if resource.save
45
+ flash[:success] = flash_message(:success)
43
46
  block_given? ? yield : redirect_to(resource)
47
+ flash.discard(:success) if REDIRECT_CODES.exclude?(response.status)
44
48
  else
49
+ flash.now[:error] = flash_message(:error)
45
50
  render :new
46
51
  end
47
52
  end
@@ -65,8 +70,11 @@ module GardenVariety
65
70
  def update
66
71
  self.resource = vest(find_resource)
67
72
  if resource.save
73
+ flash[:success] = flash_message(:success)
68
74
  block_given? ? yield : redirect_to(resource)
75
+ flash.discard(:success) if REDIRECT_CODES.exclude?(response.status)
69
76
  else
77
+ flash.now[:error] = flash_message(:error)
70
78
  render :edit
71
79
  end
72
80
  end
@@ -81,8 +89,14 @@ module GardenVariety
81
89
  def destroy
82
90
  self.resource = find_resource
83
91
  authorize(resource)
84
- resource.destroy!
85
- block_given? ? yield : redirect_to(action: :index)
92
+ if resource.destroy
93
+ flash[:success] = flash_message(:success)
94
+ block_given? ? yield : redirect_to(action: :index)
95
+ flash.discard(:success) if REDIRECT_CODES.exclude?(response.status)
96
+ else
97
+ flash.now[:error] = flash_message(:error)
98
+ render :show
99
+ end
86
100
  end
87
101
  end
88
102
 
@@ -224,5 +224,94 @@ module GardenVariety
224
224
  model.assign_attributes(permitted_attributes(model))
225
225
  model
226
226
  end
227
+
228
+ # @!visibility public
229
+ # Returns Hash of values for interpolation in flash messages via
230
+ # I18n. By default, returns +resource_name+ and
231
+ # +resource_capitalized+ values appropriate to the controller.
232
+ # Override this method to provide your own values. Be aware that
233
+ # certain option names, such as +default+ and +scope+, are reserved
234
+ # by the I18n gem, and can not be used for interpolation. See the
235
+ # {https://www.rubydoc.info/gems/i18n I18n documentation} for more
236
+ # information.
237
+ #
238
+ # @return [Hash]
239
+ def flash_options
240
+ { resource_name: resource_class.model_name.human.downcase,
241
+ resource_capitalized: resource_class.model_name.human }
242
+ end
243
+
244
+ # @!visibility public
245
+ # Returns a flash message appropriate to the controller, the current
246
+ # action, and a given status. The flash message is looked up via
247
+ # I18n using a prioritized list of possible keys. The key priority
248
+ # is as follows:
249
+ #
250
+ # * +{controller_name}.{action_name}.{status}+
251
+ # * +{controller_name}.{action_name}.{status}_html+
252
+ # * +{action_name}.{status}+
253
+ # * +{action_name}.{status}_html+
254
+ # * +{status}+
255
+ # * +{status}_html+
256
+ #
257
+ # If the controller is namespaced, the namespace will prefix
258
+ # (dot-separated) the +{controller_name}+ portion of the key.
259
+ #
260
+ # I18n string interpolation can be used in flash messages, with
261
+ # interpolated values provided by the {flash_options} method.
262
+ #
263
+ # @example Key priority
264
+ # ### config/locales/garden_variety.en.yml
265
+ # # en:
266
+ # # success: "Success!"
267
+ # # create:
268
+ # # success: "%{resource_capitalized} created."
269
+ # # delete:
270
+ # # success: "%{resource_capitalized} deleted."
271
+ # # posts:
272
+ # # create:
273
+ # # success: "Congratulations on your new post!"
274
+ #
275
+ # # via PostsController#create
276
+ # flash_message(:success) # == "Congratulations on your new post!"
277
+ #
278
+ # # via PostsController#update
279
+ # flash_message(:success) # == "Success!"
280
+ #
281
+ # # via PostsController#delete
282
+ # flash_message(:success) # == "Post deleted."
283
+ #
284
+ # @example Namespaced controller
285
+ # ### config/locales/garden_variety.en.yml
286
+ # # en:
287
+ # # create:
288
+ # # success: "Created new %{resource_name}."
289
+ # # update:
290
+ # # success: "Updated %{resource_name}."
291
+ # # messages:
292
+ # # drafts:
293
+ # # update:
294
+ # # success: "Draft saved."
295
+ #
296
+ # # via Messages::DraftsController#create
297
+ # flash_message(:success) # == "Created new draft."
298
+ #
299
+ # # via Messages::DraftsController#update
300
+ # flash_message(:success) # == "Draft saved."
301
+ #
302
+ # @param status [Symbol, String]
303
+ # @return [String]
304
+ def flash_message(status)
305
+ controller_key = controller_path.tr("/", I18n.default_separator)
306
+ keys = [
307
+ :"#{controller_key}.#{action_name}.#{status}",
308
+ :"#{controller_key}.#{action_name}.#{status}_html",
309
+ :"#{action_name}.#{status}",
310
+ :"#{action_name}.#{status}_html",
311
+ :"#{status}",
312
+ :"#{status}_html",
313
+ ]
314
+ helpers.translate(keys.shift, default: keys, **flash_options)
315
+ end
227
316
  end
228
317
  end
@@ -1,3 +1,3 @@
1
1
  module GardenVariety
2
- VERSION = "1.2"
2
+ VERSION = "2.0"
3
3
  end
@@ -0,0 +1,18 @@
1
+ require "rails/generators/base"
2
+
3
+ # @!visibility private
4
+ module Garden
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ source_root File.join(__dir__, "templates")
8
+
9
+ def copy_locales
10
+ directory "locales", "config/locales"
11
+ end
12
+
13
+ def install_pundit
14
+ generate("pundit:install", "--skip")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ en:
2
+ create:
3
+ success: '%{resource_capitalized} was successfully created.'
4
+ error: 'Could not create %{resource_name}.'
5
+ update:
6
+ success: '%{resource_capitalized} was successfully updated.'
7
+ error: 'Could not update %{resource_name}.'
8
+ destroy:
9
+ success: '%{resource_capitalized} was successfully destroyed.'
10
+ error: 'Could not destroy %{resource_name}.'
@@ -4,6 +4,8 @@ require "rails/generators/base"
4
4
  module Garden
5
5
  module Generators
6
6
  class ScaffoldGenerator < Rails::Generators::Base
7
+ source_root File.join(__dir__, "templates")
8
+
7
9
  argument :resource, type: :string
8
10
 
9
11
  # NOTE: an appropriate default value for template_engine (e.g.
@@ -20,6 +22,10 @@ module Garden
20
22
  super
21
23
  end
22
24
 
25
+ def ensure_locales
26
+ directory "../../install/templates/locales", "config/locales", skip: true
27
+ end
28
+
23
29
  def generate_scaffolding
24
30
  generate("resource", *@argv)
25
31
  generate("#{options[:template_engine]}:scaffold", *@argv)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: garden_variety
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.2'
4
+ version: '2.0'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Hefner
@@ -82,6 +82,8 @@ files:
82
82
  - lib/garden_variety/current_user_stub.rb
83
83
  - lib/garden_variety/railtie.rb
84
84
  - lib/garden_variety/version.rb
85
+ - lib/generators/garden/install/install_generator.rb
86
+ - lib/generators/garden/install/templates/locales/garden_variety.en.yml
85
87
  - lib/generators/garden/scaffold/scaffold_generator.rb
86
88
  homepage: https://github.com/jonathanhefner/garden_variety
87
89
  licenses: