garden_variety 2.0 → 3.0.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 +5 -5
- data/README.md +144 -85
- data/Rakefile +1 -8
- data/lib/garden_variety.rb +13 -0
- data/lib/garden_variety/actions.rb +14 -21
- data/lib/garden_variety/controller.rb +155 -137
- data/lib/garden_variety/railtie.rb +4 -15
- data/lib/garden_variety/talent_scout.rb +28 -0
- data/lib/garden_variety/version.rb +1 -1
- data/lib/generators/garden/install/install_generator.rb +0 -2
- data/lib/generators/garden/install/templates/locales/flash.en.yml +11 -0
- data/lib/generators/garden/scaffold/scaffold_generator.rb +9 -2
- metadata +23 -9
- data/lib/generators/garden/install/templates/locales/garden_variety.en.yml +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 505860db6877118744ce3b7f1b4de589c12e0adf8687ab8cadc518fec0a52b37
|
4
|
+
data.tar.gz: a4b18a197a210123c5db5f72c1510e1a4ea75e843c543af98635a842bc04cea3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1703aa1c557798193c1292c7b6999d18f5962d9bf2ebafc83f13c091342376d3a592f6673c519af285e2baa6228222ad9335ded99687748525c551099b5fe965
|
7
|
+
data.tar.gz: c005e563d798f172a9437423b6921713642442e44061ab350ae8aadbb062486325af39380dcc6f17cbe67e41f7ee24c234282852a1f469d8d30df9467de98a94
|
data/README.md
CHANGED
@@ -7,11 +7,10 @@ robustness (less custom code == less that can go wrong). In service of
|
|
7
7
|
this principle, *garden_variety* provides reasonable default controller
|
8
8
|
actions, with care to allow easy override.
|
9
9
|
|
10
|
-
*garden_variety* also
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
stay DRY and boring.
|
10
|
+
*garden_variety* also relies on the [Pundit](https://rubygems.org/gems/pundit)
|
11
|
+
gem to isolate authorization concerns. If you're unfamiliar with
|
12
|
+
Pundit, see its documentation for an explanation of policy objects and
|
13
|
+
how they help controller actions stay DRY and boring.
|
15
14
|
|
16
15
|
As an example, this controller using `garden_variety`...
|
17
16
|
|
@@ -27,29 +26,26 @@ end
|
|
27
26
|
class PostsController < ApplicationController
|
28
27
|
|
29
28
|
def index
|
30
|
-
authorize(
|
31
|
-
self.
|
29
|
+
authorize(self.class.model_class)
|
30
|
+
self.collection = policy_scope(find_collection)
|
32
31
|
end
|
33
32
|
|
34
33
|
def show
|
35
|
-
self.
|
36
|
-
authorize(resource)
|
34
|
+
self.model = authorize(find_model)
|
37
35
|
end
|
38
36
|
|
39
37
|
def new
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
self.resource = new_resource
|
44
|
-
authorize(resource)
|
38
|
+
self.model = authorize(new_model)
|
39
|
+
if params.key?(self.class.model_class.model_name.param_key)
|
40
|
+
assign_attributes(model)
|
45
41
|
end
|
46
42
|
end
|
47
43
|
|
48
44
|
def create
|
49
|
-
self.
|
50
|
-
if
|
45
|
+
self.model = assign_attributes(authorize(new_model))
|
46
|
+
if model.save
|
51
47
|
flash[:success] = flash_message(:success)
|
52
|
-
redirect_to
|
48
|
+
redirect_to model
|
53
49
|
else
|
54
50
|
flash.now[:error] = flash_message(:error)
|
55
51
|
render :new
|
@@ -57,15 +53,14 @@ class PostsController < ApplicationController
|
|
57
53
|
end
|
58
54
|
|
59
55
|
def edit
|
60
|
-
self.
|
61
|
-
authorize(resource)
|
56
|
+
self.model = authorize(find_model)
|
62
57
|
end
|
63
58
|
|
64
59
|
def update
|
65
|
-
self.
|
66
|
-
if
|
60
|
+
self.model = assign_attributes(authorize(find_model))
|
61
|
+
if model.save
|
67
62
|
flash[:success] = flash_message(:success)
|
68
|
-
redirect_to
|
63
|
+
redirect_to model
|
69
64
|
else
|
70
65
|
flash.now[:error] = flash_message(:error)
|
71
66
|
render :edit
|
@@ -73,9 +68,8 @@ class PostsController < ApplicationController
|
|
73
68
|
end
|
74
69
|
|
75
70
|
def destroy
|
76
|
-
self.
|
77
|
-
|
78
|
-
if resource.destroy
|
71
|
+
self.model = authorize(find_model)
|
72
|
+
if model.destroy
|
79
73
|
flash[:success] = flash_message(:success)
|
80
74
|
redirect_to action: :index
|
81
75
|
else
|
@@ -86,69 +80,68 @@ class PostsController < ApplicationController
|
|
86
80
|
|
87
81
|
private
|
88
82
|
|
89
|
-
def
|
83
|
+
def self.model_class
|
90
84
|
Post
|
91
85
|
end
|
92
86
|
|
93
|
-
def
|
87
|
+
def collection
|
94
88
|
@posts
|
95
89
|
end
|
96
90
|
|
97
|
-
def
|
91
|
+
def collection=(models)
|
98
92
|
@posts = models
|
99
93
|
end
|
100
94
|
|
101
|
-
def
|
95
|
+
def model
|
102
96
|
@post
|
103
97
|
end
|
104
98
|
|
105
|
-
def
|
99
|
+
def model=(model)
|
106
100
|
@post = model
|
107
101
|
end
|
108
102
|
|
109
|
-
def
|
110
|
-
|
103
|
+
def find_collection
|
104
|
+
self.class.model_class.all
|
111
105
|
end
|
112
106
|
|
113
|
-
def
|
114
|
-
|
107
|
+
def find_model
|
108
|
+
self.class.model_class.find(params[:id])
|
115
109
|
end
|
116
110
|
|
117
|
-
def
|
118
|
-
|
111
|
+
def new_model
|
112
|
+
self.class.model_class.new
|
119
113
|
end
|
120
114
|
|
121
|
-
def
|
122
|
-
authorize(model)
|
115
|
+
def assign_attributes(model)
|
123
116
|
model.assign_attributes(permitted_attributes(model))
|
124
117
|
model
|
125
118
|
end
|
126
119
|
|
127
120
|
def flash_options
|
128
|
-
{
|
121
|
+
{ model_name: "Post" }
|
129
122
|
end
|
130
123
|
|
131
124
|
def flash_message(status)
|
132
125
|
keys = [
|
133
|
-
:"posts.#{action_name}.#{status}",
|
134
|
-
:"posts.#{action_name}.#{status}_html",
|
135
|
-
:"
|
136
|
-
:"
|
137
|
-
:"
|
138
|
-
:"
|
126
|
+
:"flash.posts.#{action_name}.#{status}",
|
127
|
+
:"flash.posts.#{action_name}.#{status}_html",
|
128
|
+
:"flash.#{action_name}.#{status}",
|
129
|
+
:"flash.#{action_name}.#{status}_html",
|
130
|
+
:"flash.#{status}",
|
131
|
+
:"flash.#{status}_html",
|
139
132
|
]
|
140
|
-
helpers.translate(keys.first, default: keys.drop(1), **flash_options)
|
133
|
+
helpers.translate(keys.first, { default: keys.drop(1), **flash_options })
|
141
134
|
end
|
142
135
|
|
143
136
|
end
|
144
137
|
```
|
145
138
|
|
146
|
-
The
|
147
|
-
|
148
|
-
|
149
|
-
rest of the methods can be
|
150
|
-
detailed description of method
|
151
|
-
[
|
139
|
+
The `::model_class` method returns a class corresponding to the
|
140
|
+
controller name, by default. That value can be overridden using the
|
141
|
+
matching `::model_class=` setter. The `model` / `collection` accessor
|
142
|
+
methods are dictated by `::model_class`. The rest of the methods can be
|
143
|
+
overridden as normal, a la carte. For a detailed description of method
|
144
|
+
behavior, see the [API documentation](http://www.rubydoc.info/gems/garden_variety/).
|
152
145
|
(Note that the `authorize`, `policy_scope`, and `permitted_attributes`
|
153
146
|
methods are provided by Pundit.)
|
154
147
|
|
@@ -206,14 +199,23 @@ generator in a few small ways:
|
|
206
199
|
* No jbuilder templates. Only HTML templates are generated.
|
207
200
|
* `rails generate pundit:policy` is invoked for the specified model.
|
208
201
|
|
202
|
+
Additionally, if you are using the [talent_scout] gem, the scaffold
|
203
|
+
generator will invoke `rails generate talent_scout:search` for the
|
204
|
+
specified model. This behavior can be disabled with the `--skip-talent-scout`
|
205
|
+
option. For more information about integrating with *talent_scout*, see
|
206
|
+
the [Searching with talent_scout](#searching-with-talent_scout) section
|
207
|
+
below.
|
208
|
+
|
209
|
+
[talent_scout]: https://rubygems.org/gems/talent_scout
|
210
|
+
|
209
211
|
|
210
212
|
## Flash messages
|
211
213
|
|
212
214
|
Flash messages are defined using I18n. The *garden_variety* installer
|
213
|
-
(`rails generate garden:install`) will create a
|
214
|
-
|
215
|
-
|
216
|
-
|
215
|
+
(`rails generate garden:install`) will create a "config/locales/flash.en.yml"
|
216
|
+
file containing default "success" and "error" messages. You can edit
|
217
|
+
this file to customize those messages, or add your own translation files
|
218
|
+
to support other languages.
|
217
219
|
|
218
220
|
As seen in the `PostsController#flash_message` method in the example
|
219
221
|
above, a prioritized list of keys are tried when retrieving a flash
|
@@ -226,9 +228,8 @@ Internationalization guide for more information.)
|
|
226
228
|
|
227
229
|
Interpolation in flash messages is also supported (as described by
|
228
230
|
[Passing Variables to Translations]), with interpolation values provided
|
229
|
-
by the `flash_options` method. By default, `flash_options` provides
|
230
|
-
`
|
231
|
-
it to provide your own values.
|
231
|
+
by the `flash_options` method. By default, `flash_options` provides a
|
232
|
+
`model_name` value, but you can override it to provide your own values.
|
232
233
|
|
233
234
|
[Safe HTML Translations]: http://guides.rubyonrails.org/i18n.html#using-safe-html-translations
|
234
235
|
[Passing Variables to Translations]: http://guides.rubyonrails.org/i18n.html#passing-variables-to-translations
|
@@ -240,46 +241,103 @@ it to provide your own values.
|
|
240
241
|
written, including in situations where custom code is unavoidable.
|
241
242
|
|
242
243
|
|
243
|
-
###
|
244
|
+
### Eliminating N+1 queries
|
245
|
+
|
246
|
+
With proper Russian doll caching, [N+1 queries can be a feature](
|
247
|
+
https://youtu.be/ktZLpjCanvg?t=4m27s). But, if you need to eliminate
|
248
|
+
an N+1 query by using eager loading, you can override the
|
249
|
+
`find_collection` method:
|
250
|
+
|
251
|
+
```ruby
|
252
|
+
class PostsController < ApplicationController
|
253
|
+
garden_variety
|
254
|
+
|
255
|
+
def find_collection
|
256
|
+
super.includes(:author)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
```
|
260
|
+
|
261
|
+
|
262
|
+
### Pagination
|
244
263
|
|
245
264
|
You can integrate your your favorite pagination gem (*may I suggest
|
246
|
-
[
|
247
|
-
`
|
265
|
+
[moar](https://rubygems.org/gems/moar)?*) by overriding the
|
266
|
+
`find_collection` method:
|
248
267
|
|
249
268
|
```ruby
|
250
269
|
class PostsController < ApplicationController
|
251
270
|
garden_variety
|
252
271
|
|
253
|
-
def
|
254
|
-
|
272
|
+
def find_collection
|
273
|
+
moar(super.order(:created_at))
|
255
274
|
end
|
256
275
|
end
|
257
276
|
```
|
258
277
|
|
259
278
|
|
260
|
-
###
|
279
|
+
### Searching
|
261
280
|
|
262
|
-
You can
|
263
|
-
|
281
|
+
You can provide search functionality by overriding the `find_collection`
|
282
|
+
method:
|
264
283
|
|
265
284
|
```ruby
|
266
285
|
class PostsController < ApplicationController
|
267
286
|
garden_variety
|
268
287
|
|
269
|
-
def
|
270
|
-
params[:
|
288
|
+
def find_collection
|
289
|
+
params[:title] ? super.where("title LIKE ?", "%#{params[:title]}%") : super
|
271
290
|
end
|
272
291
|
end
|
273
292
|
```
|
274
293
|
|
294
|
+
#### Searching with talent_scout
|
295
|
+
|
296
|
+
If you are using the [talent_scout] gem, the default implementation of
|
297
|
+
`find_collection` will automatically instantiate your model search class
|
298
|
+
-- no override required. For example, if a `PostSearch` class is
|
299
|
+
defined, `PostsController#find_collection` will be equivalent to:
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
def find_collection
|
303
|
+
@search = PostSearch.new(params[:q])
|
304
|
+
@search.results
|
305
|
+
end
|
306
|
+
```
|
307
|
+
|
308
|
+
Notice, as a side effect, the `@search` variable is set for later use in
|
309
|
+
the view. The model search class will be chosen based on the
|
310
|
+
controller's `::model_class`. For example:
|
311
|
+
|
312
|
+
```ruby
|
313
|
+
class MyPostsController < ApplicationController
|
314
|
+
garden_variety
|
315
|
+
|
316
|
+
self.model_class = Post # find_collection will use PostSearch instead of MyPostSearch
|
317
|
+
end
|
318
|
+
```
|
319
|
+
|
320
|
+
If a corresponding model search class is not defined, `find_collection`
|
321
|
+
will fall back to its original non-search behavior.
|
322
|
+
|
323
|
+
Alternatively, you can override the model search class directly:
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
class MyPostsController < ApplicationController
|
327
|
+
garden_variety
|
328
|
+
|
329
|
+
self.model_class = Post
|
330
|
+
self.model_search_class = MyPostSearch # find_collection will use MyPostSearch
|
331
|
+
end
|
332
|
+
```
|
333
|
+
|
275
334
|
|
276
|
-
###
|
335
|
+
### Server-generated JavaScript Responses (SJR)
|
277
336
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
on-success behavior of non-GET actions:
|
337
|
+
SJR controller actions are generally the same as conventional controller
|
338
|
+
actions, with one difference: non-GET SJR actions render instead of
|
339
|
+
redirect on success. *garden_variety* provides a concise syntax for
|
340
|
+
overriding only the on-success behavior of non-GET actions:
|
283
341
|
|
284
342
|
```ruby
|
285
343
|
class PostsController < ApplicationController
|
@@ -323,7 +381,7 @@ messages. If a redirect is replaced with a render, the flash message
|
|
323
381
|
will be set such that it does not affect the next request.
|
324
382
|
|
325
383
|
|
326
|
-
###
|
384
|
+
### Authentication
|
327
385
|
|
328
386
|
The details of integrating authentication will depend on your chosen
|
329
387
|
authentication library. [Devise](https://rubygems.org/gems/devise) is
|
@@ -359,7 +417,7 @@ this conflict, add the following line to your Clearance initializer:
|
|
359
417
|
```
|
360
418
|
|
361
419
|
|
362
|
-
###
|
420
|
+
### Form Objects
|
363
421
|
|
364
422
|
The Form Object pattern is used to mitigate the complexity of handling
|
365
423
|
forms which need special processing logic, such as context-dependent
|
@@ -430,20 +488,21 @@ end
|
|
430
488
|
|
431
489
|
```ruby
|
432
490
|
class PublishedPostsController < ApplicationController
|
433
|
-
|
491
|
+
self.model_class = Post
|
492
|
+
garden_variety :index
|
434
493
|
|
435
|
-
def
|
494
|
+
def find_collection
|
436
495
|
super.where(published: true)
|
437
496
|
end
|
438
497
|
end
|
439
498
|
```
|
440
499
|
|
441
|
-
|
442
|
-
|
443
|
-
`
|
444
|
-
|
500
|
+
Notice the call to `::model_class=`. The model class for
|
501
|
+
`PublishedPostsController` is overridden as `Post` instead of derived as
|
502
|
+
`PublishedPost`. And because of this override, the `@posts` instance
|
503
|
+
variable will be used instead of `@published_posts`.
|
445
504
|
|
446
|
-
This example may be somewhat contrived, but
|
505
|
+
This example may be somewhat contrived, but here is an excellent talk
|
447
506
|
from RailsConf which delves deeper into the principle:
|
448
507
|
[In Relentless Pursuit of REST](https://www.youtube.com/watch?v=HctYHe-YjnE)
|
449
508
|
([slides](https://speakerdeck.com/derekprior/in-relentless-pursuit-of-rest)).
|
@@ -463,7 +522,7 @@ Then execute:
|
|
463
522
|
$ bundle install
|
464
523
|
```
|
465
524
|
|
466
|
-
And finally, run the
|
525
|
+
And finally, run the install generator:
|
467
526
|
|
468
527
|
```bash
|
469
528
|
$ rails generate garden:install
|
data/Rakefile
CHANGED
@@ -9,21 +9,14 @@ require 'yard'
|
|
9
9
|
YARD::Rake::YardocTask.new(:doc) do |t|
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
12
|
require 'bundler/gem_tasks'
|
19
13
|
|
20
14
|
require 'rake/testtask'
|
21
15
|
|
22
16
|
Rake::TestTask.new(:test) do |t|
|
23
17
|
t.libs << 'test'
|
24
|
-
t.
|
18
|
+
t.test_files = FileList['test/**/*_test.rb'].exclude('test/tmp/**/*')
|
25
19
|
t.verbose = false
|
26
20
|
end
|
27
21
|
|
28
|
-
|
29
22
|
task default: :test
|
data/lib/garden_variety.rb
CHANGED
@@ -1,2 +1,15 @@
|
|
1
|
+
require "pundit"
|
1
2
|
require "garden_variety/version"
|
3
|
+
require "garden_variety/actions"
|
4
|
+
require "garden_variety/controller"
|
5
|
+
require "garden_variety/current_user_stub"
|
6
|
+
|
7
|
+
begin
|
8
|
+
require "talent_scout"
|
9
|
+
rescue LoadError
|
10
|
+
# do nothing
|
11
|
+
else
|
12
|
+
require "garden_variety/talent_scout"
|
13
|
+
end
|
14
|
+
|
2
15
|
require "garden_variety/railtie"
|
@@ -6,8 +6,8 @@ module GardenVariety
|
|
6
6
|
# Garden variety controller +index+ action.
|
7
7
|
# @return [void]
|
8
8
|
def index
|
9
|
-
authorize(
|
10
|
-
self.
|
9
|
+
authorize(self.class.model_class)
|
10
|
+
self.collection = policy_scope(find_collection)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -15,8 +15,7 @@ module GardenVariety
|
|
15
15
|
# Garden variety controller +show+ action.
|
16
16
|
# @return [void]
|
17
17
|
def show
|
18
|
-
self.
|
19
|
-
authorize(resource)
|
18
|
+
self.model = authorize(find_model)
|
20
19
|
end
|
21
20
|
end
|
22
21
|
|
@@ -24,12 +23,8 @@ module GardenVariety
|
|
24
23
|
# Garden variety controller +new+ action.
|
25
24
|
# @return [void]
|
26
25
|
def new
|
27
|
-
|
28
|
-
|
29
|
-
else
|
30
|
-
self.resource = new_resource
|
31
|
-
authorize(resource)
|
32
|
-
end
|
26
|
+
self.model = (model = authorize(new_model))
|
27
|
+
assign_attributes(model) if params.key?(self.class.model_name.param_key)
|
33
28
|
end
|
34
29
|
end
|
35
30
|
|
@@ -40,10 +35,10 @@ module GardenVariety
|
|
40
35
|
# @yield on-success callback, replaces default redirect
|
41
36
|
# @return [void]
|
42
37
|
def create
|
43
|
-
self.
|
44
|
-
if
|
38
|
+
self.model = (model = assign_attributes(authorize(new_model)))
|
39
|
+
if model.save
|
45
40
|
flash[:success] = flash_message(:success)
|
46
|
-
block_given? ? yield : redirect_to(
|
41
|
+
block_given? ? yield : redirect_to(model)
|
47
42
|
flash.discard(:success) if REDIRECT_CODES.exclude?(response.status)
|
48
43
|
else
|
49
44
|
flash.now[:error] = flash_message(:error)
|
@@ -56,8 +51,7 @@ module GardenVariety
|
|
56
51
|
# Garden variety controller +edit+ action.
|
57
52
|
# @return [void]
|
58
53
|
def edit
|
59
|
-
self.
|
60
|
-
authorize(resource)
|
54
|
+
self.model = authorize(find_model)
|
61
55
|
end
|
62
56
|
end
|
63
57
|
|
@@ -68,10 +62,10 @@ module GardenVariety
|
|
68
62
|
# @yield on-success callback, replaces default redirect
|
69
63
|
# @return [void]
|
70
64
|
def update
|
71
|
-
self.
|
72
|
-
if
|
65
|
+
self.model = (model = assign_attributes(authorize(find_model)))
|
66
|
+
if model.save
|
73
67
|
flash[:success] = flash_message(:success)
|
74
|
-
block_given? ? yield : redirect_to(
|
68
|
+
block_given? ? yield : redirect_to(model)
|
75
69
|
flash.discard(:success) if REDIRECT_CODES.exclude?(response.status)
|
76
70
|
else
|
77
71
|
flash.now[:error] = flash_message(:error)
|
@@ -87,9 +81,8 @@ module GardenVariety
|
|
87
81
|
# @yield on-success callback, replaces default redirect
|
88
82
|
# @return [void]
|
89
83
|
def destroy
|
90
|
-
self.
|
91
|
-
|
92
|
-
if resource.destroy
|
84
|
+
self.model = (model = authorize(find_model))
|
85
|
+
if model.destroy
|
93
86
|
flash[:success] = flash_message(:success)
|
94
87
|
block_given? ? yield : redirect_to(action: :index)
|
95
88
|
flash.discard(:success) if REDIRECT_CODES.exclude?(response.status)
|
@@ -1,6 +1,3 @@
|
|
1
|
-
require "pundit"
|
2
|
-
require "garden_variety/actions"
|
3
|
-
|
4
1
|
module GardenVariety
|
5
2
|
module Controller
|
6
3
|
extend ActiveSupport::Concern
|
@@ -13,15 +10,14 @@ module GardenVariety
|
|
13
10
|
# typical REST actions (index, show, new, create, edit, update,
|
14
11
|
# destroy) are included.
|
15
12
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# instance variables dictated by the +resources:+ parameter.
|
13
|
+
# See also:
|
14
|
+
# - {GardenVariety::IndexAction}
|
15
|
+
# - {GardenVariety::ShowAction}
|
16
|
+
# - {GardenVariety::NewAction}
|
17
|
+
# - {GardenVariety::CreateAction}
|
18
|
+
# - {GardenVariety::EditAction}
|
19
|
+
# - {GardenVariety::UpdateAction}
|
20
|
+
# - {GardenVariety::DestroyAction}
|
25
21
|
#
|
26
22
|
# @example default usage
|
27
23
|
# # This...
|
@@ -38,92 +34,30 @@ module GardenVariety
|
|
38
34
|
# include GardenVariety::EditAction
|
39
35
|
# include GardenVariety::UpdateAction
|
40
36
|
# include GardenVariety::DestroyAction
|
41
|
-
#
|
42
|
-
# private
|
43
|
-
#
|
44
|
-
# def resource_class
|
45
|
-
# Post
|
46
|
-
# end
|
47
|
-
#
|
48
|
-
# def resources
|
49
|
-
# @posts
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# def resources=(models)
|
53
|
-
# @posts = models
|
54
|
-
# end
|
55
|
-
#
|
56
|
-
# def resource
|
57
|
-
# @post
|
58
|
-
# end
|
59
|
-
#
|
60
|
-
# def resource=(model)
|
61
|
-
# @post = model
|
62
|
-
# end
|
63
37
|
# end
|
64
38
|
#
|
65
|
-
# @example
|
39
|
+
# @example specific usage
|
66
40
|
# # This...
|
67
|
-
# class
|
68
|
-
# garden_variety :index,
|
41
|
+
# class PostsController < ApplicationController
|
42
|
+
# garden_variety :index, :show
|
69
43
|
# end
|
70
44
|
#
|
71
45
|
# # ...is equivalent to:
|
72
|
-
# class
|
46
|
+
# class PostsController < ApplicationController
|
73
47
|
# include GardenVariety::IndexAction
|
74
|
-
#
|
75
|
-
# private
|
76
|
-
#
|
77
|
-
# def resource_class
|
78
|
-
# Location
|
79
|
-
# end
|
80
|
-
#
|
81
|
-
# def resources
|
82
|
-
# @locations
|
83
|
-
# end
|
84
|
-
#
|
85
|
-
# def resources=(models)
|
86
|
-
# @locations = models
|
87
|
-
# end
|
88
|
-
#
|
89
|
-
# def resource
|
90
|
-
# @location
|
91
|
-
# end
|
92
|
-
#
|
93
|
-
# def resource=(model)
|
94
|
-
# @location = model
|
95
|
-
# end
|
48
|
+
# include GardenVariety::ShowAction
|
96
49
|
# end
|
97
50
|
#
|
98
51
|
# @param actions [Array<:index, :show, :new, :create, :edit, :update, :destroy>]
|
99
|
-
# @param resources [Symbol, String]
|
100
52
|
# @return [void]
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
def resource_class # optimized override
|
108
|
-
#{resources.to_s.classify}
|
53
|
+
# @raise [ArgumentError]
|
54
|
+
# if an invalid action is specified
|
55
|
+
def garden_variety(*actions)
|
56
|
+
actions.each do |action|
|
57
|
+
unless ::GardenVariety::ACTION_MODULES.key?(action)
|
58
|
+
raise ArgumentError, "Invalid action: #{action.inspect}"
|
109
59
|
end
|
110
|
-
|
111
|
-
def resources
|
112
|
-
@#{resources_attr}
|
113
|
-
end
|
114
|
-
|
115
|
-
def resources=(models)
|
116
|
-
@#{resources_attr} = models
|
117
|
-
end
|
118
|
-
|
119
|
-
def resource
|
120
|
-
@#{resources_attr.singularize}
|
121
|
-
end
|
122
|
-
|
123
|
-
def resource=(model)
|
124
|
-
@#{resources_attr.singularize} = model
|
125
|
-
end
|
126
|
-
CODE
|
60
|
+
end
|
127
61
|
|
128
62
|
action_modules = actions.empty? ?
|
129
63
|
::GardenVariety::ACTION_MODULES.values :
|
@@ -131,38 +65,136 @@ module GardenVariety
|
|
131
65
|
|
132
66
|
action_modules.each{|m| include m }
|
133
67
|
end
|
134
|
-
end
|
135
68
|
|
69
|
+
# Returns the controller model class. Defaults to a class
|
70
|
+
# corresponding to the singular-form of the controller name.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# class PostsController < ApplicationController
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# PostsController.model_class # == Post (class)
|
77
|
+
#
|
78
|
+
# @return [Class]
|
79
|
+
def model_class
|
80
|
+
@model_class ||= controller_path.classify.constantize
|
81
|
+
end
|
82
|
+
|
83
|
+
# Sets the controller model class.
|
84
|
+
#
|
85
|
+
# @example
|
86
|
+
# class PublishedPostsController < ApplicationController
|
87
|
+
# self.model_class = Post
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# @param klass [Class]
|
91
|
+
# @return [klass]
|
92
|
+
def model_class=(klass)
|
93
|
+
@model_name = nil
|
94
|
+
@model_class = klass
|
95
|
+
end
|
96
|
+
|
97
|
+
# @!visibility private
|
98
|
+
def model_name
|
99
|
+
@model_name ||= model_class.try(:model_name) || ActiveModel::Name.new(model_class)
|
100
|
+
end
|
101
|
+
end
|
136
102
|
|
137
103
|
private
|
138
104
|
|
139
105
|
# @!visibility public
|
140
|
-
# Returns the
|
141
|
-
#
|
106
|
+
# Returns the value of the singular-form instance variable dictated
|
107
|
+
# by {::model_class}.
|
142
108
|
#
|
143
109
|
# @example
|
144
|
-
# PostsController
|
110
|
+
# class PostsController
|
111
|
+
# def show
|
112
|
+
# # This...
|
113
|
+
# self.model
|
114
|
+
# # ...is equivalent to:
|
115
|
+
# @post
|
116
|
+
# end
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# @return [Object]
|
120
|
+
def model
|
121
|
+
instance_variable_get(:"@#{self.class.model_name.singular}")
|
122
|
+
end
|
123
|
+
|
124
|
+
# @!visibility public
|
125
|
+
# Sets the value of the singular-form instance variable dictated
|
126
|
+
# by {::model_class}.
|
127
|
+
#
|
128
|
+
# @example
|
129
|
+
# class PostsController
|
130
|
+
# def show
|
131
|
+
# # This...
|
132
|
+
# self.model = value
|
133
|
+
# # ...is equivalent to:
|
134
|
+
# @post = value
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# @param value [Object]
|
139
|
+
# @return [value]
|
140
|
+
def model=(value)
|
141
|
+
instance_variable_set(:"@#{self.class.model_name.singular}", value)
|
142
|
+
end
|
143
|
+
|
144
|
+
# @!visibility public
|
145
|
+
# Returns the value of the plural-form instance variable dictated
|
146
|
+
# by {::model_class}.
|
147
|
+
#
|
148
|
+
# @example
|
149
|
+
# class PostsController
|
150
|
+
# def index
|
151
|
+
# # This...
|
152
|
+
# self.collection
|
153
|
+
# # ...is equivalent to:
|
154
|
+
# @posts
|
155
|
+
# end
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
# @return [Object]
|
159
|
+
def collection
|
160
|
+
instance_variable_get(:"@#{self.class.model_name.plural}")
|
161
|
+
end
|
162
|
+
|
163
|
+
# @!visibility public
|
164
|
+
# Sets the value of the plural-form instance variable dictated
|
165
|
+
# by {::model_class}.
|
145
166
|
#
|
146
|
-
# @
|
147
|
-
|
148
|
-
|
167
|
+
# @example
|
168
|
+
# class PostsController
|
169
|
+
# def index
|
170
|
+
# # This...
|
171
|
+
# self.collection = values
|
172
|
+
# # ...is equivalent to:
|
173
|
+
# @posts = values
|
174
|
+
# end
|
175
|
+
# end
|
176
|
+
#
|
177
|
+
# @param values [Object]
|
178
|
+
# @return [values]
|
179
|
+
def collection=(values)
|
180
|
+
instance_variable_set(:"@#{self.class.model_name.plural}", values)
|
149
181
|
end
|
150
182
|
|
151
183
|
# @!visibility public
|
152
|
-
# Returns an ActiveRecord::Relation representing
|
184
|
+
# Returns an ActiveRecord::Relation representing model instances
|
153
185
|
# corresponding to the controller. Designed for use in generic
|
154
186
|
# +index+ action methods.
|
155
187
|
#
|
156
188
|
# @example
|
157
189
|
# class PostsController < ApplicationController
|
158
190
|
# def index
|
159
|
-
# @posts =
|
191
|
+
# @posts = find_collection.where(status: "published")
|
160
192
|
# end
|
161
193
|
# end
|
162
194
|
#
|
163
195
|
# @return [ActiveRecord::Relation]
|
164
|
-
def
|
165
|
-
|
196
|
+
def find_collection
|
197
|
+
self.class.model_class.all
|
166
198
|
end
|
167
199
|
|
168
200
|
# @!visibility public
|
@@ -174,13 +206,13 @@ module GardenVariety
|
|
174
206
|
# @example
|
175
207
|
# class PostsController < ApplicationController
|
176
208
|
# def show
|
177
|
-
# @post =
|
209
|
+
# @post = find_model
|
178
210
|
# end
|
179
211
|
# end
|
180
212
|
#
|
181
213
|
# @return [ActiveRecord::Base]
|
182
|
-
def
|
183
|
-
|
214
|
+
def find_model
|
215
|
+
self.class.model_class.find(params[:id])
|
184
216
|
end
|
185
217
|
|
186
218
|
# @!visibility public
|
@@ -190,25 +222,24 @@ module GardenVariety
|
|
190
222
|
# @example
|
191
223
|
# class PostsController < ApplicationController
|
192
224
|
# def new
|
193
|
-
# @post =
|
225
|
+
# @post = new_model
|
194
226
|
# end
|
195
227
|
# end
|
196
228
|
#
|
197
229
|
# @return [ActiveRecord::Base]
|
198
|
-
def
|
199
|
-
|
230
|
+
def new_model
|
231
|
+
self.class.model_class.new
|
200
232
|
end
|
201
233
|
|
202
234
|
# @!visibility public
|
203
|
-
#
|
204
|
-
#
|
205
|
-
# request params permitted by the model policy. Returns the given
|
235
|
+
# Populates the given model's attributes with the current request
|
236
|
+
# params permitted by the model's Pundit policy. Returns the given
|
206
237
|
# model modified but not persisted.
|
207
238
|
#
|
208
239
|
# @example
|
209
240
|
# class PostsController < ApplicationController
|
210
241
|
# def create
|
211
|
-
# @post =
|
242
|
+
# @post = assign_attributes(authorize(Post.new))
|
212
243
|
# if @post.save
|
213
244
|
# redirect_to @post
|
214
245
|
# else
|
@@ -219,16 +250,15 @@ module GardenVariety
|
|
219
250
|
#
|
220
251
|
# @param model [ActiveRecord::Base]
|
221
252
|
# @return [ActiveRecord::Base]
|
222
|
-
def
|
223
|
-
authorize(model)
|
253
|
+
def assign_attributes(model)
|
224
254
|
model.assign_attributes(permitted_attributes(model))
|
225
255
|
model
|
226
256
|
end
|
227
257
|
|
228
258
|
# @!visibility public
|
229
259
|
# Returns Hash of values for interpolation in flash messages via
|
230
|
-
# I18n. By default, returns +
|
231
|
-
#
|
260
|
+
# I18n. By default, returns a +model_name+ key / value pair based
|
261
|
+
# on the controller's {Controller::ClassMethods#model_name}.
|
232
262
|
# Override this method to provide your own values. Be aware that
|
233
263
|
# certain option names, such as +default+ and +scope+, are reserved
|
234
264
|
# by the I18n gem, and can not be used for interpolation. See the
|
@@ -237,8 +267,7 @@ module GardenVariety
|
|
237
267
|
#
|
238
268
|
# @return [Hash]
|
239
269
|
def flash_options
|
240
|
-
{
|
241
|
-
resource_capitalized: resource_class.model_name.human }
|
270
|
+
{ model_name: self.class.model_name.human }
|
242
271
|
end
|
243
272
|
|
244
273
|
# @!visibility public
|
@@ -265,12 +294,16 @@ module GardenVariety
|
|
265
294
|
# # en:
|
266
295
|
# # success: "Success!"
|
267
296
|
# # create:
|
268
|
-
# # success: "%{
|
297
|
+
# # success: "%{model_name} created."
|
269
298
|
# # delete:
|
270
|
-
# # success: "%{
|
299
|
+
# # success: "%{model_name} deleted."
|
271
300
|
# # posts:
|
272
301
|
# # create:
|
273
302
|
# # success: "Congratulations on your new post!"
|
303
|
+
# # messages:
|
304
|
+
# # drafts:
|
305
|
+
# # update:
|
306
|
+
# # success: "Draft saved."
|
274
307
|
#
|
275
308
|
# # via PostsController#create
|
276
309
|
# flash_message(:success) # == "Congratulations on your new post!"
|
@@ -281,21 +314,6 @@ module GardenVariety
|
|
281
314
|
# # via PostsController#delete
|
282
315
|
# flash_message(:success) # == "Post deleted."
|
283
316
|
#
|
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
317
|
# # via Messages::DraftsController#update
|
300
318
|
# flash_message(:success) # == "Draft saved."
|
301
319
|
#
|
@@ -304,14 +322,14 @@ module GardenVariety
|
|
304
322
|
def flash_message(status)
|
305
323
|
controller_key = controller_path.tr("/", I18n.default_separator)
|
306
324
|
keys = [
|
307
|
-
:"
|
308
|
-
:"
|
309
|
-
:"
|
310
|
-
:"
|
311
|
-
:"
|
312
|
-
:"
|
325
|
+
:"flash.#{controller_key}.#{action_name}.#{status}",
|
326
|
+
:"flash.#{controller_key}.#{action_name}.#{status}_html",
|
327
|
+
:"flash.#{action_name}.#{status}",
|
328
|
+
:"flash.#{action_name}.#{status}_html",
|
329
|
+
:"flash.#{status}",
|
330
|
+
:"flash.#{status}_html",
|
313
331
|
]
|
314
|
-
helpers.translate(keys.shift, default: keys
|
332
|
+
helpers.translate(keys.shift, { default: keys }.merge!(flash_options))
|
315
333
|
end
|
316
334
|
end
|
317
335
|
end
|
@@ -1,10 +1,6 @@
|
|
1
|
-
require "rails/railtie"
|
2
|
-
require "garden_variety/controller"
|
3
|
-
require "garden_variety/current_user_stub"
|
4
|
-
|
5
1
|
module GardenVariety
|
6
2
|
# @!visibility private
|
7
|
-
class Railtie < Rails::Railtie
|
3
|
+
class Railtie < ::Rails::Railtie
|
8
4
|
# Render 404 on Pundit::NotAuthorizedError in production. (Helpful
|
9
5
|
# error pages will still be shown in development.) Code 404 is used
|
10
6
|
# because it is more discreet than 403, because it is explicitly
|
@@ -12,17 +8,10 @@ module GardenVariety
|
|
12
8
|
# and because Rails includes a default 404 page, but not a 403 page.
|
13
9
|
config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] ||= :not_found
|
14
10
|
|
15
|
-
initializer "garden_variety
|
16
|
-
ActiveSupport.on_load :action_controller do
|
17
|
-
unless ActionController::Base.instance_methods.include?(:current_user)
|
18
|
-
ActionController::Base.send :include, GardenVariety::CurrentUserStub
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
initializer "garden_variety.extend_action_controller" do |app|
|
11
|
+
initializer "garden_variety" do |app|
|
24
12
|
ActiveSupport.on_load :action_controller do
|
25
|
-
|
13
|
+
include GardenVariety::CurrentUserStub unless instance_methods.include?(:current_user)
|
14
|
+
include GardenVariety::Controller
|
26
15
|
end
|
27
16
|
end
|
28
17
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module GardenVariety
|
2
|
+
# @!visibility private
|
3
|
+
module TalentScout
|
4
|
+
|
5
|
+
module ModelSearchClassOverride
|
6
|
+
def model_search_class
|
7
|
+
@model_search_class ||= "#{model_class}Search".constantize
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
::TalentScout::Controller::ClassMethods.prepend(ModelSearchClassOverride)
|
12
|
+
|
13
|
+
module FindCollectionOverride
|
14
|
+
private
|
15
|
+
def find_collection
|
16
|
+
if self.class.model_search_class?
|
17
|
+
@search = model_search
|
18
|
+
@search.results.all
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
::GardenVariety::Controller.prepend(FindCollectionOverride)
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
en:
|
2
|
+
flash:
|
3
|
+
create:
|
4
|
+
success: '%{model_name} was successfully created.'
|
5
|
+
error: '%{model_name} could not be created.'
|
6
|
+
update:
|
7
|
+
success: '%{model_name} was successfully updated.'
|
8
|
+
error: '%{model_name} could not be updated.'
|
9
|
+
destroy:
|
10
|
+
success: '%{model_name} was successfully destroyed.'
|
11
|
+
error: '%{model_name} could not be destroyed.'
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require "rails/generators/base"
|
2
|
-
|
3
1
|
# @!visibility private
|
4
2
|
module Garden
|
5
3
|
module Generators
|
@@ -13,6 +11,9 @@ module Garden
|
|
13
11
|
# inherited from Rails::Generators::Base
|
14
12
|
class_option :template_engine
|
15
13
|
|
14
|
+
class_option :talent_scout, type: :boolean, default: true,
|
15
|
+
description: "Invoke talent_scout:search generator"
|
16
|
+
|
16
17
|
# override +initialize+ because it is the only way to reliably
|
17
18
|
# capture the raw input arguments in order to pass them on to
|
18
19
|
# `rails generate resource` (Thor neglects to provide an accessor,
|
@@ -40,6 +41,12 @@ module Garden
|
|
40
41
|
def generate_pundit_policy
|
41
42
|
generate("pundit:policy", resource)
|
42
43
|
end
|
44
|
+
|
45
|
+
def generate_talent_scout_search
|
46
|
+
if defined?(::TalentScout) && options.talent_scout?
|
47
|
+
generate("talent_scout:search", resource)
|
48
|
+
end
|
49
|
+
end
|
43
50
|
end
|
44
51
|
end
|
45
52
|
end
|
metadata
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: garden_variety
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Hefner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-03-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '5.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '5.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: sqlite3
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0.9'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: talent_scout
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
description:
|
70
84
|
email:
|
71
85
|
- jonathan.hefner@gmail.com
|
@@ -81,9 +95,10 @@ files:
|
|
81
95
|
- lib/garden_variety/controller.rb
|
82
96
|
- lib/garden_variety/current_user_stub.rb
|
83
97
|
- lib/garden_variety/railtie.rb
|
98
|
+
- lib/garden_variety/talent_scout.rb
|
84
99
|
- lib/garden_variety/version.rb
|
85
100
|
- lib/generators/garden/install/install_generator.rb
|
86
|
-
- lib/generators/garden/install/templates/locales/
|
101
|
+
- lib/generators/garden/install/templates/locales/flash.en.yml
|
87
102
|
- lib/generators/garden/scaffold/scaffold_generator.rb
|
88
103
|
homepage: https://github.com/jonathanhefner/garden_variety
|
89
104
|
licenses:
|
@@ -104,8 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
119
|
- !ruby/object:Gem::Version
|
105
120
|
version: '0'
|
106
121
|
requirements: []
|
107
|
-
|
108
|
-
rubygems_version: 2.5.2.1
|
122
|
+
rubygems_version: 3.0.1
|
109
123
|
signing_key:
|
110
124
|
specification_version: 4
|
111
125
|
summary: Delightfully boring Rails controllers
|
@@ -1,10 +0,0 @@
|
|
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}.'
|