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 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}.'