reactive-actions 0.1.0.pre.alpha.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +21 -0
- data/README.md +714 -0
- data/Rakefile +15 -0
- data/app/assets/javascripts/reactive_actions.js +100 -0
- data/app/controllers/reactive_actions/application_controller.rb +10 -0
- data/app/controllers/reactive_actions/reactive_actions_controller.rb +183 -0
- data/config/initializers/reactive_actions_logger.rb +4 -0
- data/config/routes.rb +5 -0
- data/lib/generators/reactive_actions/install/install_generator.rb +261 -0
- data/lib/generators/reactive_actions/install/templates/README +32 -0
- data/lib/generators/reactive_actions/install/templates/example_action.rb +26 -0
- data/lib/generators/reactive_actions/install/templates/initializer.rb +13 -0
- data/lib/reactive-actions.rb +3 -0
- data/lib/reactive_actions/configuration.rb +33 -0
- data/lib/reactive_actions/engine.rb +64 -0
- data/lib/reactive_actions/errors.rb +24 -0
- data/lib/reactive_actions/reactive_action.rb +67 -0
- data/lib/reactive_actions/version.rb +5 -0
- data/lib/reactive_actions.rb +19 -0
- metadata +93 -0
data/README.md
ADDED
@@ -0,0 +1,714 @@
|
|
1
|
+
# ReactiveActions
|
2
|
+
|
3
|
+
ReactiveActions is a Rails gem that provides a framework for handling reactive actions in your Rails application.
|
4
|
+
|
5
|
+
## ๐ง Status
|
6
|
+
|
7
|
+
This gem is currently in alpha (0.1.0-alpha.1). The API may change between versions.
|
8
|
+
|
9
|
+
## ๐ฆ Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'reactive-actions', '0.1.0-alpha.1'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
```bash
|
19
|
+
$ bundle install
|
20
|
+
```
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
```bash
|
24
|
+
gem install reactive-actions --pre
|
25
|
+
```
|
26
|
+
|
27
|
+
After installing the gem, run the generator to set up the necessary files:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
$ rails generate reactive_actions:install
|
31
|
+
```
|
32
|
+
|
33
|
+
## ๐ฎ Interactive Installation
|
34
|
+
|
35
|
+
The install generator provides an interactive setup experience that is fully compatible with Rails 8's native asset pipeline (Propshaft + Importmap):
|
36
|
+
|
37
|
+
### Basic Interactive Installation
|
38
|
+
|
39
|
+
```bash
|
40
|
+
$ rails generate reactive_actions:install
|
41
|
+
```
|
42
|
+
|
43
|
+
This will guide you through the setup process with prompts like:
|
44
|
+
|
45
|
+
```
|
46
|
+
Welcome to ReactiveActions installer!
|
47
|
+
This will help you set up ReactiveActions in your Rails application.
|
48
|
+
|
49
|
+
Create ReactiveActions initializer? (recommended) (y/n) y
|
50
|
+
โ Created initializer
|
51
|
+
|
52
|
+
Create app/reactive_actions directory? (y/n) y
|
53
|
+
โ Created actions directory
|
54
|
+
|
55
|
+
Generate example action file? (y/n) y
|
56
|
+
What should the example action be called? [example] user_login
|
57
|
+
โ Created user_login_action.rb
|
58
|
+
|
59
|
+
Add ReactiveActions routes to your application? (y/n) y
|
60
|
+
Mount path for ReactiveActions: [/reactive_actions] /api/actions
|
61
|
+
โ Added route mounting ReactiveActions at /api/actions
|
62
|
+
|
63
|
+
Add ReactiveActions JavaScript client? (y/n) y
|
64
|
+
โ Added JavaScript client to importmap
|
65
|
+
โ Added ReactiveActions import to app/javascript/application.js
|
66
|
+
|
67
|
+
Configure advanced options? (y/n) n
|
68
|
+
|
69
|
+
================================================================
|
70
|
+
ReactiveActions installation complete!
|
71
|
+
================================================================
|
72
|
+
```
|
73
|
+
|
74
|
+
### Installation Options
|
75
|
+
|
76
|
+
You can also use command line options to skip prompts or customize the installation:
|
77
|
+
|
78
|
+
```bash
|
79
|
+
# Skip specific components
|
80
|
+
$ rails generate reactive_actions:install --skip-routes --skip-javascript
|
81
|
+
|
82
|
+
# Use custom mount path
|
83
|
+
$ rails generate reactive_actions:install --mount-path=/api/reactive
|
84
|
+
|
85
|
+
# Skip example action generation
|
86
|
+
$ rails generate reactive_actions:install --skip-example
|
87
|
+
|
88
|
+
# Quiet installation with defaults
|
89
|
+
$ rails generate reactive_actions:install --quiet
|
90
|
+
```
|
91
|
+
|
92
|
+
### Available Options
|
93
|
+
|
94
|
+
- `--skip-routes` - Skip adding routes to your application
|
95
|
+
- `--skip-javascript` - Skip adding JavaScript imports and setup
|
96
|
+
- `--skip-example` - Skip generating the example action file
|
97
|
+
- `--mount-path=PATH` - Specify custom mount path (default: `/reactive_actions`)
|
98
|
+
- `--quiet` - Run installation with minimal output and default settings
|
99
|
+
|
100
|
+
### What Gets Installed
|
101
|
+
|
102
|
+
The generator will:
|
103
|
+
- โ
Add the necessary routes to your `config/routes.rb`
|
104
|
+
- โ
Create the `app/reactive_actions` directory
|
105
|
+
- โ
Generate an example action file (customizable name)
|
106
|
+
- โ
Add JavaScript to your `config/importmap.rb` (Rails 8 native)
|
107
|
+
- โ
Automatically import ReactiveActions in your `application.js`
|
108
|
+
- โ
Create an initializer file with configuration options
|
109
|
+
- โ
Optionally configure advanced settings like custom delegated methods
|
110
|
+
|
111
|
+
## โก Rails 8 Native JavaScript Integration
|
112
|
+
|
113
|
+
ReactiveActions now uses Rails 8's native JavaScript approach with **Importmap + Propshaft**, providing seamless integration without additional build steps.
|
114
|
+
|
115
|
+
### Automatic Setup
|
116
|
+
|
117
|
+
The installer automatically:
|
118
|
+
1. **Pins the module** in your `config/importmap.rb`:
|
119
|
+
```ruby
|
120
|
+
pin "reactive_actions", to: "reactive_actions.js"
|
121
|
+
```
|
122
|
+
|
123
|
+
2. **Imports it globally** in your `app/javascript/application.js`:
|
124
|
+
```javascript
|
125
|
+
// Import ReactiveActions to make it globally available
|
126
|
+
import "reactive_actions"
|
127
|
+
```
|
128
|
+
|
129
|
+
3. **Makes it available everywhere** as `window.ReactiveActions`
|
130
|
+
|
131
|
+
### Manual Import (Optional)
|
132
|
+
|
133
|
+
You can also import it explicitly in specific files:
|
134
|
+
|
135
|
+
```javascript
|
136
|
+
import ReactiveActions from "reactive_actions"
|
137
|
+
|
138
|
+
// Use it locally
|
139
|
+
ReactiveActions.execute('action_name', { param: 'value' })
|
140
|
+
```
|
141
|
+
|
142
|
+
### Backward Compatibility
|
143
|
+
|
144
|
+
The JavaScript client supports both Rails 8 (Importmap) and older setups (Sprockets), automatically detecting and configuring the appropriate approach.
|
145
|
+
|
146
|
+
## ๐ Usage
|
147
|
+
|
148
|
+
### HTTP API
|
149
|
+
|
150
|
+
Once installed, you can access the reactive actions by sending requests to your configured endpoint:
|
151
|
+
|
152
|
+
```
|
153
|
+
GET/POST/PUT/PATCH/DELETE /reactive_actions/execute
|
154
|
+
```
|
155
|
+
|
156
|
+
Or if you used a custom mount path:
|
157
|
+
|
158
|
+
```
|
159
|
+
GET/POST/PUT/PATCH/DELETE /your-custom-path/execute
|
160
|
+
```
|
161
|
+
|
162
|
+
You can pass parameters:
|
163
|
+
- `action_name`: The name of the action to execute
|
164
|
+
- `action_params`: Parameters for the action
|
165
|
+
|
166
|
+
Example:
|
167
|
+
```ruby
|
168
|
+
# Using Rails
|
169
|
+
response = Net::HTTP.post(
|
170
|
+
URI.parse("http://localhost:3000/reactive_actions/execute"),
|
171
|
+
{ action_name: "update_user", action_params: { id: 1, name: "New Name" } }.to_json,
|
172
|
+
"Content-Type" => "application/json"
|
173
|
+
)
|
174
|
+
```
|
175
|
+
|
176
|
+
### Creating Custom Actions
|
177
|
+
|
178
|
+
You can create custom actions by inheriting from `ReactiveActions::ReactiveAction`:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
# app/reactive_actions/update_user_action.rb
|
182
|
+
class UpdateUserAction < ReactiveActions::ReactiveAction
|
183
|
+
def action
|
184
|
+
user = User.find(action_params[:id])
|
185
|
+
user.update(action_params[:user_attributes])
|
186
|
+
|
187
|
+
@result = {
|
188
|
+
success: true,
|
189
|
+
user: user.as_json
|
190
|
+
}
|
191
|
+
end
|
192
|
+
|
193
|
+
def response
|
194
|
+
render json: {
|
195
|
+
success: true,
|
196
|
+
data: @result
|
197
|
+
}
|
198
|
+
end
|
199
|
+
end
|
200
|
+
```
|
201
|
+
|
202
|
+
### Action Directory Structure
|
203
|
+
|
204
|
+
Actions are placed in the `app/reactive_actions` directory structure:
|
205
|
+
|
206
|
+
```
|
207
|
+
app/
|
208
|
+
โโโ reactive_actions/
|
209
|
+
โ โโโ simple_action.rb
|
210
|
+
โ โโโ user_actions/
|
211
|
+
โ โ โโโ create_user_action.rb
|
212
|
+
โ โ โโโ update_user_action.rb
|
213
|
+
โ โ โโโ delete_user_action.rb
|
214
|
+
โ โโโ product_actions/
|
215
|
+
โ โโโ create_product_action.rb
|
216
|
+
โ โโโ update_product_action.rb
|
217
|
+
```
|
218
|
+
|
219
|
+
Actions in subdirectories are automatically loaded and namespaced under `ReactiveActions`. For example, a file at `app/reactive_actions/user_actions/create_user_action.rb` becomes accessible as `ReactiveActions::CreateUserAction`.
|
220
|
+
|
221
|
+
### Action Naming Convention
|
222
|
+
|
223
|
+
Action files should follow the naming convention:
|
224
|
+
- File name: `snake_case_action.rb` (e.g., `update_user_action.rb`)
|
225
|
+
- Class name: `CamelCaseAction` (e.g., `UpdateUserAction`)
|
226
|
+
- HTTP parameter: `snake_case` without the `_action` suffix (e.g., `update_user`)
|
227
|
+
|
228
|
+
Examples:
|
229
|
+
- `create_user_action.rb` โ `CreateUserAction` โ called with `action_name: "create_user"`
|
230
|
+
- `fetch_product_action.rb` โ `FetchProductAction` โ called with `action_name: "fetch_product"`
|
231
|
+
|
232
|
+
### Advanced Examples
|
233
|
+
|
234
|
+
#### Complex Action with Validation and Error Handling
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
# app/reactive_actions/process_payment_action.rb
|
238
|
+
class ProcessPaymentAction < ReactiveActions::ReactiveAction
|
239
|
+
def action
|
240
|
+
# Validate required parameters
|
241
|
+
validate_parameters!
|
242
|
+
|
243
|
+
# Process the payment
|
244
|
+
payment_service = PaymentService.new(
|
245
|
+
amount: action_params[:amount],
|
246
|
+
currency: action_params[:currency],
|
247
|
+
payment_method: action_params[:payment_method]
|
248
|
+
)
|
249
|
+
|
250
|
+
@result = payment_service.process
|
251
|
+
|
252
|
+
# Log the transaction
|
253
|
+
PaymentLog.create(
|
254
|
+
amount: action_params[:amount],
|
255
|
+
status: @result[:status],
|
256
|
+
transaction_id: @result[:transaction_id]
|
257
|
+
)
|
258
|
+
rescue PaymentError => e
|
259
|
+
@error = { type: 'payment_failed', message: e.message }
|
260
|
+
rescue ValidationError => e
|
261
|
+
@error = { type: 'validation_failed', message: e.message }
|
262
|
+
end
|
263
|
+
|
264
|
+
def response
|
265
|
+
if @error
|
266
|
+
render json: { success: false, error: @error }, status: :unprocessable_entity
|
267
|
+
else
|
268
|
+
render json: { success: true, data: @result }
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
|
274
|
+
def validate_parameters!
|
275
|
+
required_params = %i[amount currency payment_method]
|
276
|
+
missing_params = required_params.select { |param| action_params[param].blank? }
|
277
|
+
|
278
|
+
raise ValidationError, "Missing parameters: #{missing_params.join(', ')}" if missing_params.any?
|
279
|
+
raise ValidationError, "Invalid amount" unless action_params[:amount].to_f > 0
|
280
|
+
end
|
281
|
+
end
|
282
|
+
```
|
283
|
+
|
284
|
+
#### Action with Background Job Integration
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
# app/reactive_actions/generate_report_action.rb
|
288
|
+
class GenerateReportAction < ReactiveActions::ReactiveAction
|
289
|
+
def action
|
290
|
+
# Queue the report generation job
|
291
|
+
job = ReportGenerationJob.perform_later(
|
292
|
+
user_id: action_params[:user_id],
|
293
|
+
report_type: action_params[:report_type],
|
294
|
+
filters: action_params[:filters] || {}
|
295
|
+
)
|
296
|
+
|
297
|
+
@result = {
|
298
|
+
job_id: job.job_id,
|
299
|
+
status: 'queued',
|
300
|
+
estimated_completion: 5.minutes.from_now
|
301
|
+
}
|
302
|
+
end
|
303
|
+
|
304
|
+
def response
|
305
|
+
render json: {
|
306
|
+
success: true,
|
307
|
+
message: 'Report generation started',
|
308
|
+
data: @result
|
309
|
+
}
|
310
|
+
end
|
311
|
+
end
|
312
|
+
```
|
313
|
+
|
314
|
+
## ๐ป JavaScript Client
|
315
|
+
|
316
|
+
ReactiveActions includes a modern JavaScript client that's automatically available after installation.
|
317
|
+
|
318
|
+
### Global Usage (Recommended)
|
319
|
+
|
320
|
+
After installation, `ReactiveActions` is globally available:
|
321
|
+
|
322
|
+
```javascript
|
323
|
+
// Basic usage (POST method by default)
|
324
|
+
ReactiveActions.execute('update_user', { id: 1, name: 'New Name' })
|
325
|
+
.then(response => {
|
326
|
+
if (response.ok) {
|
327
|
+
console.log('Success:', response);
|
328
|
+
} else {
|
329
|
+
console.error('Error:', response);
|
330
|
+
}
|
331
|
+
});
|
332
|
+
|
333
|
+
// Using specific HTTP methods
|
334
|
+
ReactiveActions.get('fetch_user', { id: 1 });
|
335
|
+
ReactiveActions.post('create_user', { name: 'New User' });
|
336
|
+
ReactiveActions.put('update_user', { id: 1, name: 'Updated Name' });
|
337
|
+
ReactiveActions.patch('partial_update', { id: 1, status: 'active' });
|
338
|
+
ReactiveActions.delete('delete_user', { id: 1 });
|
339
|
+
|
340
|
+
// With custom options
|
341
|
+
ReactiveActions.execute('custom_action', { data: 'value' }, {
|
342
|
+
method: 'POST',
|
343
|
+
contentType: 'application/json'
|
344
|
+
});
|
345
|
+
```
|
346
|
+
|
347
|
+
### ES Module Import (Advanced)
|
348
|
+
|
349
|
+
For more control, you can import it explicitly:
|
350
|
+
|
351
|
+
```javascript
|
352
|
+
import ReactiveActions from "reactive_actions"
|
353
|
+
|
354
|
+
// Use it in your module
|
355
|
+
ReactiveActions.execute('action_name', { param: 'value' })
|
356
|
+
```
|
357
|
+
|
358
|
+
### Client Features
|
359
|
+
|
360
|
+
The client automatically:
|
361
|
+
- โ
**Handles CSRF tokens** - Automatically includes Rails CSRF protection
|
362
|
+
- โ
**Formats requests** - Properly formats GET vs POST/PUT/PATCH/DELETE requests
|
363
|
+
- โ
**Parses responses** - Automatically parses JSON responses
|
364
|
+
- โ
**Returns promises** - Modern async/await compatible
|
365
|
+
- โ
**Error handling** - Provides structured error information
|
366
|
+
- โ
**Multiple HTTP methods** - Support for GET, POST, PUT, PATCH, DELETE
|
367
|
+
|
368
|
+
## Security
|
369
|
+
|
370
|
+
ReactiveActions implements several security measures to protect your application:
|
371
|
+
|
372
|
+
### ๐ **Built-in Security Features**
|
373
|
+
|
374
|
+
#### Parameter Sanitization
|
375
|
+
- **Input validation**: Action names are validated against safe patterns (`/\A[a-zA-Z_][a-zA-Z0-9_]*\z/`)
|
376
|
+
- **Parameter key sanitization**: Only alphanumeric characters, underscores, and hyphens allowed
|
377
|
+
- **String length limits**: Prevents memory exhaustion attacks (max 10,000 chars)
|
378
|
+
- **Dangerous prefix filtering**: Blocks parameters starting with `__`, `eval`, `exec`, `system`, etc.
|
379
|
+
|
380
|
+
#### CSRF Protection
|
381
|
+
- **Automatic CSRF tokens**: JavaScript client automatically includes Rails CSRF tokens
|
382
|
+
- **Same-origin requests**: Credentials are sent only to same-origin requests
|
383
|
+
- **Controller integration**: Inherits from `ActionController::Base` with CSRF protection
|
384
|
+
|
385
|
+
#### Code Injection Prevention
|
386
|
+
- **Class name validation**: Action names are sanitized before constant lookup
|
387
|
+
- **Namespace isolation**: Actions are properly namespaced to prevent conflicts
|
388
|
+
- **Parameter filtering**: Recursive parameter sanitization for nested structures
|
389
|
+
|
390
|
+
### ๐ก๏ธ **Security Best Practices**
|
391
|
+
|
392
|
+
```ruby
|
393
|
+
# app/reactive_actions/secure_action.rb
|
394
|
+
class SecureAction < ReactiveActions::ReactiveAction
|
395
|
+
def action
|
396
|
+
# Always validate user permissions
|
397
|
+
raise ReactiveActions::UnauthorizedError unless current_user&.admin?
|
398
|
+
|
399
|
+
# Validate and sanitize inputs
|
400
|
+
user_id = action_params[:user_id].to_i
|
401
|
+
raise ReactiveActions::InvalidParametersError, "Invalid user ID" if user_id <= 0
|
402
|
+
|
403
|
+
# Use strong parameters if integrating with models
|
404
|
+
permitted_params = action_params.slice(:name, :email).permit!
|
405
|
+
|
406
|
+
@result = User.find(user_id).update(permitted_params)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
```
|
410
|
+
|
411
|
+
### โ ๏ธ **Security Considerations**
|
412
|
+
|
413
|
+
- **Always validate user permissions** in your actions
|
414
|
+
- **Use Rails strong parameters** when working with model updates
|
415
|
+
- **Sanitize file uploads** if handling file parameters
|
416
|
+
- **Implement rate limiting** for public-facing actions
|
417
|
+
- **Log security events** for audit trails
|
418
|
+
|
419
|
+
## Performance
|
420
|
+
|
421
|
+
### ๐ **Performance Characteristics**
|
422
|
+
|
423
|
+
ReactiveActions is designed to be lightweight and efficient:
|
424
|
+
|
425
|
+
- **Minimal overhead**: Direct controller execution without complex middleware chains
|
426
|
+
- **No database dependencies**: Core functionality doesn't require database connections
|
427
|
+
- **Efficient autoloading**: Actions are loaded on-demand using Rails' autoloading
|
428
|
+
- **Memory efficient**: Parameter sanitization prevents memory exhaustion attacks
|
429
|
+
|
430
|
+
### ๐ **Performance Best Practices**
|
431
|
+
|
432
|
+
#### Action Design
|
433
|
+
```ruby
|
434
|
+
# โ
Good: Lightweight action with focused responsibility
|
435
|
+
class QuickUpdateAction < ReactiveActions::ReactiveAction
|
436
|
+
def action
|
437
|
+
User.where(id: action_params[:id]).update_all(
|
438
|
+
last_seen_at: Time.current
|
439
|
+
)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
# โ Avoid: Heavy operations that should be background jobs
|
444
|
+
class SlowReportAction < ReactiveActions::ReactiveAction
|
445
|
+
def action
|
446
|
+
# This should be a background job instead
|
447
|
+
@result = generate_complex_report_synchronously
|
448
|
+
end
|
449
|
+
end
|
450
|
+
```
|
451
|
+
|
452
|
+
#### Use Background Jobs for Heavy Operations
|
453
|
+
```ruby
|
454
|
+
# โ
Better approach for time-consuming operations
|
455
|
+
class InitiateReportAction < ReactiveActions::ReactiveAction
|
456
|
+
def action
|
457
|
+
ReportGenerationJob.perform_later(action_params)
|
458
|
+
@result = { status: 'queued', job_id: SecureRandom.uuid }
|
459
|
+
end
|
460
|
+
end
|
461
|
+
```
|
462
|
+
|
463
|
+
#### Optimize Database Queries
|
464
|
+
```ruby
|
465
|
+
class OptimizedAction < ReactiveActions::ReactiveAction
|
466
|
+
def action
|
467
|
+
# Use includes to avoid N+1 queries
|
468
|
+
@users = User.includes(:profile, :posts)
|
469
|
+
.where(id: action_params[:user_ids])
|
470
|
+
|
471
|
+
# Use select to limit returned columns
|
472
|
+
@summary = User.select(:id, :name, :created_at)
|
473
|
+
.where(active: true)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
```
|
477
|
+
|
478
|
+
### ๐ **Monitoring and Optimization**
|
479
|
+
|
480
|
+
- **Monitor response times**: Actions should typically complete in < 100ms
|
481
|
+
- **Use Rails logging**: ReactiveActions logs execution details at debug level
|
482
|
+
- **Profile memory usage**: Large parameter sets can impact memory
|
483
|
+
- **Consider caching**: Use Rails caching for frequently accessed data
|
484
|
+
|
485
|
+
## โ๏ธ Configuration
|
486
|
+
|
487
|
+
The gem can be configured using an initializer (automatically created by the install generator):
|
488
|
+
|
489
|
+
```ruby
|
490
|
+
# config/initializers/reactive_actions.rb
|
491
|
+
ReactiveActions.configure do |config|
|
492
|
+
# Configure methods to delegate from the controller to action classes
|
493
|
+
config.delegated_controller_methods += [:custom_method]
|
494
|
+
|
495
|
+
# Configure instance variables to delegate from the controller to action classes
|
496
|
+
config.delegated_instance_variables += [:custom_variable]
|
497
|
+
end
|
498
|
+
|
499
|
+
# Set the logger for ReactiveActions
|
500
|
+
ReactiveActions.logger = Rails.logger
|
501
|
+
```
|
502
|
+
|
503
|
+
### Advanced Configuration
|
504
|
+
|
505
|
+
During installation, you can choose to configure advanced options:
|
506
|
+
|
507
|
+
- **Custom Controller Methods**: Add additional controller methods to delegate to action classes
|
508
|
+
- **Logging Level**: Set a custom logging level for ReactiveActions
|
509
|
+
- **Instance Variables**: Configure which instance variables to delegate from controllers to actions
|
510
|
+
|
511
|
+
### Default Delegated Methods
|
512
|
+
|
513
|
+
By default, the following controller methods are available in your actions:
|
514
|
+
- `render`
|
515
|
+
- `redirect_to`
|
516
|
+
- `head`
|
517
|
+
- `params`
|
518
|
+
- `session`
|
519
|
+
- `cookies`
|
520
|
+
- `flash`
|
521
|
+
- `request`
|
522
|
+
- `response`
|
523
|
+
|
524
|
+
## โ Error Handling
|
525
|
+
|
526
|
+
ReactiveActions provides structured error handling with specific error types:
|
527
|
+
|
528
|
+
- `ActionNotFoundError`: When the requested action doesn't exist
|
529
|
+
- `MissingParameterError`: When required parameters are missing
|
530
|
+
- `InvalidParametersError`: When parameters have invalid types or formats
|
531
|
+
- `UnauthorizedError`: When the user lacks permission for the action
|
532
|
+
- `ActionExecutionError`: When an error occurs during action execution
|
533
|
+
|
534
|
+
All errors return JSON responses with consistent structure:
|
535
|
+
|
536
|
+
```json
|
537
|
+
{
|
538
|
+
"success": false,
|
539
|
+
"error": {
|
540
|
+
"type": "ActionNotFoundError",
|
541
|
+
"message": "Action 'non_existent' not found",
|
542
|
+
"code": "NOT_FOUND"
|
543
|
+
}
|
544
|
+
}
|
545
|
+
```
|
546
|
+
|
547
|
+
## ๐ง Troubleshooting
|
548
|
+
|
549
|
+
### Common Issues and Solutions
|
550
|
+
|
551
|
+
#### โ **Action Not Found Errors**
|
552
|
+
|
553
|
+
**Problem**: Getting `ActionNotFoundError` for existing actions
|
554
|
+
|
555
|
+
**Solutions**:
|
556
|
+
```bash
|
557
|
+
# 1. Check file naming convention
|
558
|
+
# File: app/reactive_actions/my_action.rb
|
559
|
+
# Class: MyAction
|
560
|
+
# Call with: action_name: "my"
|
561
|
+
|
562
|
+
# 2. Restart Rails to reload autoloading
|
563
|
+
rails restart
|
564
|
+
|
565
|
+
# 3. Check for syntax errors in action file
|
566
|
+
rails console
|
567
|
+
> MyAction # Should load without errors
|
568
|
+
```
|
569
|
+
|
570
|
+
#### โ **JavaScript Client Not Working**
|
571
|
+
|
572
|
+
**Problem**: `ReactiveActions is not defined` in browser
|
573
|
+
|
574
|
+
**Solutions**:
|
575
|
+
```javascript
|
576
|
+
// 1. Check importmap.rb includes the pin
|
577
|
+
// config/importmap.rb should have:
|
578
|
+
pin "reactive_actions", to: "reactive_actions.js"
|
579
|
+
|
580
|
+
// 2. Check application.js imports it
|
581
|
+
// app/javascript/application.js should have:
|
582
|
+
import "reactive_actions"
|
583
|
+
|
584
|
+
// 3. Clear browser cache and restart Rails
|
585
|
+
```
|
586
|
+
|
587
|
+
#### โ **CSRF Token Errors**
|
588
|
+
|
589
|
+
**Problem**: Getting `Can't verify CSRF token authenticity`
|
590
|
+
|
591
|
+
**Solutions**:
|
592
|
+
```erb
|
593
|
+
<!-- 1. Ensure CSRF meta tags are in your layout -->
|
594
|
+
<%= csrf_meta_tags %>
|
595
|
+
|
596
|
+
<!-- 2. Check that protect_from_forgery is enabled -->
|
597
|
+
<%# In your ApplicationController %>
|
598
|
+
protect_from_forgery with: :exception
|
599
|
+
```
|
600
|
+
|
601
|
+
#### โ **Parameter Sanitization Issues**
|
602
|
+
|
603
|
+
**Problem**: Parameters are being rejected or modified unexpectedly
|
604
|
+
|
605
|
+
**Solutions**:
|
606
|
+
```ruby
|
607
|
+
# 1. Check parameter key format (alphanumeric, underscore, hyphen only)
|
608
|
+
# โ
Good: { user_name: "John", user-id: 123 }
|
609
|
+
# โ Bad: { "__eval": "code", "system()": "bad" }
|
610
|
+
|
611
|
+
# 2. Check string length limits (max 10,000 characters)
|
612
|
+
# 3. Review logs for specific sanitization messages
|
613
|
+
```
|
614
|
+
|
615
|
+
#### โ **Performance Issues**
|
616
|
+
|
617
|
+
**Problem**: Actions are slow or timing out
|
618
|
+
|
619
|
+
**Solutions**:
|
620
|
+
```ruby
|
621
|
+
# 1. Move heavy operations to background jobs
|
622
|
+
class SlowAction < ReactiveActions::ReactiveAction
|
623
|
+
def action
|
624
|
+
# Instead of this:
|
625
|
+
# heavy_operation
|
626
|
+
|
627
|
+
# Do this:
|
628
|
+
HeavyOperationJob.perform_later(action_params)
|
629
|
+
@result = { status: 'queued' }
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
# 2. Optimize database queries
|
634
|
+
# 3. Add caching where appropriate
|
635
|
+
# 4. Monitor with Rails logs at debug level
|
636
|
+
```
|
637
|
+
|
638
|
+
### Debug Mode
|
639
|
+
|
640
|
+
Enable detailed logging for troubleshooting:
|
641
|
+
|
642
|
+
```ruby
|
643
|
+
# config/initializers/reactive_actions.rb
|
644
|
+
ReactiveActions.logger.level = :debug
|
645
|
+
|
646
|
+
# This will log:
|
647
|
+
# - Action execution details
|
648
|
+
# - Parameter sanitization steps
|
649
|
+
# - Error stack traces
|
650
|
+
# - Performance metrics
|
651
|
+
```
|
652
|
+
|
653
|
+
## ๐ Rails 8 Compatibility
|
654
|
+
|
655
|
+
This gem is designed specifically for Rails 8 and takes advantage of:
|
656
|
+
|
657
|
+
- โ
**Propshaft** - Modern asset pipeline without compilation
|
658
|
+
- โ
**Importmap** - Native ES module support without bundling
|
659
|
+
- โ
**Rails 8 conventions** - Follows current Rails best practices
|
660
|
+
- โ
**Modern JavaScript** - ES6+ classes and async/await
|
661
|
+
- โ
**Backward compatibility** - Still works with Sprockets if needed
|
662
|
+
|
663
|
+
## ๐บ๏ธ Roadmap & Future Improvements
|
664
|
+
|
665
|
+
Planned improvements for ReactiveActions:
|
666
|
+
|
667
|
+
* Security hooks - methods that run before actions for authentication and authorization checks
|
668
|
+
* Rate limiting and throttling capabilities
|
669
|
+
* Enhanced error handling with more granular error types
|
670
|
+
* Action composition - ability to build complex workflows from smaller actions
|
671
|
+
* Improved generators for common action patterns
|
672
|
+
* Built-in testing utilities and helpers
|
673
|
+
* Auto-generated API documentation
|
674
|
+
* And much more
|
675
|
+
|
676
|
+
## ๐ ๏ธ Development
|
677
|
+
|
678
|
+
After checking out the repo, run the following to install dependencies:
|
679
|
+
|
680
|
+
```bash
|
681
|
+
$ bundle install
|
682
|
+
```
|
683
|
+
|
684
|
+
Then, run the tests:
|
685
|
+
|
686
|
+
```bash
|
687
|
+
$ bundle exec rspec
|
688
|
+
```
|
689
|
+
|
690
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
691
|
+
|
692
|
+
To install this gem onto your local machine, run:
|
693
|
+
|
694
|
+
```bash
|
695
|
+
$ bundle exec rake install
|
696
|
+
```
|
697
|
+
|
698
|
+
## ๐งช Testing
|
699
|
+
|
700
|
+
The gem repository includes a dummy Rails application for development and testing purposes. To run the tests:
|
701
|
+
|
702
|
+
```bash
|
703
|
+
$ bundle exec rspec
|
704
|
+
```
|
705
|
+
|
706
|
+
**Note**: The dummy application is only available in the source repository and is not included in the distributed gem.
|
707
|
+
|
708
|
+
## ๐ค Contributing
|
709
|
+
|
710
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/IstvanMs/reactive-actions.
|
711
|
+
|
712
|
+
## ๐ License
|
713
|
+
|
714
|
+
The gem is available as open source under the terms of the MIT License.
|