easy-admin-rails 0.1.14 → 0.2.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/builds/easy_admin.base.js +254 -18
- data/app/assets/builds/easy_admin.base.js.map +4 -4
- data/app/assets/builds/easy_admin.css +112 -18
- data/app/components/easy_admin/base_component.rb +1 -0
- data/app/components/easy_admin/form_tabs_component.rb +5 -2
- data/app/components/easy_admin/navbar_component.rb +5 -1
- data/app/components/easy_admin/permissions/user_role_assignment_component.rb +254 -0
- data/app/components/easy_admin/permissions/user_role_permissions_component.rb +186 -0
- data/app/components/easy_admin/resources/index_component.rb +1 -4
- data/app/components/easy_admin/sidebar_component.rb +67 -2
- data/app/components/easy_admin/versions/diff_modal_component.rb +5 -1
- data/app/controllers/easy_admin/application_controller.rb +131 -1
- data/app/controllers/easy_admin/batch_actions_controller.rb +27 -0
- data/app/controllers/easy_admin/concerns/belongs_to_editing.rb +201 -0
- data/app/controllers/easy_admin/concerns/inline_field_editing.rb +297 -0
- data/app/controllers/easy_admin/concerns/resource_authorization.rb +55 -0
- data/app/controllers/easy_admin/concerns/resource_filtering.rb +178 -0
- data/app/controllers/easy_admin/concerns/resource_loading.rb +149 -0
- data/app/controllers/easy_admin/concerns/resource_pagination.rb +135 -0
- data/app/controllers/easy_admin/dashboard_controller.rb +2 -1
- data/app/controllers/easy_admin/dashboards_controller.rb +6 -40
- data/app/controllers/easy_admin/resources_controller.rb +13 -762
- data/app/controllers/easy_admin/row_actions_controller.rb +25 -0
- data/app/helpers/easy_admin/fields_helper.rb +61 -9
- data/app/javascript/easy_admin/controllers/event_emitter_controller.js +2 -4
- data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +0 -10
- data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +1 -4
- data/app/javascript/easy_admin/controllers/permission_toggle_controller.js +227 -0
- data/app/javascript/easy_admin/controllers/role_preview_controller.js +93 -0
- data/app/javascript/easy_admin/controllers/select_field_controller.js +1 -2
- data/app/javascript/easy_admin/controllers/settings_button_controller.js +1 -2
- data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +1 -4
- data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +0 -2
- data/app/javascript/easy_admin/controllers.js +5 -1
- data/app/models/easy_admin/admin_user.rb +6 -0
- data/app/policies/admin_user_policy.rb +36 -0
- data/app/policies/application_policy.rb +83 -0
- data/app/views/easy_admin/application/authorization_failure.turbo_stream.erb +8 -0
- data/app/views/easy_admin/dashboards/card.html.erb +5 -0
- data/app/views/easy_admin/dashboards/card.turbo_stream.erb +7 -0
- data/app/views/easy_admin/dashboards/card_error.html.erb +3 -0
- data/app/views/easy_admin/dashboards/card_error.turbo_stream.erb +5 -0
- data/app/views/easy_admin/dashboards/show.turbo_stream.erb +7 -0
- data/app/views/easy_admin/resources/belongs_to_edit_attached.html.erb +6 -0
- data/app/views/easy_admin/resources/belongs_to_edit_attached.turbo_stream.erb +8 -0
- data/app/views/easy_admin/resources/belongs_to_reattach.html.erb +5 -0
- data/app/views/easy_admin/resources/edit.html.erb +1 -1
- data/app/views/easy_admin/resources/edit_field.html.erb +5 -0
- data/app/views/easy_admin/resources/edit_field.turbo_stream.erb +7 -0
- data/app/views/easy_admin/resources/index.html.erb +1 -1
- data/app/views/easy_admin/resources/index_frame.html.erb +8 -142
- data/app/views/easy_admin/resources/update_belongs_to_attached.turbo_stream.erb +25 -0
- data/app/views/layouts/easy_admin/application.html.erb +15 -2
- data/config/initializers/easy_admin_permissions.rb +73 -0
- data/db/seeds/easy_admin_permissions.rb +121 -0
- data/lib/easy-admin-rails.rb +2 -0
- data/lib/easy_admin/permissions/component.rb +168 -0
- data/lib/easy_admin/permissions/configuration.rb +37 -0
- data/lib/easy_admin/permissions/controller.rb +164 -0
- data/lib/easy_admin/permissions/dsl.rb +180 -0
- data/lib/easy_admin/permissions/models.rb +44 -0
- data/lib/easy_admin/permissions/permission_denied_component.rb +121 -0
- data/lib/easy_admin/permissions/resource_permissions.rb +231 -0
- data/lib/easy_admin/permissions/role_definition.rb +45 -0
- data/lib/easy_admin/permissions/role_denied_component.rb +159 -0
- data/lib/easy_admin/permissions/role_dsl.rb +73 -0
- data/lib/easy_admin/permissions/user_extensions.rb +129 -0
- data/lib/easy_admin/permissions.rb +113 -0
- data/lib/easy_admin/resource/base.rb +119 -0
- data/lib/easy_admin/resource/configuration.rb +148 -0
- data/lib/easy_admin/resource/dsl.rb +117 -0
- data/lib/easy_admin/resource/field_registry.rb +189 -0
- data/lib/easy_admin/resource/form_builder.rb +123 -0
- data/lib/easy_admin/resource/layout_builder.rb +249 -0
- data/lib/easy_admin/resource/scope_manager.rb +252 -0
- data/lib/easy_admin/resource/show_builder.rb +359 -0
- data/lib/easy_admin/resource.rb +8 -835
- data/lib/easy_admin/resource_modules.rb +11 -0
- data/lib/easy_admin/version.rb +1 -1
- data/lib/generators/easy_admin/permissions/install_generator.rb +90 -0
- data/lib/generators/easy_admin/permissions/templates/initializers/permissions.rb +37 -0
- data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +27 -0
- data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +6 -0
- data/lib/generators/easy_admin/permissions/templates/models/permission.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/role.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/role_permission.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/models/user_role.rb +9 -0
- data/lib/generators/easy_admin/permissions/templates/policies/application_policy.rb +47 -0
- data/lib/generators/easy_admin/permissions/templates/policies/user_policy.rb +36 -0
- data/lib/generators/easy_admin/permissions/templates/seeds/permissions.rb +89 -0
- metadata +62 -5
- data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +0 -45
@@ -0,0 +1,201 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Concerns
|
3
|
+
# BelongsToEditing concern handles belongs_to field specific editing functionality
|
4
|
+
# Provides methods for reattaching and editing attached belongs_to records
|
5
|
+
module BelongsToEditing
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Reattach a belongs_to association (select different associated record)
|
9
|
+
def belongs_to_reattach
|
10
|
+
@field_name = params[:field]
|
11
|
+
@field_config = find_field_config_by_name_or_association(@field_name)
|
12
|
+
|
13
|
+
unless @field_config
|
14
|
+
render json: { error: "Field '#{@field_name}' not found" }, status: :not_found
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
# Convert belongs_to field to select field format for reattaching
|
19
|
+
@select_field_config = prepare_belongs_to_as_select(@field_config)
|
20
|
+
|
21
|
+
respond_to do |format|
|
22
|
+
format.html { render template: 'easy_admin/resources/belongs_to_reattach', layout: false }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Edit the attached record in a belongs_to association
|
27
|
+
def belongs_to_edit_attached
|
28
|
+
@field_name = params[:field]
|
29
|
+
@field_config = find_field_config_by_name_or_association(@field_name)
|
30
|
+
|
31
|
+
unless @field_config
|
32
|
+
render json: { error: "Field '#{@field_name}' not found" }, status: :not_found
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
unless @field_config[:type] == :belongs_to
|
37
|
+
render json: { error: "Field '#{@field_name}' is not a belongs_to field" }, status: :bad_request
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get the association info - use explicit association name if provided
|
42
|
+
association_name = @field_config[:association] || @field_config[:name]
|
43
|
+
model_class = @resource_class.model_class
|
44
|
+
association_reflection = model_class.reflect_on_association(association_name)
|
45
|
+
|
46
|
+
unless association_reflection
|
47
|
+
render json: { error: "Association '#{association_name}' not found on #{model_class.name}" }, status: :bad_request
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get the associated record using the association name
|
52
|
+
@associated_record = @record.public_send(association_name)
|
53
|
+
|
54
|
+
unless @associated_record
|
55
|
+
render json: { error: "No associated record found" }, status: :not_found
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get the resource class for the associated record
|
60
|
+
associated_model_class = association_reflection.klass
|
61
|
+
@associated_resource_class = find_resource_class_for_model(associated_model_class)
|
62
|
+
|
63
|
+
unless @associated_resource_class
|
64
|
+
render json: { error: "No resource class found for #{associated_model_class.name}" }, status: :not_found
|
65
|
+
return
|
66
|
+
end
|
67
|
+
|
68
|
+
respond_to do |format|
|
69
|
+
format.html { render template: 'easy_admin/resources/belongs_to_edit_attached', layout: false }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Update the attached record in a belongs_to association
|
74
|
+
def update_belongs_to_attached
|
75
|
+
@field_name = params[:field]
|
76
|
+
@field_config = find_field_config_by_name_or_association(@field_name)
|
77
|
+
@attached_id = params[:attached_id]
|
78
|
+
|
79
|
+
unless @field_config
|
80
|
+
render json: { error: "Field '#{@field_name}' not found" }, status: :not_found
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get the association info - use explicit association name if provided
|
85
|
+
association_name = @field_config[:association] || @field_config[:name]
|
86
|
+
model_class = @resource_class.model_class
|
87
|
+
association_reflection = model_class.reflect_on_association(association_name)
|
88
|
+
|
89
|
+
unless association_reflection
|
90
|
+
render json: { error: "Association '#{association_name}' not found on #{model_class.name}" }, status: :bad_request
|
91
|
+
return
|
92
|
+
end
|
93
|
+
|
94
|
+
associated_model_class = association_reflection.klass
|
95
|
+
associated_resource_class = find_resource_class_for_model(associated_model_class)
|
96
|
+
|
97
|
+
# Find the attached record
|
98
|
+
attached_record = associated_model_class.find(@attached_id)
|
99
|
+
|
100
|
+
unless attached_record
|
101
|
+
render json: { error: "Attached record not found" }, status: :not_found
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
# Get the update parameters for the attached record
|
106
|
+
raw_params = params.dig(associated_resource_class.param_key) || {}
|
107
|
+
|
108
|
+
# Get permitted attributes from the resource class
|
109
|
+
permitted_attributes = get_permitted_attributes(associated_resource_class)
|
110
|
+
update_params = raw_params.permit(*permitted_attributes)
|
111
|
+
|
112
|
+
if attached_record.update(update_params)
|
113
|
+
@success = true
|
114
|
+
@attached_record = attached_record
|
115
|
+
@association_name = association_name
|
116
|
+
else
|
117
|
+
@success = false
|
118
|
+
@attached_record = attached_record
|
119
|
+
end
|
120
|
+
|
121
|
+
respond_to do |format|
|
122
|
+
format.turbo_stream { render template: 'easy_admin/resources/update_belongs_to_attached' }
|
123
|
+
format.json do
|
124
|
+
if @success
|
125
|
+
render json: { status: 'success' }
|
126
|
+
else
|
127
|
+
render json: { errors: @attached_record.errors }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
# Helper method to find field config by name or association name
|
136
|
+
def find_field_config_by_name_or_association(field_identifier)
|
137
|
+
# Try to find field by name first
|
138
|
+
field_config = find_field_config(field_identifier)
|
139
|
+
|
140
|
+
# If not found by field name, try to find by association name
|
141
|
+
if !field_config
|
142
|
+
field_config = @resource_class.fields_config.find { |f| f[:association].to_s == field_identifier }
|
143
|
+
end
|
144
|
+
|
145
|
+
field_config
|
146
|
+
end
|
147
|
+
|
148
|
+
# Convert belongs_to field to select field format for reattaching
|
149
|
+
def prepare_belongs_to_as_select(field_config)
|
150
|
+
association_name = field_config[:association] || field_config[:name]
|
151
|
+
foreign_key = get_foreign_key_name(association_name)
|
152
|
+
|
153
|
+
# Get available options for the select
|
154
|
+
options = get_belongs_to_options(association_name, field_config)
|
155
|
+
|
156
|
+
# Return field config formatted as a select field, preserving original info
|
157
|
+
field_config.merge(
|
158
|
+
type: :select,
|
159
|
+
name: foreign_key, # Use foreign key instead of association name
|
160
|
+
label: "Select #{field_config[:label]}",
|
161
|
+
options: options,
|
162
|
+
original_type: :belongs_to,
|
163
|
+
original_name: association_name
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Get options for belongs_to field selection
|
168
|
+
def get_belongs_to_options(association_name, field_config)
|
169
|
+
model_class = @resource_class.model_class
|
170
|
+
|
171
|
+
# Get the associated model class using reflection
|
172
|
+
association_reflection = model_class.reflect_on_association(association_name)
|
173
|
+
if association_reflection
|
174
|
+
association_class = association_reflection.klass
|
175
|
+
else
|
176
|
+
# Fallback: try to constantize the association name if reflection is not found
|
177
|
+
association_class = association_name.to_s.classify.constantize rescue nil
|
178
|
+
end
|
179
|
+
|
180
|
+
return [] unless association_class
|
181
|
+
|
182
|
+
# Get display method from field config
|
183
|
+
display_method = field_config[:display_method] || :name
|
184
|
+
|
185
|
+
# Get all records and format as [label, value] pairs
|
186
|
+
association_class.all.limit(100).map do |record|
|
187
|
+
label = if record.respond_to?(display_method)
|
188
|
+
record.public_send(display_method)
|
189
|
+
elsif record.respond_to?(:name)
|
190
|
+
record.name
|
191
|
+
elsif record.respond_to?(:title)
|
192
|
+
record.title
|
193
|
+
else
|
194
|
+
record.to_s
|
195
|
+
end
|
196
|
+
[label, record.id]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,297 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Concerns
|
3
|
+
# InlineFieldEditing concern handles inline field editing functionality
|
4
|
+
# Provides methods for editing individual fields without full form submission
|
5
|
+
module InlineFieldEditing
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Field editing actions
|
9
|
+
def edit_field
|
10
|
+
@field_name = params[:field]
|
11
|
+
@field_config = find_field_config_by_name_or_association(@field_name)
|
12
|
+
|
13
|
+
unless @field_config
|
14
|
+
render json: { error: "Field '#{@field_name}' not found" }, status: :not_found
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
respond_to do |format|
|
19
|
+
format.html { render template: 'easy_admin/resources/edit_field', layout: false }
|
20
|
+
format.turbo_stream { render template: 'easy_admin/resources/edit_field' }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_field
|
25
|
+
@field_name = params[:field]
|
26
|
+
@field_config = find_field_config_by_name_or_association(@field_name)
|
27
|
+
|
28
|
+
unless @field_config
|
29
|
+
render json: { error: "Field '#{@field_name}' not found" }, status: :not_found
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
# Determine the correct parameter key (handle complex model names)
|
34
|
+
param_key = determine_param_key_for_update_field
|
35
|
+
|
36
|
+
# For belongs_to fields, we need to handle foreign key updates
|
37
|
+
if @field_config[:type] == :belongs_to
|
38
|
+
update_attrs = build_belongs_to_update_attributes(param_key)
|
39
|
+
else
|
40
|
+
update_attrs = build_regular_field_update_attributes(param_key)
|
41
|
+
end
|
42
|
+
|
43
|
+
if @record.update(update_attrs)
|
44
|
+
handle_successful_field_update
|
45
|
+
else
|
46
|
+
handle_failed_field_update
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get autocomplete suggestions for fields
|
51
|
+
def suggest
|
52
|
+
# Try to find field by name first, then by association name
|
53
|
+
field_config = @resource_class.fields_config.find { |f| f[:name].to_s == params[:field] }
|
54
|
+
|
55
|
+
# If not found by field name, try to find by association name
|
56
|
+
if !field_config
|
57
|
+
field_config = @resource_class.fields_config.find { |f| f[:association].to_s == params[:field] }
|
58
|
+
end
|
59
|
+
|
60
|
+
Rails.logger.debug "Suggest field_config for #{params[:field]}: #{field_config}"
|
61
|
+
|
62
|
+
unless field_config && field_config[:suggest]
|
63
|
+
render json: { error: "Field not found or suggest not configured" }, status: :not_found
|
64
|
+
return
|
65
|
+
end
|
66
|
+
|
67
|
+
search_term = params[:q].to_s.strip
|
68
|
+
limit = field_config[:suggest][:limit] || 10
|
69
|
+
|
70
|
+
results = get_suggest_options(field_config, search_term, limit)
|
71
|
+
|
72
|
+
render json: { options: results }
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# Helper method to find field config by name or association name
|
78
|
+
def find_field_config_by_name_or_association(field_identifier)
|
79
|
+
# Try to find field by name first
|
80
|
+
field_config = find_field_config(field_identifier)
|
81
|
+
|
82
|
+
# If not found by field name, try to find by association name
|
83
|
+
if !field_config
|
84
|
+
field_config = @resource_class.fields_config.find { |f| f[:association].to_s == field_identifier }
|
85
|
+
end
|
86
|
+
|
87
|
+
field_config
|
88
|
+
end
|
89
|
+
|
90
|
+
# Build update attributes for belongs_to fields
|
91
|
+
def build_belongs_to_update_attributes(param_key)
|
92
|
+
association_name = @field_config[:name]
|
93
|
+
foreign_key = get_foreign_key_name(association_name)
|
94
|
+
field_value = params.dig(param_key, foreign_key)
|
95
|
+
{ foreign_key.to_sym => field_value }
|
96
|
+
end
|
97
|
+
|
98
|
+
# Build update attributes for regular fields
|
99
|
+
def build_regular_field_update_attributes(param_key)
|
100
|
+
# Create a field object to handle normalization
|
101
|
+
field_obj = EasyAdmin::Field.new(@field_name, @field_config[:type], @field_config)
|
102
|
+
|
103
|
+
# Get the field value from params
|
104
|
+
field_value = params.dig(param_key, @field_name)
|
105
|
+
|
106
|
+
# Normalize the input
|
107
|
+
update_attrs = { @field_name => field_value }
|
108
|
+
field_obj.normalize_input!(update_attrs)
|
109
|
+
update_attrs
|
110
|
+
end
|
111
|
+
|
112
|
+
# Handle successful field update response
|
113
|
+
def handle_successful_field_update
|
114
|
+
respond_to do |format|
|
115
|
+
format.turbo_stream do
|
116
|
+
# Update the table cell with the new value
|
117
|
+
render turbo_stream: [
|
118
|
+
turbo_stream.replace(
|
119
|
+
"#{helpers.dom_id(@record)}_#{@field_config[:name]}",
|
120
|
+
EasyAdmin::Resources::TableCellComponent.new(
|
121
|
+
record: @record.reload,
|
122
|
+
field_config: @field_config,
|
123
|
+
resource_class: @resource_class
|
124
|
+
).call
|
125
|
+
),
|
126
|
+
turbo_stream.update("notifications",
|
127
|
+
EasyAdmin::NotificationComponent.new(
|
128
|
+
type: :success,
|
129
|
+
message: "#{@field_config[:label]} updated successfully!",
|
130
|
+
title: "Success"
|
131
|
+
).call
|
132
|
+
)
|
133
|
+
]
|
134
|
+
end
|
135
|
+
format.json { render json: { status: 'success' } }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Handle failed field update response
|
140
|
+
def handle_failed_field_update
|
141
|
+
respond_to do |format|
|
142
|
+
format.turbo_stream do
|
143
|
+
render turbo_stream: turbo_stream.update("notifications",
|
144
|
+
EasyAdmin::NotificationComponent.new(
|
145
|
+
type: :error,
|
146
|
+
message: @record.errors.full_messages.join(', '),
|
147
|
+
title: "Error"
|
148
|
+
).call
|
149
|
+
)
|
150
|
+
end
|
151
|
+
format.json { render json: { errors: @record.errors } }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Get suggestion options for field autocomplete
|
156
|
+
def get_suggest_options(field_config, search_term, limit)
|
157
|
+
# Handle different field types that support suggest
|
158
|
+
case field_config[:type]
|
159
|
+
when :belongs_to
|
160
|
+
association_name = field_config[:association] || field_config[:name]
|
161
|
+
get_belongs_to_suggest_options(association_name, field_config, search_term, limit)
|
162
|
+
when :has_many
|
163
|
+
association_name = field_config[:association] || field_config[:name]
|
164
|
+
get_has_many_suggest_options(association_name, field_config, search_term, limit)
|
165
|
+
when :select
|
166
|
+
# For regular select fields, filter static options
|
167
|
+
get_static_suggest_options(field_config, search_term, limit)
|
168
|
+
else
|
169
|
+
[]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Get suggestion options for belongs_to fields
|
174
|
+
def get_belongs_to_suggest_options(association_name, field_config, search_term, limit)
|
175
|
+
model_class = @resource_class.model_class
|
176
|
+
|
177
|
+
# Get the associated model class
|
178
|
+
if model_class.reflect_on_association(association_name)
|
179
|
+
association_class = model_class.reflect_on_association(association_name).klass
|
180
|
+
else
|
181
|
+
association_class = association_name.to_s.classify.constantize rescue nil
|
182
|
+
end
|
183
|
+
|
184
|
+
return [] unless association_class
|
185
|
+
|
186
|
+
# Get display method and search fields from field config
|
187
|
+
display_method = field_config[:display_method] || :name
|
188
|
+
suggest_config = field_config[:suggest] || {}
|
189
|
+
search_fields = suggest_config[:search_fields] || [display_method]
|
190
|
+
|
191
|
+
# Build search query
|
192
|
+
records = association_class.all
|
193
|
+
|
194
|
+
if search_term.present?
|
195
|
+
# Build WHERE conditions for search across multiple fields
|
196
|
+
conditions = search_fields.map do |field|
|
197
|
+
if association_class.columns_hash[field.to_s]&.type == :string
|
198
|
+
"#{field} LIKE ?"
|
199
|
+
else
|
200
|
+
"CAST(#{field} AS TEXT) LIKE ?"
|
201
|
+
end
|
202
|
+
end.join(" OR ")
|
203
|
+
|
204
|
+
search_pattern = "%#{search_term}%"
|
205
|
+
records = records.where(conditions, *([search_pattern] * search_fields.length))
|
206
|
+
end
|
207
|
+
|
208
|
+
# Apply limit and format as [label, value] pairs
|
209
|
+
records.limit(limit).map do |record|
|
210
|
+
label = if record.respond_to?(display_method)
|
211
|
+
record.public_send(display_method)
|
212
|
+
elsif record.respond_to?(:name)
|
213
|
+
record.name
|
214
|
+
elsif record.respond_to?(:title)
|
215
|
+
record.title
|
216
|
+
else
|
217
|
+
record.to_s
|
218
|
+
end
|
219
|
+
[label, record.id]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Get suggestion options for has_many fields
|
224
|
+
def get_has_many_suggest_options(association_name, field_config, search_term, limit)
|
225
|
+
model_class = @resource_class.model_class
|
226
|
+
|
227
|
+
# Get the associated model class
|
228
|
+
if model_class.reflect_on_association(association_name)
|
229
|
+
association_class = model_class.reflect_on_association(association_name).klass
|
230
|
+
else
|
231
|
+
association_class = association_name.to_s.singularize.classify.constantize rescue nil
|
232
|
+
end
|
233
|
+
|
234
|
+
return [] unless association_class
|
235
|
+
|
236
|
+
# Get display method and search fields from field config
|
237
|
+
display_method = field_config[:display_method] || :name
|
238
|
+
suggest_config = field_config[:suggest] || {}
|
239
|
+
search_fields = suggest_config[:search_fields] || [display_method]
|
240
|
+
|
241
|
+
Rails.logger.debug "HasMany suggest: association=#{association_name}, display_method=#{display_method}, search_fields=#{search_fields}"
|
242
|
+
|
243
|
+
# Build search query
|
244
|
+
records = association_class.all
|
245
|
+
|
246
|
+
if search_term.present?
|
247
|
+
# Build WHERE conditions for search across multiple fields
|
248
|
+
conditions = search_fields.map do |field|
|
249
|
+
if association_class.columns_hash[field.to_s]&.type == :string
|
250
|
+
"#{field} LIKE ?"
|
251
|
+
else
|
252
|
+
"CAST(#{field} AS TEXT) LIKE ?"
|
253
|
+
end
|
254
|
+
end.join(" OR ")
|
255
|
+
|
256
|
+
search_pattern = "%#{search_term}%"
|
257
|
+
records = records.where(conditions, *([search_pattern] * search_fields.length))
|
258
|
+
end
|
259
|
+
|
260
|
+
# Apply limit and format as [label, value] pairs
|
261
|
+
results = records.limit(limit).map do |record|
|
262
|
+
label = if record.respond_to?(display_method)
|
263
|
+
raw_label = record.public_send(display_method)
|
264
|
+
Rails.logger.debug "HasMany record #{record.id}: raw_label=#{raw_label.class}:#{raw_label}"
|
265
|
+
raw_label.to_s
|
266
|
+
elsif record.respond_to?(:name)
|
267
|
+
record.name.to_s
|
268
|
+
elsif record.respond_to?(:title)
|
269
|
+
record.title.to_s
|
270
|
+
else
|
271
|
+
record.to_s
|
272
|
+
end
|
273
|
+
[label, record.id]
|
274
|
+
end
|
275
|
+
|
276
|
+
Rails.logger.debug "HasMany suggest final results: #{results}"
|
277
|
+
results
|
278
|
+
end
|
279
|
+
|
280
|
+
# Get suggestion options for static select fields
|
281
|
+
def get_static_suggest_options(field_config, search_term, limit)
|
282
|
+
options = field_config[:options] || []
|
283
|
+
|
284
|
+
if search_term.present?
|
285
|
+
# Filter options based on search term
|
286
|
+
filtered_options = options.select do |option|
|
287
|
+
text = option.is_a?(Array) ? option[0] : option.to_s
|
288
|
+
text.downcase.include?(search_term.downcase)
|
289
|
+
end
|
290
|
+
filtered_options.take(limit)
|
291
|
+
else
|
292
|
+
options.take(limit)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Concerns
|
3
|
+
# ResourceAuthorization concern handles authorization checks for resource actions
|
4
|
+
# Provides before_action callbacks and authorization methods for all CRUD operations
|
5
|
+
module ResourceAuthorization
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
before_action :authorize_resource_access!, only: [:index]
|
10
|
+
before_action :authorize_record_access!, only: [:show]
|
11
|
+
before_action :authorize_record_creation!, only: [:new, :create]
|
12
|
+
before_action :authorize_record_update!, only: [
|
13
|
+
:edit, :update, :edit_field, :update_field,
|
14
|
+
:belongs_to_reattach, :belongs_to_edit_attached, :update_belongs_to_attached
|
15
|
+
]
|
16
|
+
before_action :authorize_record_destruction!, only: [:destroy]
|
17
|
+
before_action :authorize_versioning_access!, only: [:versions, :revert_version, :version_diff]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Authorize access to resource index
|
23
|
+
def authorize_resource_access!
|
24
|
+
authorize! @resource_class.model_class, to: :index?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Authorize access to view a specific record
|
28
|
+
def authorize_record_access!
|
29
|
+
authorize! @record, to: :show?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Authorize creation of new records
|
33
|
+
def authorize_record_creation!
|
34
|
+
authorize! @resource_class.model_class, to: :create?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Authorize updating of existing records
|
38
|
+
def authorize_record_update!
|
39
|
+
authorize! @record, to: :update?
|
40
|
+
end
|
41
|
+
|
42
|
+
# Authorize destruction of existing records
|
43
|
+
def authorize_record_destruction!
|
44
|
+
authorize! @record, to: :destroy?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Authorize access to versioning features (PaperTrail integration)
|
48
|
+
def authorize_versioning_access!
|
49
|
+
# Check if user can view record and has versioning permissions
|
50
|
+
authorize! @record, to: :show?
|
51
|
+
authorize! @record, to: :manage_versions?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|