rails_admin_next 1.0.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 (184) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +46 -0
  3. data/LICENSE.md +21 -0
  4. data/README.md +317 -0
  5. data/Rakefile +25 -0
  6. data/app/assets/javascripts/rails_admin/custom/ui.js +1 -0
  7. data/app/assets/stylesheets/rails_admin/framework.css +2375 -0
  8. data/app/assets/stylesheets/rails_admin/skin.css +472 -0
  9. data/app/assets/stylesheets/rails_admin/tokens.css +281 -0
  10. data/app/assets/stylesheets/rails_admin.css +47 -0
  11. data/app/controllers/rails_admin_next/application_controller.rb +98 -0
  12. data/app/controllers/rails_admin_next/main_controller.rb +185 -0
  13. data/app/helpers/rails_admin_next/application_helper.rb +254 -0
  14. data/app/helpers/rails_admin_next/form_builder.rb +173 -0
  15. data/app/helpers/rails_admin_next/main_helper.rb +63 -0
  16. data/app/views/layouts/rails_admin_next/_head.html.erb +29 -0
  17. data/app/views/layouts/rails_admin_next/_navigation.html.erb +18 -0
  18. data/app/views/layouts/rails_admin_next/_secondary_navigation.html.erb +27 -0
  19. data/app/views/layouts/rails_admin_next/_sidebar_navigation.html.erb +6 -0
  20. data/app/views/layouts/rails_admin_next/application.html.erb +42 -0
  21. data/app/views/layouts/rails_admin_next/content.html.erb +22 -0
  22. data/app/views/layouts/rails_admin_next/modal.js.erb +7 -0
  23. data/app/views/rails_admin_next/main/_dashboard_history.html.erb +46 -0
  24. data/app/views/rails_admin_next/main/_delete_notice.html.erb +36 -0
  25. data/app/views/rails_admin_next/main/_form_action_text.html.erb +2 -0
  26. data/app/views/rails_admin_next/main/_form_boolean.html.erb +15 -0
  27. data/app/views/rails_admin_next/main/_form_colorpicker.html.erb +6 -0
  28. data/app/views/rails_admin_next/main/_form_datetime.html.erb +14 -0
  29. data/app/views/rails_admin_next/main/_form_enumeration.html.erb +26 -0
  30. data/app/views/rails_admin_next/main/_form_field.html.erb +2 -0
  31. data/app/views/rails_admin_next/main/_form_file_upload.html.erb +18 -0
  32. data/app/views/rails_admin_next/main/_form_filtering_multiselect.html.erb +20 -0
  33. data/app/views/rails_admin_next/main/_form_filtering_select.html.erb +22 -0
  34. data/app/views/rails_admin_next/main/_form_multiple_file_upload.html.erb +21 -0
  35. data/app/views/rails_admin_next/main/_form_nested_many.html.erb +20 -0
  36. data/app/views/rails_admin_next/main/_form_nested_one.html.erb +19 -0
  37. data/app/views/rails_admin_next/main/_form_polymorphic_association.html.erb +24 -0
  38. data/app/views/rails_admin_next/main/_form_text.html.erb +2 -0
  39. data/app/views/rails_admin_next/main/_pagination.html.erb +54 -0
  40. data/app/views/rails_admin_next/main/_submit_buttons.html.erb +26 -0
  41. data/app/views/rails_admin_next/main/bulk_delete.html.erb +19 -0
  42. data/app/views/rails_admin_next/main/dashboard.html.erb +64 -0
  43. data/app/views/rails_admin_next/main/delete.html.erb +21 -0
  44. data/app/views/rails_admin_next/main/edit.html.erb +3 -0
  45. data/app/views/rails_admin_next/main/export.html.erb +149 -0
  46. data/app/views/rails_admin_next/main/history.html.erb +80 -0
  47. data/app/views/rails_admin_next/main/index.html.erb +186 -0
  48. data/app/views/rails_admin_next/main/new.html.erb +3 -0
  49. data/app/views/rails_admin_next/main/show.html.erb +30 -0
  50. data/config/brakeman.ignore +145 -0
  51. data/config/importmap.rails_admin.rb +29 -0
  52. data/config/initializers/active_record_extensions.rb +21 -0
  53. data/config/locales/rails_admin_next.en.yml +158 -0
  54. data/config/routes.rb +14 -0
  55. data/lib/generators/rails_admin_next/install_generator.rb +39 -0
  56. data/lib/generators/rails_admin_next/templates/initializer.erb +40 -0
  57. data/lib/generators/rails_admin_next/utils.rb +21 -0
  58. data/lib/rails_admin_next/abstract_model.rb +266 -0
  59. data/lib/rails_admin_next/adapters/active_record/association.rb +130 -0
  60. data/lib/rails_admin_next/adapters/active_record/object_extension.rb +13 -0
  61. data/lib/rails_admin_next/adapters/active_record/property.rb +58 -0
  62. data/lib/rails_admin_next/adapters/active_record.rb +371 -0
  63. data/lib/rails_admin_next/config/actions/base.rb +160 -0
  64. data/lib/rails_admin_next/config/actions/bulk_delete.rb +67 -0
  65. data/lib/rails_admin_next/config/actions/dashboard.rb +62 -0
  66. data/lib/rails_admin_next/config/actions/delete.rb +58 -0
  67. data/lib/rails_admin_next/config/actions/edit.rb +56 -0
  68. data/lib/rails_admin_next/config/actions/export.rb +47 -0
  69. data/lib/rails_admin_next/config/actions/history_index.rb +40 -0
  70. data/lib/rails_admin_next/config/actions/history_show.rb +40 -0
  71. data/lib/rails_admin_next/config/actions/index.rb +108 -0
  72. data/lib/rails_admin_next/config/actions/new.rb +69 -0
  73. data/lib/rails_admin_next/config/actions/show.rb +36 -0
  74. data/lib/rails_admin_next/config/actions/show_in_app.rb +37 -0
  75. data/lib/rails_admin_next/config/actions.rb +122 -0
  76. data/lib/rails_admin_next/config/configurable.rb +107 -0
  77. data/lib/rails_admin_next/config/const_load_suppressor.rb +78 -0
  78. data/lib/rails_admin_next/config/fields/association.rb +169 -0
  79. data/lib/rails_admin_next/config/fields/base.rb +391 -0
  80. data/lib/rails_admin_next/config/fields/collection_association.rb +90 -0
  81. data/lib/rails_admin_next/config/fields/factories/action_text.rb +14 -0
  82. data/lib/rails_admin_next/config/fields/factories/active_storage.rb +34 -0
  83. data/lib/rails_admin_next/config/fields/factories/association.rb +32 -0
  84. data/lib/rails_admin_next/config/fields/factories/encrypted_password.rb +28 -0
  85. data/lib/rails_admin_next/config/fields/factories/enum.rb +23 -0
  86. data/lib/rails_admin_next/config/fields/factories/password.rb +19 -0
  87. data/lib/rails_admin_next/config/fields/group.rb +77 -0
  88. data/lib/rails_admin_next/config/fields/singular_association.rb +62 -0
  89. data/lib/rails_admin_next/config/fields/types/action_text.rb +20 -0
  90. data/lib/rails_admin_next/config/fields/types/active_record_enum.rb +62 -0
  91. data/lib/rails_admin_next/config/fields/types/active_storage.rb +70 -0
  92. data/lib/rails_admin_next/config/fields/types/all.rb +32 -0
  93. data/lib/rails_admin_next/config/fields/types/belongs_to_association.rb +48 -0
  94. data/lib/rails_admin_next/config/fields/types/boolean.rb +70 -0
  95. data/lib/rails_admin_next/config/fields/types/citext.rb +15 -0
  96. data/lib/rails_admin_next/config/fields/types/color.rb +43 -0
  97. data/lib/rails_admin_next/config/fields/types/date.rb +38 -0
  98. data/lib/rails_admin_next/config/fields/types/datetime.rb +79 -0
  99. data/lib/rails_admin_next/config/fields/types/decimal.rb +23 -0
  100. data/lib/rails_admin_next/config/fields/types/enum.rb +52 -0
  101. data/lib/rails_admin_next/config/fields/types/file_upload.rb +87 -0
  102. data/lib/rails_admin_next/config/fields/types/float.rb +23 -0
  103. data/lib/rails_admin_next/config/fields/types/has_and_belongs_to_many_association.rb +16 -0
  104. data/lib/rails_admin_next/config/fields/types/has_many_association.rb +16 -0
  105. data/lib/rails_admin_next/config/fields/types/has_one_association.rb +35 -0
  106. data/lib/rails_admin_next/config/fields/types/hidden.rb +31 -0
  107. data/lib/rails_admin_next/config/fields/types/inet.rb +15 -0
  108. data/lib/rails_admin_next/config/fields/types/integer.rb +20 -0
  109. data/lib/rails_admin_next/config/fields/types/json.rb +37 -0
  110. data/lib/rails_admin_next/config/fields/types/multiple_active_storage.rb +82 -0
  111. data/lib/rails_admin_next/config/fields/types/multiple_file_upload.rb +134 -0
  112. data/lib/rails_admin_next/config/fields/types/numeric.rb +24 -0
  113. data/lib/rails_admin_next/config/fields/types/password.rb +45 -0
  114. data/lib/rails_admin_next/config/fields/types/polymorphic_association.rb +118 -0
  115. data/lib/rails_admin_next/config/fields/types/serialized.rb +28 -0
  116. data/lib/rails_admin_next/config/fields/types/string.rb +50 -0
  117. data/lib/rails_admin_next/config/fields/types/string_like.rb +25 -0
  118. data/lib/rails_admin_next/config/fields/types/text.rb +28 -0
  119. data/lib/rails_admin_next/config/fields/types/time.rb +39 -0
  120. data/lib/rails_admin_next/config/fields/types/timestamp.rb +16 -0
  121. data/lib/rails_admin_next/config/fields/types/uuid.rb +15 -0
  122. data/lib/rails_admin_next/config/fields/types.rb +29 -0
  123. data/lib/rails_admin_next/config/fields.rb +88 -0
  124. data/lib/rails_admin_next/config/groupable.rb +20 -0
  125. data/lib/rails_admin_next/config/has_description.rb +14 -0
  126. data/lib/rails_admin_next/config/has_fields.rb +144 -0
  127. data/lib/rails_admin_next/config/has_groups.rb +27 -0
  128. data/lib/rails_admin_next/config/hideable.rb +30 -0
  129. data/lib/rails_admin_next/config/inspectable.rb +39 -0
  130. data/lib/rails_admin_next/config/lazy_model.rb +74 -0
  131. data/lib/rails_admin_next/config/model.rb +126 -0
  132. data/lib/rails_admin_next/config/proxyable/proxy.rb +42 -0
  133. data/lib/rails_admin_next/config/proxyable.rb +26 -0
  134. data/lib/rails_admin_next/config/sections/base.rb +36 -0
  135. data/lib/rails_admin_next/config/sections/create.rb +13 -0
  136. data/lib/rails_admin_next/config/sections/edit.rb +13 -0
  137. data/lib/rails_admin_next/config/sections/export.rb +13 -0
  138. data/lib/rails_admin_next/config/sections/list.rb +63 -0
  139. data/lib/rails_admin_next/config/sections/modal.rb +12 -0
  140. data/lib/rails_admin_next/config/sections/nested.rb +12 -0
  141. data/lib/rails_admin_next/config/sections/show.rb +12 -0
  142. data/lib/rails_admin_next/config/sections/update.rb +12 -0
  143. data/lib/rails_admin_next/config/sections.rb +39 -0
  144. data/lib/rails_admin_next/config.rb +436 -0
  145. data/lib/rails_admin_next/engine.rb +91 -0
  146. data/lib/rails_admin_next/extension.rb +35 -0
  147. data/lib/rails_admin_next/extensions/cancancan/authorization_adapter.rb +90 -0
  148. data/lib/rails_admin_next/extensions/cancancan.rb +5 -0
  149. data/lib/rails_admin_next/extensions/controller_extension.rb +8 -0
  150. data/lib/rails_admin_next/extensions/paper_trail/auditing_adapter.rb +192 -0
  151. data/lib/rails_admin_next/extensions/paper_trail.rb +5 -0
  152. data/lib/rails_admin_next/extensions/pundit/authorization_adapter.rb +72 -0
  153. data/lib/rails_admin_next/extensions/pundit.rb +5 -0
  154. data/lib/rails_admin_next/extensions/url_for_extension.rb +15 -0
  155. data/lib/rails_admin_next/icons.rb +143 -0
  156. data/lib/rails_admin_next/support/composite_keys_serializer.rb +15 -0
  157. data/lib/rails_admin_next/support/csv_converter.rb +100 -0
  158. data/lib/rails_admin_next/support/hash_helper.rb +31 -0
  159. data/lib/rails_admin_next/support/paginated_collection.rb +79 -0
  160. data/lib/rails_admin_next/version.rb +17 -0
  161. data/lib/rails_admin_next.rb +72 -0
  162. data/lib/tasks/rails_admin.rake +40 -0
  163. data/src/rails_admin/application.js +15 -0
  164. data/src/rails_admin/base.js +12 -0
  165. data/src/rails_admin/controllers/alert_controller.js +28 -0
  166. data/src/rails_admin/controllers/collapse_controller.js +16 -0
  167. data/src/rails_admin/controllers/dropdown_controller.js +110 -0
  168. data/src/rails_admin/controllers/feedback_controller.js +73 -0
  169. data/src/rails_admin/controllers/filter_box_controller.js +346 -0
  170. data/src/rails_admin/controllers/filtering_multiselect_controller.js +386 -0
  171. data/src/rails_admin/controllers/filtering_select_controller.js +509 -0
  172. data/src/rails_admin/controllers/index.js +31 -0
  173. data/src/rails_admin/controllers/modal_controller.js +142 -0
  174. data/src/rails_admin/controllers/nested_form_controller.js +117 -0
  175. data/src/rails_admin/controllers/polymorphic_controller.js +26 -0
  176. data/src/rails_admin/controllers/remote_form_controller.js +173 -0
  177. data/src/rails_admin/controllers/sidescroll_controller.js +23 -0
  178. data/src/rails_admin/dom-ready.js +20 -0
  179. data/src/rails_admin/dom.js +19 -0
  180. data/src/rails_admin/i18n.js +20 -0
  181. data/src/rails_admin/icons.js +64 -0
  182. data/src/rails_admin/interactions.js +221 -0
  183. data/src/rails_admin/select_query.js +65 -0
  184. metadata +346 -0
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAdminNext
4
+ module Adapters
5
+ module ActiveRecord
6
+ class Property
7
+ attr_reader :property, :model
8
+
9
+ def initialize(property, model)
10
+ @property = property
11
+ @model = model
12
+ end
13
+
14
+ def name
15
+ property.name.to_sym
16
+ end
17
+
18
+ def pretty_name
19
+ property.name.to_s.tr("_", " ").capitalize
20
+ end
21
+
22
+ def type
23
+ if serialized?
24
+ :serialized
25
+ else
26
+ property.type
27
+ end
28
+ end
29
+
30
+ def length
31
+ property.limit
32
+ end
33
+
34
+ def nullable?
35
+ property.null
36
+ end
37
+
38
+ def serial?
39
+ model.primary_key == property.name
40
+ end
41
+
42
+ def association?
43
+ false
44
+ end
45
+
46
+ def read_only?
47
+ model.readonly_attributes.include? property.name.to_s
48
+ end
49
+
50
+ private
51
+
52
+ def serialized?
53
+ model.type_for_attribute(property.name).instance_of?(::ActiveRecord::Type::Serialized)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,371 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require "rails_admin_next/adapters/active_record/association"
5
+ require "rails_admin_next/adapters/active_record/object_extension"
6
+ require "rails_admin_next/adapters/active_record/property"
7
+
8
+ module RailsAdminNext
9
+ module Adapters
10
+ module ActiveRecord
11
+ DISABLED_COLUMN_TYPES = %i[tsvector blob binary spatial hstore geometry].freeze
12
+
13
+ def new(params = {})
14
+ model.new(params).extend(ObjectExtension)
15
+ end
16
+
17
+ def get(id, scope = scoped)
18
+ object = primary_key_scope(scope, id).first
19
+ return unless object
20
+
21
+ object.extend(ObjectExtension)
22
+ end
23
+
24
+ def scoped
25
+ model.all
26
+ end
27
+
28
+ def first(options = {}, scope = nil)
29
+ all(options, scope).first
30
+ end
31
+
32
+ def all(options = {}, scope = nil)
33
+ scope ||= scoped
34
+ scope = scope.includes(options[:include]) if options[:include]
35
+ scope = scope.limit(options[:limit]) if options[:limit]
36
+ scope = bulk_scope(scope, options) if options[:bulk_ids]
37
+ scope = query_scope(scope, options[:query]) if options[:query]
38
+ scope = filter_scope(scope, options[:filters]) if options[:filters]
39
+ scope = sort_scope(scope, options) if options[:sort]
40
+ scope = paginate_scope(scope, options[:page], options[:per]) if options[:page] && options[:per]
41
+ scope
42
+ end
43
+
44
+ def count(options = {}, scope = nil)
45
+ all(options.merge(limit: false, page: false), scope).count(:all)
46
+ end
47
+
48
+ def destroy(objects)
49
+ Array.wrap(objects).each(&:destroy)
50
+ end
51
+
52
+ def associations
53
+ model.reflect_on_all_associations.collect do |association|
54
+ Association.new(association, model)
55
+ end
56
+ end
57
+
58
+ def properties
59
+ columns = model.columns.reject do |c|
60
+ c.type.blank? ||
61
+ DISABLED_COLUMN_TYPES.include?(c.type.to_sym) ||
62
+ c.try(:array)
63
+ end
64
+ columns.collect do |property|
65
+ Property.new(property, model)
66
+ end
67
+ end
68
+
69
+ def base_class
70
+ model.base_class
71
+ end
72
+
73
+ delegate :primary_key, :table_name, to: :model, prefix: false
74
+
75
+ def quoted_table_name
76
+ model.quoted_table_name
77
+ end
78
+
79
+ def quote_column_name(name)
80
+ model.connection.quote_column_name(name)
81
+ end
82
+
83
+ def encoding
84
+ adapter = ::ActiveRecord::Base.connection_db_config.configuration_hash[:adapter]
85
+ case adapter
86
+ when "postgresql"
87
+ ::ActiveRecord::Base.connection.select_one("SELECT ''::text AS str;").values.first.encoding
88
+ when "mysql2"
89
+ if RUBY_ENGINE == "jruby"
90
+ ::ActiveRecord::Base.connection.select_one("SELECT '' AS str;").values.first.encoding
91
+ else
92
+ ::ActiveRecord::Base.connection.raw_connection.encoding
93
+ end
94
+ when "oracle_enhanced"
95
+ ::ActiveRecord::Base.connection.select_one("SELECT dummy FROM DUAL").values.first.encoding
96
+ else
97
+ ::ActiveRecord::Base.connection.select_one("SELECT '' AS str;").values.first.encoding
98
+ end
99
+ end
100
+
101
+ def embedded?
102
+ false
103
+ end
104
+
105
+ def cyclic?
106
+ false
107
+ end
108
+
109
+ def adapter_supports_joins?
110
+ true
111
+ end
112
+
113
+ def format_id(id)
114
+ if primary_key.is_a? Array
115
+ RailsAdminNext.config.composite_keys_serializer.serialize(id)
116
+ else
117
+ id
118
+ end
119
+ end
120
+
121
+ def parse_id(id)
122
+ if primary_key.is_a?(Array)
123
+ ids = RailsAdminNext.config.composite_keys_serializer.deserialize(id)
124
+ primary_key.each_with_index do |key, i|
125
+ ids[i] = model.type_for_attribute(key).cast(ids[i])
126
+ end
127
+ ids
128
+ else
129
+ id
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def primary_key_scope(scope, id)
136
+ if primary_key.is_a? Array
137
+ scope.where(primary_key.zip(parse_id(id)).to_h)
138
+ else
139
+ scope.where(primary_key => id)
140
+ end
141
+ end
142
+
143
+ def bulk_scope(scope, options)
144
+ if primary_key.is_a? Array
145
+ options[:bulk_ids].map { |id| primary_key_scope(scope, id) }.reduce(&:or)
146
+ else
147
+ scope.where(primary_key => options[:bulk_ids])
148
+ end
149
+ end
150
+
151
+ def sort_scope(scope, options)
152
+ direction = options[:sort_reverse] ? :asc : :desc
153
+ case options[:sort]
154
+ when String, Symbol
155
+ scope.reorder("#{options[:sort]} #{direction}")
156
+ when Array
157
+ scope.reorder(options[:sort].zip(Array.new(options[:sort].size) { direction }).to_h)
158
+ when Hash
159
+ scope.reorder(options[:sort].map { |table_name, column| "#{table_name}.#{column}" }
160
+ .zip(Array.new(options[:sort].size) { direction }).to_h)
161
+ else
162
+ raise ArgumentError.new("Unsupported sort value: #{options[:sort]}")
163
+ end
164
+ end
165
+
166
+ # Offset-mode pagination over the (already ordered/filtered) relation.
167
+ # geared_pagination runs the real LIMIT/OFFSET/COUNT; the returned
168
+ # PaginatedCollection is the thin view-facing wrapper.
169
+ def paginate_scope(scope, page, per)
170
+ per = [per.to_i, 1].max
171
+ recordset = GearedPagination::Recordset.new(scope, per_page: per)
172
+ RailsAdminNext::PaginatedCollection.from_recordset(recordset, page, per)
173
+ end
174
+
175
+ class WhereBuilder
176
+ def initialize(scope)
177
+ @statements = []
178
+ @values = []
179
+ @tables = []
180
+ @scope = scope
181
+ end
182
+
183
+ def add(field, value, operator)
184
+ field.searchable_columns.flatten.each do |column_infos|
185
+ statement, value1, value2 = StatementBuilder.new(column_infos[:column], column_infos[:type], value, operator, @scope.connection.adapter_name).to_statement
186
+ @statements << statement if statement.present?
187
+ @values << value1 unless value1.nil?
188
+ @values << value2 unless value2.nil?
189
+ table, column = column_infos[:column].split(".")
190
+ @tables.push(table) if column
191
+ end
192
+ end
193
+
194
+ def build
195
+ scope = @scope.where(@statements.join(" OR "), *@values)
196
+ scope = scope.references(*@tables.uniq) if @tables.any?
197
+ scope
198
+ end
199
+ end
200
+
201
+ def query_scope(scope, query, fields = config.list.fields.select(&:queryable?))
202
+ if config.list.search_by
203
+ scope.send(config.list.search_by, query)
204
+ else
205
+ wb = WhereBuilder.new(scope)
206
+ fields.each do |field|
207
+ value = parse_field_value(field, query)
208
+ wb.add(field, value, field.search_operator)
209
+ end
210
+ # OR all query statements
211
+ wb.build
212
+ end
213
+ end
214
+
215
+ # filters example => {"string_field"=>{"0055"=>{"o"=>"like", "v"=>"test_value"}}, ...}
216
+ # "0055" is the filter index, no use here. o is the operator, v the value
217
+ def filter_scope(scope, filters, fields = config.list.fields.select(&:filterable?))
218
+ filters.each_pair do |field_name, filters_dump|
219
+ filters_dump.each_value do |filter_dump|
220
+ wb = WhereBuilder.new(scope)
221
+ field = fields.detect { |f| f.name.to_s == field_name }
222
+ value = parse_field_value(field, filter_dump[:v])
223
+
224
+ wb.add(field, value, filter_dump[:o] || RailsAdminNext::Config.default_search_operator)
225
+ # AND current filter statements to other filter statements
226
+ scope = wb.build
227
+ end
228
+ end
229
+ scope
230
+ end
231
+
232
+ def build_statement(column, type, value, operator)
233
+ StatementBuilder.new(column, type, value, operator, model.connection.adapter_name).to_statement
234
+ end
235
+
236
+ class StatementBuilder < RailsAdminNext::AbstractModel::StatementBuilder
237
+ def initialize(column, type, value, operator, adapter_name)
238
+ super(column, type, value, operator)
239
+ @adapter_name = adapter_name
240
+ end
241
+
242
+ protected
243
+
244
+ def unary_operators
245
+ case @type
246
+ when :boolean
247
+ boolean_unary_operators
248
+ when :uuid
249
+ uuid_unary_operators
250
+ when :integer, :decimal, :float
251
+ numeric_unary_operators
252
+ else
253
+ generic_unary_operators
254
+ end
255
+ end
256
+
257
+ private
258
+
259
+ def generic_unary_operators
260
+ {
261
+ "_blank" => ["(#{@column} IS NULL OR #{@column} = '')"],
262
+ "_present" => ["(#{@column} IS NOT NULL AND #{@column} != '')"],
263
+ "_null" => ["(#{@column} IS NULL)"],
264
+ "_not_null" => ["(#{@column} IS NOT NULL)"],
265
+ "_empty" => ["(#{@column} = '')"],
266
+ "_not_empty" => ["(#{@column} != '')"]
267
+ }
268
+ end
269
+
270
+ def boolean_unary_operators
271
+ generic_unary_operators.merge(
272
+ "_blank" => ["(#{@column} IS NULL)"],
273
+ "_empty" => ["(#{@column} IS NULL)"],
274
+ "_present" => ["(#{@column} IS NOT NULL)"],
275
+ "_not_empty" => ["(#{@column} IS NOT NULL)"]
276
+ )
277
+ end
278
+ alias_method :numeric_unary_operators, :boolean_unary_operators
279
+ alias_method :uuid_unary_operators, :boolean_unary_operators
280
+
281
+ def range_filter(min, max)
282
+ if min && max && min == max
283
+ ["(#{@column} = ?)", min]
284
+ elsif min && max
285
+ ["(#{@column} BETWEEN ? AND ?)", min, max]
286
+ elsif min
287
+ ["(#{@column} >= ?)", min]
288
+ elsif max
289
+ ["(#{@column} <= ?)", max]
290
+ end
291
+ end
292
+
293
+ def build_statement_for_type
294
+ case @type
295
+ when :boolean then build_statement_for_boolean
296
+ when :integer, :decimal, :float then build_statement_for_integer_decimal_or_float
297
+ when :string, :text, :citext then build_statement_for_string_or_text
298
+ when :enum then build_statement_for_enum
299
+ when :belongs_to_association then build_statement_for_belongs_to_association
300
+ when :uuid then build_statement_for_uuid
301
+ end
302
+ end
303
+
304
+ def build_statement_for_boolean
305
+ case @value
306
+ when "false", "f", "0"
307
+ ["(#{@column} IS NULL OR #{@column} = ?)", false]
308
+ when "true", "t", "1"
309
+ ["(#{@column} = ?)", true]
310
+ end
311
+ end
312
+
313
+ def column_for_value(value)
314
+ ["(#{@column} = ?)", value]
315
+ end
316
+
317
+ def build_statement_for_belongs_to_association
318
+ return if @value.blank?
319
+
320
+ ["(#{@column} = ?)", @value.to_i] if @value.to_i.to_s == @value
321
+ end
322
+
323
+ def build_statement_for_string_or_text
324
+ return if @value.blank?
325
+
326
+ return ["(#{@column} = ?)", @value] if ["is", "="].include?(@operator)
327
+
328
+ @value = @value.downcase unless %w[postgresql postgis].include? ar_adapter
329
+
330
+ @value =
331
+ case @operator
332
+ when "default", "like", "not_like"
333
+ "%#{@value}%"
334
+ when "starts_with"
335
+ "#{@value}%"
336
+ when "ends_with"
337
+ "%#{@value}"
338
+ else
339
+ return
340
+ end
341
+
342
+ if %w[postgresql postgis].include? ar_adapter
343
+ if @operator == "not_like"
344
+ ["(#{@column} NOT ILIKE ?)", @value]
345
+ else
346
+ ["(#{@column} ILIKE ?)", @value]
347
+ end
348
+ elsif @operator == "not_like"
349
+ ["(LOWER(#{@column}) NOT LIKE ?)", @value]
350
+ else
351
+ ["(LOWER(#{@column}) LIKE ?)", @value]
352
+ end
353
+ end
354
+
355
+ def build_statement_for_enum
356
+ return if @value.blank?
357
+
358
+ ["(#{@column} IN (?))", Array.wrap(@value)]
359
+ end
360
+
361
+ def build_statement_for_uuid
362
+ column_for_value(@value) if /\A[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\z/.match?(@value.to_s)
363
+ end
364
+
365
+ def ar_adapter
366
+ @adapter_name.downcase
367
+ end
368
+ end
369
+ end
370
+ end
371
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_admin_next/config/proxyable"
4
+ require "rails_admin_next/config/configurable"
5
+ require "rails_admin_next/config/hideable"
6
+
7
+ module RailsAdminNext
8
+ module Config
9
+ module Actions
10
+ class Base
11
+ include RailsAdminNext::Config::Proxyable
12
+ include RailsAdminNext::Config::Configurable
13
+ include RailsAdminNext::Config::Hideable
14
+
15
+ register_instance_option :only do
16
+ nil
17
+ end
18
+
19
+ register_instance_option :except do
20
+ []
21
+ end
22
+
23
+ register_instance_option :show_in_navigation do
24
+ root?
25
+ end
26
+
27
+ register_instance_option :show_in_sidebar do
28
+ !show_in_navigation
29
+ end
30
+
31
+ register_instance_option :show_in_menu do
32
+ true
33
+ end
34
+
35
+ register_instance_option :sidebar_label do
36
+ nil
37
+ end
38
+
39
+ # Logical icon name resolved to inline SVG by rails_admin_icon (see RailsAdminNext::Icons::GLYPHS).
40
+ register_instance_option :link_icon do
41
+ :bulk_delete
42
+ end
43
+
44
+ # Should the action be visible
45
+ register_instance_option :visible? do
46
+ authorized?
47
+ end
48
+
49
+ register_instance_option :enabled? do
50
+ bindings[:abstract_model].nil? || (only.nil? || [only].flatten.collect(&:to_s).include?(bindings[:abstract_model].to_s)) &&
51
+ ![except].flatten.collect(&:to_s).include?(bindings[:abstract_model].to_s) &&
52
+ !bindings[:abstract_model].config.excluded? && (!respond_to?(:writable?) || writable?)
53
+ end
54
+
55
+ register_instance_option :authorized? do
56
+ enabled? && (
57
+ bindings[:controller].try(:authorization_adapter).nil? || bindings[:controller].authorization_adapter.authorized?(authorization_key, bindings[:abstract_model], bindings[:object])
58
+ )
59
+ end
60
+
61
+ # Is the action acting on the root level (Example: /admin/contact)
62
+ register_instance_option :root? do
63
+ false
64
+ end
65
+
66
+ # Is the action on a model scope (Example: /admin/team/export)
67
+ register_instance_option :collection? do
68
+ false
69
+ end
70
+
71
+ # Is the action on an object scope (Example: /admin/team/1/edit)
72
+ register_instance_option :member? do
73
+ false
74
+ end
75
+
76
+ # Target window [_self, _blank]
77
+ register_instance_option :link_target do
78
+ nil
79
+ end
80
+
81
+ # Determines whether to navigate via Turbo Drive or not
82
+ register_instance_option :turbo? do
83
+ true
84
+ end
85
+
86
+ # This block is evaluated in the context of the controller when action is called
87
+ # You can access:
88
+ # - @objects if you're on a model scope
89
+ # - @abstract_model & @model_config if you're on a model or object scope
90
+ # - @object if you're on an object scope
91
+ register_instance_option :controller do
92
+ proc do
93
+ render action: @action.template_name
94
+ end
95
+ end
96
+
97
+ # Model scoped actions only. You will need to handle params[:bulk_ids] in controller
98
+ register_instance_option :bulkable? do
99
+ false
100
+ end
101
+
102
+ # View partial name (called in default :controller block)
103
+ register_instance_option :template_name do
104
+ key.to_sym
105
+ end
106
+
107
+ # For CanCanCan and the like
108
+ register_instance_option :authorization_key do
109
+ key.to_sym
110
+ end
111
+
112
+ # List of methods allowed. Note that you are responsible for correctly handling them in :controller block
113
+ register_instance_option :http_methods do
114
+ [:get]
115
+ end
116
+
117
+ # Url fragment
118
+ register_instance_option :route_fragment do
119
+ custom_key.to_s
120
+ end
121
+
122
+ # Controller action name
123
+ register_instance_option :action_name do
124
+ custom_key.to_sym
125
+ end
126
+
127
+ # I18n key
128
+ register_instance_option :i18n_key do
129
+ key
130
+ end
131
+
132
+ # User should override only custom_key (action name and route fragment change, allows for duplicate actions)
133
+ register_instance_option :custom_key do
134
+ key
135
+ end
136
+
137
+ # Breadcrumb parent
138
+ register_instance_option :breadcrumb_parent do
139
+ if root?
140
+ [:dashboard]
141
+ elsif collection?
142
+ [:index, bindings[:abstract_model]]
143
+ elsif member?
144
+ [:show, bindings[:abstract_model], bindings[:object]]
145
+ end
146
+ end
147
+
148
+ # Off API.
149
+
150
+ def key
151
+ self.class.key
152
+ end
153
+
154
+ def self.key
155
+ name.to_s.demodulize.underscore.to_sym
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAdminNext
4
+ module Config
5
+ module Actions
6
+ class BulkDelete < RailsAdminNext::Config::Actions::Base
7
+ RailsAdminNext::Config::Actions.register(self)
8
+
9
+ register_instance_option :collection do
10
+ true
11
+ end
12
+
13
+ register_instance_option :http_methods do
14
+ %i[post delete]
15
+ end
16
+
17
+ register_instance_option :controller do
18
+ proc do
19
+ if request.post? # BULK DELETE
20
+ @objects = list_entries(@model_config, :destroy)
21
+
22
+ if @objects.blank?
23
+ flash[:error] = t("admin.flash.error", name: pluralize(0, @model_config.label), action: t("admin.actions.delete.done"))
24
+ redirect_to index_path
25
+ else
26
+ render @action.template_name
27
+ end
28
+
29
+ elsif request.delete? # BULK DESTROY
30
+
31
+ destroyed = nil
32
+ not_destroyed = nil
33
+
34
+ unless params[:bulk_ids].blank?
35
+ @objects = list_entries(@model_config, :destroy)
36
+ unless @objects.blank?
37
+ processed_objects = @abstract_model.destroy(@objects)
38
+ destroyed = processed_objects.select(&:destroyed?)
39
+ not_destroyed = processed_objects - destroyed
40
+ destroyed.each do |object|
41
+ @auditing_adapter&.delete_object(object, @abstract_model, _current_user)
42
+ end
43
+ end
44
+ end
45
+
46
+ if destroyed.nil?
47
+ flash[:error] = t("admin.flash.error", name: pluralize(0, @model_config.label), action: t("admin.actions.delete.done"))
48
+ else
49
+ flash[:success] = t("admin.flash.successful", name: pluralize(destroyed.count, @model_config.label), action: t("admin.actions.delete.done")) unless destroyed.empty?
50
+ flash[:error] = t("admin.flash.error", name: pluralize(not_destroyed.count, @model_config.label), action: t("admin.actions.delete.done")) unless not_destroyed.empty?
51
+ end
52
+ redirect_to back_or_index
53
+ end
54
+ end
55
+ end
56
+
57
+ register_instance_option :authorization_key do
58
+ :destroy
59
+ end
60
+
61
+ register_instance_option :bulkable? do
62
+ true
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end