garden_variety 2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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}.'
|