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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/easy_admin.base.js +254 -18
  3. data/app/assets/builds/easy_admin.base.js.map +4 -4
  4. data/app/assets/builds/easy_admin.css +112 -18
  5. data/app/components/easy_admin/base_component.rb +1 -0
  6. data/app/components/easy_admin/form_tabs_component.rb +5 -2
  7. data/app/components/easy_admin/navbar_component.rb +5 -1
  8. data/app/components/easy_admin/permissions/user_role_assignment_component.rb +254 -0
  9. data/app/components/easy_admin/permissions/user_role_permissions_component.rb +186 -0
  10. data/app/components/easy_admin/resources/index_component.rb +1 -4
  11. data/app/components/easy_admin/sidebar_component.rb +67 -2
  12. data/app/components/easy_admin/versions/diff_modal_component.rb +5 -1
  13. data/app/controllers/easy_admin/application_controller.rb +131 -1
  14. data/app/controllers/easy_admin/batch_actions_controller.rb +27 -0
  15. data/app/controllers/easy_admin/concerns/belongs_to_editing.rb +201 -0
  16. data/app/controllers/easy_admin/concerns/inline_field_editing.rb +297 -0
  17. data/app/controllers/easy_admin/concerns/resource_authorization.rb +55 -0
  18. data/app/controllers/easy_admin/concerns/resource_filtering.rb +178 -0
  19. data/app/controllers/easy_admin/concerns/resource_loading.rb +149 -0
  20. data/app/controllers/easy_admin/concerns/resource_pagination.rb +135 -0
  21. data/app/controllers/easy_admin/dashboard_controller.rb +2 -1
  22. data/app/controllers/easy_admin/dashboards_controller.rb +6 -40
  23. data/app/controllers/easy_admin/resources_controller.rb +13 -762
  24. data/app/controllers/easy_admin/row_actions_controller.rb +25 -0
  25. data/app/helpers/easy_admin/fields_helper.rb +61 -9
  26. data/app/javascript/easy_admin/controllers/event_emitter_controller.js +2 -4
  27. data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +0 -10
  28. data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +1 -4
  29. data/app/javascript/easy_admin/controllers/permission_toggle_controller.js +227 -0
  30. data/app/javascript/easy_admin/controllers/role_preview_controller.js +93 -0
  31. data/app/javascript/easy_admin/controllers/select_field_controller.js +1 -2
  32. data/app/javascript/easy_admin/controllers/settings_button_controller.js +1 -2
  33. data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +1 -4
  34. data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +0 -2
  35. data/app/javascript/easy_admin/controllers.js +5 -1
  36. data/app/models/easy_admin/admin_user.rb +6 -0
  37. data/app/policies/admin_user_policy.rb +36 -0
  38. data/app/policies/application_policy.rb +83 -0
  39. data/app/views/easy_admin/application/authorization_failure.turbo_stream.erb +8 -0
  40. data/app/views/easy_admin/dashboards/card.html.erb +5 -0
  41. data/app/views/easy_admin/dashboards/card.turbo_stream.erb +7 -0
  42. data/app/views/easy_admin/dashboards/card_error.html.erb +3 -0
  43. data/app/views/easy_admin/dashboards/card_error.turbo_stream.erb +5 -0
  44. data/app/views/easy_admin/dashboards/show.turbo_stream.erb +7 -0
  45. data/app/views/easy_admin/resources/belongs_to_edit_attached.html.erb +6 -0
  46. data/app/views/easy_admin/resources/belongs_to_edit_attached.turbo_stream.erb +8 -0
  47. data/app/views/easy_admin/resources/belongs_to_reattach.html.erb +5 -0
  48. data/app/views/easy_admin/resources/edit.html.erb +1 -1
  49. data/app/views/easy_admin/resources/edit_field.html.erb +5 -0
  50. data/app/views/easy_admin/resources/edit_field.turbo_stream.erb +7 -0
  51. data/app/views/easy_admin/resources/index.html.erb +1 -1
  52. data/app/views/easy_admin/resources/index_frame.html.erb +8 -142
  53. data/app/views/easy_admin/resources/update_belongs_to_attached.turbo_stream.erb +25 -0
  54. data/app/views/layouts/easy_admin/application.html.erb +15 -2
  55. data/config/initializers/easy_admin_permissions.rb +73 -0
  56. data/db/seeds/easy_admin_permissions.rb +121 -0
  57. data/lib/easy-admin-rails.rb +2 -0
  58. data/lib/easy_admin/permissions/component.rb +168 -0
  59. data/lib/easy_admin/permissions/configuration.rb +37 -0
  60. data/lib/easy_admin/permissions/controller.rb +164 -0
  61. data/lib/easy_admin/permissions/dsl.rb +180 -0
  62. data/lib/easy_admin/permissions/models.rb +44 -0
  63. data/lib/easy_admin/permissions/permission_denied_component.rb +121 -0
  64. data/lib/easy_admin/permissions/resource_permissions.rb +231 -0
  65. data/lib/easy_admin/permissions/role_definition.rb +45 -0
  66. data/lib/easy_admin/permissions/role_denied_component.rb +159 -0
  67. data/lib/easy_admin/permissions/role_dsl.rb +73 -0
  68. data/lib/easy_admin/permissions/user_extensions.rb +129 -0
  69. data/lib/easy_admin/permissions.rb +113 -0
  70. data/lib/easy_admin/resource/base.rb +119 -0
  71. data/lib/easy_admin/resource/configuration.rb +148 -0
  72. data/lib/easy_admin/resource/dsl.rb +117 -0
  73. data/lib/easy_admin/resource/field_registry.rb +189 -0
  74. data/lib/easy_admin/resource/form_builder.rb +123 -0
  75. data/lib/easy_admin/resource/layout_builder.rb +249 -0
  76. data/lib/easy_admin/resource/scope_manager.rb +252 -0
  77. data/lib/easy_admin/resource/show_builder.rb +359 -0
  78. data/lib/easy_admin/resource.rb +8 -835
  79. data/lib/easy_admin/resource_modules.rb +11 -0
  80. data/lib/easy_admin/version.rb +1 -1
  81. data/lib/generators/easy_admin/permissions/install_generator.rb +90 -0
  82. data/lib/generators/easy_admin/permissions/templates/initializers/permissions.rb +37 -0
  83. data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +27 -0
  84. data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +6 -0
  85. data/lib/generators/easy_admin/permissions/templates/models/permission.rb +9 -0
  86. data/lib/generators/easy_admin/permissions/templates/models/role.rb +9 -0
  87. data/lib/generators/easy_admin/permissions/templates/models/role_permission.rb +9 -0
  88. data/lib/generators/easy_admin/permissions/templates/models/user_role.rb +9 -0
  89. data/lib/generators/easy_admin/permissions/templates/policies/application_policy.rb +47 -0
  90. data/lib/generators/easy_admin/permissions/templates/policies/user_policy.rb +36 -0
  91. data/lib/generators/easy_admin/permissions/templates/seeds/permissions.rb +89 -0
  92. metadata +62 -5
  93. 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