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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b80f333fab73447d743d99ef4cd170a997011a8e
4
- data.tar.gz: ab23ee38fd1a658f8fa7a82eeac8708110346bb4
2
+ SHA256:
3
+ metadata.gz: 505860db6877118744ce3b7f1b4de589c12e0adf8687ab8cadc518fec0a52b37
4
+ data.tar.gz: a4b18a197a210123c5db5f72c1510e1a4ea75e843c543af98635a842bc04cea3
5
5
  SHA512:
6
- metadata.gz: 1a36d05543168c2739e1b2677aa4b870c534ca4a5fc340a80ae80f18e920b238c59017179cd16e5824f2bce62ac36db3ca73a4c80f98a5e8f61381de597ef336
7
- data.tar.gz: a93377a1e7c252f6ad547febe70bdaedc83a62383819e04766bda20b604f5db055571d17fa7d56b5a4680eccb0d124bf54d5171787a5a1858cdb472b80cbc58f
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 uses the excellent
11
- [Pundit](https://rubygems.org/gems/pundit) gem to isolate authorization
12
- concerns. If you're unfamiliar with Pundit, see its documentation for
13
- an explanation of policy objects and how they help controller actions
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(resource_class)
31
- self.resources = policy_scope(list_resources)
29
+ authorize(self.class.model_class)
30
+ self.collection = policy_scope(find_collection)
32
31
  end
33
32
 
34
33
  def show
35
- self.resource = find_resource
36
- authorize(resource)
34
+ self.model = authorize(find_model)
37
35
  end
38
36
 
39
37
  def new
40
- if params.key?(resource_class.model_name.param_key)
41
- self.resource = vest(new_resource)
42
- else
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.resource = vest(new_resource)
50
- if resource.save
45
+ self.model = assign_attributes(authorize(new_model))
46
+ if model.save
51
47
  flash[:success] = flash_message(:success)
52
- redirect_to resource
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.resource = find_resource
61
- authorize(resource)
56
+ self.model = authorize(find_model)
62
57
  end
63
58
 
64
59
  def update
65
- self.resource = vest(find_resource)
66
- if resource.save
60
+ self.model = assign_attributes(authorize(find_model))
61
+ if model.save
67
62
  flash[:success] = flash_message(:success)
68
- redirect_to resource
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.resource = find_resource
77
- authorize(resource)
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 resource_class
83
+ def self.model_class
90
84
  Post
91
85
  end
92
86
 
93
- def resources
87
+ def collection
94
88
  @posts
95
89
  end
96
90
 
97
- def resources=(models)
91
+ def collection=(models)
98
92
  @posts = models
99
93
  end
100
94
 
101
- def resource
95
+ def model
102
96
  @post
103
97
  end
104
98
 
105
- def resource=(model)
99
+ def model=(model)
106
100
  @post = model
107
101
  end
108
102
 
109
- def list_resources
110
- resource_class.all
103
+ def find_collection
104
+ self.class.model_class.all
111
105
  end
112
106
 
113
- def find_resource
114
- resource_class.find(params[:id])
107
+ def find_model
108
+ self.class.model_class.find(params[:id])
115
109
  end
116
110
 
117
- def new_resource
118
- resource_class.new
111
+ def new_model
112
+ self.class.model_class.new
119
113
  end
120
114
 
121
- def vest(model)
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
- { resource_name: "post", resource_capitalized: "Post" }
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
- :"#{action_name}.#{status}",
136
- :"#{action_name}.#{status}_html",
137
- :"#{status}",
138
- :"#{status}_html",
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 implementations of the `resource_class` and `resource` / `resources`
147
- accessor methods are generated based on the controller name. They can
148
- be altered with an optional argument to the `garden_variety` macro. The
149
- rest of the methods can be overridden as normal, a la carte. For a
150
- detailed description of method behavior, see the
151
- [full documentation](http://www.rubydoc.info/gems/garden_variety/).
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
- "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.
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
- `resource_name` and `resource_capitalized` values, but you can override
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
- ### Integrating with pagination
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
- [foliate](https://rubygems.org/gems/foliate)?*) by overriding the
247
- `list_resources` method:
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 list_resources
254
- paginate(super)
272
+ def find_collection
273
+ moar(super.order(:created_at))
255
274
  end
256
275
  end
257
276
  ```
258
277
 
259
278
 
260
- ### Integrating with search
279
+ ### Searching
261
280
 
262
- You can also integrate search functionality by overriding the
263
- `list_resources` method:
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 list_resources
270
- params[:author] ? super.where(author: params[:author]) : super
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
- ### Integrating with SJR
335
+ ### Server-generated JavaScript Responses (SJR)
277
336
 
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:
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
- ### Integrating with authentication
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
- ### Integrating with Form Objects
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
- garden_variety :index, resources: :posts
491
+ self.model_class = Post
492
+ garden_variety :index
434
493
 
435
- def list_resources
494
+ def find_collection
436
495
  super.where(published: true)
437
496
  end
438
497
  end
439
498
  ```
440
499
 
441
- Note the `resources:` argument to the `garden_variety` macro. The
442
- resource class for `PublishedPostsController` will be overridden as
443
- `Post` instead of derived as `PublishedPost`. Likewise, the `@posts`
444
- instance variable will be used instead of `@published_posts`.
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 there is an excellent talk
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 *garden_variety* install generator:
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.pattern = 'test/**/*_test.rb'
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
@@ -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(resource_class)
10
- self.resources = policy_scope(list_resources)
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.resource = find_resource
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
- if params.key?(resource_class.model_name.param_key)
28
- self.resource = vest(new_resource)
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.resource = vest(new_resource)
44
- if resource.save
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(resource)
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.resource = find_resource
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.resource = vest(find_resource)
72
- if resource.save
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(resource)
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.resource = find_resource
91
- authorize(resource)
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
- # The optional +resources:+ parameter dictates which model class
17
- # and instance variables these actions use. The parameter's
18
- # default value derives from the controller name. The value must
19
- # be a resource name in plural form.
20
- #
21
- # The macro also defines the following accessor methods for use in
22
- # generic action and helper methods: +resources+, +resources=+,
23
- # +resource+, and +resource=+. These accessors get and set the
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 custom usage
39
+ # @example specific usage
66
40
  # # This...
67
- # class CountriesController < ApplicationController
68
- # garden_variety :index, resources: :locations
41
+ # class PostsController < ApplicationController
42
+ # garden_variety :index, :show
69
43
  # end
70
44
  #
71
45
  # # ...is equivalent to:
72
- # class CountriesController < ApplicationController
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
- def garden_variety(*actions, resources: controller_path)
102
- resources_attr = resources.to_s.underscore.tr("/", "_")
103
-
104
- class_eval <<-CODE
105
- private
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 class of the resource corresponding to the controller
141
- # name.
106
+ # Returns the value of the singular-form instance variable dictated
107
+ # by {::model_class}.
142
108
  #
143
109
  # @example
144
- # PostsController.new.resource_class # == Post (class)
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
- # @return [Class]
147
- def resource_class
148
- @resource_class ||= controller_path.classify.constantize
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 resource instances
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 = list_resources.where(status: "published")
191
+ # @posts = find_collection.where(status: "published")
160
192
  # end
161
193
  # end
162
194
  #
163
195
  # @return [ActiveRecord::Relation]
164
- def list_resources
165
- resource_class.all
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 = find_resource
209
+ # @post = find_model
178
210
  # end
179
211
  # end
180
212
  #
181
213
  # @return [ActiveRecord::Base]
182
- def find_resource
183
- resource_class.find(params[:id])
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 = new_resource
225
+ # @post = new_model
194
226
  # end
195
227
  # end
196
228
  #
197
229
  # @return [ActiveRecord::Base]
198
- def new_resource
199
- resource_class.new
230
+ def new_model
231
+ self.class.model_class.new
200
232
  end
201
233
 
202
234
  # @!visibility public
203
- # Authorizes the given model for the current action via the model
204
- # Pundit policy, and populates the model attributes with the current
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 = vest(Post.new)
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 vest(model)
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 +resource_name+ and
231
- # +resource_capitalized+ values appropriate to the controller.
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
- { resource_name: resource_class.model_name.human.downcase,
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: "%{resource_capitalized} created."
297
+ # # success: "%{model_name} created."
269
298
  # # delete:
270
- # # success: "%{resource_capitalized} deleted."
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
- :"#{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",
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, **flash_options)
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.stub_current_user" do |app|
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
- ActionController::Base.send :include, GardenVariety::Controller
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
@@ -1,3 +1,3 @@
1
1
  module GardenVariety
2
- VERSION = "2.0"
2
+ VERSION = "3.0.0"
3
3
  end
@@ -1,5 +1,3 @@
1
- require "rails/generators/base"
2
-
3
1
  # @!visibility private
4
2
  module Garden
5
3
  module Generators
@@ -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: '2.0'
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: 2018-06-17 00:00:00.000000000 Z
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: '1.1'
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: '1.1'
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/garden_variety.en.yml
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
- rubyforge_project:
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}.'