turbo_turbo 0.2.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 +7 -0
- data/.claude/settings.local.json +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +96 -0
- data/CHANGELOG.md +50 -0
- data/LICENSE +21 -0
- data/README.md +498 -0
- data/Rakefile +12 -0
- data/app/assets/stylesheets/turbo_turbo/alerts.css +7 -0
- data/app/assets/stylesheets/turbo_turbo/base.css +4 -0
- data/app/assets/stylesheets/turbo_turbo/button.css +24 -0
- data/app/assets/stylesheets/turbo_turbo/modal.css +81 -0
- data/app/components/turbo_turbo/alerts/alert_component.html.erb +36 -0
- data/app/components/turbo_turbo/alerts/alert_component.rb +12 -0
- data/app/components/turbo_turbo/alerts/error_component.html.erb +36 -0
- data/app/components/turbo_turbo/alerts/error_component.rb +12 -0
- data/app/components/turbo_turbo/alerts/info_component.html.erb +36 -0
- data/app/components/turbo_turbo/alerts/info_component.rb +12 -0
- data/app/components/turbo_turbo/alerts/success_component.html.erb +47 -0
- data/app/components/turbo_turbo/alerts/success_component.rb +12 -0
- data/app/components/turbo_turbo/alerts/warning_component.html.erb +36 -0
- data/app/components/turbo_turbo/alerts/warning_component.rb +12 -0
- data/app/components/turbo_turbo/modal_component.html.erb +20 -0
- data/app/components/turbo_turbo/modal_component.rb +9 -0
- data/app/components/turbo_turbo/modal_footer_component.html.erb +6 -0
- data/app/components/turbo_turbo/modal_footer_component.rb +10 -0
- data/config/locales/en.yml +15 -0
- data/config/routes/turbo_turbo_routes.rb +20 -0
- data/lib/generators/turbo_turbo/install_generator.rb +351 -0
- data/lib/generators/turbo_turbo/layout_generator.rb +221 -0
- data/lib/generators/turbo_turbo/templates/config/routes/turbo_turbo_routes.rb +20 -0
- data/lib/generators/turbo_turbo/templates/turbo_turbo/_error_message.html.erb +15 -0
- data/lib/generators/turbo_turbo/templates/turbo_turbo/_flashes.html.erb +8 -0
- data/lib/generators/turbo_turbo/templates/turbo_turbo/_modal_background.html.erb +2 -0
- data/lib/generators/turbo_turbo/templates/turbo_turbo/flash_controller.js +28 -0
- data/lib/generators/turbo_turbo/templates/turbo_turbo/modal_controller.js +114 -0
- data/lib/generators/turbo_turbo/views_generator.rb +57 -0
- data/lib/turbo_turbo/controller_helpers.rb +157 -0
- data/lib/turbo_turbo/engine.rb +19 -0
- data/lib/turbo_turbo/form_helper.rb +135 -0
- data/lib/turbo_turbo/parameter_sanitizer.rb +69 -0
- data/lib/turbo_turbo/standard_actions.rb +96 -0
- data/lib/turbo_turbo/test_helpers.rb +64 -0
- data/lib/turbo_turbo/version.rb +5 -0
- data/lib/turbo_turbo.rb +15 -0
- data/sig/turbo_turbo.rbs +4 -0
- data/turbo_turbo.gemspec +42 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 54f42ce2e6f7ed159b2d7aec601352045eecc86b5619f528fc85c8e091f47d76
|
4
|
+
data.tar.gz: ea4af57484afb31fe80efc194f48db5104b8edd487378244848799ef7a30542e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '08e33ca5a2884173f62eaad731be0a9a0db1f95e7478ceb22c5dbaf008ef929b6070e91e75009176e3a932196e9394985a476edecd65bca587f9c44c79a7a1f7'
|
7
|
+
data.tar.gz: 80fcff79f36604c58899de2433d3b88315565b4b4f081c446c56f698c37235d1554a2296b99495c82b6fb1965f173b287301c019037586ac1554a02df554f9fb
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
AllCops:
|
2
|
+
NewCops: enable
|
3
|
+
TargetRubyVersion: 3.3.6
|
4
|
+
Exclude:
|
5
|
+
- 'bin/**/*'
|
6
|
+
- 'log/**/*'
|
7
|
+
- 'tmp/**/*'
|
8
|
+
- 'vendor/**/*'
|
9
|
+
- 'node_modules/**/*'
|
10
|
+
|
11
|
+
# Exclude generator files from complex metrics since they are one-time setup code
|
12
|
+
Metrics/ClassLength:
|
13
|
+
Exclude:
|
14
|
+
- 'lib/generators/**/*'
|
15
|
+
- 'spec/**/*'
|
16
|
+
|
17
|
+
Metrics/MethodLength:
|
18
|
+
Exclude:
|
19
|
+
- 'lib/generators/**/*'
|
20
|
+
- 'spec/**/*'
|
21
|
+
- 'lib/turbo_turbo/form_helper.rb'
|
22
|
+
- 'lib/turbo_turbo/controller_helpers.rb'
|
23
|
+
|
24
|
+
Metrics/AbcSize:
|
25
|
+
Exclude:
|
26
|
+
- 'lib/generators/**/*'
|
27
|
+
- 'spec/**/*'
|
28
|
+
- 'lib/turbo_turbo/form_helper.rb'
|
29
|
+
|
30
|
+
Metrics/CyclomaticComplexity:
|
31
|
+
Exclude:
|
32
|
+
- 'lib/generators/**/*'
|
33
|
+
- 'spec/**/*'
|
34
|
+
- 'lib/turbo_turbo/form_helper.rb'
|
35
|
+
|
36
|
+
Metrics/PerceivedComplexity:
|
37
|
+
Exclude:
|
38
|
+
- 'lib/generators/**/*'
|
39
|
+
- 'spec/**/*'
|
40
|
+
- 'lib/turbo_turbo/form_helper.rb'
|
41
|
+
|
42
|
+
Metrics/BlockLength:
|
43
|
+
Exclude:
|
44
|
+
- 'spec/**/*'
|
45
|
+
- '*.gemspec'
|
46
|
+
- 'lib/turbo_turbo/standard_actions.rb'
|
47
|
+
|
48
|
+
Metrics/ModuleLength:
|
49
|
+
Exclude:
|
50
|
+
- 'spec/**/*'
|
51
|
+
- 'lib/turbo_turbo/controller_helpers.rb'
|
52
|
+
|
53
|
+
Layout/LineLength:
|
54
|
+
Max: 120
|
55
|
+
Exclude:
|
56
|
+
- 'lib/generators/**/*'
|
57
|
+
- 'spec/**/*'
|
58
|
+
- 'lib/turbo_turbo/form_helper.rb'
|
59
|
+
|
60
|
+
# ViewComponent `initialize` methods don't need to call super
|
61
|
+
# This is a false positive - ViewComponent handles initialization differently
|
62
|
+
Lint/MissingSuper:
|
63
|
+
Exclude:
|
64
|
+
- 'app/components/**/*'
|
65
|
+
|
66
|
+
# Allow constants in blocks for specs
|
67
|
+
Lint/ConstantDefinitionInBlock:
|
68
|
+
Exclude:
|
69
|
+
- 'spec/**/*'
|
70
|
+
|
71
|
+
# Specs can use predicate methods without ?
|
72
|
+
Naming/PredicateMethod:
|
73
|
+
Exclude:
|
74
|
+
- 'spec/**/*'
|
75
|
+
|
76
|
+
# Allow getter/setter methods in specs
|
77
|
+
Naming/AccessorMethodName:
|
78
|
+
Exclude:
|
79
|
+
- 'spec/**/*'
|
80
|
+
|
81
|
+
Style/StringLiterals:
|
82
|
+
EnforcedStyle: double_quotes
|
83
|
+
|
84
|
+
# Allow boolean parameters in specs
|
85
|
+
Style/OptionalBooleanParameter:
|
86
|
+
Exclude:
|
87
|
+
- 'spec/**/*'
|
88
|
+
|
89
|
+
# Allow string concatenation in specs
|
90
|
+
Style/StringConcatenation:
|
91
|
+
Exclude:
|
92
|
+
- 'spec/**/*'
|
93
|
+
|
94
|
+
# Documentation is handled in README for this gem
|
95
|
+
Style/Documentation:
|
96
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [0.2.0] - 2025-01-23
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
- Renamed routes file from `turbo_turbo.rb` to `turbo_turbo_routes.rb` to prevent loading issues
|
12
|
+
- Updated install generator to use the new routes filename
|
13
|
+
- Fixed routes configuration to use `draw(:turbo_turbo_routes)` instead of `draw(:turbo_turbo)`
|
14
|
+
|
15
|
+
### Added
|
16
|
+
- Routes spec to ensure routes file loads properly
|
17
|
+
|
18
|
+
## [0.1.0] - 2025-01-15
|
19
|
+
|
20
|
+
### Added
|
21
|
+
- Initial release of TurboTurbo gem
|
22
|
+
- Controller helpers for standardized Turbo Stream responses
|
23
|
+
- Modal management system with JavaScript controller
|
24
|
+
- Flash message handling with auto-dismiss functionality
|
25
|
+
- ViewComponent alert components (Success, Error, Warning, Info, Alert)
|
26
|
+
- Rails generator for easy installation with automatic configuration
|
27
|
+
- Complete test suite with RSpec
|
28
|
+
- Comprehensive documentation and usage examples
|
29
|
+
- Enhanced install generator with automatic ApplicationController setup
|
30
|
+
- Automatic CSS import handling
|
31
|
+
- Custom layout generator for multi-layout applications
|
32
|
+
- Form helper for standardized modal forms with builder support
|
33
|
+
- Parameter sanitization with `sanitize_for_model` method
|
34
|
+
- `turbo_actions` DSL for convention-based CRUD operations
|
35
|
+
- Test helpers for system and integration testing
|
36
|
+
|
37
|
+
### Features
|
38
|
+
- `process_turbo_response` for handling CRUD operations
|
39
|
+
- `render_turbo_modal` for modal workflows
|
40
|
+
- `turbo_success_response` and `turbo_error_response` for custom responses
|
41
|
+
- `turbo_form_for` form helper with SimpleForm, form_with, and form_for support
|
42
|
+
- `turbo_actions` DSL supporting :create, :update, :destroy, :show, :new, :edit
|
43
|
+
- Automatic flash message generation with proper grammar and I18n support
|
44
|
+
- Modal controller with remote content loading
|
45
|
+
- Flash controller with fade-out animations
|
46
|
+
- Parameter sanitization with boolean conversion and string trimming
|
47
|
+
- Smart install generator that configures ApplicationController and imports CSS
|
48
|
+
- Rails 7+ compatibility
|
49
|
+
- Turbo-rails integration
|
50
|
+
- ViewComponent integration
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Dan Brown
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,498 @@
|
|
1
|
+
# TurboTurbo
|
2
|
+
|
3
|
+
A simplified DSL for responding in common ways to common controller actions so you can write as little code in CRUD routes as possible. TurboTurbo provides a set of controller helpers that streamline Turbo Stream responses, modal management, and flash message handling in Rails applications.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- **Convention-based DSL** - Eliminate CRUD boilerplate with `turbo_actions :create, :update, :destroy`
|
8
|
+
- **Modal form abstraction** - Transform 10+ lines of form boilerplate into 3 lines
|
9
|
+
- **Smart parameter sanitization** - Automatic boolean conversion and string trimming with `sanitize_for_model`
|
10
|
+
- **Simplified Turbo Stream responses** - Standardized patterns for all CRUD operations
|
11
|
+
- **Modal management** - Complete modal system with namespaced Stimulus controllers
|
12
|
+
- **Flash message handling** - Automatic flash message generation with I18n support
|
13
|
+
- **ViewComponent integration** - Pre-built alert and modal components
|
14
|
+
- **Configurable action groups** - Customize Turbo Stream behaviors by action type
|
15
|
+
- **Rails 7+ compatible** - Built for modern Rails applications
|
16
|
+
- **Zero dependencies** - No external template engines or SVG processors required
|
17
|
+
- **Test helpers included** - Built-in helpers for system/integration testing
|
18
|
+
|
19
|
+
## Quick Start
|
20
|
+
|
21
|
+
### 1. Installation
|
22
|
+
|
23
|
+
Add this line to your application's Gemfile:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
gem 'turbo_turbo'
|
27
|
+
```
|
28
|
+
|
29
|
+
And then execute:
|
30
|
+
|
31
|
+
```bash
|
32
|
+
bundle install
|
33
|
+
rails generate turbo_turbo:install
|
34
|
+
```
|
35
|
+
|
36
|
+
**⚠️ Important: Restart your Rails server after installation for the gem's helpers to be available.**
|
37
|
+
|
38
|
+
### 2. Start using the DSL
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class MessagesController < ApplicationController
|
42
|
+
turbo_actions :create, :update, :destroy, :show, :new, :edit
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def message_params
|
47
|
+
params.require(:message)
|
48
|
+
.permit(:content, :title)
|
49
|
+
.sanitize_for_model(:message)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
## What the Installer Does
|
55
|
+
|
56
|
+
The installer automatically:
|
57
|
+
- **Copies namespaced JavaScript controllers** to `app/javascript/controllers/turbo_turbo/`
|
58
|
+
- **Registers controllers** (`turbo-turbo--modal`, `turbo-turbo--flash`) in Stimulus index.js
|
59
|
+
- **Configures ApplicationController** with `TurboTurbo::ControllerHelpers` and flash types
|
60
|
+
- **Copies TurboTurbo CSS** to `app/assets/stylesheets/turbo_turbo/` (includes component CSS files and base.css, which just imports the component CSS files)
|
61
|
+
- **Imports TurboTurbo CSS** into your main stylesheet
|
62
|
+
- **Copies layout partials** for modal background and flashes to `app/views/turbo_turbo/`
|
63
|
+
- **Intelligently modifies your layout file** to include required components
|
64
|
+
- **ViewComponents work directly from the gem** (no copying required)
|
65
|
+
|
66
|
+
### Automatic Layout Setup
|
67
|
+
|
68
|
+
The installer automatically modifies your `app/views/layouts/application.html.erb` or `application.html.slim` to:
|
69
|
+
|
70
|
+
✅ **Add `turbo-turbo--modal` controller** to existing `data-controller` attribute, or create it if missing
|
71
|
+
✅ **Insert flash messages render** after the body tag
|
72
|
+
✅ **Insert modal background render** after flashes
|
73
|
+
✅ **Append TurboTurbo::ModalComponent** at the end of the body
|
74
|
+
✅ **Configure ApplicationController** with TurboTurbo helpers and flash types
|
75
|
+
✅ **Copy TurboTurbo CSS** to `app/assets/stylesheets/turbo_turbo/` (all CSS files)
|
76
|
+
✅ **Import TurboTurbo CSS** into your main stylesheet
|
77
|
+
|
78
|
+
**Before:**
|
79
|
+
```erb
|
80
|
+
<body data-controller="navigation search">
|
81
|
+
<%= yield %>
|
82
|
+
</body>
|
83
|
+
```
|
84
|
+
|
85
|
+
**After installation:**
|
86
|
+
```erb
|
87
|
+
<body data-controller="navigation search turbo-turbo--modal">
|
88
|
+
<%= render 'turbo_turbo/flashes' %>
|
89
|
+
<%= render 'turbo_turbo/modal_background' %>
|
90
|
+
<%= yield %>
|
91
|
+
<%= render TurboTurbo::ModalComponent.new %>
|
92
|
+
</body>
|
93
|
+
```
|
94
|
+
|
95
|
+
### Custom Layout Installation
|
96
|
+
|
97
|
+
For custom layouts (admin, dashboard, etc.):
|
98
|
+
|
99
|
+
```bash
|
100
|
+
# Basic usage - adds all components to admin layout
|
101
|
+
rails generate turbo_turbo:layout admin
|
102
|
+
|
103
|
+
# Skip flash-related components only
|
104
|
+
rails generate turbo_turbo:layout admin --skip-flash
|
105
|
+
|
106
|
+
# Skip modal-related components only
|
107
|
+
rails generate turbo_turbo:layout admin --skip-modal
|
108
|
+
|
109
|
+
# Skip everything - prints warning and exits
|
110
|
+
rails generate turbo_turbo:layout admin --skip-flash --skip-modal
|
111
|
+
```
|
112
|
+
|
113
|
+
## Core Features
|
114
|
+
|
115
|
+
### 1. Convention-Based DSL
|
116
|
+
|
117
|
+
**Before (traditional approach):**
|
118
|
+
```ruby
|
119
|
+
class MessagesController < ApplicationController
|
120
|
+
def create
|
121
|
+
@message = Message.new(message_params)
|
122
|
+
process_turbo_response(@message, @message.save)
|
123
|
+
end
|
124
|
+
|
125
|
+
def update
|
126
|
+
@message = Message.find(params[:id])
|
127
|
+
process_turbo_response(@message, @message.update(message_params))
|
128
|
+
end
|
129
|
+
|
130
|
+
def destroy
|
131
|
+
@message = Message.find(params[:id])
|
132
|
+
process_turbo_response(@message, @message.destroy)
|
133
|
+
end
|
134
|
+
|
135
|
+
# ... more CRUD actions
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
**After (with TurboTurbo DSL):**
|
140
|
+
```ruby
|
141
|
+
class MessagesController < ApplicationController
|
142
|
+
turbo_actions :create, :update, :destroy, :show, :new, :edit
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def message_params
|
147
|
+
params.require(:message).permit(:content, :title)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
**Supported Actions:**
|
153
|
+
- `:create` - Creates new model instance and calls `process_turbo_response`
|
154
|
+
- `:update` - Finds and updates model instance
|
155
|
+
- `:destroy` - Finds and destroys model instance
|
156
|
+
- `:show` - Finds model instance and renders modal
|
157
|
+
- `:new` - Creates new model instance and renders modal
|
158
|
+
- `:edit` - Finds model instance and renders modal
|
159
|
+
|
160
|
+
**Requirements:**
|
161
|
+
- Controller must follow Rails naming conventions (`MessagesController` → `Message` model)
|
162
|
+
- Must define a params method (e.g., `message_params`)
|
163
|
+
- Works with any model that responds to `save`, `update`, and `destroy`
|
164
|
+
|
165
|
+
**Advanced Customization:**
|
166
|
+
```ruby
|
167
|
+
class MessagesController < ApplicationController
|
168
|
+
turbo_actions :create, :update, :destroy
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
# Custom params for create vs update
|
173
|
+
def create_params
|
174
|
+
params.require(:message).permit(:content, :title, :author_id)
|
175
|
+
end
|
176
|
+
|
177
|
+
def message_params
|
178
|
+
params.require(:message).permit(:content, :title)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Custom eager loading per action
|
182
|
+
def model_instance_for_show
|
183
|
+
Message.includes(:author, :comments).find(params[:id])
|
184
|
+
end
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
### 2. Modal Form Abstraction
|
189
|
+
|
190
|
+
**Before (what you had to write):**
|
191
|
+
```erb
|
192
|
+
<%= simple_form_for service_provider, url: [:admin, service_provider],
|
193
|
+
data: {
|
194
|
+
turbo_turbo__modal_target: "form",
|
195
|
+
action: "turbo:submit-end->turbo-turbo--modal#closeOnSuccess"
|
196
|
+
} do |form| %>
|
197
|
+
<div class="ModalContent-turbo-turbo">
|
198
|
+
<div id="error_message_turbo_turbo"></div>
|
199
|
+
<!-- your form fields -->
|
200
|
+
</div>
|
201
|
+
<% end %>
|
202
|
+
```
|
203
|
+
|
204
|
+
**After (with TurboTurbo form helper):**
|
205
|
+
```erb
|
206
|
+
<%= turbo_form_for(service_provider, url: [:admin, service_provider]) do |form| %>
|
207
|
+
<!-- just your form fields -->
|
208
|
+
<% end %>
|
209
|
+
```
|
210
|
+
|
211
|
+
**Form Builder Support:**
|
212
|
+
```erb
|
213
|
+
<!-- SimpleForm (default) -->
|
214
|
+
<%= turbo_form_for(object) do |form| %>
|
215
|
+
<%= form.input :name %>
|
216
|
+
<% end %>
|
217
|
+
|
218
|
+
<!-- Rails form_with -->
|
219
|
+
<%= turbo_form_for(object, builder: :form_with) do |form| %>
|
220
|
+
<%= form.text_field :name %>
|
221
|
+
<% end %>
|
222
|
+
|
223
|
+
<!-- Rails form_for -->
|
224
|
+
<%= turbo_form_for(object, builder: :form_for) do |form| %>
|
225
|
+
<%= form.text_field :name %>
|
226
|
+
<% end %>
|
227
|
+
```
|
228
|
+
|
229
|
+
**Customization Options:**
|
230
|
+
```erb
|
231
|
+
<%= turbo_form_for(object,
|
232
|
+
url: custom_path, # Custom form URL
|
233
|
+
data: { custom: "attribute" } # Additional data attributes
|
234
|
+
) do |form| %>
|
235
|
+
<!-- fields -->
|
236
|
+
<% end %>
|
237
|
+
```
|
238
|
+
|
239
|
+
**The form helper automatically:**
|
240
|
+
- Wraps your form in the proper modal structure
|
241
|
+
- Sets up Turbo Stream integration for modal closing
|
242
|
+
- Handles error message containers
|
243
|
+
- Integrates with all major Rails form builders
|
244
|
+
|
245
|
+
### 3. Parameter Sanitization
|
246
|
+
|
247
|
+
TurboTurbo extends `ActionController::Parameters` with a powerful `sanitize_for_model` method that automatically:
|
248
|
+
|
249
|
+
- **Converts boolean fields** from strings (\"true\"/\"false\", \"1\"/\"0\") to actual booleans based on your model schema
|
250
|
+
- **Trims whitespace** from all string inputs
|
251
|
+
- **Cleans arrays** by trimming strings and removing empty values
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
class MessagesController < ApplicationController
|
255
|
+
private
|
256
|
+
|
257
|
+
def message_params
|
258
|
+
params.require(:message)
|
259
|
+
.permit(:title, :content, :active, :priority, tags: [])
|
260
|
+
.sanitize_for_model(:message)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
```
|
264
|
+
|
265
|
+
**Before sanitization:**
|
266
|
+
```ruby
|
267
|
+
params = {
|
268
|
+
title: " My Message ",
|
269
|
+
content: " Hello world ",
|
270
|
+
active: "true", # String from HTML form
|
271
|
+
priority: "1", # String from HTML form
|
272
|
+
tags: [" ruby ", "", " rails ", nil]
|
273
|
+
}
|
274
|
+
```
|
275
|
+
|
276
|
+
**After sanitization:**
|
277
|
+
```ruby
|
278
|
+
{
|
279
|
+
title: "My Message", # Trimmed
|
280
|
+
content: "Hello world", # Trimmed
|
281
|
+
active: true, # Converted to boolean
|
282
|
+
priority: true, # Converted to boolean (if priority is boolean column)
|
283
|
+
tags: ["ruby", "rails"] # Trimmed and cleaned
|
284
|
+
}
|
285
|
+
```
|
286
|
+
|
287
|
+
The method is **model-aware** - it only converts fields that are actually boolean columns in your database schema, leaving other fields (like integer `priority`) unchanged.
|
288
|
+
|
289
|
+
### 4. Modal Workflows
|
290
|
+
|
291
|
+
Create modal forms that integrate seamlessly with Turbo:
|
292
|
+
|
293
|
+
```erb
|
294
|
+
<!-- Trigger modal with remote content -->
|
295
|
+
<%= link_to "New Message",
|
296
|
+
new_message_path,
|
297
|
+
data: {
|
298
|
+
action: "turbo-turbo--modal#setRemoteSource",
|
299
|
+
url: new_message_path,
|
300
|
+
title: "Create New Message",
|
301
|
+
subtitle: "Fill out the form below"
|
302
|
+
},
|
303
|
+
class: "btn btn-primary" %>
|
304
|
+
|
305
|
+
<!-- Modal will automatically open and load the form -->
|
306
|
+
```
|
307
|
+
|
308
|
+
For custom behavior, you can still use the helper methods directly:
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
class MessagesController < ApplicationController
|
312
|
+
def create
|
313
|
+
message = Message.new(message_params)
|
314
|
+
process_turbo_response(message, message.save)
|
315
|
+
end
|
316
|
+
|
317
|
+
def update
|
318
|
+
message = Message.find(params[:id])
|
319
|
+
process_turbo_response(message, message.update(message_params))
|
320
|
+
end
|
321
|
+
|
322
|
+
def destroy
|
323
|
+
message = Message.find(params[:id])
|
324
|
+
process_turbo_response(message, message.destroy)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
```
|
328
|
+
|
329
|
+
## ViewComponents
|
330
|
+
|
331
|
+
TurboTurbo includes several pre-built ViewComponents that work directly from the gem, all properly namespaced under the `TurboTurbo` module:
|
332
|
+
|
333
|
+
### Modal Components
|
334
|
+
|
335
|
+
**TurboTurbo::ModalComponent**
|
336
|
+
- Main modal wrapper component
|
337
|
+
- Options: `show_close: true/false` - Controls visibility of close button
|
338
|
+
- Usage: `<%= render TurboTurbo::ModalComponent.new(show_close: true) %>`
|
339
|
+
|
340
|
+
**TurboTurbo::ModalFooterComponent**
|
341
|
+
- Modal footer with cancel button and content area
|
342
|
+
- Options: `skip_close: true/false`, `close_label: "Custom Cancel Text"`
|
343
|
+
- Usage: `<%= render TurboTurbo::ModalFooterComponent.new(close_label: "Close") { content } %>`
|
344
|
+
|
345
|
+
### Alert Components
|
346
|
+
|
347
|
+
TurboTurbo includes pre-styled alert components, all namespaced under `TurboTurbo::Alerts`:
|
348
|
+
|
349
|
+
- `TurboTurbo::Alerts::SuccessComponent`
|
350
|
+
- `TurboTurbo::Alerts::ErrorComponent`
|
351
|
+
- `TurboTurbo::Alerts::WarningComponent`
|
352
|
+
- `TurboTurbo::Alerts::InfoComponent`
|
353
|
+
- `TurboTurbo::Alerts::AlertComponent` (base component)
|
354
|
+
|
355
|
+
All alert components support:
|
356
|
+
- Custom headers and messages
|
357
|
+
- Array of messages for lists
|
358
|
+
- Dismissible functionality
|
359
|
+
- Consistent styling
|
360
|
+
|
361
|
+
Example usage:
|
362
|
+
```erb
|
363
|
+
<%= render TurboTurbo::Alerts::SuccessComponent.new({ header: "Success!", message: "Operation completed" }) %>
|
364
|
+
<%= render TurboTurbo::Alerts::ErrorComponent.new({ message: ["Error 1", "Error 2"] }) %>
|
365
|
+
```
|
366
|
+
|
367
|
+
## Customization
|
368
|
+
|
369
|
+
### ViewComponents Customization
|
370
|
+
|
371
|
+
To customize ViewComponents, copy them to your application:
|
372
|
+
|
373
|
+
```bash
|
374
|
+
rails generate turbo_turbo:views
|
375
|
+
```
|
376
|
+
|
377
|
+
This copies all ViewComponent classes and templates to your `app/components/turbo_turbo/` directory for customization. Components in your app directory will override the gem versions.
|
378
|
+
|
379
|
+
### I18n Flash Messages
|
380
|
+
|
381
|
+
TurboTurbo supports I18n for flash messages. Add to your locale files:
|
382
|
+
|
383
|
+
```yaml
|
384
|
+
# config/locales/en.yml
|
385
|
+
en:
|
386
|
+
turbo_turbo:
|
387
|
+
flash:
|
388
|
+
success:
|
389
|
+
create: "%{resource} created successfully!"
|
390
|
+
destroy: "%{resource} deleted!"
|
391
|
+
archive: "%{resource} archived!"
|
392
|
+
# Add custom actions...
|
393
|
+
error:
|
394
|
+
default: "We encountered an error trying to %{action} %{resource}."
|
395
|
+
```
|
396
|
+
|
397
|
+
### Action Groups Configuration
|
398
|
+
|
399
|
+
Customize which actions trigger different Turbo Stream behaviors:
|
400
|
+
|
401
|
+
```ruby
|
402
|
+
# In an initializer or controller
|
403
|
+
TurboTurbo::ControllerHelpers.turbo_response_actions[:remove] << "archive"
|
404
|
+
TurboTurbo::ControllerHelpers.error_response_actions[:validation_errors] << "upload"
|
405
|
+
```
|
406
|
+
|
407
|
+
Available action groups:
|
408
|
+
- **turbo_response_actions**: `:prepend`, `:replace`, `:remove`
|
409
|
+
- **error_response_actions**: `:validation_errors`, `:flash_errors`
|
410
|
+
|
411
|
+
## Architecture
|
412
|
+
|
413
|
+
### Namespacing
|
414
|
+
|
415
|
+
TurboTurbo uses namespaced controllers and CSS to prevent conflicts:
|
416
|
+
|
417
|
+
- **Stimulus Controllers**: `turbo-turbo--modal`, `turbo-turbo--flash` (installed to `app/javascript/controllers/turbo_turbo/`)
|
418
|
+
- **CSS Files**: Served from gem at `turbo_turbo/*.css`
|
419
|
+
- **ViewComponents**: All namespaced under `TurboTurbo::` and `TurboTurbo::Alerts::`
|
420
|
+
- **Layout Partials**: Installed to `app/views/turbo_turbo/`
|
421
|
+
- **No conflicts** with your existing controllers or styles
|
422
|
+
|
423
|
+
### Component Architecture
|
424
|
+
|
425
|
+
- **Modal Controller**: Added to body tag, manages global modal behavior
|
426
|
+
- **Flash Controllers**: Added to individual flash messages for auto-fade and dismiss
|
427
|
+
- **ViewComponents**: Served directly from gem, optionally copyable for customization
|
428
|
+
- **CSS**: Served from gem asset pipeline, automatically imported
|
429
|
+
|
430
|
+
## Development
|
431
|
+
|
432
|
+
After checking out the repo, run:
|
433
|
+
|
434
|
+
```bash
|
435
|
+
bundle install
|
436
|
+
rspec
|
437
|
+
```
|
438
|
+
|
439
|
+
## Test Helpers
|
440
|
+
|
441
|
+
TurboTurbo includes test helpers for system/integration testing:
|
442
|
+
|
443
|
+
```ruby
|
444
|
+
# In your system specs
|
445
|
+
RSpec.describe "Modal functionality", type: :system, js: true do
|
446
|
+
include TurboTurbo::TestHelpers
|
447
|
+
|
448
|
+
it "creates a record via modal" do
|
449
|
+
visit some_path
|
450
|
+
|
451
|
+
click_new_button("user") # Clicks #new_user button
|
452
|
+
fill_in "Name", with: "John"
|
453
|
+
submit_modal # Submits TurboTurbo modal form
|
454
|
+
|
455
|
+
expect(page).to have_content("John")
|
456
|
+
expect(modal_closed?).to be true
|
457
|
+
end
|
458
|
+
|
459
|
+
it "edits a record via modal" do
|
460
|
+
user = create(:user)
|
461
|
+
visit users_path
|
462
|
+
|
463
|
+
click_on_row_link(user, "edit") # Clicks edit button in user's table row
|
464
|
+
fill_in "Name", with: "Jane"
|
465
|
+
submit_modal
|
466
|
+
|
467
|
+
user.reload
|
468
|
+
expect(user.name).to eq("Jane")
|
469
|
+
end
|
470
|
+
end
|
471
|
+
```
|
472
|
+
|
473
|
+
**Available Helpers:**
|
474
|
+
- `submit_modal` - Submit a TurboTurbo modal form
|
475
|
+
- `click_new_button(resource)` - Click new button for resource type
|
476
|
+
- `click_on_row_link(resource, action, confirm: false)` - Click action link in table row
|
477
|
+
- `close_modal` - Close modal and verify it's closed
|
478
|
+
- `modal_open?` - Check if modal is currently open
|
479
|
+
- `modal_closed?` - Check if modal is currently closed
|
480
|
+
- `table_row(resource)` - Get DOM selector for resource's table row
|
481
|
+
|
482
|
+
To install this gem onto your local machine:
|
483
|
+
|
484
|
+
```bash
|
485
|
+
bundle exec rake install
|
486
|
+
```
|
487
|
+
|
488
|
+
## Contributing
|
489
|
+
|
490
|
+
1. Fork it
|
491
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
492
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
493
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
494
|
+
5. Create new Pull Request
|
495
|
+
|
496
|
+
## License
|
497
|
+
|
498
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|