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.
- checksums.yaml +7 -0
- data/Gemfile +46 -0
- data/LICENSE.md +21 -0
- data/README.md +317 -0
- data/Rakefile +25 -0
- data/app/assets/javascripts/rails_admin/custom/ui.js +1 -0
- data/app/assets/stylesheets/rails_admin/framework.css +2375 -0
- data/app/assets/stylesheets/rails_admin/skin.css +472 -0
- data/app/assets/stylesheets/rails_admin/tokens.css +281 -0
- data/app/assets/stylesheets/rails_admin.css +47 -0
- data/app/controllers/rails_admin_next/application_controller.rb +98 -0
- data/app/controllers/rails_admin_next/main_controller.rb +185 -0
- data/app/helpers/rails_admin_next/application_helper.rb +254 -0
- data/app/helpers/rails_admin_next/form_builder.rb +173 -0
- data/app/helpers/rails_admin_next/main_helper.rb +63 -0
- data/app/views/layouts/rails_admin_next/_head.html.erb +29 -0
- data/app/views/layouts/rails_admin_next/_navigation.html.erb +18 -0
- data/app/views/layouts/rails_admin_next/_secondary_navigation.html.erb +27 -0
- data/app/views/layouts/rails_admin_next/_sidebar_navigation.html.erb +6 -0
- data/app/views/layouts/rails_admin_next/application.html.erb +42 -0
- data/app/views/layouts/rails_admin_next/content.html.erb +22 -0
- data/app/views/layouts/rails_admin_next/modal.js.erb +7 -0
- data/app/views/rails_admin_next/main/_dashboard_history.html.erb +46 -0
- data/app/views/rails_admin_next/main/_delete_notice.html.erb +36 -0
- data/app/views/rails_admin_next/main/_form_action_text.html.erb +2 -0
- data/app/views/rails_admin_next/main/_form_boolean.html.erb +15 -0
- data/app/views/rails_admin_next/main/_form_colorpicker.html.erb +6 -0
- data/app/views/rails_admin_next/main/_form_datetime.html.erb +14 -0
- data/app/views/rails_admin_next/main/_form_enumeration.html.erb +26 -0
- data/app/views/rails_admin_next/main/_form_field.html.erb +2 -0
- data/app/views/rails_admin_next/main/_form_file_upload.html.erb +18 -0
- data/app/views/rails_admin_next/main/_form_filtering_multiselect.html.erb +20 -0
- data/app/views/rails_admin_next/main/_form_filtering_select.html.erb +22 -0
- data/app/views/rails_admin_next/main/_form_multiple_file_upload.html.erb +21 -0
- data/app/views/rails_admin_next/main/_form_nested_many.html.erb +20 -0
- data/app/views/rails_admin_next/main/_form_nested_one.html.erb +19 -0
- data/app/views/rails_admin_next/main/_form_polymorphic_association.html.erb +24 -0
- data/app/views/rails_admin_next/main/_form_text.html.erb +2 -0
- data/app/views/rails_admin_next/main/_pagination.html.erb +54 -0
- data/app/views/rails_admin_next/main/_submit_buttons.html.erb +26 -0
- data/app/views/rails_admin_next/main/bulk_delete.html.erb +19 -0
- data/app/views/rails_admin_next/main/dashboard.html.erb +64 -0
- data/app/views/rails_admin_next/main/delete.html.erb +21 -0
- data/app/views/rails_admin_next/main/edit.html.erb +3 -0
- data/app/views/rails_admin_next/main/export.html.erb +149 -0
- data/app/views/rails_admin_next/main/history.html.erb +80 -0
- data/app/views/rails_admin_next/main/index.html.erb +186 -0
- data/app/views/rails_admin_next/main/new.html.erb +3 -0
- data/app/views/rails_admin_next/main/show.html.erb +30 -0
- data/config/brakeman.ignore +145 -0
- data/config/importmap.rails_admin.rb +29 -0
- data/config/initializers/active_record_extensions.rb +21 -0
- data/config/locales/rails_admin_next.en.yml +158 -0
- data/config/routes.rb +14 -0
- data/lib/generators/rails_admin_next/install_generator.rb +39 -0
- data/lib/generators/rails_admin_next/templates/initializer.erb +40 -0
- data/lib/generators/rails_admin_next/utils.rb +21 -0
- data/lib/rails_admin_next/abstract_model.rb +266 -0
- data/lib/rails_admin_next/adapters/active_record/association.rb +130 -0
- data/lib/rails_admin_next/adapters/active_record/object_extension.rb +13 -0
- data/lib/rails_admin_next/adapters/active_record/property.rb +58 -0
- data/lib/rails_admin_next/adapters/active_record.rb +371 -0
- data/lib/rails_admin_next/config/actions/base.rb +160 -0
- data/lib/rails_admin_next/config/actions/bulk_delete.rb +67 -0
- data/lib/rails_admin_next/config/actions/dashboard.rb +62 -0
- data/lib/rails_admin_next/config/actions/delete.rb +58 -0
- data/lib/rails_admin_next/config/actions/edit.rb +56 -0
- data/lib/rails_admin_next/config/actions/export.rb +47 -0
- data/lib/rails_admin_next/config/actions/history_index.rb +40 -0
- data/lib/rails_admin_next/config/actions/history_show.rb +40 -0
- data/lib/rails_admin_next/config/actions/index.rb +108 -0
- data/lib/rails_admin_next/config/actions/new.rb +69 -0
- data/lib/rails_admin_next/config/actions/show.rb +36 -0
- data/lib/rails_admin_next/config/actions/show_in_app.rb +37 -0
- data/lib/rails_admin_next/config/actions.rb +122 -0
- data/lib/rails_admin_next/config/configurable.rb +107 -0
- data/lib/rails_admin_next/config/const_load_suppressor.rb +78 -0
- data/lib/rails_admin_next/config/fields/association.rb +169 -0
- data/lib/rails_admin_next/config/fields/base.rb +391 -0
- data/lib/rails_admin_next/config/fields/collection_association.rb +90 -0
- data/lib/rails_admin_next/config/fields/factories/action_text.rb +14 -0
- data/lib/rails_admin_next/config/fields/factories/active_storage.rb +34 -0
- data/lib/rails_admin_next/config/fields/factories/association.rb +32 -0
- data/lib/rails_admin_next/config/fields/factories/encrypted_password.rb +28 -0
- data/lib/rails_admin_next/config/fields/factories/enum.rb +23 -0
- data/lib/rails_admin_next/config/fields/factories/password.rb +19 -0
- data/lib/rails_admin_next/config/fields/group.rb +77 -0
- data/lib/rails_admin_next/config/fields/singular_association.rb +62 -0
- data/lib/rails_admin_next/config/fields/types/action_text.rb +20 -0
- data/lib/rails_admin_next/config/fields/types/active_record_enum.rb +62 -0
- data/lib/rails_admin_next/config/fields/types/active_storage.rb +70 -0
- data/lib/rails_admin_next/config/fields/types/all.rb +32 -0
- data/lib/rails_admin_next/config/fields/types/belongs_to_association.rb +48 -0
- data/lib/rails_admin_next/config/fields/types/boolean.rb +70 -0
- data/lib/rails_admin_next/config/fields/types/citext.rb +15 -0
- data/lib/rails_admin_next/config/fields/types/color.rb +43 -0
- data/lib/rails_admin_next/config/fields/types/date.rb +38 -0
- data/lib/rails_admin_next/config/fields/types/datetime.rb +79 -0
- data/lib/rails_admin_next/config/fields/types/decimal.rb +23 -0
- data/lib/rails_admin_next/config/fields/types/enum.rb +52 -0
- data/lib/rails_admin_next/config/fields/types/file_upload.rb +87 -0
- data/lib/rails_admin_next/config/fields/types/float.rb +23 -0
- data/lib/rails_admin_next/config/fields/types/has_and_belongs_to_many_association.rb +16 -0
- data/lib/rails_admin_next/config/fields/types/has_many_association.rb +16 -0
- data/lib/rails_admin_next/config/fields/types/has_one_association.rb +35 -0
- data/lib/rails_admin_next/config/fields/types/hidden.rb +31 -0
- data/lib/rails_admin_next/config/fields/types/inet.rb +15 -0
- data/lib/rails_admin_next/config/fields/types/integer.rb +20 -0
- data/lib/rails_admin_next/config/fields/types/json.rb +37 -0
- data/lib/rails_admin_next/config/fields/types/multiple_active_storage.rb +82 -0
- data/lib/rails_admin_next/config/fields/types/multiple_file_upload.rb +134 -0
- data/lib/rails_admin_next/config/fields/types/numeric.rb +24 -0
- data/lib/rails_admin_next/config/fields/types/password.rb +45 -0
- data/lib/rails_admin_next/config/fields/types/polymorphic_association.rb +118 -0
- data/lib/rails_admin_next/config/fields/types/serialized.rb +28 -0
- data/lib/rails_admin_next/config/fields/types/string.rb +50 -0
- data/lib/rails_admin_next/config/fields/types/string_like.rb +25 -0
- data/lib/rails_admin_next/config/fields/types/text.rb +28 -0
- data/lib/rails_admin_next/config/fields/types/time.rb +39 -0
- data/lib/rails_admin_next/config/fields/types/timestamp.rb +16 -0
- data/lib/rails_admin_next/config/fields/types/uuid.rb +15 -0
- data/lib/rails_admin_next/config/fields/types.rb +29 -0
- data/lib/rails_admin_next/config/fields.rb +88 -0
- data/lib/rails_admin_next/config/groupable.rb +20 -0
- data/lib/rails_admin_next/config/has_description.rb +14 -0
- data/lib/rails_admin_next/config/has_fields.rb +144 -0
- data/lib/rails_admin_next/config/has_groups.rb +27 -0
- data/lib/rails_admin_next/config/hideable.rb +30 -0
- data/lib/rails_admin_next/config/inspectable.rb +39 -0
- data/lib/rails_admin_next/config/lazy_model.rb +74 -0
- data/lib/rails_admin_next/config/model.rb +126 -0
- data/lib/rails_admin_next/config/proxyable/proxy.rb +42 -0
- data/lib/rails_admin_next/config/proxyable.rb +26 -0
- data/lib/rails_admin_next/config/sections/base.rb +36 -0
- data/lib/rails_admin_next/config/sections/create.rb +13 -0
- data/lib/rails_admin_next/config/sections/edit.rb +13 -0
- data/lib/rails_admin_next/config/sections/export.rb +13 -0
- data/lib/rails_admin_next/config/sections/list.rb +63 -0
- data/lib/rails_admin_next/config/sections/modal.rb +12 -0
- data/lib/rails_admin_next/config/sections/nested.rb +12 -0
- data/lib/rails_admin_next/config/sections/show.rb +12 -0
- data/lib/rails_admin_next/config/sections/update.rb +12 -0
- data/lib/rails_admin_next/config/sections.rb +39 -0
- data/lib/rails_admin_next/config.rb +436 -0
- data/lib/rails_admin_next/engine.rb +91 -0
- data/lib/rails_admin_next/extension.rb +35 -0
- data/lib/rails_admin_next/extensions/cancancan/authorization_adapter.rb +90 -0
- data/lib/rails_admin_next/extensions/cancancan.rb +5 -0
- data/lib/rails_admin_next/extensions/controller_extension.rb +8 -0
- data/lib/rails_admin_next/extensions/paper_trail/auditing_adapter.rb +192 -0
- data/lib/rails_admin_next/extensions/paper_trail.rb +5 -0
- data/lib/rails_admin_next/extensions/pundit/authorization_adapter.rb +72 -0
- data/lib/rails_admin_next/extensions/pundit.rb +5 -0
- data/lib/rails_admin_next/extensions/url_for_extension.rb +15 -0
- data/lib/rails_admin_next/icons.rb +143 -0
- data/lib/rails_admin_next/support/composite_keys_serializer.rb +15 -0
- data/lib/rails_admin_next/support/csv_converter.rb +100 -0
- data/lib/rails_admin_next/support/hash_helper.rb +31 -0
- data/lib/rails_admin_next/support/paginated_collection.rb +79 -0
- data/lib/rails_admin_next/version.rb +17 -0
- data/lib/rails_admin_next.rb +72 -0
- data/lib/tasks/rails_admin.rake +40 -0
- data/src/rails_admin/application.js +15 -0
- data/src/rails_admin/base.js +12 -0
- data/src/rails_admin/controllers/alert_controller.js +28 -0
- data/src/rails_admin/controllers/collapse_controller.js +16 -0
- data/src/rails_admin/controllers/dropdown_controller.js +110 -0
- data/src/rails_admin/controllers/feedback_controller.js +73 -0
- data/src/rails_admin/controllers/filter_box_controller.js +346 -0
- data/src/rails_admin/controllers/filtering_multiselect_controller.js +386 -0
- data/src/rails_admin/controllers/filtering_select_controller.js +509 -0
- data/src/rails_admin/controllers/index.js +31 -0
- data/src/rails_admin/controllers/modal_controller.js +142 -0
- data/src/rails_admin/controllers/nested_form_controller.js +117 -0
- data/src/rails_admin/controllers/polymorphic_controller.js +26 -0
- data/src/rails_admin/controllers/remote_form_controller.js +173 -0
- data/src/rails_admin/controllers/sidescroll_controller.js +23 -0
- data/src/rails_admin/dom-ready.js +20 -0
- data/src/rails_admin/dom.js +19 -0
- data/src/rails_admin/i18n.js +20 -0
- data/src/rails_admin/icons.js +64 -0
- data/src/rails_admin/interactions.js +221 -0
- data/src/rails_admin/select_query.js +65 -0
- 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
|