rolemodel-rails 2.3.1 → 2.4.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
2
  SHA256:
3
- metadata.gz: c9809271c3cc1c97337717b8d1d79b934a215d343f9b62e4cd6be25d9a906a39
4
- data.tar.gz: 7e6493f9b61aaa33adadc0957ca25e897ff36228eeb6a667b07a9f846fadccec
3
+ metadata.gz: 41af565ee256cafc1b08a53dcdce9bf029e1503d8a368d2e946bb4bebec284a2
4
+ data.tar.gz: f7247392ea0c87346ba0915d14506852f34ced3380331905e7e9209f4b2436d7
5
5
  SHA512:
6
- metadata.gz: 25500fd7928c067bc0dd8405fd6140d23b932aa7742e566e008c9f3c8386a74efe44321675147c2951f6cb9dabe5b047445b126b688abf651f8ccfe7b719118e
7
- data.tar.gz: 2f1bf50daa539e964159cd300f24022cd6d2614cd19886608f0acc323fbfb16e207c25d40b192a4fc783bb09b9c3272d17d84d08d9429ce07e29becd62adff51
6
+ metadata.gz: f5fe95adeb421eb88bf8c04e9630b6c688403237fd09b68b4185391254430d12161cd3de74cceabda0b11680a56ee2848fba273b2070a3bc36b7a5ce3701b29d
7
+ data.tar.gz: 89343467c51120526e7ef218d3037fdeb920b75ab846ba5c09a45464897e6484db0532d70d66bf9a3d3477d3d2c13bf34197fe432fd980ff47ad1d6ca50ef058
data/README.md CHANGED
@@ -219,6 +219,98 @@ DRY_RUN=true rake users:dev[_,bob@example.com]
219
219
  #=> -> 0.0000s
220
220
  ```
221
221
 
222
+ ### `Rolemodel::ResourceFor::ControllerExtension`
223
+
224
+ Provides the private `resource_for` utility method to all controller classes throughout your app. Use it in conjunction with Routing concerns.
225
+
226
+ #### The Problem It Solves
227
+
228
+ Apps accumulate resources that hang off many different parents. Good examples include `comments`, `reports`, `duplications`.
229
+
230
+ The naive approach involves many namespaced controllers that all do the same thing but with a different parent resource, or adding several non-restful actions to each parent controller.
231
+
232
+ The following example illustrates a better pattern.
233
+
234
+ ##### Routing
235
+
236
+ Declare the child resource inside a route concern and pass the parent's class name as a route default using `parent_resource`:
237
+
238
+ ```ruby
239
+ concern :commentable do
240
+ resources :comments, commentable_type: parent_resource.name.classify
241
+ end
242
+
243
+ concern :reportable do
244
+ resources :generated_reports, only: %i[show create], reportable_type: parent_resource.name.classify
245
+ end
246
+
247
+ shallow do
248
+ resources :accounts do
249
+ resources :estimates, concerns: %i[commentable reportable] do
250
+ resources :widgets, concerns: %i[commentable reportable]
251
+ end
252
+ end
253
+ end
254
+ ```
255
+
256
+ ##### Controller
257
+
258
+ One controller serves every parent:
259
+
260
+ ```ruby
261
+ class CommentsController < ApplicationController
262
+ before_action :set_commentable, only: %i[index new create]
263
+ before_action :set_comment, except: %i[index new create]
264
+
265
+ def create
266
+ # ...
267
+ end
268
+
269
+ private
270
+
271
+ def set_commentable
272
+ @commentable = resource_for(:commentable_type) # pass in the symbol specified in your routing concern.
273
+ end
274
+ end
275
+ ```
276
+
277
+ Under `shallow: true`, only the collection actions (`index`, `new`, `create`) carry the parent id — hence the `only:`/`except:` split above. Member actions find the child directly by `params[:id]` and reach the parent through its own association.
278
+
279
+ `resource_for` returns the record itself, so it composes with authorization and presentation:
280
+
281
+ ```ruby
282
+ def set_resource
283
+ @resource = authorize resource_for(:resource_type)
284
+ end
285
+ ```
286
+
287
+ #### Guarding User-Supplied Types
288
+
289
+ Route defaults are merged into `params` last, so a route-supplied type cannot be overridden by a query string or request body. If a type ever arrives from user input instead, allowlist it before calling `resource_for` — `safe_constantize` will happily resolve any constant in the app:
290
+
291
+ ```ruby
292
+ REPORT_CONTEXTS = %w[Accessory Estimate PartProxy Tank].freeze
293
+
294
+ before_action :verify_context_type, :set_context, only: %i[create]
295
+
296
+ private
297
+
298
+ def verify_context_type
299
+ return if REPORT_CONTEXTS.include?(params[:context_type])
300
+
301
+ redirect_back_or_to root_url, alert: 'Invalid Request'
302
+ end
303
+ ```
304
+
305
+ Doing this even for route-supplied types is cheap insurance: it documents which parents the controller actually supports and fails loudly when a new route wires up a parent the controller cannot handle.
306
+
307
+ #### Notes
308
+
309
+ * Raises `ActiveRecord::RecordNotFound` when the id does not resolve, which Rails renders as a 404 — the same behavior as any other `find`.
310
+ * `safe_constantize` returns `nil` for an unknown constant, producing a `NoMethodError`; allowlisting avoids that.
311
+ * The class name is demodulized when deriving the id param, so `Reporting::Tank` looks for `params[:tank_id]`.
312
+ * Included via `ActiveSupport.on_load(:action_controller_base)`, so `ActionController::API` controllers do not get it.
313
+
222
314
  ## Development
223
315
 
224
316
  Install the versions of Node and Ruby specified in `.node-version` and `.ruby-version` on your machine. https://asdf-vm.com/ is a great tool for managing language versions. Then run `corepack enable` to activate the Yarn 4+ version pinned by each project's `packageManager` field.
@@ -18,8 +18,7 @@ module Rolemodel
18
18
  ].freeze
19
19
 
20
20
  def install_eslint
21
- ensure_yarn
22
- run "yarn add --dev #{DEV_DEPENDENCIES.join(' ')}"
21
+ yarn_command "add --dev #{DEV_DEPENDENCIES.join(' ')}"
23
22
  end
24
23
 
25
24
  def add_config
@@ -6,8 +6,7 @@ module Rolemodel
6
6
  def add_optics_package
7
7
  say 'installing Optics package', :green
8
8
 
9
- ensure_yarn
10
- run 'yarn add @rolemodel/optics'
9
+ yarn_command 'add @rolemodel/optics'
11
10
  end
12
11
 
13
12
  def copy_templates
@@ -6,8 +6,7 @@ module Rolemodel
6
6
  @add_react = yes?('Would you like to add react?')
7
7
 
8
8
  if @add_react
9
- ensure_yarn
10
- run 'yarn add react react-dom'
9
+ yarn_command 'add react react-dom'
11
10
  end
12
11
  end
13
12
 
@@ -43,8 +43,7 @@ module Rolemodel
43
43
  def add_js_dependencies
44
44
  say 'Adding Sentry JS dependencies to package.json', :green
45
45
 
46
- ensure_yarn
47
- run "yarn add --dev #{JS_DEPS.join(' ')}"
46
+ yarn_command "add --dev #{JS_DEPS.join(' ')}"
48
47
  end
49
48
 
50
49
  def add_js_initializer
@@ -7,8 +7,7 @@ module Rolemodel
7
7
  def add_tailored_select_package
8
8
  say 'Installing Tailored Select package', :green
9
9
 
10
- ensure_yarn
11
- run 'yarn add @rolemodel/tailored-select'
10
+ yarn_command 'add @rolemodel/tailored-select'
12
11
  end
13
12
 
14
13
  def add_simple_form_input
@@ -18,8 +18,10 @@ module Rolemodel
18
18
  raise Thor::InvocationError, 'a --github_package_token option or GITHUB_PACKAGES_TOKEN environment variable is required' if options[:github_package_token].blank?
19
19
  end
20
20
 
21
- def enable_corepack_and_yarn
22
- ensure_yarn
21
+ def add_dev_dependencies
22
+ say 'Adding new dev dependency to package.json', :green
23
+
24
+ yarn_command "add --dev #{DEV_DEPENDENCIES.join(' ')}"
23
25
  end
24
26
 
25
27
  def add_browser_test_script
@@ -34,12 +36,6 @@ module Rolemodel
34
36
  create_file '.node-version', NODE_VERSION, force: true
35
37
  end
36
38
 
37
- def add_dev_dependencies
38
- say 'Adding new dev dependency to package.json', :green
39
-
40
- run "yarn add --dev #{DEV_DEPENDENCIES.join(' ')}"
41
- end
42
-
43
39
  def add_spec_config_files
44
40
  say 'Adding Jasmine Playwright configuration files', :green
45
41
 
@@ -27,9 +27,8 @@ module Rolemodel
27
27
  def install_playwright
28
28
  say 'Installing Playwright for system tests', :green
29
29
 
30
- ensure_yarn
31
- run 'yarn add --dev playwright'
32
- run 'yarn run playwright install'
30
+ yarn_command 'add --dev playwright'
31
+ yarn_command 'run playwright install'
33
32
  end
34
33
 
35
34
  def add_spec_files
@@ -14,17 +14,16 @@ module Rolemodel
14
14
  @vitest/ui
15
15
  ].freeze
16
16
 
17
- def update_test_script
18
- say 'Update yarn test command', :green
19
- ensure_yarn
17
+ def add_dev_dependencies
18
+ say 'Adding new dev dependency to package.json', :green
20
19
 
21
- add_package_json_script 'test', TEST_COMMAND
20
+ yarn_command "add --dev #{DEV_DEPENDENCIES.join(' ')}"
22
21
  end
23
22
 
24
- def add_dev_dependencies
25
- say 'Adding new dev dependency to package.json', :green
23
+ def update_test_script
24
+ say 'Update yarn test command', :green
26
25
 
27
- run "yarn add --dev #{DEV_DEPENDENCIES.join(' ')}"
26
+ add_package_json_script 'test', TEST_COMMAND
28
27
  end
29
28
 
30
29
  def add_spec_config_files
@@ -6,8 +6,7 @@ module Rolemodel
6
6
  def add_turbo_confirm_package
7
7
  say 'Installing Turbo Confirm package', :green
8
8
 
9
- ensure_yarn
10
- run 'yarn add @rolemodel/turbo-confirm'
9
+ yarn_command 'add @rolemodel/turbo-confirm'
11
10
  end
12
11
 
13
12
  def add_initializer
@@ -6,8 +6,7 @@ module Rolemodel
6
6
  def add_rails_request_package
7
7
  say 'Installing @rails/request.js package', :green
8
8
 
9
- ensure_yarn
10
- run 'yarn add @rails/request.js'
9
+ yarn_command 'add @rails/request.js'
11
10
  end
12
11
 
13
12
  def add_stimulus_controller
@@ -25,8 +25,7 @@ module Rolemodel
25
25
  def install_shoelace
26
26
  say 'Installing Shoelace package', :green
27
27
 
28
- ensure_yarn
29
- run 'yarn add @shoelace-style/shoelace'
28
+ yarn_command 'add @shoelace-style/shoelace'
30
29
  end
31
30
 
32
31
  def add_shoelace_javascript_imports
@@ -41,12 +41,6 @@ module Rolemodel
41
41
  create_file '.node-version', NODE_VERSION, force: true
42
42
  end
43
43
 
44
- def enable_corepack_and_yarn
45
- say 'Enabling Corepack and pinning the project to Yarn 4+', :green
46
-
47
- ensure_yarn
48
- end
49
-
50
44
  def force_node_to_use_es_modules
51
45
  say 'Configuring project to use ES Modules instead of CommonJS', :green
52
46
 
@@ -56,14 +50,14 @@ module Rolemodel
56
50
  def remove_obsolete_javascript_dependencies
57
51
  say 'Removing webpack & webpack-cli from package.json dependencies', :green
58
52
 
59
- run 'yarn remove webpack webpack-cli'
53
+ yarn_command 'remove webpack webpack-cli'
60
54
  end
61
55
 
62
56
  def add_npm_packages
63
57
  say 'Adding new dev dependencies to package.json', :green
64
58
 
65
59
  dependencies = DEV_DEPS + POSTCSS_PKGS + WEBPACK_CSS_PKGS
66
- run "yarn add --dev #{dependencies.join(' ')}"
60
+ yarn_command "add --dev #{dependencies.join(' ')}"
67
61
  end
68
62
 
69
63
  def add_webpack_config
@@ -3,9 +3,16 @@
3
3
  module Rolemodel
4
4
  class Engine < ::Rails::Engine
5
5
  require_relative 'generator_base'
6
+ require_relative 'resource_for/controller_extension'
6
7
 
7
8
  generators do
8
9
  require 'generators/rolemodel/all_generator'
9
10
  end
11
+
12
+ initializer 'rolemodel.action_controller' do
13
+ ActiveSupport.on_load(:action_controller_base) do
14
+ include Rolemodel::ResourceFor::ControllerExtension
15
+ end
16
+ end
10
17
  end
11
18
  end
@@ -9,19 +9,25 @@ module Rolemodel
9
9
  class GeneratorBase < ::Rails::Generators::Base
10
10
  include ::Rails::Generators::BundleHelper, ReplaceContentHelper
11
11
 
12
-
13
12
  private
14
13
  # based on https://github.com/rails/rails/blob/main/railties/lib/rails/generators/app_base.rb#L713
15
14
  def run_bundle
16
15
  bundle_command("install --quiet", "BUNDLE_IGNORE_MESSAGES" => "1")
17
16
  end
18
17
 
18
+ def yarn_command(command)
19
+ ensure_yarn
20
+
21
+ run "yarn #{command}"
22
+ end
23
+
19
24
  # Enable Corepack and pin the project to Yarn 4+ before running any `yarn`
20
25
  # command. Empty args/opts are required: without them, Thor forwards this
21
26
  # generator's own CLI args (e.g. the test harness's --skip-bundle
22
27
  # --skip-bootsnap) into Rolemodel::Yarn#setup, which takes none and raises
23
28
  # Thor::InvocationError.
24
29
  def ensure_yarn
30
+ return if Rails.root.join(destination_root, '.yarnrc.yml').exist?
25
31
  invoke 'rolemodel:yarn:setup', [], {}
26
32
  end
27
33
 
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rolemodel
4
+ module ResourceFor
5
+ module ControllerExtension
6
+ extend ActiveSupport::Concern
7
+
8
+ private
9
+
10
+ def resource_for(type_symbol)
11
+ params[type_symbol].safe_constantize.find(params[params[type_symbol].foreign_key])
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rolemodel
4
- VERSION = '2.3.1'
4
+ VERSION = '2.4.0'
5
5
  end
@@ -5,11 +5,10 @@ module Rolemodel
5
5
  class Yarn < ::Thor
6
6
  include ::Thor::Actions
7
7
 
8
- # Enable Corepack and pin the project to Yarn 4+ (instead of the classic
9
- # Yarn 1.22). Idempotent, so any generator can call it before running a
10
- # `yarn` command to guarantee the modern toolchain is in place.
8
+ # Enable Corepack and pin the project to Yarn 4+ (instead of the classic Yarn 1.22).
11
9
  desc 'setup', 'Set the current stable version of Yarn, enable corepack, and initialize a package.json file'
12
10
  def setup
11
+ say 'Setting up Yarn 4+ and Corepack', :green
13
12
  # Configure the node-modules linker so webpack, Playwright, and the Rails
14
13
  # asset pipeline keep working (Yarn 4 defaults to Plug'n'Play otherwise).
15
14
  # skip if exists? to avoid overwriting any other existing config
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rolemodel-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.1
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - RoleModel Software Inc
@@ -436,6 +436,7 @@ files:
436
436
  - lib/rolemodel/optics/phosphor_icon_builder.rb
437
437
  - lib/rolemodel/optics/tabler_icon_builder.rb
438
438
  - lib/rolemodel/replace_content_helper.rb
439
+ - lib/rolemodel/resource_for/controller_extension.rb
439
440
  - lib/rolemodel/utility.rb
440
441
  - lib/rolemodel/utility/task_tools.rb
441
442
  - lib/rolemodel/version.rb