easy-admin-rails 0.1.15 → 0.2.1

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 (92) 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/controllers/easy_admin/application_controller.rb +131 -1
  13. data/app/controllers/easy_admin/batch_actions_controller.rb +27 -0
  14. data/app/controllers/easy_admin/concerns/belongs_to_editing.rb +201 -0
  15. data/app/controllers/easy_admin/concerns/inline_field_editing.rb +297 -0
  16. data/app/controllers/easy_admin/concerns/resource_authorization.rb +55 -0
  17. data/app/controllers/easy_admin/concerns/resource_filtering.rb +178 -0
  18. data/app/controllers/easy_admin/concerns/resource_loading.rb +149 -0
  19. data/app/controllers/easy_admin/concerns/resource_pagination.rb +135 -0
  20. data/app/controllers/easy_admin/dashboard_controller.rb +2 -1
  21. data/app/controllers/easy_admin/dashboards_controller.rb +6 -40
  22. data/app/controllers/easy_admin/resources_controller.rb +13 -762
  23. data/app/controllers/easy_admin/row_actions_controller.rb +25 -0
  24. data/app/helpers/easy_admin/fields_helper.rb +61 -9
  25. data/app/javascript/easy_admin/controllers/event_emitter_controller.js +2 -4
  26. data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +0 -10
  27. data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +1 -4
  28. data/app/javascript/easy_admin/controllers/permission_toggle_controller.js +227 -0
  29. data/app/javascript/easy_admin/controllers/role_preview_controller.js +93 -0
  30. data/app/javascript/easy_admin/controllers/select_field_controller.js +1 -2
  31. data/app/javascript/easy_admin/controllers/settings_button_controller.js +1 -2
  32. data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +1 -4
  33. data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +0 -2
  34. data/app/javascript/easy_admin/controllers.js +5 -1
  35. data/app/models/easy_admin/admin_user.rb +6 -0
  36. data/app/policies/admin_user_policy.rb +36 -0
  37. data/app/policies/application_policy.rb +83 -0
  38. data/app/views/easy_admin/application/authorization_failure.turbo_stream.erb +8 -0
  39. data/app/views/easy_admin/dashboards/card.html.erb +5 -0
  40. data/app/views/easy_admin/dashboards/card.turbo_stream.erb +7 -0
  41. data/app/views/easy_admin/dashboards/card_error.html.erb +3 -0
  42. data/app/views/easy_admin/dashboards/card_error.turbo_stream.erb +5 -0
  43. data/app/views/easy_admin/dashboards/show.turbo_stream.erb +7 -0
  44. data/app/views/easy_admin/resources/belongs_to_edit_attached.html.erb +6 -0
  45. data/app/views/easy_admin/resources/belongs_to_edit_attached.turbo_stream.erb +8 -0
  46. data/app/views/easy_admin/resources/belongs_to_reattach.html.erb +5 -0
  47. data/app/views/easy_admin/resources/edit.html.erb +1 -1
  48. data/app/views/easy_admin/resources/edit_field.html.erb +5 -0
  49. data/app/views/easy_admin/resources/edit_field.turbo_stream.erb +7 -0
  50. data/app/views/easy_admin/resources/index.html.erb +1 -1
  51. data/app/views/easy_admin/resources/index_frame.html.erb +8 -142
  52. data/app/views/easy_admin/resources/update_belongs_to_attached.turbo_stream.erb +25 -0
  53. data/app/views/layouts/easy_admin/application.html.erb +15 -2
  54. data/config/initializers/easy_admin_permissions.rb +73 -0
  55. data/db/seeds/easy_admin_permissions.rb +121 -0
  56. data/lib/easy-admin-rails.rb +2 -0
  57. data/lib/easy_admin/permissions/component.rb +168 -0
  58. data/lib/easy_admin/permissions/configuration.rb +37 -0
  59. data/lib/easy_admin/permissions/controller.rb +164 -0
  60. data/lib/easy_admin/permissions/dsl.rb +160 -0
  61. data/lib/easy_admin/permissions/models.rb +44 -0
  62. data/lib/easy_admin/permissions/permission_denied_component.rb +121 -0
  63. data/lib/easy_admin/permissions/resource_permissions.rb +231 -0
  64. data/lib/easy_admin/permissions/role_definition.rb +45 -0
  65. data/lib/easy_admin/permissions/role_denied_component.rb +159 -0
  66. data/lib/easy_admin/permissions/role_dsl.rb +73 -0
  67. data/lib/easy_admin/permissions/user_extensions.rb +129 -0
  68. data/lib/easy_admin/permissions.rb +113 -0
  69. data/lib/easy_admin/resource/base.rb +119 -0
  70. data/lib/easy_admin/resource/configuration.rb +148 -0
  71. data/lib/easy_admin/resource/dsl.rb +117 -0
  72. data/lib/easy_admin/resource/field_registry.rb +189 -0
  73. data/lib/easy_admin/resource/form_builder.rb +123 -0
  74. data/lib/easy_admin/resource/layout_builder.rb +249 -0
  75. data/lib/easy_admin/resource/scope_manager.rb +252 -0
  76. data/lib/easy_admin/resource/show_builder.rb +359 -0
  77. data/lib/easy_admin/resource.rb +8 -835
  78. data/lib/easy_admin/resource_modules.rb +11 -0
  79. data/lib/easy_admin/version.rb +1 -1
  80. data/lib/generators/easy_admin/permissions/install_generator.rb +90 -0
  81. data/lib/generators/easy_admin/permissions/templates/initializers/permissions.rb +37 -0
  82. data/lib/generators/easy_admin/permissions/templates/migrations/create_permission_tables.rb +27 -0
  83. data/lib/generators/easy_admin/permissions/templates/migrations/update_users_for_permissions.rb +6 -0
  84. data/lib/generators/easy_admin/permissions/templates/models/permission.rb +9 -0
  85. data/lib/generators/easy_admin/permissions/templates/models/role.rb +9 -0
  86. data/lib/generators/easy_admin/permissions/templates/models/role_permission.rb +9 -0
  87. data/lib/generators/easy_admin/permissions/templates/models/user_role.rb +9 -0
  88. data/lib/generators/easy_admin/permissions/templates/policies/application_policy.rb +47 -0
  89. data/lib/generators/easy_admin/permissions/templates/policies/user_policy.rb +36 -0
  90. data/lib/generators/easy_admin/permissions/templates/seeds/permissions.rb +89 -0
  91. metadata +62 -5
  92. data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +0 -45
@@ -0,0 +1,123 @@
1
+ module EasyAdmin
2
+ module ResourceModules
3
+ # FormBuilder class for form DSL
4
+ # Handles all form tab and field definitions within forms
5
+ class FormBuilder
6
+ def initialize(resource_class)
7
+ @resource_class = resource_class
8
+ @current_tab = nil
9
+ end
10
+
11
+ def tab(name = "General", **options, &block)
12
+ tab_config = {
13
+ name: name,
14
+ label: options[:label] || name,
15
+ icon: options[:icon],
16
+ fields: []
17
+ }
18
+
19
+ @current_tab = tab_config
20
+ @resource_class.add_form_tab(tab_config)
21
+
22
+ instance_eval(&block) if block_given?
23
+ @current_tab = nil
24
+ end
25
+
26
+ # Field definition methods within tabs - delegate to resource
27
+ def field(name, type = :string, **options)
28
+ field_config = @resource_class.build_field_config(name, type, **options)
29
+
30
+ if @current_tab
31
+ @current_tab[:fields] << field_config
32
+ end
33
+
34
+ # Also add to main fields_config for compatibility
35
+ @resource_class.add_field_to_registry(field_config)
36
+ end
37
+
38
+ # Convenience methods for form fields
39
+ def text_field(name, **options)
40
+ field(name, :string, **options)
41
+ end
42
+
43
+ def textarea_field(name, **options)
44
+ field(name, :text, **options)
45
+ end
46
+
47
+ def number_field(name, **options)
48
+ field(name, :number, **options)
49
+ end
50
+
51
+ def email_field(name, **options)
52
+ field(name, :email, **options)
53
+ end
54
+
55
+ def date_field(name, **options)
56
+ field(name, :date, **options)
57
+ end
58
+
59
+ def datetime_field(name, **options)
60
+ field(name, :datetime, **options)
61
+ end
62
+
63
+ def boolean_field(name, **options)
64
+ field(name, :boolean, **options)
65
+ end
66
+
67
+ def select_field(name, options:, **other_options)
68
+ field(name, :select, options: options, **other_options)
69
+ end
70
+
71
+ def belongs_to_field(name, **options)
72
+ field(name, :belongs_to, **options)
73
+ end
74
+
75
+ def has_many_field(name, **options)
76
+ field(name, :has_many, **options)
77
+ end
78
+
79
+ def file_field(name, **options)
80
+ field(name, :file, **options)
81
+ end
82
+
83
+ def json_field(name, **options)
84
+ field(name, :json, **options)
85
+ end
86
+
87
+ def password_field(name = :password, **options)
88
+ field(name, :password, **options)
89
+ field("#{name}_confirmation".to_sym, :password,
90
+ label: options[:confirmation_label] || "Confirm #{name.to_s.humanize}",
91
+ **options.except(:confirmation_label))
92
+ end
93
+
94
+ # Support for rendering custom components in forms
95
+ def render(component_class_or_instance, **options)
96
+ content_config = {
97
+ type: :custom_component,
98
+ component_class: component_class_or_instance.class,
99
+ component_options: options,
100
+ component_instance: component_class_or_instance
101
+ }
102
+
103
+ if @current_tab
104
+ @current_tab[:fields] << content_config
105
+ end
106
+
107
+ component_class_or_instance
108
+ end
109
+
110
+ # Support for custom content blocks in forms
111
+ def content(&block)
112
+ content_config = {
113
+ type: :custom_content,
114
+ block: block
115
+ }
116
+
117
+ if @current_tab
118
+ @current_tab[:fields] << content_config
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,249 @@
1
+ module EasyAdmin
2
+ module ResourceModules
3
+ # LayoutBuilder module for handling form and show layout configuration
4
+ # Manages form tabs and show page layout elements
5
+ module LayoutBuilder
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :form_tabs_config, :show_layout_config
10
+
11
+ def self.initialize_layout_builder
12
+ self.form_tabs_config = []
13
+ self.show_layout_config = []
14
+ end
15
+ end
16
+
17
+ class_methods do
18
+ # Form tabs management
19
+ def add_form_tab(tab_config)
20
+ self.form_tabs_config = (self.form_tabs_config || []) + [tab_config]
21
+ end
22
+
23
+ def form_tabs
24
+ form_tabs_config
25
+ end
26
+
27
+ def has_form_tabs?
28
+ form_tabs_config.any?
29
+ end
30
+
31
+ def clear_form_tabs
32
+ self.form_tabs_config = []
33
+ end
34
+
35
+ # Show layout management
36
+ def add_show_layout_element(element_config)
37
+ self.show_layout_config = (self.show_layout_config || []) + [element_config]
38
+ end
39
+
40
+ def show_layout
41
+ show_layout_config
42
+ end
43
+
44
+ def has_show_layout?
45
+ show_layout_config.any?
46
+ end
47
+
48
+ def clear_show_layout
49
+ self.show_layout_config = []
50
+ end
51
+
52
+ # Form tab utilities
53
+ def find_form_tab(tab_name)
54
+ form_tabs_config.find { |tab| tab[:name].to_s == tab_name.to_s }
55
+ end
56
+
57
+ def form_tab_names
58
+ form_tabs_config.map { |tab| tab[:name] }
59
+ end
60
+
61
+ def form_tab_fields(tab_name)
62
+ tab = find_form_tab(tab_name)
63
+ tab ? tab[:fields] : []
64
+ end
65
+
66
+ def total_form_tabs_count
67
+ form_tabs_config.count
68
+ end
69
+
70
+ # Show layout utilities
71
+ def show_layout_rows
72
+ show_layout_config.select { |element| element[:type] == :row }
73
+ end
74
+
75
+ def show_layout_elements_count
76
+ count = 0
77
+ show_layout_config.each do |element|
78
+ count += count_nested_elements(element)
79
+ end
80
+ count
81
+ end
82
+
83
+ def find_show_layout_element(element_type)
84
+ find_element_in_layout(show_layout_config, element_type)
85
+ end
86
+
87
+ def has_show_layout_tabs?
88
+ find_show_layout_element(:tabs).present?
89
+ end
90
+
91
+ def has_show_layout_cards?
92
+ find_show_layout_element(:card).present?
93
+ end
94
+
95
+ def has_metric_cards?
96
+ find_show_layout_element(:metric_card).present?
97
+ end
98
+
99
+ def has_chart_cards?
100
+ find_show_layout_element(:chart_card).present?
101
+ end
102
+
103
+ # Layout validation
104
+ def validate_form_structure
105
+ errors = []
106
+
107
+ if has_form_tabs?
108
+ form_tabs_config.each_with_index do |tab, index|
109
+ unless tab[:name].present?
110
+ errors << "Form tab at index #{index} is missing a name"
111
+ end
112
+
113
+ unless tab[:fields].is_a?(Array)
114
+ errors << "Form tab '#{tab[:name]}' fields must be an array"
115
+ end
116
+ end
117
+ end
118
+
119
+ errors
120
+ end
121
+
122
+ def validate_show_layout_structure
123
+ errors = []
124
+
125
+ show_layout_config.each_with_index do |element, index|
126
+ case element[:type]
127
+ when :row
128
+ unless element[:columns].is_a?(Array)
129
+ errors << "Row at index #{index} must have columns array"
130
+ end
131
+
132
+ if element[:columns].empty?
133
+ errors << "Row at index #{index} cannot have empty columns"
134
+ end
135
+ when :column
136
+ unless element[:elements].is_a?(Array)
137
+ errors << "Column at index #{index} must have elements array"
138
+ end
139
+ end
140
+ end
141
+
142
+ errors
143
+ end
144
+
145
+ # Layout rebuild methods
146
+ def rebuild_form_from_fields
147
+ return if fields_config.empty?
148
+
149
+ # Create a default tab with all non-readonly fields
150
+ default_tab = {
151
+ name: "General",
152
+ label: "General",
153
+ icon: nil,
154
+ fields: fields_config.reject { |field| field[:readonly] }
155
+ }
156
+
157
+ self.form_tabs_config = [default_tab]
158
+ end
159
+
160
+ def rebuild_show_layout_from_fields
161
+ return if fields_config.empty?
162
+
163
+ # Create a simple single-column layout with all fields
164
+ row_config = {
165
+ type: :row,
166
+ columns_count: 1,
167
+ spacing: "medium",
168
+ columns: [{
169
+ type: :column,
170
+ size: 12,
171
+ elements: [{
172
+ type: :card,
173
+ title: "Details",
174
+ color: "light",
175
+ padding: "medium",
176
+ shadow: "small",
177
+ elements: fields_config.map { |field|
178
+ {
179
+ type: :field,
180
+ name: field[:name],
181
+ label: field[:label],
182
+ field_type: :default,
183
+ format: field[:format],
184
+ css_classes: nil,
185
+ show_label: true
186
+ }
187
+ }
188
+ }]
189
+ }]
190
+ }
191
+
192
+ self.show_layout_config = [row_config]
193
+ end
194
+
195
+ private
196
+
197
+ def count_nested_elements(element)
198
+ count = 1
199
+
200
+ case element[:type]
201
+ when :row
202
+ element[:columns]&.each { |column| count += count_nested_elements(column) }
203
+ when :column
204
+ element[:elements]&.each { |child| count += count_nested_elements(child) }
205
+ when :card
206
+ element[:elements]&.each { |child| count += count_nested_elements(child) }
207
+ when :tabs
208
+ element[:tabs]&.each { |tab| count += count_nested_elements(tab) }
209
+ when :tab
210
+ element[:elements]&.each { |child| count += count_nested_elements(child) }
211
+ end
212
+
213
+ count
214
+ end
215
+
216
+ def find_element_in_layout(layout, element_type)
217
+ layout.each do |element|
218
+ return element if element[:type] == element_type
219
+
220
+ case element[:type]
221
+ when :row
222
+ element[:columns]&.each do |column|
223
+ found = find_element_in_layout([column], element_type)
224
+ return found if found
225
+ end
226
+ when :column, :card
227
+ if element[:elements]
228
+ found = find_element_in_layout(element[:elements], element_type)
229
+ return found if found
230
+ end
231
+ when :tabs
232
+ element[:tabs]&.each do |tab|
233
+ found = find_element_in_layout([tab], element_type)
234
+ return found if found
235
+ end
236
+ when :tab
237
+ if element[:elements]
238
+ found = find_element_in_layout(element[:elements], element_type)
239
+ return found if found
240
+ end
241
+ end
242
+ end
243
+
244
+ nil
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,252 @@
1
+ module EasyAdmin
2
+ module ResourceModules
3
+ # ScopeManager module for handling resource scopes and filtering
4
+ # Manages scope definitions, default scopes, and scope-related queries
5
+ module ScopeManager
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :scopes_config
10
+
11
+ def self.initialize_scope_manager
12
+ self.scopes_config = []
13
+ end
14
+ end
15
+
16
+ class_methods do
17
+ # Core scope definition method
18
+ def scope(name, **options)
19
+ scope_config = {
20
+ name: name,
21
+ label: options[:label] || name.to_s.humanize,
22
+ scope_method: options[:scope] || name,
23
+ default: options.fetch(:default, false),
24
+ icon: options[:icon],
25
+ color: options[:color] || 'blue',
26
+ count: options.fetch(:show_count, true),
27
+ condition: options[:if] # Conditional scope visibility
28
+ }
29
+
30
+ add_scope(scope_config)
31
+ end
32
+
33
+ # Add scope to configuration
34
+ def add_scope(scope_config)
35
+ self.scopes_config = (self.scopes_config || []) + [scope_config]
36
+ end
37
+
38
+ # Scope query methods
39
+ def scopes
40
+ scopes_config
41
+ end
42
+
43
+ def has_scopes?
44
+ scopes_config.any?
45
+ end
46
+
47
+ def scope_names
48
+ scopes_config.map { |scope| scope[:name] }
49
+ end
50
+
51
+ def find_scope(name)
52
+ scopes_config.find { |scope| scope[:name].to_s == name.to_s }
53
+ end
54
+
55
+ def scope_exists?(name)
56
+ find_scope(name).present?
57
+ end
58
+
59
+ # Default scope management
60
+ def default_scope
61
+ scopes_config.find { |scope| scope[:default] } || scopes_config.first
62
+ end
63
+
64
+ def set_default_scope(name)
65
+ # Remove existing default
66
+ scopes_config.each { |scope| scope[:default] = false }
67
+
68
+ # Set new default
69
+ scope = find_scope(name)
70
+ scope[:default] = true if scope
71
+ end
72
+
73
+ def has_default_scope?
74
+ default_scope.present?
75
+ end
76
+
77
+ # Conditional scopes
78
+ def visible_scopes(context = {})
79
+ scopes_config.select do |scope_config|
80
+ condition = scope_config[:condition]
81
+ condition.nil? || (condition.respond_to?(:call) ? context.instance_eval(&condition) : condition)
82
+ end
83
+ end
84
+
85
+ def conditional_scopes
86
+ scopes_config.select { |scope| scope[:condition].present? }
87
+ end
88
+
89
+ # Scope application methods
90
+ def apply_scope_to_records(records, scope_name)
91
+ return records unless scope_exists?(scope_name)
92
+
93
+ scope_config = find_scope(scope_name)
94
+ return records if scope_config[:name] == :all
95
+
96
+ scope_method = scope_config[:scope_method]
97
+ if records.respond_to?(scope_method)
98
+ records.public_send(scope_method)
99
+ else
100
+ records
101
+ end
102
+ end
103
+
104
+ def apply_default_scope_to_records(records)
105
+ return records unless has_default_scope?
106
+
107
+ default = default_scope
108
+ return records if default[:name] == :all
109
+
110
+ apply_scope_to_records(records, default[:name])
111
+ end
112
+
113
+ # Scope counts calculation
114
+ def calculate_scope_counts(base_records = nil)
115
+ base_records ||= model_class.all
116
+ counts = {}
117
+
118
+ scopes_config.each do |scope_config|
119
+ scope_name = scope_config[:name]
120
+ next unless scope_config[:count] # Skip if count is disabled
121
+
122
+ if scope_name == :all
123
+ counts[scope_name] = base_records.count
124
+ else
125
+ scope_method = scope_config[:scope_method]
126
+ if model_class.respond_to?(scope_method)
127
+ counts[scope_name] = model_class.public_send(scope_method).count
128
+ else
129
+ counts[scope_name] = 0
130
+ end
131
+ end
132
+ end
133
+
134
+ counts
135
+ end
136
+
137
+ def scope_count(scope_name, base_records = nil)
138
+ return 0 unless scope_exists?(scope_name)
139
+
140
+ scope_config = find_scope(scope_name)
141
+ return 0 unless scope_config[:count]
142
+
143
+ if scope_name == :all
144
+ base_records ||= model_class.all
145
+ base_records.count
146
+ else
147
+ scope_method = scope_config[:scope_method]
148
+ if model_class.respond_to?(scope_method)
149
+ model_class.public_send(scope_method).count
150
+ else
151
+ 0
152
+ end
153
+ end
154
+ end
155
+
156
+ # Scope utilities
157
+ def scope_label(scope_name)
158
+ scope_config = find_scope(scope_name)
159
+ scope_config ? scope_config[:label] : scope_name.to_s.humanize
160
+ end
161
+
162
+ def scope_icon(scope_name)
163
+ scope_config = find_scope(scope_name)
164
+ scope_config&.dig(:icon)
165
+ end
166
+
167
+ def scope_color(scope_name)
168
+ scope_config = find_scope(scope_name)
169
+ scope_config&.dig(:color) || 'blue'
170
+ end
171
+
172
+ def scope_method_name(scope_name)
173
+ scope_config = find_scope(scope_name)
174
+ scope_config ? scope_config[:scope_method] : scope_name
175
+ end
176
+
177
+ # Scope validation
178
+ def validate_scopes
179
+ errors = []
180
+
181
+ scopes_config.each do |scope_config|
182
+ scope_name = scope_config[:name]
183
+ scope_method = scope_config[:scope_method]
184
+
185
+ # Check if scope method exists on model (skip :all)
186
+ if scope_name != :all && !model_class.respond_to?(scope_method)
187
+ errors << "Scope method '#{scope_method}' does not exist on #{model_class.name}"
188
+ end
189
+
190
+ # Check for duplicate scope names
191
+ duplicate_count = scopes_config.count { |s| s[:name] == scope_name }
192
+ if duplicate_count > 1
193
+ errors << "Duplicate scope name '#{scope_name}' found"
194
+ end
195
+ end
196
+
197
+ errors
198
+ end
199
+
200
+ def valid_scopes?
201
+ validate_scopes.empty?
202
+ end
203
+
204
+ # Scope management
205
+ def clear_scopes
206
+ self.scopes_config = []
207
+ end
208
+
209
+ def remove_scope(scope_name)
210
+ self.scopes_config = scopes_config.reject { |scope| scope[:name] == scope_name }
211
+ end
212
+
213
+ def total_scopes_count
214
+ scopes_config.count
215
+ end
216
+
217
+ def enabled_scope_counts_count
218
+ scopes_config.count { |scope| scope[:count] }
219
+ end
220
+
221
+ # Predefined scope helpers
222
+ def add_all_scope(label: "All", default: true)
223
+ scope(:all, label: label, scope: :all, default: default)
224
+ end
225
+
226
+ def add_recent_scope(days: 7, label: "Recent")
227
+ return unless model_class.column_names.include?('created_at')
228
+
229
+ scope(:recent,
230
+ label: label,
231
+ scope: -> { where('created_at >= ?', days.days.ago) })
232
+ end
233
+
234
+ def add_published_scope(column: :published, label: "Published")
235
+ return unless model_class.column_names.include?(column.to_s)
236
+
237
+ scope(:published,
238
+ label: label,
239
+ scope: -> { where(column => true) })
240
+ end
241
+
242
+ def add_unpublished_scope(column: :published, label: "Unpublished")
243
+ return unless model_class.column_names.include?(column.to_s)
244
+
245
+ scope(:unpublished,
246
+ label: label,
247
+ scope: -> { where(column => [false, nil]) })
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end