garden_variety 1.2 → 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
  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: