plutonium 0.23.4 → 0.23.5
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/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 +605 -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/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,618 @@
|
|
1
|
+
---
|
2
|
+
title: Resource Record Module
|
3
|
+
---
|
4
|
+
|
5
|
+
# Resource Record Module
|
6
|
+
|
7
|
+
The Resource Record module (`Plutonium::Resource::Record`) provides enhanced ActiveRecord functionality specifically designed for Plutonium resources. It includes monetary handling, routing enhancements, labeling, field introspection, association management, and entity scoping capabilities.
|
8
|
+
|
9
|
+
::: tip Usage
|
10
|
+
Include this module in your ActiveRecord models to gain access to all Plutonium resource functionality:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
class Product < ApplicationRecord
|
14
|
+
include Plutonium::Resource::Record
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
Or inherit from your base resource record class:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
class Product < MyApp::ResourceRecord
|
22
|
+
# MyApp::ResourceRecord includes Plutonium::Resource::Record
|
23
|
+
end
|
24
|
+
```
|
25
|
+
:::
|
26
|
+
|
27
|
+
## Included Modules
|
28
|
+
|
29
|
+
The Resource Record module automatically includes six specialized modules:
|
30
|
+
|
31
|
+
1. **Plutonium::Models::HasCents** - Monetary value handling
|
32
|
+
2. **Plutonium::Resource::Record::Routes** - URL parameter and routing enhancements
|
33
|
+
3. **Plutonium::Resource::Record::Labeling** - Human-readable record labels
|
34
|
+
4. **Plutonium::Resource::Record::FieldNames** - Field introspection and categorization
|
35
|
+
5. **Plutonium::Resource::Record::Associations** - Enhanced association methods with SGID support
|
36
|
+
6. **Plutonium::Resource::Record::AssociatedWith** - Entity scoping and association queries
|
37
|
+
|
38
|
+
---
|
39
|
+
|
40
|
+
## Monetary Handling (HasCents)
|
41
|
+
|
42
|
+
The `HasCents` module provides sophisticated monetary value handling, storing amounts as integers (cents) while exposing decimal interfaces for easy manipulation.
|
43
|
+
|
44
|
+
### Basic Usage
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class Product < ApplicationRecord
|
48
|
+
include Plutonium::Resource::Record
|
49
|
+
|
50
|
+
# Define monetary fields
|
51
|
+
has_cents :price_cents # Creates price getter/setter
|
52
|
+
has_cents :cost_cents, name: :wholesale # Custom name
|
53
|
+
has_cents :tax_cents, rate: 1000 # Custom rate (1000 = 3 decimal places)
|
54
|
+
has_cents :total_cents, suffix: "amount" # Custom suffix
|
55
|
+
end
|
56
|
+
|
57
|
+
# Usage
|
58
|
+
product = Product.new
|
59
|
+
product.price = 19.99
|
60
|
+
product.price_cents # => 1999
|
61
|
+
product.price # => 19.99
|
62
|
+
|
63
|
+
product.wholesale = 12.50
|
64
|
+
product.cost_cents # => 1250
|
65
|
+
```
|
66
|
+
|
67
|
+
### Advanced Features
|
68
|
+
|
69
|
+
**Precision and Truncation**
|
70
|
+
```ruby
|
71
|
+
product.price = 10.999 # Truncates, doesn't round
|
72
|
+
product.price_cents # => 1099
|
73
|
+
product.price # => 10.99
|
74
|
+
```
|
75
|
+
|
76
|
+
**Custom Conversion Rates**
|
77
|
+
```ruby
|
78
|
+
class Product < ApplicationRecord
|
79
|
+
has_cents :weight_cents, name: :weight, rate: 1000 # 3 decimal places
|
80
|
+
has_cents :quantity_cents, name: :quantity, rate: 1 # Whole numbers only
|
81
|
+
end
|
82
|
+
|
83
|
+
product.weight = 1.234
|
84
|
+
product.weight_cents # => 1234
|
85
|
+
|
86
|
+
product.quantity = 5
|
87
|
+
product.quantity_cents # => 5
|
88
|
+
```
|
89
|
+
|
90
|
+
**Validation Integration**
|
91
|
+
```ruby
|
92
|
+
class Product < ApplicationRecord
|
93
|
+
has_cents :price_cents
|
94
|
+
|
95
|
+
validates :price_cents, numericality: { greater_than: 0 }
|
96
|
+
end
|
97
|
+
|
98
|
+
product = Product.new(price: -10)
|
99
|
+
product.valid? # => false
|
100
|
+
product.errors[:price_cents] # => ["must be greater than 0"]
|
101
|
+
product.errors[:price] # => ["is invalid"]
|
102
|
+
```
|
103
|
+
|
104
|
+
### Class Methods
|
105
|
+
|
106
|
+
**Introspection**
|
107
|
+
```ruby
|
108
|
+
Product.has_cents_attributes
|
109
|
+
# => {
|
110
|
+
# price_cents: { name: :price, rate: 100 },
|
111
|
+
# cost_cents: { name: :wholesale, rate: 100 }
|
112
|
+
# }
|
113
|
+
|
114
|
+
Product.has_cents_attribute?(:price_cents) # => true
|
115
|
+
Product.has_cents_attribute?(:name) # => false
|
116
|
+
```
|
117
|
+
|
118
|
+
---
|
119
|
+
|
120
|
+
## Routing Enhancements
|
121
|
+
|
122
|
+
The Routes module provides flexible URL parameter handling and association route discovery.
|
123
|
+
|
124
|
+
### URL Parameters
|
125
|
+
|
126
|
+
**Default Behavior**
|
127
|
+
```ruby
|
128
|
+
# Uses :id by default
|
129
|
+
user = User.find(1)
|
130
|
+
user.to_param # => "1"
|
131
|
+
```
|
132
|
+
|
133
|
+
**Custom Path Parameters**
|
134
|
+
```ruby
|
135
|
+
class User < ApplicationRecord
|
136
|
+
include Plutonium::Resource::Record
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def path_parameter(param_name)
|
141
|
+
# Uses specified field as URL parameter
|
142
|
+
path_parameter :username
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
user = User.create(username: "john_doe")
|
147
|
+
user.to_param # => "john_doe"
|
148
|
+
# URLs become /users/john_doe instead of /users/1
|
149
|
+
```
|
150
|
+
|
151
|
+
**Dynamic Path Parameters**
|
152
|
+
```ruby
|
153
|
+
class Article < ApplicationRecord
|
154
|
+
include Plutonium::Resource::Record
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def dynamic_path_parameter(param_name)
|
159
|
+
# Creates SEO-friendly URLs with ID prefix
|
160
|
+
dynamic_path_parameter :title
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
article = Article.create(title: "My Great Article")
|
165
|
+
article.to_param # => "1-my-great-article"
|
166
|
+
# URLs become /articles/1-my-great-article
|
167
|
+
```
|
168
|
+
|
169
|
+
### Association Route Discovery
|
170
|
+
|
171
|
+
**Has Many Routes**
|
172
|
+
```ruby
|
173
|
+
class User < ApplicationRecord
|
174
|
+
has_many :posts
|
175
|
+
has_many :comments
|
176
|
+
end
|
177
|
+
|
178
|
+
User.has_many_association_routes
|
179
|
+
# => ["posts", "comments"]
|
180
|
+
```
|
181
|
+
|
182
|
+
**Nested Attributes Detection**
|
183
|
+
```ruby
|
184
|
+
class User < ApplicationRecord
|
185
|
+
has_many :posts
|
186
|
+
accepts_nested_attributes_for :posts
|
187
|
+
end
|
188
|
+
|
189
|
+
User.all_nested_attributes_options
|
190
|
+
# => {
|
191
|
+
# posts: {
|
192
|
+
# allow_destroy: false,
|
193
|
+
# update_only: false,
|
194
|
+
# macro: :has_many,
|
195
|
+
# class: Post
|
196
|
+
# }
|
197
|
+
# }
|
198
|
+
```
|
199
|
+
|
200
|
+
### Scopes
|
201
|
+
|
202
|
+
**Path Parameter Lookup**
|
203
|
+
```ruby
|
204
|
+
# Automatically included scope for parameter-based lookups
|
205
|
+
User.from_path_param("john_doe") # Uses configured parameter field
|
206
|
+
Article.from_path_param("1-my-great-article") # Extracts ID from dynamic parameter
|
207
|
+
```
|
208
|
+
|
209
|
+
---
|
210
|
+
|
211
|
+
## Record Labeling
|
212
|
+
|
213
|
+
The Labeling module provides intelligent human-readable labels for records.
|
214
|
+
|
215
|
+
### Automatic Label Generation
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
class User < ApplicationRecord
|
219
|
+
include Plutonium::Resource::Record
|
220
|
+
|
221
|
+
# Will try :name first, then :title, then fallback
|
222
|
+
end
|
223
|
+
|
224
|
+
user = User.new(name: "John Doe")
|
225
|
+
user.to_label # => "John Doe"
|
226
|
+
|
227
|
+
user_without_name = User.create(id: 1)
|
228
|
+
user_without_name.to_label # => "User #1"
|
229
|
+
```
|
230
|
+
|
231
|
+
### Label Priority
|
232
|
+
|
233
|
+
The `to_label` method checks fields in this order:
|
234
|
+
1. `:name` attribute (if present and not blank)
|
235
|
+
2. `:title` attribute (if present and not blank)
|
236
|
+
3. Fallback: `"#{model_name.human} ##{to_param}"`
|
237
|
+
|
238
|
+
### Custom Labels
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
class Product < ApplicationRecord
|
242
|
+
include Plutonium::Resource::Record
|
243
|
+
|
244
|
+
# Override to_label for custom behavior
|
245
|
+
def to_label
|
246
|
+
"#{name} (#{sku})"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
product = Product.new(name: "Widget", sku: "W123")
|
251
|
+
product.to_label # => "Widget (W123)"
|
252
|
+
```
|
253
|
+
|
254
|
+
---
|
255
|
+
|
256
|
+
## Field Introspection
|
257
|
+
|
258
|
+
The FieldNames module provides comprehensive field categorization and introspection capabilities.
|
259
|
+
|
260
|
+
### Field Categories
|
261
|
+
|
262
|
+
**Resource Fields**
|
263
|
+
```ruby
|
264
|
+
class User < ApplicationRecord
|
265
|
+
include Plutonium::Resource::Record
|
266
|
+
end
|
267
|
+
|
268
|
+
User.resource_field_names
|
269
|
+
# => [:id, :name, :email, :created_at, :updated_at, ...]
|
270
|
+
```
|
271
|
+
|
272
|
+
**Association Fields**
|
273
|
+
```ruby
|
274
|
+
class Post < ApplicationRecord
|
275
|
+
belongs_to :user
|
276
|
+
has_many :comments
|
277
|
+
has_one :featured_image
|
278
|
+
end
|
279
|
+
|
280
|
+
Post.belongs_to_association_field_names # => [:user]
|
281
|
+
Post.has_one_association_field_names # => [:featured_image]
|
282
|
+
Post.has_many_association_field_names # => [:comments]
|
283
|
+
```
|
284
|
+
|
285
|
+
**Attachment Fields**
|
286
|
+
```ruby
|
287
|
+
class User < ApplicationRecord
|
288
|
+
has_one_attached :avatar
|
289
|
+
has_many_attached :documents
|
290
|
+
end
|
291
|
+
|
292
|
+
User.has_one_attached_field_names # => [:avatar]
|
293
|
+
User.has_many_attached_field_names # => [:documents]
|
294
|
+
```
|
295
|
+
|
296
|
+
### Field Filtering
|
297
|
+
|
298
|
+
The module automatically filters out Rails internal associations:
|
299
|
+
- `*_attachment` and `*_blob` associations are excluded from has_one results
|
300
|
+
- `*_attachments` and `*_blobs` associations are excluded from has_many results
|
301
|
+
|
302
|
+
---
|
303
|
+
|
304
|
+
## Enhanced Associations
|
305
|
+
|
306
|
+
The Associations module enhances standard Rails associations with Signed Global ID (SGID) support for secure serialization.
|
307
|
+
|
308
|
+
### SGID Methods
|
309
|
+
|
310
|
+
**Singular Associations (belongs_to, has_one)**
|
311
|
+
```ruby
|
312
|
+
class Post < ApplicationRecord
|
313
|
+
include Plutonium::Resource::Record
|
314
|
+
belongs_to :user
|
315
|
+
has_one :featured_image
|
316
|
+
end
|
317
|
+
|
318
|
+
post = Post.first
|
319
|
+
|
320
|
+
# SGID getters
|
321
|
+
post.user_sgid # => "BAh7CEkiCG..."
|
322
|
+
post.featured_image_sgid # => "BAh7CEkiCG..."
|
323
|
+
|
324
|
+
# SGID setters
|
325
|
+
post.user_sgid = "BAh7CEkiCG..." # Finds and assigns user
|
326
|
+
post.featured_image_sgid = "BAh7CEkiCG..."
|
327
|
+
```
|
328
|
+
|
329
|
+
**Collection Associations (has_many, has_and_belongs_to_many)**
|
330
|
+
```ruby
|
331
|
+
class User < ApplicationRecord
|
332
|
+
include Plutonium::Resource::Record
|
333
|
+
has_many :posts
|
334
|
+
has_and_belongs_to_many :roles
|
335
|
+
end
|
336
|
+
|
337
|
+
user = User.first
|
338
|
+
|
339
|
+
# Collection SGID methods
|
340
|
+
user.post_sgids # => ["BAh7CEkiCG...", "BAh7CEkiCG..."]
|
341
|
+
user.role_sgids # => ["BAh7CEkiCG...", "BAh7CEkiCG..."]
|
342
|
+
|
343
|
+
# Collection SGID assignment
|
344
|
+
user.post_sgids = ["BAh7CEkiCG...", "BAh7CEkiCG..."]
|
345
|
+
user.role_sgids = ["BAh7CEkiCG...", "BAh7CEkiCG..."]
|
346
|
+
|
347
|
+
# Individual manipulation
|
348
|
+
user.add_post_sgid("BAh7CEkiCG...") # Adds post to collection
|
349
|
+
user.remove_post_sgid("BAh7CEkiCG...") # Removes post from collection
|
350
|
+
```
|
351
|
+
|
352
|
+
### Security Benefits
|
353
|
+
|
354
|
+
SGID methods provide:
|
355
|
+
- **Secure serialization**: Records can be safely serialized without exposing internal IDs
|
356
|
+
|
357
|
+
---
|
358
|
+
|
359
|
+
## Entity Scoping (AssociatedWith)
|
360
|
+
|
361
|
+
The AssociatedWith module provides sophisticated entity scoping for multi-tenant applications and complex association queries.
|
362
|
+
|
363
|
+
### Basic Usage
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
class Document < ApplicationRecord
|
367
|
+
include Plutonium::Resource::Record
|
368
|
+
belongs_to :user
|
369
|
+
end
|
370
|
+
|
371
|
+
class User < ApplicationRecord
|
372
|
+
has_many :documents
|
373
|
+
end
|
374
|
+
|
375
|
+
# Find all documents associated with a specific user
|
376
|
+
user = User.first
|
377
|
+
Document.associated_with(user)
|
378
|
+
# Equivalent to: Document.where(user: user)
|
379
|
+
```
|
380
|
+
|
381
|
+
### Automatic Association Detection
|
382
|
+
|
383
|
+
The module automatically detects associations in both directions:
|
384
|
+
|
385
|
+
**Direct Association (Preferred)**
|
386
|
+
```ruby
|
387
|
+
class Comment < ApplicationRecord
|
388
|
+
belongs_to :post # Direct association
|
389
|
+
end
|
390
|
+
|
391
|
+
# Automatically uses the direct association
|
392
|
+
Comment.associated_with(post) # => Comment.where(post: post)
|
393
|
+
```
|
394
|
+
|
395
|
+
**Reverse Association (With Performance Warning)**
|
396
|
+
```ruby
|
397
|
+
class Post < ApplicationRecord
|
398
|
+
has_many :comments # Reverse association
|
399
|
+
end
|
400
|
+
|
401
|
+
# Uses reverse association with performance warning
|
402
|
+
Comment.associated_with(post)
|
403
|
+
# Warning: Using indirect association from Post to Comment
|
404
|
+
# via 'comments'. This may result in poor query performance...
|
405
|
+
```
|
406
|
+
|
407
|
+
### Custom Scopes
|
408
|
+
|
409
|
+
For optimal performance, where a direct association is not possible, define custom scopes:
|
410
|
+
|
411
|
+
```ruby
|
412
|
+
class Comment < ApplicationRecord
|
413
|
+
include Plutonium::Resource::Record
|
414
|
+
|
415
|
+
# Custom scope for better performance
|
416
|
+
scope :associated_with_post, ->(post) { where(post_id: post.id) }
|
417
|
+
end
|
418
|
+
|
419
|
+
# Automatically uses the custom scope
|
420
|
+
Comment.associated_with(post) # Uses :associated_with_post scope
|
421
|
+
```
|
422
|
+
|
423
|
+
### Association Query Types
|
424
|
+
|
425
|
+
**Belongs To**
|
426
|
+
```ruby
|
427
|
+
class Comment < ApplicationRecord
|
428
|
+
belongs_to :post
|
429
|
+
end
|
430
|
+
|
431
|
+
Comment.associated_with(post)
|
432
|
+
# Generates: Comment.where(post: post)
|
433
|
+
```
|
434
|
+
|
435
|
+
**Has One**
|
436
|
+
```ruby
|
437
|
+
class Profile < ApplicationRecord
|
438
|
+
has_one :user
|
439
|
+
end
|
440
|
+
|
441
|
+
Profile.associated_with(user)
|
442
|
+
# Generates: Profile.joins(:user).where(user: {id: user.id})
|
443
|
+
```
|
444
|
+
|
445
|
+
**Has Many**
|
446
|
+
```ruby
|
447
|
+
class Post < ApplicationRecord
|
448
|
+
has_many :comments
|
449
|
+
end
|
450
|
+
|
451
|
+
# When finding posts associated with a comment
|
452
|
+
Post.associated_with(comment)
|
453
|
+
# Generates: Post.joins(:comments).where(comments: {id: comment.id})
|
454
|
+
```
|
455
|
+
|
456
|
+
### Error Handling
|
457
|
+
|
458
|
+
When associations cannot be resolved:
|
459
|
+
|
460
|
+
```ruby
|
461
|
+
class UnrelatedModel < ApplicationRecord
|
462
|
+
include Plutonium::Resource::Record
|
463
|
+
end
|
464
|
+
|
465
|
+
UnrelatedModel.associated_with(user)
|
466
|
+
# Raises: Could not resolve the association between 'UnrelatedModel' and 'User'
|
467
|
+
#
|
468
|
+
# Define:
|
469
|
+
# 1. the associations between the models
|
470
|
+
# 2. a named scope on UnrelatedModel e.g.
|
471
|
+
#
|
472
|
+
# scope :associated_with_user, ->(user) { do_something_here }
|
473
|
+
```
|
474
|
+
|
475
|
+
---
|
476
|
+
|
477
|
+
## Generator Integration
|
478
|
+
|
479
|
+
The Resource Record module integrates seamlessly with Plutonium generators:
|
480
|
+
|
481
|
+
### Model Generation
|
482
|
+
|
483
|
+
```bash
|
484
|
+
# Generate a model with monetary fields
|
485
|
+
rails generate pu:res:model Product name:string price_cents:integer
|
486
|
+
|
487
|
+
# Generated model includes has_cents automatically
|
488
|
+
class Product < MyApp::ResourceRecord
|
489
|
+
has_cents :price_cents
|
490
|
+
validates :name, presence: true
|
491
|
+
end
|
492
|
+
```
|
493
|
+
|
494
|
+
### Automatic Field Detection
|
495
|
+
|
496
|
+
Generators automatically detect and configure:
|
497
|
+
- `*_cents` fields get `has_cents` declarations
|
498
|
+
- Reference fields get `belongs_to` associations
|
499
|
+
- Required fields get presence validations
|
500
|
+
|
501
|
+
---
|
502
|
+
|
503
|
+
## Best Practices
|
504
|
+
|
505
|
+
### Monetary Fields
|
506
|
+
|
507
|
+
```ruby
|
508
|
+
# ✅ Good: Use descriptive names
|
509
|
+
has_cents :price_cents
|
510
|
+
has_cents :shipping_cost_cents, name: :shipping_cost
|
511
|
+
|
512
|
+
# ❌ Avoid: Generic names
|
513
|
+
has_cents :amount_cents # What kind of amount?
|
514
|
+
```
|
515
|
+
|
516
|
+
### Custom Path Parameters
|
517
|
+
|
518
|
+
```ruby
|
519
|
+
# ✅ Good: Use stable, unique fields
|
520
|
+
class User < ApplicationRecord
|
521
|
+
private
|
522
|
+
|
523
|
+
def path_parameter(param_name)
|
524
|
+
path_parameter :username # Stable and unique
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
# ❌ Avoid: Changeable fields
|
529
|
+
class User < ApplicationRecord
|
530
|
+
private
|
531
|
+
|
532
|
+
def dynamic_path_parameter(param_name)
|
533
|
+
dynamic_path_parameter :name # Can change, breaks bookmarks
|
534
|
+
end
|
535
|
+
end
|
536
|
+
```
|
537
|
+
|
538
|
+
### Entity Scoping
|
539
|
+
|
540
|
+
```ruby
|
541
|
+
# ✅ Good: Define custom scopes for complex queries
|
542
|
+
class Order < ApplicationRecord
|
543
|
+
scope :associated_with_customer, ->(customer) do
|
544
|
+
joins(:customer).where(customers: { id: customer.id })
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
# ✅ Good: Use direct associations when possible
|
549
|
+
class OrderItem < ApplicationRecord
|
550
|
+
belongs_to :order
|
551
|
+
# associated_with will automatically use the direct association
|
552
|
+
end
|
553
|
+
```
|
554
|
+
|
555
|
+
### Field Introspection
|
556
|
+
|
557
|
+
```ruby
|
558
|
+
# ✅ Good: Use field introspection in dynamic code
|
559
|
+
def build_form_fields
|
560
|
+
resource_class.resource_field_names.each do |field|
|
561
|
+
# Build form field dynamically
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
# ✅ Good: Cache results in production
|
566
|
+
def expensive_field_analysis
|
567
|
+
Rails.cache.fetch("#{model_name}_field_analysis", expires_in: 1.hour) do
|
568
|
+
analyze_fields(resource_field_names)
|
569
|
+
end
|
570
|
+
end
|
571
|
+
```
|
572
|
+
|
573
|
+
---
|
574
|
+
|
575
|
+
## Performance Considerations
|
576
|
+
|
577
|
+
### Field Introspection Caching
|
578
|
+
|
579
|
+
Field introspection methods are automatically cached in non-local environments:
|
580
|
+
|
581
|
+
```ruby
|
582
|
+
# Cached in production/staging
|
583
|
+
User.resource_field_names
|
584
|
+
User.has_many_association_field_names
|
585
|
+
|
586
|
+
# Always fresh in development
|
587
|
+
Rails.env.local? # => true, no caching
|
588
|
+
```
|
589
|
+
|
590
|
+
### Association Query Optimization
|
591
|
+
|
592
|
+
```ruby
|
593
|
+
# ✅ Efficient: Direct association
|
594
|
+
Comment.associated_with(post) # Uses WHERE clause
|
595
|
+
|
596
|
+
# ⚠️ Less efficient: Reverse association
|
597
|
+
Post.associated_with(comment) # Uses JOIN + WHERE
|
598
|
+
|
599
|
+
# ✅ Optimal: Direct association
|
600
|
+
belongs_to :post
|
601
|
+
|
602
|
+
# ✅ Alternative: Custom scope (when direct association is not possible)
|
603
|
+
scope :associated_with_comment, ->(comment) do
|
604
|
+
where(id: comment.post_id) # Direct ID lookup
|
605
|
+
end
|
606
|
+
```
|
607
|
+
|
608
|
+
### SGID Performance
|
609
|
+
|
610
|
+
```ruby
|
611
|
+
# ✅ Efficient: Batch operations
|
612
|
+
user.post_sgids = sgid_array # Single assignment
|
613
|
+
|
614
|
+
# ❌ Inefficient: Individual operations
|
615
|
+
sgid_array.each { |sgid| user.add_post_sgid(sgid) } # Multiple queries
|
616
|
+
```
|
617
|
+
|
618
|
+
The Resource Record module provides a comprehensive foundation for building robust, feature-rich ActiveRecord models within the Plutonium framework, handling everything from monetary values to complex association queries with performance and security in mind.
|