rolemodel-rails 2.5.0 → 2.6.1

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -0
  3. data/lib/generators/rolemodel/README.md +12 -1
  4. data/lib/generators/rolemodel/all_generator.rb +2 -0
  5. data/lib/generators/rolemodel/core_setup/README.md +3 -1
  6. data/lib/generators/rolemodel/core_setup/core_setup_generator.rb +6 -1
  7. data/lib/generators/rolemodel/github/README.md +0 -4
  8. data/lib/generators/rolemodel/github/USAGE +0 -1
  9. data/lib/generators/rolemodel/github/github_generator.rb +0 -5
  10. data/lib/generators/rolemodel/heroku/templates/deploy-app/SKILL.md +10 -8
  11. data/lib/generators/rolemodel/linters/rubocop/templates/.rubocop/cops/form_error_response.rb +54 -0
  12. data/lib/generators/rolemodel/linters/rubocop/templates/.rubocop/cops/no_chrome_tag.rb +45 -0
  13. data/lib/generators/rolemodel/linters/rubocop/templates/.rubocop.yml +74 -0
  14. data/lib/generators/rolemodel/optics/README.md +3 -0
  15. data/lib/generators/rolemodel/optics/all_generator.rb +5 -1
  16. data/lib/generators/rolemodel/optics/icons/icon_library_options.rb +23 -0
  17. data/lib/generators/rolemodel/optics/icons/icons_generator.rb +1 -1
  18. data/lib/generators/rolemodel/readme/readme_generator.rb +1 -1
  19. data/lib/generators/rolemodel/sentry/templates/config/initializers/sentry.rb +0 -1
  20. data/lib/generators/rolemodel/simple_form/simple_form_generator.rb +1 -3
  21. data/lib/generators/rolemodel/testing/parallel_tests/templates/.rspec_parallel +3 -0
  22. data/lib/generators/rolemodel/testing/rspec/rspec_generator.rb +1 -0
  23. data/lib/generators/rolemodel/testing/rspec/templates/.rspec +1 -0
  24. data/lib/generators/rolemodel/tom_select/README.md +15 -0
  25. data/lib/generators/rolemodel/tom_select/USAGE +7 -0
  26. data/lib/generators/rolemodel/tom_select/templates/app/inputs/grouped_tom_select_input.rb +15 -0
  27. data/lib/generators/rolemodel/tom_select/templates/app/inputs/tom_select_input.rb +17 -0
  28. data/lib/generators/rolemodel/tom_select/templates/app/javascript/controllers/tom_select_controller.js +36 -0
  29. data/lib/generators/rolemodel/tom_select/tom_select_generator.rb +43 -0
  30. data/lib/generators/rolemodel/turbo/all/README.md +1 -1
  31. data/lib/generators/rolemodel/turbo/form/README.md +4 -3
  32. data/lib/generators/rolemodel/turbo/form/form_generator.rb +4 -19
  33. data/lib/generators/rolemodel/webpack/webpack_generator.rb +1 -0
  34. data/lib/rolemodel/generator_base.rb +8 -2
  35. data/lib/rolemodel/version.rb +1 -1
  36. data/lib/rolemodel/yarn.rb +5 -1
  37. metadata +27 -4
  38. data/lib/generators/rolemodel/github/templates/instructions +0 -1
  39. data/lib/generators/rolemodel/turbo/form/templates/app/javascript/controllers/turbo_form_controller.js +0 -15
  40. data/lib/generators/rolemodel/turbo/form/templates/spec/support/helpers/turbo_form_helper.rb +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fede781704d5a6d3f851f7dfafe1ab6a6d3be865609e009b6eaff8610c510656
4
- data.tar.gz: ae9be94c16906a8ccb1bca770b9e6f385ef6f7e5e8e97a8c6c830502d567b145
3
+ metadata.gz: 54ba8996a84dfe7f2f989442444f039ebd5868a9a3f4800f800223d035f6a9d2
4
+ data.tar.gz: f79e30570b359cd0dc120b514127d207348589e85eb2f06414b0938aa3cd08d6
5
5
  SHA512:
6
- metadata.gz: bd3700f56df8e270774d4036bdebe388d86c37bcd190fb75f39022640dee94200ae7fe7eef23474a58c2c10e5aafea2809619bc34a6b6d4e86777798287b603b
7
- data.tar.gz: d75431fd64ce262417b5468ae9af0f60ec5125d499c3f2bdbb91b6278c60fe20bb3d312bce1a3b85dc2882aef931968cc499a91e5e457579667248795e603ec6
6
+ metadata.gz: 8b08a3d1b935ca08181f45fb948112194888325c6e60971c3c18b279b4cd8e9073dec7f01d8c26373bc9276e54b071c238fbece40ced0bb60da864f2c20b61c2
7
+ data.tar.gz: 96a5ea26590b5074da86fd3059ca6bb04e49bc3bd82fd4e2b56a3f18415dd80f2a807ef31ab396f6313034394d2e90e591eb75711a50f795744d00636ca2b298
data/README.md CHANGED
@@ -107,6 +107,7 @@ bin/rails g
107
107
  * [GoodJob](./lib/generators/rolemodel/good_job)
108
108
  * [Editors](./lib/generators/rolemodel/editors)
109
109
  * [Tailored Select](./lib/generators/rolemodel/tailored_select)
110
+ * [Tom Select](./lib/generators/rolemodel/tom_select)
110
111
  * [Lograge](./lib/generators/rolemodel/lograge)
111
112
 
112
113
  ## Utilities
@@ -331,6 +332,19 @@ We use the embeded Rails apps (`example_rails_current` & `example_rails_legacy`)
331
332
 
332
333
  ## Testing
333
334
 
335
+ Run the full suite with `bin/ci`. It splits the spec files across parallel processes, so it is much faster than a plain `bundle exec rspec`. CI uses the same command.
336
+
337
+ ```shell
338
+ bin/ci
339
+ ```
340
+
341
+ During development, you can still run a single file or example with RSpec directly:
342
+
343
+ ```shell
344
+ bundle exec rspec spec/generators/rolemodel/turbo/form_generator_spec.rb
345
+ bundle exec rspec spec/generators/rolemodel/turbo/form_generator_spec.rb:11
346
+ ```
347
+
334
348
  Generator specs should be added to the [spec](./spec) directory.
335
349
 
336
350
  Setup & Teardown of the test-dummy app is handled for you. All you need to do is run the provided helper method:
@@ -4,20 +4,31 @@
4
4
 
5
5
  ## What you get
6
6
 
7
+ * [Editors](./editors)
7
8
  * [Github](./github)
8
9
  * [GoodJob](./good_job)
9
10
  * [Heroku](./heroku)
11
+ * [Kaminari](./kaminari)
10
12
  * [Linters](./linters)
13
+ * [Lograge](./lograge)
11
14
  * [Mailers](./mailers)
12
- * [Modals](./modals)
13
15
  * [Optics](./optics)
16
+ * [React](./react)
14
17
  * [README](./readme)
15
18
  * [SaaS](./saas)
19
+ * [Sentry](./sentry)
16
20
  * [SimpleForm](./simple_form)
17
21
  * [Slim](./slim)
18
22
  * [SoftDestroyable](./soft_destroyable)
19
23
  * [Source Map](./source_map)
20
24
  * [Testing](./testing)
25
+ * [Tom Select](./tom_select)
26
+ * [Turbo](./turbo/all)
27
+ * [Ready](./turbo/ready)
28
+ * [Confirm](./turbo/confirm)
29
+ * [Modals](./turbo/modals)
30
+ * [Form](./turbo/form)
31
+ * [UI Components](./ui_components)
21
32
  * [Webpack](./webpack)
22
33
 
23
34
  ## Helpful documentation
@@ -12,7 +12,9 @@ module Rolemodel
12
12
  generate 'rolemodel:slim'
13
13
  generate 'rolemodel:optics:all'
14
14
  generate 'rolemodel:testing:all'
15
+ generate 'rolemodel:turbo:all'
15
16
  generate 'rolemodel:simple_form'
17
+ generate 'rolemodel:tom_select'
16
18
  generate 'rolemodel:soft_destroyable'
17
19
  generate 'rolemodel:saas:all'
18
20
  generate 'rolemodel:mailers'
@@ -17,10 +17,12 @@ app you can push straight to Heroku right after generation.
17
17
  - [Webpack](../webpack) — Webpack v5 for JS and CSS
18
18
  - [Sentry](../sentry) — error monitoring for Ruby and JavaScript
19
19
  - [Slim](../slim) — Slim templates
20
- - [Optics](../optics) — Optics design system
20
+ - [Optics](../optics) — Optics design system (pass an icon library flag such as
21
+ `--material` to choose it up front; otherwise the icons generator asks)
21
22
  - [Testing](../testing) — RSpec, FactoryBot, parallel_tests, TestProf
22
23
  (pass `--js-runner` to include jasmine-playwright-runner)
23
24
  - [SimpleForm](../simple_form) — SimpleForm with our configuration
25
+ - [Tom Select](../tom_select) — Tom Select with a Stimulus controller and SimpleForm inputs
24
26
  - [Linters](../linters) — Rubocop and ESLint
25
27
  - [UI Components](../ui_components) — flash, the modal pattern, & Turbo 8 support
26
28
  - [Editors](../editors) — EditorConfig and recommended VSCode extensions
@@ -1,12 +1,17 @@
1
+ require_relative '../optics/icons/icon_library_options'
2
+
1
3
  module Rolemodel
2
4
  class CoreSetupGenerator < ::Rolemodel::GeneratorBase
5
+ include Optics::IconLibraryOptions
6
+
3
7
  class_option :skip_deployable, type: :boolean, default: false, desc: 'skip generators for app deployment (Heroku, Sentry, Lograge)'
4
8
 
5
9
  def run_the_core_generators
6
10
  generate 'rolemodel:slim'
7
11
  generate 'rolemodel:webpack'
8
- generate 'rolemodel:optics:all'
12
+ generate 'rolemodel:optics:all', *icon_library_flags
9
13
  generate 'rolemodel:simple_form'
14
+ generate 'rolemodel:tom_select'
10
15
  generate 'rolemodel:testing:all'
11
16
  generate 'rolemodel:turbo:all'
12
17
  generate 'rolemodel:ui_components:flash'
@@ -16,9 +16,5 @@ It doesn't need to be run first, but the parallel_tests generator must be run in
16
16
  - Pull Request Template
17
17
  - When you open a Pull Request in Github it will use the Markdown file as a [template](./templates/pull_request_template.md) for the content of the PR.
18
18
  - Helpful for reminding collaborators to add specific details to the PR.
19
- - Copilot Instructions
20
- - Installed into `.github/instructions`, these are context-specific instructions for Copilot to help it give more accurate
21
- and relevant results. These are a good starting point but they should be tweaked for your project's frameworks and
22
- standards.
23
19
  - Dependabot Configuration
24
20
  - defines a set of rules that dependabot will use to keep your applications dependencies up-to-date on a weekly basis.
@@ -5,7 +5,6 @@ Example:
5
5
  rails generate rolemodel:github
6
6
 
7
7
  This will create:
8
- .github/instructions/
9
8
  .github/workflows/ci.yml
10
9
  .github/pull_request_template.md
11
10
  .github/dependabot.yml
@@ -3,10 +3,6 @@
3
3
  module Rolemodel
4
4
  class GithubGenerator < GeneratorBase
5
5
  GITHUB_ACTIONS_REPO = 'https://github.com/RoleModel/actions.git'
6
- # Files which are both used by the gem source and copied to the target app without modification
7
- # are placed in the `.github` folder at the top level of this repository. This folder is then
8
- # symlinked to the `templates` folder relative to this generator so they can still be copied over.
9
- # Any files which are significantly different or not used by the gem source are just in `templates`.
10
6
  source_root File.expand_path('templates', __dir__)
11
7
 
12
8
  class_option :playwright, type: :boolean, default: true,
@@ -22,7 +18,6 @@ module Rolemodel
22
18
  end
23
19
 
24
20
  def install_github_config
25
- directory 'instructions', '.github/instructions'
26
21
  directory 'workflows', '.github/workflows', force: true
27
22
  template 'pull_request_template.md', '.github/pull_request_template.md'
28
23
  end
@@ -24,12 +24,16 @@ go. Ask the user questions **one at a time**.
24
24
 
25
25
  ## Phase 0: Preflight
26
26
 
27
- Run these checks before touching anything. Stop and tell the user how to fix any that
28
- fail:
29
-
30
- 1. `git remote get-url origin` — must be a GitHub repo. Derive `REPO_NAME` from
31
- `gh repo view --json name -q .name` (fallback: basename of the origin URL).
32
- 2. `gh auth status` — GitHub CLI authenticated.
27
+ Run these checks before touching anything (creating a missing GitHub repo is the one
28
+ exception). Stop and tell the user how to fix any that fail:
29
+
30
+ 1. `gh auth status` — GitHub CLI authenticated.
31
+ 2. `git remote get-url origin` — must be a GitHub repo. A brand-new app has no `origin` yet:
32
+ offer to create one, confirming the name (default: the app directory's name) and that it
33
+ should be private in the RoleModel org, then
34
+ `gh repo create RoleModel/<name> --private --source . --remote origin --push`.
35
+ Derive `REPO_NAME` from `gh repo view --json name -q .name` (fallback: basename of the
36
+ origin URL).
33
37
  3. `heroku auth:whoami` — Heroku CLI installed and logged in.
34
38
  4. Sentry MCP tools available (try listing organizations). If no Sentry MCP server is
35
39
  connected, ask the user to add/authorize the Sentry MCP server
@@ -41,10 +45,8 @@ fail:
41
45
  - reads `Rails.application.credentials...` → **credentials mode**
42
46
  7. Deploy-readiness (recent `rolemodel-rails` bakes these in; older apps need the manual
43
47
  fixes noted in each phase). Confirm the generated app already has:
44
- - `terser-webpack-plugin` in `package.json` (webpack config imports it)
45
48
  - custom cops under `.rubocop/cops/`, not `lib/cops/` (the latter crashes prod boot)
46
49
  - `config/initializers/lograge.rb` uses `controller.try(:current_user)`
47
- - `config/database.yml` `production:` block uses `url: <%= ENV["DATABASE_URL"] %>`
48
50
  - a `ruby` directive in the `Gemfile` (so the version reaches `Gemfile.lock`)
49
51
  - `stackprof` in the `Gemfile` (Sentry profiling)
50
52
  If any are missing, apply the fix inline and note the app predates the generator update.
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cops
4
+ # Custom Cop to Ensure Form Error Response Statuses
5
+ #
6
+ # There are three levels of matching here:
7
+ # Methods (def on_if)
8
+ # This scans code and only stops on the method specified.
9
+ # E.G. `on_if` will only move into the below checks when it encounters an if statement.
10
+ #
11
+ # Node matcher (def_node_matcher :form_error)
12
+ # This scans the block of code (in this case, the else branch of the if block)
13
+ # using AST (Abstract Tree Syntax) to match certain patterns
14
+ #
15
+ # Regex
16
+ # This checks the specific matching line to ensure it meets our expectations
17
+ class FormErrorResponse < RuboCop::Cop::Base
18
+ extend RuboCop::Cop::AutoCorrector
19
+
20
+ def self.rack_version
21
+ Gem.loaded_specs['rack'].version
22
+ end
23
+
24
+ def self.http_status
25
+ if rack_version >= '3.1'
26
+ :unprocessable_content
27
+ else
28
+ :unprocessable_entity
29
+ end
30
+ end
31
+
32
+ # Match code in the else branch that either: is the only line and looks like `render :something, ...`
33
+ # or is not the first line and looks like the same.
34
+ def_node_matcher :form_error, <<~PATTERN
35
+ {(... $(send nil? :render (:sym _) ...)) | $(send nil? :render (:sym _) ...)}
36
+ PATTERN
37
+
38
+ MSG = "Use status: :#{self.http_status} for invalid form requests."
39
+
40
+ # Ensure the render is returning the correct status, otherwise add the offense
41
+ STATUS_PAIR = /\(pair\s+\(sym :status\)\s+\(sym :#{self.http_status}\)\)/m
42
+
43
+ # Only check within if blocks
44
+ def on_if(node)
45
+ form_error(node.else_branch) do |name|
46
+ next if name.to_s.match?(STATUS_PAIR)
47
+
48
+ add_offense(name) do |corrector|
49
+ corrector.insert_after(name, ", status: :#{self.class.http_status}")
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ module Cops
6
+ # Detect occurences of the chrome tag in specs, and warn and remove them.
7
+ class NoChromeTag < RuboCop::Cop::Base
8
+ include RuboCop::Cop::RangeHelp
9
+ extend RuboCop::Cop::AutoCorrector
10
+
11
+ MSG = 'The :chrome tag is only for testing, and should not be checked into the repository.'
12
+ RESTRICT_ON_SEND = %i[describe context it feature scenario].freeze
13
+
14
+ def on_send(node) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
15
+ node.arguments.each do |a|
16
+ if a.sym_type? && a.value == :chrome
17
+ range = range_with_surrounding_comma(a.source_range)
18
+ add_offense(range) do |corrector|
19
+ corrector.replace(range, '')
20
+ end
21
+ end
22
+
23
+ next unless a.hash_type?
24
+
25
+ a.pairs.each do |pair|
26
+ next unless pair.key.value == :chrome
27
+
28
+ range = range_with_surrounding_comma(pair.source_range)
29
+ add_offense(range) do |corrector|
30
+ corrector.replace(range, '')
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def range_with_surrounding_comma(source_range)
37
+ buffer = source_range.source_buffer
38
+ src = buffer.source
39
+ end_pos = source_range.end_pos
40
+ comma_index = src[...end_pos].rindex(/\s*,\s*/)
41
+
42
+ Parser::Source::Range.new(buffer, comma_index, end_pos)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,74 @@
1
+ require:
2
+ - ./.rubocop/cops/form_error_response.rb
3
+ - ./.rubocop/cops/no_chrome_tag.rb
4
+ plugins:
5
+ - rubocop-rails
6
+
7
+ AllCops:
8
+ Exclude:
9
+ - bin/**/*
10
+ - db/**/*
11
+ - vendor/**/*
12
+ - config/**/*
13
+ - node_modules/**/*
14
+ - tmp/**/*
15
+ NewCops: enable
16
+
17
+ Metrics:
18
+ Severity: refactor
19
+
20
+ # RSpec tests and rake tasks are expected to have
21
+ # large blocks, so exclude them from this cop.
22
+ Metrics/BlockLength:
23
+ Exclude:
24
+ - 'Rakefile'
25
+ - '**/*.rake'
26
+ - 'spec/**/*.rb'
27
+
28
+ # Hard to make this pass always and want to keep flexibility
29
+ Layout/ArrayAlignment:
30
+ Enabled: false
31
+
32
+ # Best Practices for readability
33
+ Layout:
34
+ Severity: refactor
35
+
36
+ Layout/FirstArrayElementIndentation:
37
+ EnforcedStyle: consistent
38
+
39
+ Layout/FirstHashElementIndentation:
40
+ EnforcedStyle: consistent
41
+
42
+ Lint/UnusedMethodArgument:
43
+ Severity: refactor
44
+ AutoCorrect: false
45
+
46
+ Lint/UnusedBlockArgument:
47
+ Severity: refactor
48
+ AutoCorrect: false
49
+
50
+ Lint/UselessAssignment:
51
+ Severity: refactor
52
+ AutoCorrect: false
53
+
54
+ Lint/UnderscorePrefixedVariableName:
55
+ Enabled: false
56
+
57
+ Lint/Loop:
58
+ Enabled: false
59
+
60
+ Lint/SuppressedException:
61
+ Enabled: false
62
+
63
+ Style:
64
+ Severity: refactor
65
+
66
+ # It is ok to use inject at times
67
+ Style/EachWithObject:
68
+ Enabled: false
69
+
70
+ Style/Documentation:
71
+ Enabled: false
72
+
73
+ Cops/NoChromeTag:
74
+ AutoCorrect: contextual
@@ -2,6 +2,9 @@
2
2
 
3
3
  [USAGE](./USAGE)
4
4
 
5
+ `rolemodel:optics:all` accepts the icons generator's library flags (`--material`, `--phosphor`,
6
+ `--tabler`, `--feather`, `--lucide`, `--custom`) and passes them along.
7
+
5
8
  ## Sub Generators
6
9
 
7
10
  * [Base Optics](./base)
@@ -1,11 +1,15 @@
1
+ require_relative 'icons/icon_library_options'
2
+
1
3
  module Rolemodel
2
4
  module Optics
3
5
  class AllGenerator < Rolemodel::GeneratorBase
6
+ include IconLibraryOptions
7
+
4
8
  source_root File.expand_path('templates', __dir__)
5
9
 
6
10
  def run_all_the_generators
7
11
  generate 'rolemodel:optics:base'
8
- generate 'rolemodel:optics:icons'
12
+ generate 'rolemodel:optics:icons', *icon_library_flags
9
13
  end
10
14
  end
11
15
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'icons_generator'
4
+
5
+ module Rolemodel
6
+ module Optics
7
+ # For generators that run optics:icons: accept its icon library flag (e.g. --material) and pass
8
+ # it along, so the icons generator doesn't stop to ask.
9
+ module IconLibraryOptions
10
+ LIBRARIES = IconsGenerator::SUPPORTED_LIBRARIES.keys.freeze
11
+
12
+ def self.included(base)
13
+ base.class_exclusive do
14
+ LIBRARIES.each { base.class_option it, type: :boolean, lazy_default: false, desc: "Use #{it} icon library" }
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def icon_library_flags = LIBRARIES.select { options[it] }.map { "--#{it}" }
21
+ end
22
+ end
23
+ end
@@ -42,7 +42,7 @@ module Rolemodel
42
42
  private
43
43
 
44
44
  def capture_user_selection
45
- options.slice(*SUPPORTED_LIBRARIES.keys).invert[true] || ask(
45
+ options.slice(*SUPPORTED_LIBRARIES.keys).invert[true] || ask_or_default(
46
46
  'What icon library would you like to add?',
47
47
  default: SUPPORTED_LIBRARIES.keys.first.to_s,
48
48
  limited_to: SUPPORTED_LIBRARIES.keys.map(&:to_s)
@@ -5,7 +5,7 @@ module Rolemodel
5
5
  def install_readme
6
6
  @project_name = Rails.application.class.try(:parent_name) || Rails.application.class.module_parent_name
7
7
  @project = @project_name.underscore
8
- template 'README.md.erb', 'README.md'
8
+ template 'README.md.erb', 'README.md', force: true
9
9
  end
10
10
  end
11
11
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  Sentry.init do |config|
4
4
  config.dsn = ENV['SENTRY_DSN']
5
- config.enable_logs = true
6
5
  config.debug = Rails.env.local?
7
6
 
8
7
  # Traces sampling — 5% for HTTP requests, 10% for other transactions
@@ -8,9 +8,7 @@ module Rolemodel
8
8
  desc: 'Install the tailored_select experimental component input'
9
9
 
10
10
  def add_gem
11
- Bundler.with_unbundled_env do
12
- bundle_command 'add simple_form'
13
- end
11
+ bundle_command 'add simple_form'
14
12
  end
15
13
 
16
14
  def add_files
@@ -0,0 +1,3 @@
1
+ --format RspecJunitFormatter --out tmp/rspec-<%= ENV['TEST_ENV_NUMBER'] %>.xml
2
+ --format ParallelTests::RSpec::RuntimeLogger --out tmp/turbo_rspec_runtime.log
3
+ --format ParallelTests::RSpec::FailuresLogger
@@ -34,6 +34,7 @@ module Rolemodel
34
34
  def add_spec_files
35
35
  say 'Adding Support Files', :green
36
36
 
37
+ template '.rspec', '.rspec'
37
38
  directory 'spec'
38
39
 
39
40
  append_file '.gitignore', 'spec/examples.txt'
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -0,0 +1,15 @@
1
+ # Tom Select Generator
2
+
3
+ `rails g rolemodel:tom_select`
4
+
5
+ ## What you get
6
+
7
+ * The [Tom Select](https://tom-select.js.org/) package and its stylesheet
8
+ * A `tom-select` Stimulus controller that survives Turbo morphs
9
+ * `tom_select` and `grouped_tom_select` SimpleForm inputs, if the app uses SimpleForm
10
+
11
+ ```slim
12
+ = f.input :project_manager, as: :tom_select, collection: User.all
13
+ = f.input :substance, as: :tom_select, collection: Substance.all, allow_create: true
14
+ = f.input :material, as: :grouped_tom_select, collection: Category.all, group_method: :materials
15
+ ```
@@ -0,0 +1,7 @@
1
+ Description:
2
+ Installs the tom-select package, its stylesheet, and a tom-select Stimulus
3
+ controller. If the app is using SimpleForm, it also installs the matching
4
+ tom_select and grouped_tom_select SimpleForm inputs.
5
+
6
+ Example:
7
+ rails generate rolemodel:tom_select
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GroupedTomSelectInput < SimpleForm::Inputs::GroupedCollectionSelectInput
4
+ def input(wrapper_options = nil)
5
+ super(merge_wrapper_options(tom_select_options, wrapper_options))
6
+ end
7
+
8
+ def input_html_classes = []
9
+
10
+ private
11
+
12
+ def tom_select_options
13
+ { data: { controller: 'tom-select', action: TomSelectInput::MORPH_ACTIONS } }
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TomSelectInput < SimpleForm::Inputs::CollectionSelectInput
4
+ MORPH_ACTIONS = 'turbo:before-morph-element->tom-select#disconnect:self turbo:morph-element->tom-select#afterMorph:self'
5
+
6
+ def input(wrapper_options = nil)
7
+ super(merge_wrapper_options(tom_select_options, wrapper_options))
8
+ end
9
+
10
+ def input_html_classes = []
11
+
12
+ private
13
+
14
+ def tom_select_options
15
+ { data: { controller: 'tom-select', tom_select_create_value: options.delete(:allow_create) || false, action: MORPH_ACTIONS } }
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import TomSelect from 'tom-select'
3
+
4
+ export default class extends Controller {
5
+ static values = { create: Boolean }
6
+
7
+ connect() {
8
+ this.tomSelect = new TomSelect(this.element, this.settings)
9
+ this.element.tomSelect = this.tomSelect
10
+ }
11
+
12
+ disconnect() {
13
+ // In case a Turbo morph is happening, we need to update TomSelect's revertSettings,
14
+ // in order to preserve the current value(s) when reconnecting.
15
+ this.tomSelect.revertSettings.innerHTML = this.element.innerHTML
16
+
17
+ this.tomSelect.destroy()
18
+ delete this.element.tomSelect
19
+ }
20
+
21
+ afterMorph() {
22
+ queueMicrotask(() => this.connect())
23
+ }
24
+
25
+ get settings() {
26
+ return {
27
+ create: this.createValue,
28
+ persist: false,
29
+ maxOptions: null,
30
+ delimiter: null,
31
+ maxItems: this.element.multiple ? null : 1,
32
+ plugins: this.element.multiple ? ['remove_button'] : [],
33
+ onItemAdd: () => this.tomSelect.setTextboxValue(''), // clear the input text box when a selection is made
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rolemodel
4
+ class TomSelectGenerator < GeneratorBase
5
+ source_root File.expand_path('templates', __dir__)
6
+
7
+ def add_tom_select_package
8
+ say 'Installing Tom Select package', :green
9
+
10
+ yarn_command 'add tom-select'
11
+ end
12
+
13
+ def import_stylesheet
14
+ say 'Importing Tom Select stylesheet', :green
15
+
16
+ prepend_to_file application_stylesheet_path, <<~CSS
17
+ @import 'tom-select/dist/css/tom-select.css';
18
+ CSS
19
+ end
20
+
21
+ def add_stimulus_controller
22
+ say 'Adding Tom Select Stimulus controller', :green
23
+
24
+ directory 'app/javascript/controllers'
25
+
26
+ rails_command 'stimulus:manifest:update'
27
+ end
28
+
29
+ def add_simple_form_inputs
30
+ return unless simple_form?
31
+
32
+ say 'Installing the Tom Select SimpleForm inputs', :green
33
+
34
+ directory 'app/inputs'
35
+ end
36
+
37
+ private
38
+
39
+ def simple_form?
40
+ File.exist?(File.expand_path('config/initializers/simple_form.rb', destination_root))
41
+ end
42
+ end
43
+ end
@@ -13,4 +13,4 @@ Runs all Turbo Generators
13
13
  - Turbo meta tags in HEAD
14
14
  - TurboConfirm setup
15
15
  - Turbo Frame Modals setup
16
- - Turbo-Form stimulus controller
16
+ - turbo_form gem for dynamic forms
@@ -1,8 +1,9 @@
1
1
  # Form Generator
2
2
 
3
- Enable the "turbo-fetch" (now Turbo-Form) pattern in new/existing rails apps.
3
+ Enable dynamic form interactions in new/existing rails apps with the [turbo_form](https://github.com/OutlawAndy/turbo_form) gem.
4
4
 
5
5
  ## What you get
6
6
 
7
- - turbo-form controller
8
- - system spec helper
7
+ - the `turbo_form` gem, set up by its `turbo_form:install` generator
8
+
9
+ Draw the `:turbo_form` route concern on each resource with a dynamic form. See the gem's README for usage.
@@ -1,28 +1,13 @@
1
1
  module Rolemodel
2
2
  module Turbo
3
3
  class FormGenerator < GeneratorBase
4
- source_root File.expand_path('templates', __dir__)
4
+ def install_turbo_form
5
+ say 'Installing turbo_form', :green
5
6
 
6
- def add_rails_request_package
7
- say 'Installing @rails/request.js package', :green
7
+ bundle_command 'add turbo_form'
8
8
 
9
- yarn_command 'add @rails/request.js'
10
- end
11
-
12
- def add_stimulus_controller
13
- say 'Adding Turbo Form Stimulus Controller', :green
14
-
15
- directory 'app/javascript/controllers'
16
-
17
- rails_command 'stimulus:manifest:update'
18
- end
19
-
20
- def add_system_spec_helper
21
- say 'Adding Turbo Form System Spec Helper', :green
22
-
23
- directory 'spec/support/helpers'
9
+ generate 'turbo_form:install'
24
10
  end
25
11
  end
26
12
  end
27
13
  end
28
-
@@ -5,6 +5,7 @@ module Rolemodel
5
5
  DEV_DEPS = %w[
6
6
  esbuild
7
7
  esbuild-loader
8
+ strip-sourcemap-loader
8
9
  webpack
9
10
  webpack-cli
10
11
  ]
@@ -10,6 +10,12 @@ module Rolemodel
10
10
  include ::Rails::Generators::BundleHelper, ReplaceContentHelper
11
11
 
12
12
  private
13
+
14
+ # Thor's ask loops forever on EOF when limited_to is set, so only prompt when someone can answer.
15
+ def ask_or_default(statement, default:, **)
16
+ $stdin.tty? ? ask(statement, default:, **) : default
17
+ end
18
+
13
19
  # based on https://github.com/rails/rails/blob/main/railties/lib/rails/generators/app_base.rb#L713
14
20
  def run_bundle
15
21
  bundle_command("install --quiet", "BUNDLE_IGNORE_MESSAGES" => "1")
@@ -22,13 +28,13 @@ module Rolemodel
22
28
  end
23
29
 
24
30
  # Enable Corepack and pin the project to Yarn 4+ before running any `yarn`
25
- # command. Empty args/opts are required: without them, Thor forwards this
31
+ # command. Args/opts are passed explicitly: otherwise Thor forwards this
26
32
  # generator's own CLI args (e.g. the test harness's --skip-bundle
27
33
  # --skip-bootsnap) into Rolemodel::Yarn#setup, which takes none and raises
28
34
  # Thor::InvocationError.
29
35
  def ensure_yarn
30
36
  return if Rails.root.join(destination_root, '.yarnrc.yml').exist?
31
- invoke 'rolemodel:yarn:setup', [], {}
37
+ invoke 'rolemodel:yarn:setup', [], options.slice(:pretend)
32
38
  end
33
39
 
34
40
  def application_stylesheet_path
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rolemodel
4
- VERSION = '2.5.0'
4
+ VERSION = '2.6.1'
5
5
  end
@@ -16,7 +16,8 @@ module Rolemodel
16
16
 
17
17
  yes! 'yarn set version stable'
18
18
  yes! 'corepack enable'
19
- yes! 'yarn init'
19
+ # `yarn init` replaces an existing package.json, dropping the scripts and packages Rails added.
20
+ yes! 'yarn init' unless File.exist?(File.expand_path('package.json', destination_root))
20
21
 
21
22
  ignore_yarn_install_state
22
23
  end
@@ -32,6 +33,9 @@ module Rolemodel
32
33
  end
33
34
 
34
35
  def yes!(command)
36
+ say_status :run, command
37
+ return if options[:pretend]
38
+
35
39
  Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
36
40
  stdin.puts 'y'
37
41
  stdin.close
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.5.0
4
+ version: 2.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - RoleModel Software Inc
@@ -65,6 +65,20 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0.10'
68
+ - !ruby/object:Gem::Dependency
69
+ name: parallel_tests
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
68
82
  - !ruby/object:Gem::Dependency
69
83
  name: rake
70
84
  requirement: !ruby/object:Gem::Requirement
@@ -189,7 +203,6 @@ files:
189
203
  - lib/generators/rolemodel/github/github_generator.rb
190
204
  - lib/generators/rolemodel/github/templates/CODEOWNERS
191
205
  - lib/generators/rolemodel/github/templates/dependabot.yml
192
- - lib/generators/rolemodel/github/templates/instructions
193
206
  - lib/generators/rolemodel/github/templates/pull_request_template.md
194
207
  - lib/generators/rolemodel/github/templates/workflows/ci.yml.tt
195
208
  - lib/generators/rolemodel/github/templates/workflows/deploy-production.yml
@@ -225,6 +238,9 @@ files:
225
238
  - lib/generators/rolemodel/linters/rubocop/README.md
226
239
  - lib/generators/rolemodel/linters/rubocop/USAGE
227
240
  - lib/generators/rolemodel/linters/rubocop/rubocop_generator.rb
241
+ - lib/generators/rolemodel/linters/rubocop/templates/.rubocop.yml
242
+ - lib/generators/rolemodel/linters/rubocop/templates/.rubocop/cops/form_error_response.rb
243
+ - lib/generators/rolemodel/linters/rubocop/templates/.rubocop/cops/no_chrome_tag.rb
228
244
  - lib/generators/rolemodel/lograge/README.md
229
245
  - lib/generators/rolemodel/lograge/USAGE
230
246
  - lib/generators/rolemodel/lograge/lograge_generator.rb
@@ -248,6 +264,7 @@ files:
248
264
  - lib/generators/rolemodel/optics/base/base_generator.rb
249
265
  - lib/generators/rolemodel/optics/icons/README.md
250
266
  - lib/generators/rolemodel/optics/icons/USAGE
267
+ - lib/generators/rolemodel/optics/icons/icon_library_options.rb
251
268
  - lib/generators/rolemodel/optics/icons/icons_generator.rb
252
269
  - lib/generators/rolemodel/optics/icons/templates/app/helpers/icon_helper.rb.tt
253
270
  - lib/generators/rolemodel/react/README.md
@@ -357,9 +374,11 @@ files:
357
374
  - lib/generators/rolemodel/testing/parallel_tests/README.md
358
375
  - lib/generators/rolemodel/testing/parallel_tests/USAGE
359
376
  - lib/generators/rolemodel/testing/parallel_tests/parallel_tests_generator.rb
377
+ - lib/generators/rolemodel/testing/parallel_tests/templates/.rspec_parallel
360
378
  - lib/generators/rolemodel/testing/rspec/README.md
361
379
  - lib/generators/rolemodel/testing/rspec/USAGE
362
380
  - lib/generators/rolemodel/testing/rspec/rspec_generator.rb
381
+ - lib/generators/rolemodel/testing/rspec/templates/.rspec
363
382
  - lib/generators/rolemodel/testing/rspec/templates/spec/rails_helper.rb.tt
364
383
  - lib/generators/rolemodel/testing/rspec/templates/spec/spec_helper.rb.tt
365
384
  - lib/generators/rolemodel/testing/rspec/templates/spec/support/capybara_config.rb.tt
@@ -379,6 +398,12 @@ files:
379
398
  - lib/generators/rolemodel/testing/vitest/templates/spec/javascript/test-setup.js
380
399
  - lib/generators/rolemodel/testing/vitest/templates/vitest.config.js
381
400
  - lib/generators/rolemodel/testing/vitest/vitest_generator.rb
401
+ - lib/generators/rolemodel/tom_select/README.md
402
+ - lib/generators/rolemodel/tom_select/USAGE
403
+ - lib/generators/rolemodel/tom_select/templates/app/inputs/grouped_tom_select_input.rb
404
+ - lib/generators/rolemodel/tom_select/templates/app/inputs/tom_select_input.rb
405
+ - lib/generators/rolemodel/tom_select/templates/app/javascript/controllers/tom_select_controller.js
406
+ - lib/generators/rolemodel/tom_select/tom_select_generator.rb
382
407
  - lib/generators/rolemodel/turbo/all/README.md
383
408
  - lib/generators/rolemodel/turbo/all/USAGE
384
409
  - lib/generators/rolemodel/turbo/all/all_generator.rb
@@ -390,8 +415,6 @@ files:
390
415
  - lib/generators/rolemodel/turbo/form/README.md
391
416
  - lib/generators/rolemodel/turbo/form/USAGE
392
417
  - lib/generators/rolemodel/turbo/form/form_generator.rb
393
- - lib/generators/rolemodel/turbo/form/templates/app/javascript/controllers/turbo_form_controller.js
394
- - lib/generators/rolemodel/turbo/form/templates/spec/support/helpers/turbo_form_helper.rb
395
418
  - lib/generators/rolemodel/turbo/modals/README.md
396
419
  - lib/generators/rolemodel/turbo/modals/USAGE
397
420
  - lib/generators/rolemodel/turbo/modals/modals_generator.rb
@@ -1 +0,0 @@
1
- ../../../../../.github/instructions
@@ -1,15 +0,0 @@
1
- import { Controller } from '@hotwired/stimulus'
2
- import { patch } from '@rails/request.js'
3
-
4
- export default class extends Controller {
5
- static values = { url: String, count: Number }
6
-
7
- async perform({ params: { url: urlParam, query: queryParams } }) {
8
- const body = new FormData(this.element)
9
-
10
- if (queryParams) Object.keys(queryParams).forEach(key => body.append(key, queryParams[key]))
11
-
12
- const response = await patch(urlParam || this.urlValue, { body, responseKind: 'turbo-stream' })
13
- if (response.ok) this.countValue += 1
14
- }
15
- }
@@ -1,8 +0,0 @@
1
- module TurboFormHelper
2
- def expect_turbo_form_request
3
- count_value = find("[data-controller='turbo-form']")['data-turbo-form-count-value'] || 0
4
- yield
5
- expect(page).to have_selector("[data-turbo-form-count-value='#{count_value.to_i + 1}']"),
6
- 'Expected a successful Turbo-Form request, but none was detected'
7
- end
8
- end