Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ bin/rails g
* [GoodJob](./lib/generators/rolemodel/good_job)
* [Editors](./lib/generators/rolemodel/editors)
* [Tailored Select](./lib/generators/rolemodel/tailored_select)
* [Tom Select](./lib/generators/rolemodel/tom_select)
* [Lograge](./lib/generators/rolemodel/lograge)

## Utilities
Expand Down
13 changes: 12 additions & 1 deletion lib/generators/rolemodel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,31 @@

## What you get

* [Editors](./editors)
* [Github](./github)
* [GoodJob](./good_job)
* [Heroku](./heroku)
* [Kaminari](./kaminari)
* [Linters](./linters)
* [Lograge](./lograge)
* [Mailers](./mailers)
* [Modals](./modals)
* [Optics](./optics)
* [React](./react)
* [README](./readme)
* [SaaS](./saas)
* [Sentry](./sentry)
* [SimpleForm](./simple_form)
* [Slim](./slim)
* [SoftDestroyable](./soft_destroyable)
* [Source Map](./source_map)
* [Testing](./testing)
* [Tom Select](./tom_select)
* [Turbo](./turbo/all)
* [Ready](./turbo/ready)
* [Confirm](./turbo/confirm)
* [Modals](./turbo/modals)
* [Form](./turbo/form)
* [UI Components](./ui_components)
* [Webpack](./webpack)

## Helpful documentation
Expand Down
2 changes: 2 additions & 0 deletions lib/generators/rolemodel/all_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ def run_all_the_generators
generate 'rolemodel:slim'
generate 'rolemodel:optics:all'
generate 'rolemodel:testing:all'
generate 'rolemodel:turbo:all'
generate 'rolemodel:simple_form'
generate 'rolemodel:tom_select'
generate 'rolemodel:soft_destroyable'
generate 'rolemodel:saas:all'
generate 'rolemodel:mailers'
Expand Down
1 change: 1 addition & 0 deletions lib/generators/rolemodel/core_setup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ app you can push straight to Heroku right after generation.
- [Testing](../testing) — RSpec, FactoryBot, parallel_tests, TestProf
(pass `--js-runner` to include jasmine-playwright-runner)
- [SimpleForm](../simple_form) — SimpleForm with our configuration
- [Tom Select](../tom_select) — Tom Select with a Stimulus controller and SimpleForm inputs
- [Linters](../linters) — Rubocop and ESLint
- [UI Components](../ui_components) — flash, the modal pattern, & Turbo 8 support
- [Editors](../editors) — EditorConfig and recommended VSCode extensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def run_the_core_generators
generate 'rolemodel:webpack'
generate 'rolemodel:optics:all'
generate 'rolemodel:simple_form'
generate 'rolemodel:tom_select'
generate 'rolemodel:testing:all'
generate 'rolemodel:turbo:all'
generate 'rolemodel:ui_components:flash'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ class SimpleFormGenerator < GeneratorBase
desc: 'Install the tailored_select experimental component input'

def add_gem
Bundler.with_unbundled_env do
bundle_command 'add simple_form'
end
bundle_command 'add simple_form'
end

def add_files
Expand Down
15 changes: 15 additions & 0 deletions lib/generators/rolemodel/tom_select/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Tom Select Generator

`rails g rolemodel:tom_select`

## What you get

* The [Tom Select](https://tom-select.js.org/) package and its stylesheet
* A `tom-select` Stimulus controller that survives Turbo morphs
* `tom_select` and `grouped_tom_select` SimpleForm inputs, if the app uses SimpleForm

```slim
= f.input :project_manager, as: :tom_select, collection: User.all
= f.input :substance, as: :tom_select, collection: Substance.all, allow_create: true
= f.input :material, as: :grouped_tom_select, collection: Category.all, group_method: :materials
```
7 changes: 7 additions & 0 deletions lib/generators/rolemodel/tom_select/USAGE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Description:
Installs the tom-select package, its stylesheet, and a tom-select Stimulus
controller. If the app is using SimpleForm, it also installs the matching
tom_select and grouped_tom_select SimpleForm inputs.

Example:
rails generate rolemodel:tom_select
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

class GroupedTomSelectInput < SimpleForm::Inputs::GroupedCollectionSelectInput
def input(wrapper_options = nil)
super(merge_wrapper_options(tom_select_options, wrapper_options))
end

def input_html_classes = []

private

def tom_select_options
{ data: { controller: 'tom-select', action: TomSelectInput::MORPH_ACTIONS } }
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

class TomSelectInput < SimpleForm::Inputs::CollectionSelectInput
MORPH_ACTIONS = 'turbo:before-morph-element->tom-select#disconnect:self turbo:morph-element->tom-select#afterMorph:self'

def input(wrapper_options = nil)
super(merge_wrapper_options(tom_select_options, wrapper_options))
end

def input_html_classes = []

private

def tom_select_options
{ data: { controller: 'tom-select', tom_select_create_value: options.delete(:allow_create) || false, action: MORPH_ACTIONS } }
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Controller } from '@hotwired/stimulus'
import TomSelect from 'tom-select'

export default class extends Controller {
static values = { create: Boolean }

connect() {
this.tomSelect = new TomSelect(this.element, this.settings)
this.element.tomSelect = this.tomSelect
}

disconnect() {
// In case a Turbo morph is happening, we need to update TomSelect's revertSettings,
// in order to preserve the current value(s) when reconnecting.
this.tomSelect.revertSettings.innerHTML = this.element.innerHTML

this.tomSelect.destroy()
delete this.element.tomSelect
}

afterMorph() {
queueMicrotask(() => this.connect())
}

get settings() {
return {
create: this.createValue,
persist: false,
maxOptions: null,
delimiter: null,
maxItems: this.element.multiple ? null : 1,
plugins: this.element.multiple ? ['remove_button'] : [],
onItemAdd: () => this.tomSelect.setTextboxValue(''), // clear the input text box when a selection is made
}
}
}
43 changes: 43 additions & 0 deletions lib/generators/rolemodel/tom_select/tom_select_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

module Rolemodel
class TomSelectGenerator < GeneratorBase
source_root File.expand_path('templates', __dir__)

def add_tom_select_package
say 'Installing Tom Select package', :green

yarn_command 'add tom-select'
end

def import_stylesheet
say 'Importing Tom Select stylesheet', :green

prepend_to_file application_stylesheet_path, <<~CSS
@import 'tom-select/dist/css/tom-select.css';
CSS
end

def add_stimulus_controller
say 'Adding Tom Select Stimulus controller', :green

directory 'app/javascript/controllers'

rails_command 'stimulus:manifest:update'
end

def add_simple_form_inputs
return unless simple_form?

say 'Installing the Tom Select SimpleForm inputs', :green

directory 'app/inputs'
end

private

def simple_form?
File.exist?(File.expand_path('config/initializers/simple_form.rb', destination_root))
end
end
end
56 changes: 18 additions & 38 deletions spec/generators/rolemodel/all_generator_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
RSpec.describe Rolemodel::AllGenerator, type: :generator do
# Each sub-generator is exercised by its own spec, so here we only verify
# which generators get delegated to (and in what order).
# which generators get delegated to.
let(:invoked_generators) { [] }
let(:generators_root) { File.expand_path('../../../lib/generators/rolemodel', __dir__) }
let(:excluded_generators) do
{
'rolemodel:core_setup' => 'a subset of rolemodel:all',
'rolemodel:tailored_select' => 'not production ready'
}
end

# A directory with its own all generator is delegated to as a group.
def generators_on_disk
Dir.children(generators_root).select { File.directory?(File.join(generators_root, it)) }.map do |dir|
grouped = Dir.exist?(File.join(generators_root, dir, 'all')) || File.exist?(File.join(generators_root, dir, 'all_generator.rb'))
grouped ? "rolemodel:#{dir}:all" : "rolemodel:#{dir}"
end
end

before do
# Stub on the Actions module rather than the generator class: stubbing the
Expand All @@ -14,42 +29,7 @@
run_generators
end

it 'delegates to every rolemodel generator in order' do
expect(invoked_generators).to eq([
'rolemodel:github',
'rolemodel:heroku',
'rolemodel:readme',
'rolemodel:webpack',
'rolemodel:sentry',
'rolemodel:react',
'rolemodel:slim',
'rolemodel:optics:all',
'rolemodel:testing:all',
'rolemodel:simple_form',
'rolemodel:soft_destroyable',
'rolemodel:saas:all',
'rolemodel:mailers',
'rolemodel:linters:all',
'rolemodel:ui_components:all',
'rolemodel:source_map',
'rolemodel:good_job',
'rolemodel:kaminari',
'rolemodel:editors',
'rolemodel:lograge'
])
end

it 'only delegates to generators that exist' do
unknown = invoked_generators.reject { |name| Rails::Generators.find_by_namespace(name) }

expect(unknown).to eq([])
end

it 'does not delegate to tailored_select, which is not production ready' do
expect(invoked_generators).not_to include('rolemodel:tailored_select')
end

it 'does not delegate to a semaphore generator' do
expect(invoked_generators).not_to include(a_string_matching(/semaphore/))
it 'delegates to every generator on disk that is not excluded' do
expect(invoked_generators).to match_array(generators_on_disk - excluded_generators.keys)
end
end
39 changes: 39 additions & 0 deletions spec/generators/rolemodel/tom_select_generator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
RSpec.describe Rolemodel::TomSelectGenerator, type: :generator do
it 'adds tom-select to package.json' do
run_generators

assert_file 'package.json' do |content|
expect(content).to include('"tom-select":')
end
end

it 'imports the tom-select stylesheet' do
run_generators

assert_file 'app/assets/stylesheets/application.css' do |content|
expect(content).to include("@import 'tom-select/dist/css/tom-select.css';")
end
end

it 'registers the stimulus controller' do
run_generators

assert_file 'app/javascript/controllers/tom_select_controller.js'
assert_file 'app/javascript/controllers/index.js' do |content|
expect(content).to include('application.register("tom-select", TomSelectController)')
end
end

it 'does not install the SimpleForm inputs when SimpleForm is absent' do
run_generators

assert_no_file 'app/inputs/tom_select_input.rb'
end

it 'installs the SimpleForm inputs when SimpleForm is present' do
run_generators generators: [::Rolemodel::SimpleFormGenerator, described_class]

assert_file 'app/inputs/tom_select_input.rb'
assert_file 'app/inputs/grouped_tom_select_input.rb'
end
end
Loading