elaine_crud 0.1.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/.rspec +3 -0
- data/LICENSE +21 -0
- data/README.md +225 -0
- data/Rakefile +9 -0
- data/TODO.md +496 -0
- data/app/controllers/elaine_crud/base_controller.rb +228 -0
- data/app/helpers/elaine_crud/base_helper.rb +787 -0
- data/app/helpers/elaine_crud/search_helper.rb +132 -0
- data/app/javascript/controllers/dropdown_controller.js +18 -0
- data/app/views/elaine_crud/base/_edit_row.html.erb +60 -0
- data/app/views/elaine_crud/base/_export_button.html.erb +88 -0
- data/app/views/elaine_crud/base/_foreign_key_select_refresh.html.erb +52 -0
- data/app/views/elaine_crud/base/_form.html.erb +45 -0
- data/app/views/elaine_crud/base/_form_fields.html.erb +45 -0
- data/app/views/elaine_crud/base/_index_table.html.erb +58 -0
- data/app/views/elaine_crud/base/_modal.html.erb +71 -0
- data/app/views/elaine_crud/base/_pagination.html.erb +110 -0
- data/app/views/elaine_crud/base/_per_page_selector.html.erb +30 -0
- data/app/views/elaine_crud/base/_search_bar.html.erb +75 -0
- data/app/views/elaine_crud/base/_show_details.html.erb +29 -0
- data/app/views/elaine_crud/base/_view_row.html.erb +96 -0
- data/app/views/elaine_crud/base/edit.html.erb +51 -0
- data/app/views/elaine_crud/base/index.html.erb +74 -0
- data/app/views/elaine_crud/base/new.html.erb +12 -0
- data/app/views/elaine_crud/base/new_modal.html.erb +37 -0
- data/app/views/elaine_crud/base/not_found.html.erb +49 -0
- data/app/views/elaine_crud/base/show.html.erb +32 -0
- data/docs/ARCHITECTURE.md +410 -0
- data/docs/CSS_GRID_LAYOUT.md +126 -0
- data/docs/DEMO.md +693 -0
- data/docs/DSL_EXAMPLES.md +313 -0
- data/docs/FOREIGN_KEY_EXAMPLE.rb +100 -0
- data/docs/FOREIGN_KEY_SUPPORT.md +197 -0
- data/docs/HAS_MANY_IMPLEMENTATION.md +154 -0
- data/docs/LAYOUT_EXAMPLES.md +301 -0
- data/docs/TROUBLESHOOTING.md +170 -0
- data/elaine_crud.gemspec +46 -0
- data/lib/elaine_crud/dsl_methods.rb +348 -0
- data/lib/elaine_crud/engine.rb +37 -0
- data/lib/elaine_crud/export_handling.rb +164 -0
- data/lib/elaine_crud/field_configuration.rb +422 -0
- data/lib/elaine_crud/field_configuration_methods.rb +152 -0
- data/lib/elaine_crud/layout_calculation.rb +55 -0
- data/lib/elaine_crud/parameter_handling.rb +48 -0
- data/lib/elaine_crud/record_fetching.rb +150 -0
- data/lib/elaine_crud/relationship_handling.rb +220 -0
- data/lib/elaine_crud/routing.rb +33 -0
- data/lib/elaine_crud/search_and_filtering.rb +285 -0
- data/lib/elaine_crud/sorting_concern.rb +65 -0
- data/lib/elaine_crud/version.rb +5 -0
- data/lib/elaine_crud.rb +25 -0
- data/lib/tasks/demo.rake +111 -0
- data/lib/tasks/spec.rake +26 -0
- metadata +264 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# has_many Relationship Implementation
|
|
2
|
+
|
|
3
|
+
This document describes the implementation of `has_many` relationship support in ElaineCrud using filtered flat resources (URLs like `/meetings?meeting_room_id=4`).
|
|
4
|
+
|
|
5
|
+
## Implementation Summary
|
|
6
|
+
|
|
7
|
+
The has_many implementation adds the following features:
|
|
8
|
+
|
|
9
|
+
### 1. Automatic Detection
|
|
10
|
+
- ElaineCrud automatically detects all `has_many` relationships in your ActiveRecord models
|
|
11
|
+
- Auto-configures display fields for related records
|
|
12
|
+
- Includes automatic query optimization with `includes()` to prevent N+1 queries
|
|
13
|
+
|
|
14
|
+
### 2. Parent Filtering
|
|
15
|
+
- Supports URLs like `/meetings?meeting_room_id=4` to show filtered views
|
|
16
|
+
- Automatically filters records based on parent relationship parameters
|
|
17
|
+
- Maintains parent context in forms and redirects
|
|
18
|
+
|
|
19
|
+
### 3. Smart Display
|
|
20
|
+
- **Index Views**: Shows relationship summaries with counts and previews
|
|
21
|
+
- **Links**: Automatically generates links to filtered views (e.g., "5 meetings: Daily Standup, Team Review...")
|
|
22
|
+
- **Forms**: Pre-populates parent relationships when creating from filtered context
|
|
23
|
+
|
|
24
|
+
### 4. UI Enhancements
|
|
25
|
+
- Context-aware breadcrumbs showing the parent record
|
|
26
|
+
- "View All" links to remove filters
|
|
27
|
+
- Parent information display in forms
|
|
28
|
+
|
|
29
|
+
## Usage Examples
|
|
30
|
+
|
|
31
|
+
### Automatic Configuration (No Code Required)
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
class MeetingRoomsController < ElaineCrud::BaseController
|
|
35
|
+
model MeetingRoom
|
|
36
|
+
permit_params :name, :capacity, :building
|
|
37
|
+
# has_many :meetings relationship automatically detected and configured
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class MeetingsController < ElaineCrud::BaseController
|
|
41
|
+
model Meeting
|
|
42
|
+
permit_params :title, :start_time, :end_time, :meeting_room_id
|
|
43
|
+
# Automatic parent filtering support - no additional configuration needed
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Manual Configuration (Optional)
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
class MeetingRoomsController < ElaineCrud::BaseController
|
|
51
|
+
model MeetingRoom
|
|
52
|
+
permit_params :name, :capacity, :building
|
|
53
|
+
|
|
54
|
+
# Optional: Customize has_many relationship display
|
|
55
|
+
has_many_relation :meetings do
|
|
56
|
+
title "Room Meetings"
|
|
57
|
+
display :title
|
|
58
|
+
show_count true
|
|
59
|
+
max_preview_items 3
|
|
60
|
+
scope -> { where('start_time > ?', Time.current) }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Generated URLs and Behavior
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
# MeetingRoom index page shows:
|
|
69
|
+
# Name | Capacity | Meetings
|
|
70
|
+
# Room A | 20 | 5 meetings: Daily Standup, Team Review, All Hands
|
|
71
|
+
# Room B | 10 | 2 meetings: 1:1 Meeting, Code Review
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Clicking "5 meetings: Daily Standup, Team Review..." generates:
|
|
75
|
+
- URL: `/meetings?meeting_room_id=4`
|
|
76
|
+
- Shows filtered view with context banner
|
|
77
|
+
- "New Meeting" button pre-populates `meeting_room_id=4`
|
|
78
|
+
- After creating, redirects back to filtered view
|
|
79
|
+
|
|
80
|
+
## Technical Implementation
|
|
81
|
+
|
|
82
|
+
### Controller Changes
|
|
83
|
+
- **BaseController**: Added parent filtering logic in `fetch_records`
|
|
84
|
+
- **Auto-detection**: `auto_configure_has_many_relationships` method
|
|
85
|
+
- **Context management**: `@parent_context` instance variable for UI
|
|
86
|
+
- **Query optimization**: Enhanced `get_all_relationship_includes`
|
|
87
|
+
|
|
88
|
+
### Field Configuration
|
|
89
|
+
- **FieldConfiguration**: Added `has_many_config` attribute and methods
|
|
90
|
+
- **Display logic**: `render_has_many_display` method for relationship summaries
|
|
91
|
+
- **DSL support**: Block-style configuration with `has_many_relation`
|
|
92
|
+
|
|
93
|
+
### Helper Methods
|
|
94
|
+
- **BaseHelper**: Enhanced `display_field_value` to handle has_many
|
|
95
|
+
- **Link generation**: Automatic URL generation for filtered views
|
|
96
|
+
- **Relationship detection**: `is_has_many_relationship?` helper
|
|
97
|
+
|
|
98
|
+
### View Templates
|
|
99
|
+
- **Index**: Parent context banner with breadcrumbs
|
|
100
|
+
- **Forms**: Hidden fields and context information for parent relationships
|
|
101
|
+
- **Styling**: TailwindCSS classes for consistent UI
|
|
102
|
+
|
|
103
|
+
## Configuration Options
|
|
104
|
+
|
|
105
|
+
| Option | Type | Description | Default |
|
|
106
|
+
|--------|------|-------------|---------|
|
|
107
|
+
| `display` | Symbol/Proc | Field or method to display | Auto-detected (name, title, etc.) |
|
|
108
|
+
| `show_count` | Boolean | Show count in relationship display | `true` |
|
|
109
|
+
| `max_preview_items` | Integer | Number of items to preview | `3` |
|
|
110
|
+
| `scope` | Proc | Limits displayed relationships | `-> { all }` |
|
|
111
|
+
| `foreign_key` | Symbol | The foreign key field name | Auto-detected |
|
|
112
|
+
|
|
113
|
+
## Supported URL Patterns
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
# All supported URL patterns:
|
|
117
|
+
/meeting_rooms # All meeting rooms
|
|
118
|
+
/meeting_rooms/4 # Show specific meeting room
|
|
119
|
+
/meetings # All meetings
|
|
120
|
+
/meetings?meeting_room_id=4 # Meetings filtered by room
|
|
121
|
+
/meetings/new # Create new meeting
|
|
122
|
+
/meetings/new?meeting_room_id=4 # Create meeting for specific room
|
|
123
|
+
/meetings/123?meeting_room_id=4 # Show meeting in room context
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Migration from Manual Configuration
|
|
127
|
+
|
|
128
|
+
If you previously configured has_many relationships manually, ElaineCrud respects existing configurations:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
# Your existing configuration takes precedence
|
|
132
|
+
has_many_relation :meetings do
|
|
133
|
+
display :custom_title
|
|
134
|
+
show_count false
|
|
135
|
+
end
|
|
136
|
+
# Auto-detection skips this field since it's already configured
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Performance Considerations
|
|
140
|
+
|
|
141
|
+
- **Automatic Includes**: Related models are automatically included in queries
|
|
142
|
+
- **Lazy Loading**: Relationship counts and previews are loaded only when displayed
|
|
143
|
+
- **Query Optimization**: Uses single queries with joins rather than N+1 patterns
|
|
144
|
+
- **Caching**: Consider adding Rails caching for frequently accessed relationship displays
|
|
145
|
+
|
|
146
|
+
## Error Handling
|
|
147
|
+
|
|
148
|
+
ElaineCrud handles various error scenarios gracefully:
|
|
149
|
+
- **Missing Parent Record**: Shows error message with record ID
|
|
150
|
+
- **Configuration Errors**: Falls back to basic display in production
|
|
151
|
+
- **Missing Display Fields**: Uses fallback display methods
|
|
152
|
+
- **Database Errors**: Logs errors and shows user-friendly messages
|
|
153
|
+
|
|
154
|
+
This implementation provides a powerful, automatic has_many relationship system that works out-of-the-box while still allowing for extensive customization when needed.
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# ElaineCrud Layout System
|
|
2
|
+
|
|
3
|
+
ElaineCrud now supports a flexible layout system that allows you to customize how ActiveRecord items are displayed in a grid format.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The layout system introduces two main concepts:
|
|
8
|
+
|
|
9
|
+
1. **Layout Structure**: A nested array where the first dimension represents rows and the second dimension represents columns within each row
|
|
10
|
+
2. **Layout Header**: An array of configuration objects defining column widths, titles, and sorting behavior
|
|
11
|
+
|
|
12
|
+
## Default Behavior
|
|
13
|
+
|
|
14
|
+
By default, ElaineCrud displays all fields in a single row with equal column distribution:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
class PeopleController < ElaineCrud::BaseController
|
|
18
|
+
model Person
|
|
19
|
+
permit_params :name, :email, :bio, :status
|
|
20
|
+
|
|
21
|
+
field :name, title: "Full Name"
|
|
22
|
+
field :email, title: "Email Address"
|
|
23
|
+
field :bio, title: "Biography"
|
|
24
|
+
field :status, title: "Status"
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This will create a layout like:
|
|
29
|
+
```
|
|
30
|
+
| Name (25%) | Email (25%) | Bio (25%) | Status (25%) |
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Custom Layouts
|
|
34
|
+
|
|
35
|
+
You can override the `calculate_layout` and `calculate_layout_header` methods to create custom layouts:
|
|
36
|
+
|
|
37
|
+
### Example 1: Two-Row Layout
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
class PeopleController < ElaineCrud::BaseController
|
|
41
|
+
model Person
|
|
42
|
+
permit_params :name, :email, :bio, :status, :created_at
|
|
43
|
+
|
|
44
|
+
field :name, title: "Full Name"
|
|
45
|
+
field :email, title: "Email Address"
|
|
46
|
+
field :bio, title: "Biography"
|
|
47
|
+
field :status, title: "Status"
|
|
48
|
+
field :created_at, title: "Joined", visible: true
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# Custom layout: Name and Email on first row, Bio and metadata on second row
|
|
53
|
+
def calculate_layout(content, fields)
|
|
54
|
+
[
|
|
55
|
+
[
|
|
56
|
+
{ field_name: :name, colspan: 1 },
|
|
57
|
+
{ field_name: :email, colspan: 1 },
|
|
58
|
+
{ field_name: :status, colspan: 1 }
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
{ field_name: :bio, colspan: 2 },
|
|
62
|
+
{ field_name: :created_at, colspan: 1 }
|
|
63
|
+
]
|
|
64
|
+
]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Custom header: Define column sizes
|
|
68
|
+
def calculate_layout_header(fields)
|
|
69
|
+
["25%", "35%", "25%", "15%"] # 4 columns total
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Example 2: Conditional Layout Based on Content
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
class ProductController < ElaineCrud::BaseController
|
|
78
|
+
model Product
|
|
79
|
+
permit_params :name, :price, :description, :category, :image_url, :featured
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# Different layouts for featured vs regular products
|
|
84
|
+
def calculate_layout(content, fields)
|
|
85
|
+
if content.featured?
|
|
86
|
+
# Featured products get a prominent layout
|
|
87
|
+
[
|
|
88
|
+
[
|
|
89
|
+
{ field_name: :name, colspan: 2 },
|
|
90
|
+
{ field_name: :price, colspan: 1 }
|
|
91
|
+
],
|
|
92
|
+
[
|
|
93
|
+
{ field_name: :description, colspan: 3 }
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
{ field_name: :category, colspan: 1 },
|
|
97
|
+
{ field_name: :image_url, colspan: 2 }
|
|
98
|
+
]
|
|
99
|
+
]
|
|
100
|
+
else
|
|
101
|
+
# Regular products use compact layout
|
|
102
|
+
[
|
|
103
|
+
[
|
|
104
|
+
{ field_name: :name, colspan: 1 },
|
|
105
|
+
{ field_name: :price, colspan: 1 },
|
|
106
|
+
{ field_name: :category, colspan: 1 }
|
|
107
|
+
]
|
|
108
|
+
]
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def calculate_layout_header(fields)
|
|
113
|
+
["40%", "20%", "40%"] # Flexible 3-column grid
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Example 3: Complex Multi-Row Layout with Spans
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
class OrderController < ElaineCrud::BaseController
|
|
122
|
+
model Order
|
|
123
|
+
permit_params :order_number, :customer_name, :total, :status, :notes, :created_at
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def calculate_layout(content, fields)
|
|
128
|
+
[
|
|
129
|
+
[
|
|
130
|
+
{ field_name: :order_number, colspan: 1 },
|
|
131
|
+
{ field_name: :customer_name, colspan: 2 },
|
|
132
|
+
{ field_name: :total, colspan: 1 }
|
|
133
|
+
],
|
|
134
|
+
[
|
|
135
|
+
{ field_name: :status, colspan: 1 },
|
|
136
|
+
{ field_name: :created_at, colspan: 1 },
|
|
137
|
+
{ field_name: :notes, colspan: 2 } # Notes span 2 columns
|
|
138
|
+
]
|
|
139
|
+
]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def calculate_layout_header(fields)
|
|
143
|
+
["15%", "35%", "25%", "25%"]
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Layout Configuration Properties
|
|
149
|
+
|
|
150
|
+
Each column configuration object in the layout can have the following properties:
|
|
151
|
+
|
|
152
|
+
- `field_name`: **Required** - The name of the field to display
|
|
153
|
+
- `colspan`: **Optional** - Number of columns this field should span (default: 1)
|
|
154
|
+
- `rowspan`: **Optional** - Number of rows this field should span (default: 1)
|
|
155
|
+
- Future properties can be added as needed
|
|
156
|
+
|
|
157
|
+
## Method Signatures
|
|
158
|
+
|
|
159
|
+
### calculate_layout(content, fields)
|
|
160
|
+
|
|
161
|
+
- **content**: The ActiveRecord object for the current row
|
|
162
|
+
- **fields**: Array of field names that should be included in the layout
|
|
163
|
+
- **Returns**: Array of arrays, where each sub-array represents a row of column configurations
|
|
164
|
+
|
|
165
|
+
### calculate_layout_header(fields)
|
|
166
|
+
|
|
167
|
+
- **fields**: Array of field names that should be included in the layout
|
|
168
|
+
- **Returns**: Array of header configuration objects with width, field_name, and/or title properties
|
|
169
|
+
|
|
170
|
+
## CSS Grid Integration
|
|
171
|
+
|
|
172
|
+
The layout system generates CSS Grid structures that automatically handle:
|
|
173
|
+
|
|
174
|
+
- Column sizing based on `calculate_layout_header`
|
|
175
|
+
- Column and row spanning based on layout configuration
|
|
176
|
+
- Responsive behavior using CSS Grid's built-in capabilities
|
|
177
|
+
- Proper alignment and spacing
|
|
178
|
+
|
|
179
|
+
## Migration from Existing Configurations
|
|
180
|
+
|
|
181
|
+
**Breaking Change**: The `grid_column_span` and `grid_row_span` field configuration options have been removed in favor of the more powerful layout system.
|
|
182
|
+
|
|
183
|
+
**Before (deprecated)**:
|
|
184
|
+
```ruby
|
|
185
|
+
field :bio, grid_column_span: 2
|
|
186
|
+
field :comments, grid_column_span: 3
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**After (new layout system)**:
|
|
190
|
+
```ruby
|
|
191
|
+
private
|
|
192
|
+
|
|
193
|
+
def calculate_layout(content, fields)
|
|
194
|
+
[
|
|
195
|
+
[
|
|
196
|
+
{ field_name: :name, colspan: 1 },
|
|
197
|
+
{ field_name: :email, colspan: 1 },
|
|
198
|
+
{ field_name: :status, colspan: 1 }
|
|
199
|
+
],
|
|
200
|
+
[
|
|
201
|
+
{ field_name: :bio, colspan: 2 },
|
|
202
|
+
{ field_name: :comments, colspan: 1 }
|
|
203
|
+
]
|
|
204
|
+
]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def calculate_layout_header(fields)
|
|
208
|
+
[
|
|
209
|
+
{ width: "25%", field_name: :name }, # Sortable column
|
|
210
|
+
{ width: "25%", field_name: :email }, # Sortable column
|
|
211
|
+
{ width: "25%", title: "Personal Info" }, # Custom title, not sortable
|
|
212
|
+
{ width: "25%" } # Empty column
|
|
213
|
+
]
|
|
214
|
+
end
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
This provides much more flexibility and control over your layouts.
|
|
218
|
+
|
|
219
|
+
## Header Configuration
|
|
220
|
+
|
|
221
|
+
Each header configuration object can contain:
|
|
222
|
+
|
|
223
|
+
- `width`: **Required** - CSS width value (e.g., "25%", "200px", "1fr")
|
|
224
|
+
- `field_name`: **Optional** - Field name to display and enable sorting
|
|
225
|
+
- `title`: **Optional** - Custom column title (overrides field title)
|
|
226
|
+
|
|
227
|
+
### Header Examples
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
# Field-based sortable column
|
|
231
|
+
{ width: "30%", field_name: :name }
|
|
232
|
+
|
|
233
|
+
# Custom title with field sorting
|
|
234
|
+
{ width: "25%", field_name: :created_at, title: "Created" }
|
|
235
|
+
|
|
236
|
+
# Custom title without field (not sortable)
|
|
237
|
+
{ width: "20%", title: "Category Info" }
|
|
238
|
+
|
|
239
|
+
# Empty header column
|
|
240
|
+
{ width: "10%" }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Sorting Behavior
|
|
244
|
+
|
|
245
|
+
- **Sortable**: Columns with `field_name` get sorting links and indicators
|
|
246
|
+
- **Non-sortable**: Columns without `field_name` display plain text
|
|
247
|
+
- **Custom titles**: Use `title` to override the field's configured title
|
|
248
|
+
|
|
249
|
+
## Best Practices
|
|
250
|
+
|
|
251
|
+
1. **Keep it Simple**: Start with the default layout and only customize when you have specific layout requirements
|
|
252
|
+
2. **Consistent Headers**: Make sure your `calculate_layout_header` returns the right number of columns for your layout
|
|
253
|
+
3. **Responsive Design**: Use percentage or `fr` units in your header layout for responsive behavior
|
|
254
|
+
4. **Test Edge Cases**: Consider how your layout handles missing data or varying content lengths
|
|
255
|
+
5. **Performance**: The layout methods are called for every record, so keep them efficient
|
|
256
|
+
|
|
257
|
+
## Example Controller with Layout
|
|
258
|
+
|
|
259
|
+
Here's a complete example showing the layout system in action:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
class EventController < ElaineCrud::BaseController
|
|
263
|
+
layout 'application'
|
|
264
|
+
|
|
265
|
+
model Event
|
|
266
|
+
permit_params :title, :description, :date, :location, :organizer, :capacity
|
|
267
|
+
|
|
268
|
+
field :title, title: "Event Title"
|
|
269
|
+
field :date, title: "Event Date"
|
|
270
|
+
field :location, title: "Venue"
|
|
271
|
+
field :organizer, title: "Organized By"
|
|
272
|
+
field :capacity, title: "Max Attendees"
|
|
273
|
+
field :description, title: "Description"
|
|
274
|
+
|
|
275
|
+
private
|
|
276
|
+
|
|
277
|
+
# Custom layout: Title and key info on top, description below
|
|
278
|
+
def calculate_layout(content, fields)
|
|
279
|
+
[
|
|
280
|
+
[
|
|
281
|
+
{ field_name: :title, colspan: 2 },
|
|
282
|
+
{ field_name: :date, colspan: 1 },
|
|
283
|
+
{ field_name: :capacity, colspan: 1 }
|
|
284
|
+
],
|
|
285
|
+
[
|
|
286
|
+
{ field_name: :organizer, colspan: 2 },
|
|
287
|
+
{ field_name: :location, colspan: 2 }
|
|
288
|
+
],
|
|
289
|
+
[
|
|
290
|
+
{ field_name: :description, colspan: 4 }
|
|
291
|
+
]
|
|
292
|
+
]
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def calculate_layout_header(fields)
|
|
296
|
+
["25%", "25%", "25%", "25%"] # Equal 4-column grid
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
This creates a structured layout that displays events in a clear, hierarchical format while maintaining the flexibility to customize for different types of content.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# ElaineCrud Troubleshooting Guide
|
|
2
|
+
|
|
3
|
+
## Form Changes Not Being Saved
|
|
4
|
+
|
|
5
|
+
If you're experiencing issues where form changes are not being saved when you click "Save Changes", here's how to diagnose and fix the problem:
|
|
6
|
+
|
|
7
|
+
### Step 1: Enable Debug Logging
|
|
8
|
+
|
|
9
|
+
The latest version of ElaineCrud includes detailed debug logging. Check your Rails development log after attempting to save changes. Look for lines starting with "ElaineCrud:".
|
|
10
|
+
|
|
11
|
+
### Step 2: Check Your Controller Configuration
|
|
12
|
+
|
|
13
|
+
Add this line to your controller's action (temporarily) to see the configuration:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
class MeetingsController < ElaineCrud::BaseController
|
|
17
|
+
model Meeting
|
|
18
|
+
permit_params :title, :start_time, :end_time
|
|
19
|
+
|
|
20
|
+
def index
|
|
21
|
+
debug_configuration # Add this temporarily
|
|
22
|
+
super
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This will output configuration details to your console.
|
|
28
|
+
|
|
29
|
+
### Step 3: Common Issues and Solutions
|
|
30
|
+
|
|
31
|
+
#### Issue 1: `permit_params` called before `model`
|
|
32
|
+
**Problem**: If you call `permit_params` before setting the model, foreign keys won't be auto-included.
|
|
33
|
+
|
|
34
|
+
**Wrong**:
|
|
35
|
+
```ruby
|
|
36
|
+
permit_params :title, :start_time
|
|
37
|
+
model Meeting # Model set after permit_params
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Correct**:
|
|
41
|
+
```ruby
|
|
42
|
+
model Meeting
|
|
43
|
+
permit_params :title, :start_time # Foreign keys auto-included
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Note**: Recent versions automatically handle this, but it's still best practice to call `model` first.
|
|
47
|
+
|
|
48
|
+
#### Issue 2: Missing permitted parameters
|
|
49
|
+
**Problem**: The field you're trying to update isn't in the permitted parameters list.
|
|
50
|
+
|
|
51
|
+
**Solution**: Add the field to `permit_params`:
|
|
52
|
+
```ruby
|
|
53
|
+
permit_params :title, :start_time, :end_time, :description
|
|
54
|
+
# Foreign keys like :meeting_room_id are automatically included
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Issue 3: Validation errors
|
|
58
|
+
**Problem**: The model has validation errors that prevent saving.
|
|
59
|
+
|
|
60
|
+
**Check**: Look in your Rails log for lines like:
|
|
61
|
+
```
|
|
62
|
+
ElaineCrud: Record update failed with errors: ["Title can't be blank"]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Solution**: Fix the validation errors or adjust your model validations.
|
|
66
|
+
|
|
67
|
+
#### Issue 4: Parameter namespace issues
|
|
68
|
+
**Problem**: Form parameters aren't being submitted under the correct model name.
|
|
69
|
+
|
|
70
|
+
**Check**: Look for log lines like:
|
|
71
|
+
```
|
|
72
|
+
ElaineCrud: Looking for params under key 'meeting'
|
|
73
|
+
ElaineCrud: Available param keys: ["utf8", "authenticity_token", "commit"]
|
|
74
|
+
ElaineCrud: No parameters found for model 'meeting'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Solution**: This usually indicates a form submission issue. Check that your model name matches expectations.
|
|
78
|
+
|
|
79
|
+
### Step 4: Manual Debugging
|
|
80
|
+
|
|
81
|
+
If the automatic debugging isn't sufficient, you can add this to your controller:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
def update
|
|
85
|
+
Rails.logger.info "Raw params: #{params.inspect}"
|
|
86
|
+
Rails.logger.info "Record params: #{record_params.inspect}"
|
|
87
|
+
Rails.logger.info "Permitted attributes: #{permitted_attributes.inspect}"
|
|
88
|
+
super
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Step 5: Common Log Patterns and Their Meanings
|
|
93
|
+
|
|
94
|
+
#### Successful Update
|
|
95
|
+
```
|
|
96
|
+
ElaineCrud: Attempting to update with params: {:title=>"New Title", :meeting_room_id=>2}
|
|
97
|
+
ElaineCrud: Record updated successfully: {"id"=>1, "title"=>"New Title", "meeting_room_id"=>2}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### Missing Parameters
|
|
101
|
+
```
|
|
102
|
+
ElaineCrud: Looking for params under key 'meeting'
|
|
103
|
+
ElaineCrud: No parameters found for model 'meeting'
|
|
104
|
+
ElaineCrud: Attempting to update with params: {}
|
|
105
|
+
```
|
|
106
|
+
**Fix**: Check form submission and parameter names.
|
|
107
|
+
|
|
108
|
+
#### Validation Errors
|
|
109
|
+
```
|
|
110
|
+
ElaineCrud: Record update failed with errors: ["Title can't be blank", "Meeting room must exist"]
|
|
111
|
+
```
|
|
112
|
+
**Fix**: Ensure required fields are filled and foreign key references are valid.
|
|
113
|
+
|
|
114
|
+
#### Permission Issues
|
|
115
|
+
```
|
|
116
|
+
ElaineCrud: Permitted attributes: [:title]
|
|
117
|
+
ElaineCrud: Attempting to update with params: {:title=>"New Title"}
|
|
118
|
+
```
|
|
119
|
+
**Fix**: Add missing fields to `permit_params` (foreign keys should be auto-included).
|
|
120
|
+
|
|
121
|
+
### Step 6: Browser Developer Tools
|
|
122
|
+
|
|
123
|
+
1. Open your browser's developer tools (F12)
|
|
124
|
+
2. Go to the Network tab
|
|
125
|
+
3. Attempt to save changes
|
|
126
|
+
4. Look for the POST request to your update action
|
|
127
|
+
5. Check the request payload to see what parameters are being sent
|
|
128
|
+
|
|
129
|
+
### Step 7: Test with Rails Console
|
|
130
|
+
|
|
131
|
+
You can test parameter handling directly:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
# In rails console
|
|
135
|
+
controller = YourController.new
|
|
136
|
+
controller.params = ActionController::Parameters.new({
|
|
137
|
+
your_model_name: { title: "Test", meeting_room_id: 1 }
|
|
138
|
+
})
|
|
139
|
+
puts controller.send(:record_params).inspect
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Step 8: Minimal Test Case
|
|
143
|
+
|
|
144
|
+
Create a minimal controller to isolate the issue:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
class TestController < ElaineCrud::BaseController
|
|
148
|
+
model YourModel # Replace with your actual model
|
|
149
|
+
permit_params :title # Add your basic fields
|
|
150
|
+
|
|
151
|
+
def index
|
|
152
|
+
debug_configuration
|
|
153
|
+
super
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Add a route and test with this minimal setup.
|
|
159
|
+
|
|
160
|
+
## Getting Help
|
|
161
|
+
|
|
162
|
+
If you're still experiencing issues:
|
|
163
|
+
|
|
164
|
+
1. **Check the logs**: Include relevant log output in your issue report
|
|
165
|
+
2. **Share your controller**: Include your controller code
|
|
166
|
+
3. **Share your model**: Include relevant model code (relationships, validations)
|
|
167
|
+
4. **Share the form**: Include the HTML form being submitted
|
|
168
|
+
5. **Browser network info**: Include details from browser developer tools
|
|
169
|
+
|
|
170
|
+
The debug logging should give you enough information to identify the root cause of the issue.
|
data/elaine_crud.gemspec
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/elaine_crud/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'elaine_crud'
|
|
7
|
+
spec.version = ElaineCrud::VERSION
|
|
8
|
+
spec.authors = ['Garo']
|
|
9
|
+
spec.email = ['juho.makinen@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'A Rails engine for generating CRUD UIs for ActiveRecord models'
|
|
12
|
+
spec.description = 'ElaineCrud provides a reusable BaseController and views to quickly generate CRUD interfaces for any ActiveRecord model with minimal configuration.'
|
|
13
|
+
spec.homepage = 'https://github.com/garo/elaine_crud'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
|
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
17
|
+
spec.metadata['source_code_uri'] = 'https://github.com/garo/elaine_crud'
|
|
18
|
+
|
|
19
|
+
spec.files = Dir.chdir(__dir__) do
|
|
20
|
+
`git ls-files -z 2>/dev/null`.split("\x0").reject do |f|
|
|
21
|
+
(File.expand_path(f) == __FILE__) ||
|
|
22
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
spec.require_paths = ['lib']
|
|
27
|
+
|
|
28
|
+
# Runtime dependencies
|
|
29
|
+
spec.add_dependency 'rails', '>= 6.0'
|
|
30
|
+
spec.add_dependency 'kaminari', '~> 1.2'
|
|
31
|
+
spec.add_dependency 'caxlsx', '~> 4.0'
|
|
32
|
+
spec.add_dependency 'csv', '~> 3.0'
|
|
33
|
+
spec.add_dependency 'turbo-rails', '~> 2.0'
|
|
34
|
+
|
|
35
|
+
# Development dependencies
|
|
36
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
37
|
+
spec.add_development_dependency 'rspec-rails', '~> 6.0'
|
|
38
|
+
spec.add_development_dependency 'capybara', '~> 3.0'
|
|
39
|
+
spec.add_development_dependency 'selenium-webdriver', '~> 4.0'
|
|
40
|
+
spec.add_development_dependency 'sqlite3', '~> 2.1'
|
|
41
|
+
spec.add_development_dependency 'puma', '~> 6.0'
|
|
42
|
+
spec.add_development_dependency 'roo', '~> 2.10'
|
|
43
|
+
|
|
44
|
+
# For more information and examples about making a new gem, check out our
|
|
45
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
46
|
+
end
|