plutonium 0.23.4 → 0.24.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 +4 -4
- data/app/assets/plutonium.css +2 -2
- data/config/initializers/rabl.rb +17 -0
- data/config/initializers/sqlite_json_alias.rb +1 -1
- data/docs/.vitepress/config.ts +60 -19
- data/docs/guide/cursor-rules.md +75 -0
- data/docs/guide/deep-dive/authorization.md +189 -0
- data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
- data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
- data/docs/guide/index.md +28 -0
- data/docs/guide/introduction/02-core-concepts.md +440 -0
- data/docs/guide/tutorial/01-project-setup.md +75 -0
- data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
- data/docs/guide/tutorial/03-defining-resources.md +90 -0
- data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
- data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
- data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
- data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
- data/docs/index.md +24 -31
- data/docs/modules/action.md +190 -0
- data/docs/modules/authentication.md +236 -0
- data/docs/modules/configuration.md +599 -0
- data/docs/modules/controller.md +398 -0
- data/docs/modules/core.md +316 -0
- data/docs/modules/definition.md +876 -0
- data/docs/modules/display.md +759 -0
- data/docs/modules/form.md +465 -0
- data/docs/modules/generator.md +288 -0
- data/docs/modules/index.md +167 -0
- data/docs/modules/interaction.md +470 -0
- data/docs/modules/package.md +151 -0
- data/docs/modules/policy.md +176 -0
- data/docs/modules/portal.md +710 -0
- data/docs/modules/query.md +287 -0
- data/docs/modules/resource_record.md +618 -0
- data/docs/modules/routing.md +641 -0
- data/docs/modules/table.md +293 -0
- data/docs/modules/ui.md +631 -0
- data/docs/public/plutonium.mdc +667 -0
- data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
- data/lib/plutonium/definition/nested_inputs.rb +0 -8
- data/lib/plutonium/resource/controller.rb +1 -1
- data/lib/plutonium/ui/display/resource.rb +7 -2
- data/lib/plutonium/ui/table/resource.rb +8 -3
- data/lib/plutonium/version.rb +1 -1
- metadata +36 -9
- data/docs/guide/getting-started/authorization.md +0 -296
- data/docs/guide/getting-started/core-concepts.md +0 -432
- data/docs/guide/getting-started/index.md +0 -21
- data/docs/guide/tutorial.md +0 -401
- /data/docs/guide/{what-is-plutonium.md → introduction/01-what-is-plutonium.md} +0 -0
@@ -0,0 +1,470 @@
|
|
1
|
+
# Interaction Module
|
2
|
+
|
3
|
+
The Interaction module provides a powerful architectural pattern for organizing business logic around user interactions and business actions. It builds upon the traditional MVC pattern by introducing additional layers that encapsulate business logic and improve separation of concerns.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The Interaction module is located in `lib/plutonium/interaction/` and provides:
|
8
|
+
|
9
|
+
- Business logic encapsulation separate from controllers
|
10
|
+
- Consistent handling of success and failure cases
|
11
|
+
- Flexible and expressive operation chaining
|
12
|
+
- Integration with ActiveModel for validation
|
13
|
+
- Response handling for controller actions
|
14
|
+
- Outcome-based result handling
|
15
|
+
|
16
|
+
## Key Benefits
|
17
|
+
|
18
|
+
- Clear separation of business logic from controllers
|
19
|
+
- Improved testability of business operations
|
20
|
+
- Consistent handling of success and failure cases
|
21
|
+
- Flexible and expressive way to chain operations
|
22
|
+
- Enhanced maintainability and readability of complex business processes
|
23
|
+
- Improved code organization and discoverability of business logic
|
24
|
+
|
25
|
+
## Core Components
|
26
|
+
|
27
|
+
### Interaction Base (`lib/plutonium/interaction/base.rb`)
|
28
|
+
|
29
|
+
The foundation for all interactions, integrating with ActiveModel for attributes and validations.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class CreateUserInteraction < Plutonium::Interaction::Base
|
33
|
+
attribute :first_name, :string
|
34
|
+
attribute :last_name, :string
|
35
|
+
attribute :email, :string
|
36
|
+
|
37
|
+
validates :first_name, :last_name, presence: true
|
38
|
+
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def execute
|
43
|
+
user = User.new(attributes)
|
44
|
+
if user.save
|
45
|
+
succeed(user)
|
46
|
+
.with_redirect_response(user_path(user))
|
47
|
+
.with_message("User was successfully created.")
|
48
|
+
else
|
49
|
+
failed(user.errors)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
#### Key Methods
|
56
|
+
|
57
|
+
- `call(view_context:, **attributes)` - Class method to execute the interaction
|
58
|
+
- `succeed(value)` - Create a successful outcome (aliased as `success`)
|
59
|
+
- `failed(errors)` - Create a failed outcome
|
60
|
+
- `attributes` - Access to all defined attributes
|
61
|
+
- `valid?` / `invalid?` - ActiveModel validation methods
|
62
|
+
|
63
|
+
### Outcome (`lib/plutonium/interaction/outcome.rb`)
|
64
|
+
|
65
|
+
Encapsulates the result of an interaction with success/failure state and optional response.
|
66
|
+
|
67
|
+
#### Success Outcome
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# Creating a success outcome
|
71
|
+
outcome = succeed(user)
|
72
|
+
.with_message("User created successfully")
|
73
|
+
.with_redirect_response(user_path(user))
|
74
|
+
|
75
|
+
# Checking outcome
|
76
|
+
outcome.success? # => true
|
77
|
+
outcome.failure? # => false
|
78
|
+
outcome.value # => user object
|
79
|
+
outcome.messages # => [["User created successfully", :notice]]
|
80
|
+
```
|
81
|
+
|
82
|
+
#### Failure Outcome
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
# Creating a failure outcome
|
86
|
+
outcome = failed(user.errors)
|
87
|
+
.with_message("Failed to create user", :error)
|
88
|
+
|
89
|
+
# Checking outcome
|
90
|
+
outcome.success? # => false
|
91
|
+
outcome.failure? # => true
|
92
|
+
outcome.messages # => [["Failed to create user", :error]]
|
93
|
+
```
|
94
|
+
|
95
|
+
#### Outcome Chaining
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
def execute
|
99
|
+
CreateUserInteraction.call(view_context: view_context, **user_params)
|
100
|
+
.and_then { |user| SendWelcomeEmailInteraction.call(view_context: view_context, user: user) }
|
101
|
+
.and_then { |result| LogUserCreationInteraction.call(view_context: view_context, user: result.value) }
|
102
|
+
.with_redirect_response(dashboard_path)
|
103
|
+
.with_message("Welcome! Your account has been created.")
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
### Response System (`lib/plutonium/interaction/response/`)
|
108
|
+
|
109
|
+
Handles controller responses after successful interactions.
|
110
|
+
|
111
|
+
#### Built-in Response Types
|
112
|
+
|
113
|
+
**Redirect Response**
|
114
|
+
```ruby
|
115
|
+
.with_redirect_response(user_path(user))
|
116
|
+
.with_redirect_response(posts_path, notice: "Post created")
|
117
|
+
```
|
118
|
+
|
119
|
+
**Render Response**
|
120
|
+
```ruby
|
121
|
+
.with_render_response(:show, locals: { user: user })
|
122
|
+
.with_render_response(:edit, status: :unprocessable_entity)
|
123
|
+
```
|
124
|
+
|
125
|
+
**File Response**
|
126
|
+
```ruby
|
127
|
+
.with_file_response(file_path, filename: "report.pdf")
|
128
|
+
```
|
129
|
+
|
130
|
+
**Null Response**
|
131
|
+
```ruby
|
132
|
+
# Default response when no specific response is set
|
133
|
+
# Allows controller to handle response manually
|
134
|
+
```
|
135
|
+
|
136
|
+
#### Processing Responses in Controllers
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
class ApplicationController < ActionController::Base
|
140
|
+
private
|
141
|
+
|
142
|
+
def handle_interaction_outcome(outcome)
|
143
|
+
if outcome.success?
|
144
|
+
outcome.to_response.process(self) do |value|
|
145
|
+
# Default response if no specific response is set
|
146
|
+
render json: { success: true, data: value }
|
147
|
+
end
|
148
|
+
else
|
149
|
+
outcome.messages.each { |msg, type| flash.now[type || :error] = msg }
|
150
|
+
render json: { errors: outcome.errors }, status: :unprocessable_entity
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
### Nested Attributes (`lib/plutonium/interaction/nested_attributes.rb`)
|
157
|
+
|
158
|
+
Handle nested resource attributes in interactions.
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
class CreatePostWithTagsInteraction < Plutonium::Interaction::Base
|
162
|
+
include Plutonium::Interaction::NestedAttributes
|
163
|
+
|
164
|
+
attribute :title, :string
|
165
|
+
attribute :content, :text
|
166
|
+
attribute :tags_attributes, :array
|
167
|
+
|
168
|
+
validates :title, :content, presence: true
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def execute
|
173
|
+
post = Post.new(title: title, content: content)
|
174
|
+
|
175
|
+
if post.save
|
176
|
+
process_nested_attributes(post, :tags, tags_attributes)
|
177
|
+
succeed(post)
|
178
|
+
else
|
179
|
+
failed(post.errors)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
## Usage Patterns
|
186
|
+
|
187
|
+
### Basic Interaction
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
# Define the interaction
|
191
|
+
class PublishPostInteraction < Plutonium::Interaction::Base
|
192
|
+
attribute :post_id, :integer
|
193
|
+
attribute :published_at, :datetime, default: -> { Time.current }
|
194
|
+
|
195
|
+
validates :post_id, presence: true
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def execute
|
200
|
+
post = Post.find(post_id)
|
201
|
+
|
202
|
+
if post.update(published: true, published_at: published_at)
|
203
|
+
succeed(post)
|
204
|
+
.with_message("Post published successfully")
|
205
|
+
.with_redirect_response(post_path(post))
|
206
|
+
else
|
207
|
+
failed(post.errors)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Use in controller
|
213
|
+
class PostsController < ApplicationController
|
214
|
+
def publish
|
215
|
+
outcome = PublishPostInteraction.call(
|
216
|
+
view_context: view_context,
|
217
|
+
post_id: params[:id]
|
218
|
+
)
|
219
|
+
|
220
|
+
handle_interaction_outcome(outcome)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
### Complex Business Logic
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
class ProcessOrderInteraction < Plutonium::Interaction::Base
|
229
|
+
attribute :order_id, :integer
|
230
|
+
attribute :payment_method, :string
|
231
|
+
attribute :shipping_address, :string
|
232
|
+
|
233
|
+
validates :order_id, :payment_method, :shipping_address, presence: true
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
def execute
|
238
|
+
order = Order.find(order_id)
|
239
|
+
|
240
|
+
# Validate order can be processed
|
241
|
+
return failed("Order already processed") if order.processed?
|
242
|
+
return failed("Insufficient inventory") unless check_inventory(order)
|
243
|
+
|
244
|
+
# Process payment
|
245
|
+
payment_result = process_payment(order)
|
246
|
+
return failed(payment_result.errors) unless payment_result.success?
|
247
|
+
|
248
|
+
# Update order
|
249
|
+
order.update!(
|
250
|
+
status: 'processing',
|
251
|
+
payment_method: payment_method,
|
252
|
+
shipping_address: shipping_address,
|
253
|
+
processed_at: Time.current
|
254
|
+
)
|
255
|
+
|
256
|
+
# Send notifications
|
257
|
+
OrderMailer.confirmation_email(order).deliver_later
|
258
|
+
NotifyWarehouseJob.perform_later(order)
|
259
|
+
|
260
|
+
succeed(order)
|
261
|
+
.with_message("Order processed successfully")
|
262
|
+
.with_redirect_response(order_path(order))
|
263
|
+
end
|
264
|
+
|
265
|
+
def check_inventory(order)
|
266
|
+
order.line_items.all? { |item| item.product.stock >= item.quantity }
|
267
|
+
end
|
268
|
+
|
269
|
+
def process_payment(order)
|
270
|
+
PaymentService.charge(
|
271
|
+
amount: order.total,
|
272
|
+
method: payment_method,
|
273
|
+
order_id: order.id
|
274
|
+
)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
```
|
278
|
+
|
279
|
+
### Interaction Composition
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
class CompleteUserOnboardingInteraction < Plutonium::Interaction::Base
|
283
|
+
attribute :user_id, :integer
|
284
|
+
attribute :profile_data, :hash
|
285
|
+
attribute :preferences, :hash
|
286
|
+
|
287
|
+
private
|
288
|
+
|
289
|
+
def execute
|
290
|
+
user = User.find(user_id)
|
291
|
+
|
292
|
+
# Chain multiple interactions
|
293
|
+
UpdateUserProfileInteraction.call(view_context: view_context, user: user, **profile_data)
|
294
|
+
.and_then { |result| SetUserPreferencesInteraction.call(view_context: view_context, user: result.value, **preferences) }
|
295
|
+
.and_then { |result| SendWelcomeEmailInteraction.call(view_context: view_context, user: result.value) }
|
296
|
+
.and_then { |result| CreateDefaultDashboardInteraction.call(view_context: view_context, user: result.value) }
|
297
|
+
.with_message("Welcome! Your account setup is complete.")
|
298
|
+
.with_redirect_response(dashboard_path)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
```
|
302
|
+
|
303
|
+
## Integration with Plutonium
|
304
|
+
|
305
|
+
### Resource Actions
|
306
|
+
|
307
|
+
Interactions integrate seamlessly with resource definitions:
|
308
|
+
|
309
|
+
```ruby
|
310
|
+
class PostDefinition < Plutonium::Resource::Definition
|
311
|
+
action :publish, interaction: PublishPostInteraction
|
312
|
+
action :archive, interaction: ArchivePostInteraction
|
313
|
+
action :feature, interaction: FeaturePostInteraction
|
314
|
+
end
|
315
|
+
```
|
316
|
+
|
317
|
+
### Controller Integration
|
318
|
+
|
319
|
+
Controllers can call interactions directly, but this requires manual setup:
|
320
|
+
|
321
|
+
```ruby
|
322
|
+
class PostsController < ApplicationController
|
323
|
+
include Plutonium::Resource::Controller
|
324
|
+
|
325
|
+
# Manual controller action (requires custom routing)
|
326
|
+
def bulk_publish
|
327
|
+
outcome = BulkPublishPostsInteraction.call(
|
328
|
+
view_context: view_context,
|
329
|
+
post_ids: params[:post_ids],
|
330
|
+
published_at: params[:published_at]
|
331
|
+
)
|
332
|
+
|
333
|
+
# Manual response handling
|
334
|
+
if outcome.success?
|
335
|
+
redirect_to posts_path, notice: outcome.messages.first&.first
|
336
|
+
else
|
337
|
+
redirect_back(fallback_location: posts_path, alert: "Failed to publish posts")
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
```
|
342
|
+
|
343
|
+
**Note**: For automatic integration without manual setup, define actions in resource definitions instead:
|
344
|
+
|
345
|
+
```ruby
|
346
|
+
class PostDefinition < Plutonium::Resource::Definition
|
347
|
+
# This automatically handles routing, UI, and response processing
|
348
|
+
action :bulk_publish, interaction: BulkPublishPostsInteraction
|
349
|
+
end
|
350
|
+
```
|
351
|
+
|
352
|
+
### Form Integration
|
353
|
+
|
354
|
+
```ruby
|
355
|
+
class PostDefinition < Plutonium::Resource::Definition
|
356
|
+
# Form submission automatically uses interactions
|
357
|
+
action :create, interaction: CreatePostInteraction
|
358
|
+
action :update, interaction: UpdatePostInteraction
|
359
|
+
end
|
360
|
+
```
|
361
|
+
|
362
|
+
## Best Practices
|
363
|
+
|
364
|
+
### Interaction Design
|
365
|
+
|
366
|
+
1. **Single Responsibility**: Each interaction should handle one business operation
|
367
|
+
2. **Clear Naming**: Use descriptive names that indicate the business action
|
368
|
+
3. **Validation**: Validate inputs using ActiveModel validations
|
369
|
+
4. **Error Handling**: Return meaningful error messages
|
370
|
+
5. **Idempotency**: Design interactions to be safely re-runnable when possible
|
371
|
+
|
372
|
+
### Outcome Handling
|
373
|
+
|
374
|
+
1. **Consistent Responses**: Use appropriate response types for different scenarios
|
375
|
+
2. **Meaningful Messages**: Provide clear success/failure messages
|
376
|
+
3. **Proper Chaining**: Use `and_then` for sequential operations
|
377
|
+
4. **Error Propagation**: Let failures bubble up through chains
|
378
|
+
|
379
|
+
### Testing Strategy
|
380
|
+
|
381
|
+
1. **Unit Test Interactions**: Test business logic in isolation
|
382
|
+
2. **Mock External Services**: Use mocks for external dependencies
|
383
|
+
3. **Test Both Paths**: Cover both success and failure scenarios
|
384
|
+
4. **Integration Tests**: Test controller integration with system tests
|
385
|
+
|
386
|
+
### Performance Considerations
|
387
|
+
|
388
|
+
1. **Database Transactions**: Use transactions for multi-step operations
|
389
|
+
2. **Background Jobs**: Move slow operations to background jobs
|
390
|
+
3. **Caching**: Cache expensive computations when appropriate
|
391
|
+
4. **Batch Operations**: Use batch processing for bulk operations
|
392
|
+
|
393
|
+
## Advanced Features
|
394
|
+
|
395
|
+
### Custom Response Types
|
396
|
+
|
397
|
+
```ruby
|
398
|
+
class JsonResponse < Plutonium::Interaction::Response::Base
|
399
|
+
def initialize(data, status: :ok)
|
400
|
+
super()
|
401
|
+
@data = data
|
402
|
+
@status = status
|
403
|
+
end
|
404
|
+
|
405
|
+
private
|
406
|
+
|
407
|
+
def execute(controller, &)
|
408
|
+
controller.render json: @data, status: @status
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
# Usage
|
413
|
+
succeed(user).with_response(JsonResponse.new(user.as_json))
|
414
|
+
```
|
415
|
+
|
416
|
+
### Conditional Execution
|
417
|
+
|
418
|
+
```ruby
|
419
|
+
class ConditionalInteraction < Plutonium::Interaction::Base
|
420
|
+
attribute :condition, :boolean
|
421
|
+
attribute :data, :hash
|
422
|
+
|
423
|
+
private
|
424
|
+
|
425
|
+
def execute
|
426
|
+
return succeed(nil) unless condition
|
427
|
+
|
428
|
+
# Only execute if condition is true
|
429
|
+
result = expensive_operation(data)
|
430
|
+
succeed(result)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
```
|
434
|
+
|
435
|
+
### Error Recovery
|
436
|
+
|
437
|
+
```ruby
|
438
|
+
class ResilientInteraction < Plutonium::Interaction::Base
|
439
|
+
private
|
440
|
+
|
441
|
+
def execute
|
442
|
+
primary_service_call
|
443
|
+
.or_else { fallback_service_call }
|
444
|
+
.or_else { failed("All services unavailable") }
|
445
|
+
end
|
446
|
+
|
447
|
+
def primary_service_call
|
448
|
+
# Try primary service
|
449
|
+
result = PrimaryService.call(attributes)
|
450
|
+
result.success? ? succeed(result.data) : failed(result.errors)
|
451
|
+
rescue StandardError => e
|
452
|
+
failed("Primary service error: #{e.message}")
|
453
|
+
end
|
454
|
+
|
455
|
+
def fallback_service_call
|
456
|
+
# Try fallback service
|
457
|
+
result = FallbackService.call(attributes)
|
458
|
+
result.success? ? succeed(result.data) : failed(result.errors)
|
459
|
+
rescue StandardError => e
|
460
|
+
failed("Fallback service error: #{e.message}")
|
461
|
+
end
|
462
|
+
end
|
463
|
+
```
|
464
|
+
|
465
|
+
## Related Modules
|
466
|
+
|
467
|
+
- **[Resource Record](./resource_record.md)** - Resource definitions and CRUD operations
|
468
|
+
- **[Definition](./definition.md)** - Resource definition DSL
|
469
|
+
- **[Core](./core.md)** - Base controller functionality
|
470
|
+
- **[Action](./action.md)** - Custom actions and operations
|
@@ -0,0 +1,151 @@
|
|
1
|
+
---
|
2
|
+
title: Package Module
|
3
|
+
---
|
4
|
+
|
5
|
+
# Package Module
|
6
|
+
|
7
|
+
The Package module provides the foundation for modular application organization in Plutonium. It enables Rails engines to work within the Plutonium ecosystem by providing specialized engine configuration and view path management.
|
8
|
+
|
9
|
+
::: tip
|
10
|
+
The core of the Package module is `Plutonium::Package::Engine`, which should be included in your package's `engine.rb` file.
|
11
|
+
:::
|
12
|
+
|
13
|
+
## Overview
|
14
|
+
|
15
|
+
- **Engine Foundation**: Provides base functionality for all Plutonium packages (which are Rails Engines).
|
16
|
+
- **View Path Control**: Manages view lookups for proper isolation between packages.
|
17
|
+
- **Migration Management**: Automatically includes package migrations in the application's migration path.
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
The primary way to use the Package module is by including `Plutonium::Package::Engine` in your package's engine file. This is handled automatically by the generators.
|
22
|
+
|
23
|
+
::: code-group
|
24
|
+
```bash [Generate a Feature Package]
|
25
|
+
rails generate pu:pkg:package blogging
|
26
|
+
```
|
27
|
+
```ruby [packages/blogging/lib/engine.rb]
|
28
|
+
module Blogging
|
29
|
+
class Engine < Rails::Engine
|
30
|
+
# This inclusion provides the core package functionality.
|
31
|
+
include Plutonium::Package::Engine
|
32
|
+
end
|
33
|
+
end
|
34
|
+
```
|
35
|
+
:::
|
36
|
+
|
37
|
+
::: code-group
|
38
|
+
```bash [Generate a Portal Package]
|
39
|
+
rails generate pu:pkg:portal admin
|
40
|
+
```
|
41
|
+
```ruby [packages/admin_portal/lib/engine.rb]
|
42
|
+
module AdminPortal
|
43
|
+
class Engine < Rails::Engine
|
44
|
+
# Portal::Engine includes Package::Engine, so you get both.
|
45
|
+
include Plutonium::Portal::Engine
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
:::
|
50
|
+
|
51
|
+
## Key Features
|
52
|
+
|
53
|
+
### View Path Management
|
54
|
+
|
55
|
+
The Package module intentionally prevents Rails from automatically adding a package's view paths to the global lookup. Instead, view resolution is handled at the controller level by Plutonium's `Bootable` concern. This provides finer-grained control and ensures that packages remain isolated.
|
56
|
+
|
57
|
+
### Migration Integration
|
58
|
+
|
59
|
+
Package migrations are automatically detected and added to the application's main `db/migrate` path. This allows you to run `rails db:migrate` from your application root, and it will correctly process migrations from all your packages.
|
60
|
+
|
61
|
+
::: details Package Engine Implementation
|
62
|
+
The `Plutonium::Package::Engine` concern handles this automatically.
|
63
|
+
```ruby
|
64
|
+
# lib/plutonium/package/engine.rb
|
65
|
+
module Plutonium
|
66
|
+
module Package
|
67
|
+
module Engine
|
68
|
+
extend ActiveSupport::Concern
|
69
|
+
|
70
|
+
included do
|
71
|
+
# This block hijacks the default Rails view path initializer
|
72
|
+
# and replaces it with an empty one, giving Plutonium control.
|
73
|
+
config.before_configuration do
|
74
|
+
# ... logic to find and disable the default `add_view_paths` initializer
|
75
|
+
end
|
76
|
+
|
77
|
+
# This initializer appends the package's migrations to the host app.
|
78
|
+
initializer :append_migrations do |app|
|
79
|
+
unless app.root.to_s.match root.to_s
|
80
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
81
|
+
app.config.paths["db/migrate"] << expanded_path
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
:::
|
91
|
+
|
92
|
+
## Package Loading
|
93
|
+
|
94
|
+
Packages are loaded automatically via `config/packages.rb`, which is created by the `pu:core:install` generator. This file simply finds and loads all `engine.rb` files within your `packages/` directory.
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
# config/packages.rb
|
98
|
+
# This file is required in `config/application.rb`
|
99
|
+
|
100
|
+
Dir.glob(File.expand_path("../packages/**/lib/engine.rb", __dir__)) do |package|
|
101
|
+
load package
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
::: tip
|
106
|
+
You can package and ship your packages as gems.
|
107
|
+
:::
|
108
|
+
|
109
|
+
## Generator Integration
|
110
|
+
|
111
|
+
### Package Generator
|
112
|
+
|
113
|
+
```bash
|
114
|
+
# Create a feature package
|
115
|
+
rails generate pu:pkg:package blogging
|
116
|
+
```
|
117
|
+
|
118
|
+
Generates the basic structure with `Plutonium::Package::Engine` included.
|
119
|
+
|
120
|
+
### Portal Generator
|
121
|
+
|
122
|
+
```bash
|
123
|
+
# Create a portal package
|
124
|
+
rails generate pu:pkg:portal admin
|
125
|
+
```
|
126
|
+
|
127
|
+
Generates a portal structure with `Plutonium::Portal::Engine` (which includes Package::Engine).
|
128
|
+
|
129
|
+
## Package Types
|
130
|
+
|
131
|
+
The package system supports two main types:
|
132
|
+
|
133
|
+
1. **Feature Packages**: Business logic packages that include `Plutonium::Package::Engine`
|
134
|
+
2. **Portal Packages**: User interface packages that include `Plutonium::Portal::Engine`
|
135
|
+
|
136
|
+
Both types benefit from the foundational features provided by the Package module.
|
137
|
+
|
138
|
+
## Best Practices
|
139
|
+
|
140
|
+
1. **Use Generators**: Always use `pu:pkg:package` or `pu:pkg:portal` generators
|
141
|
+
2. **Namespace Consistency**: Keep package names consistent with their directory structure
|
142
|
+
3. **Migration Organization**: Place package-specific migrations in the package's `db/migrate` directory
|
143
|
+
4. **Engine Simplicity**: Keep engine.rb files minimal - they're just configuration points
|
144
|
+
|
145
|
+
## Integration with Other Modules
|
146
|
+
|
147
|
+
The Package module works closely with:
|
148
|
+
- **Portal Module**: Provides the foundation for portal functionality
|
149
|
+
- **Generator Module**: Scaffolds package structure
|
150
|
+
- **Core Module**: Integrates with controller bootable system
|
151
|
+
- **Routing Module**: Supports resource registration within packages
|