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,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ElaineCrud
|
|
4
|
+
# Base controller providing CRUD functionality for ActiveRecord models
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# class PeopleController < ElaineCrud::BaseController
|
|
8
|
+
# model Person
|
|
9
|
+
# permit_params :name, :email, :phone
|
|
10
|
+
# end
|
|
11
|
+
class BaseController < ActionController::Base
|
|
12
|
+
# Include all concerns
|
|
13
|
+
include ElaineCrud::SortingConcern
|
|
14
|
+
include ElaineCrud::SearchAndFiltering
|
|
15
|
+
include ElaineCrud::DSLMethods
|
|
16
|
+
include ElaineCrud::FieldConfigurationMethods
|
|
17
|
+
include ElaineCrud::LayoutCalculation
|
|
18
|
+
include ElaineCrud::RelationshipHandling
|
|
19
|
+
include ElaineCrud::RecordFetching
|
|
20
|
+
include ElaineCrud::ParameterHandling
|
|
21
|
+
include ElaineCrud::ExportHandling
|
|
22
|
+
|
|
23
|
+
# Include view helpers so they're available in lambda contexts
|
|
24
|
+
include ActionView::Helpers::TagHelper
|
|
25
|
+
include ActionView::Helpers::UrlHelper
|
|
26
|
+
include ActionView::Helpers::TextHelper
|
|
27
|
+
include ActionView::Helpers::NumberHelper
|
|
28
|
+
include ActionView::Helpers::DateHelper
|
|
29
|
+
|
|
30
|
+
protect_from_forgery with: :exception
|
|
31
|
+
# No layout specified - host app controllers should set their own layout
|
|
32
|
+
|
|
33
|
+
# Handle record not found errors with custom 404 page
|
|
34
|
+
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
|
|
35
|
+
|
|
36
|
+
# Standard CRUD actions
|
|
37
|
+
def index
|
|
38
|
+
@records = fetch_records
|
|
39
|
+
@model_name = crud_model.name
|
|
40
|
+
@columns = determine_columns
|
|
41
|
+
|
|
42
|
+
# Search/filter metadata
|
|
43
|
+
@search_query = search_query
|
|
44
|
+
@active_filters = filters
|
|
45
|
+
@searchable_columns = determine_searchable_columns
|
|
46
|
+
@filterable_columns = determine_filterable_columns
|
|
47
|
+
@total_count = total_unfiltered_count if search_active?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def show
|
|
51
|
+
@record = find_record
|
|
52
|
+
@model_name = crud_model.name
|
|
53
|
+
@columns = determine_columns
|
|
54
|
+
render 'elaine_crud/base/show'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def new
|
|
58
|
+
@record = crud_model.new
|
|
59
|
+
@model_name = crud_model.name
|
|
60
|
+
|
|
61
|
+
# Pre-populate with parent relationship if filtering by parent
|
|
62
|
+
populate_parent_relationships(@record)
|
|
63
|
+
|
|
64
|
+
apply_field_defaults(@record)
|
|
65
|
+
render 'elaine_crud/base/new'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Action for rendering new form in modal (for nested creates)
|
|
69
|
+
def new_modal
|
|
70
|
+
@record = crud_model.new
|
|
71
|
+
@model_name = crud_model.name
|
|
72
|
+
@modal_mode = true
|
|
73
|
+
@return_field = params[:return_field] # The foreign key field to update
|
|
74
|
+
|
|
75
|
+
apply_field_defaults(@record)
|
|
76
|
+
render 'elaine_crud/base/new_modal', layout: false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def create
|
|
80
|
+
@record = crud_model.new(record_params)
|
|
81
|
+
|
|
82
|
+
# Ensure parent relationship is maintained
|
|
83
|
+
populate_parent_relationships(@record) if @record.errors.any?
|
|
84
|
+
|
|
85
|
+
if @record.save
|
|
86
|
+
# Handle modal mode (nested create)
|
|
87
|
+
if params[:modal_mode] == 'true' && params[:return_field].present?
|
|
88
|
+
# Get field configuration from the parent controller
|
|
89
|
+
parent_controller_class = "#{params[:parent_model].pluralize.camelize}Controller".constantize
|
|
90
|
+
parent_controller = parent_controller_class.new
|
|
91
|
+
parent_field_config = parent_controller.send(:field_config_for, params[:return_field].to_sym)
|
|
92
|
+
|
|
93
|
+
render turbo_stream: turbo_stream.replace(
|
|
94
|
+
"#{params[:return_field]}_select_wrapper",
|
|
95
|
+
partial: 'elaine_crud/base/foreign_key_select_refresh',
|
|
96
|
+
locals: {
|
|
97
|
+
field_name: params[:return_field],
|
|
98
|
+
selected_id: @record.id,
|
|
99
|
+
field_config: parent_field_config,
|
|
100
|
+
parent_model: params[:parent_model]
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
else
|
|
104
|
+
# Redirect back to filtered view if came from parent context
|
|
105
|
+
redirect_to redirect_after_create_path, notice: "#{crud_model.name} was successfully created.", status: :see_other
|
|
106
|
+
end
|
|
107
|
+
else
|
|
108
|
+
@model_name = crud_model.name
|
|
109
|
+
@modal_mode = params[:modal_mode] == 'true'
|
|
110
|
+
|
|
111
|
+
if @modal_mode
|
|
112
|
+
render 'elaine_crud/base/new_modal', layout: false, status: :unprocessable_entity
|
|
113
|
+
else
|
|
114
|
+
render 'elaine_crud/base/new', status: :unprocessable_entity
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def edit
|
|
120
|
+
@record = find_record
|
|
121
|
+
@records = fetch_records # Fetch all records for the edit page
|
|
122
|
+
@model_name = crud_model.name
|
|
123
|
+
@columns = determine_columns
|
|
124
|
+
|
|
125
|
+
# Set search/filter metadata for the edit page (which includes search bar)
|
|
126
|
+
@search_query = search_query
|
|
127
|
+
@active_filters = filters
|
|
128
|
+
@searchable_columns = determine_searchable_columns
|
|
129
|
+
@filterable_columns = determine_filterable_columns
|
|
130
|
+
@total_count = total_unfiltered_count if search_active?
|
|
131
|
+
|
|
132
|
+
# If Turbo is disabled, always render the full edit page
|
|
133
|
+
if turbo_disabled?
|
|
134
|
+
render 'elaine_crud/base/edit'
|
|
135
|
+
elsif turbo_frame_request?
|
|
136
|
+
# For Turbo Frame requests, return just the edit row partial
|
|
137
|
+
render partial: 'elaine_crud/base/edit_row', locals: { record: @record, columns: @columns }
|
|
138
|
+
else
|
|
139
|
+
# For direct access, render the full edit page showing all records with this one in edit mode
|
|
140
|
+
render 'elaine_crud/base/edit'
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def update
|
|
145
|
+
@record = find_record
|
|
146
|
+
@model_name = crud_model.name
|
|
147
|
+
@columns = determine_columns
|
|
148
|
+
|
|
149
|
+
update_params = record_params
|
|
150
|
+
|
|
151
|
+
# Debug logging for development
|
|
152
|
+
if Rails.env.development?
|
|
153
|
+
Rails.logger.info "ElaineCrud: Attempting to update with params: #{update_params.inspect}"
|
|
154
|
+
Rails.logger.info "ElaineCrud: Record before update: #{@record.attributes.inspect}"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
if @record.update(update_params)
|
|
158
|
+
Rails.logger.info "ElaineCrud: Record updated successfully: #{@record.attributes.inspect}" if Rails.env.development?
|
|
159
|
+
|
|
160
|
+
# For Turbo Frame requests, return the view row partial
|
|
161
|
+
if turbo_frame_request?
|
|
162
|
+
# Use bg-white for consistency when returning from edit (alternating colors handled by full page refresh)
|
|
163
|
+
render partial: 'elaine_crud/base/view_row', locals: { record: @record, columns: @columns, row_bg_class: 'bg-white', is_last_record: false }
|
|
164
|
+
else
|
|
165
|
+
redirect_to polymorphic_path(@record), notice: "#{crud_model.name} was successfully updated.", status: :see_other
|
|
166
|
+
end
|
|
167
|
+
else
|
|
168
|
+
# Handle validation errors
|
|
169
|
+
Rails.logger.warn "ElaineCrud: Record update failed with errors: #{@record.errors.full_messages}" if Rails.env.development?
|
|
170
|
+
|
|
171
|
+
if turbo_frame_request?
|
|
172
|
+
# Return edit row partial with errors
|
|
173
|
+
render partial: 'elaine_crud/base/edit_row', locals: { record: @record, columns: @columns }, status: :unprocessable_entity
|
|
174
|
+
else
|
|
175
|
+
# Render full edit page with errors - need search/filter metadata
|
|
176
|
+
@records = fetch_records
|
|
177
|
+
@search_query = search_query
|
|
178
|
+
@active_filters = filters
|
|
179
|
+
@searchable_columns = determine_searchable_columns
|
|
180
|
+
@filterable_columns = determine_filterable_columns
|
|
181
|
+
@total_count = total_unfiltered_count if search_active?
|
|
182
|
+
render 'elaine_crud/base/edit', status: :unprocessable_entity
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def cancel_edit
|
|
188
|
+
@record = find_record
|
|
189
|
+
@model_name = crud_model.name
|
|
190
|
+
@columns = determine_columns
|
|
191
|
+
|
|
192
|
+
# For Turbo Frame requests, return just the view row partial
|
|
193
|
+
if turbo_frame_request?
|
|
194
|
+
header_layout = calculate_layout_header(@columns.map(&:to_sym))
|
|
195
|
+
render partial: 'elaine_crud/base/view_row', locals: { record: @record, columns: @columns, header_layout: header_layout }
|
|
196
|
+
else
|
|
197
|
+
# For direct access, redirect to index
|
|
198
|
+
redirect_to action: :index
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def destroy
|
|
203
|
+
@record = find_record
|
|
204
|
+
@record.destroy
|
|
205
|
+
redirect_to polymorphic_path(crud_model), notice: "#{crud_model.name} was successfully deleted.", status: :see_other
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
private
|
|
209
|
+
|
|
210
|
+
# Check if the request is coming from a Turbo Frame
|
|
211
|
+
def turbo_frame_request?
|
|
212
|
+
request.headers['Turbo-Frame'].present?
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Handle ActiveRecord::RecordNotFound with custom 404 page
|
|
216
|
+
def record_not_found(exception)
|
|
217
|
+
@model_name = crud_model.name
|
|
218
|
+
@model_class = crud_model
|
|
219
|
+
@resource_id = params[:id]
|
|
220
|
+
@exception_message = exception.message
|
|
221
|
+
|
|
222
|
+
respond_to do |format|
|
|
223
|
+
format.html { render 'elaine_crud/base/not_found', status: :not_found }
|
|
224
|
+
format.json { render json: { error: 'Record not found' }, status: :not_found }
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|