rails_admin 3.3.0 → 4.0.0.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +7 -5
- data/README.md +11 -11
- data/Rakefile +3 -11
- data/app/assets/builds/rails_admin.css +10 -0
- data/app/assets/builds/rails_admin.js +34 -0
- data/app/controllers/rails_admin/main_controller.rb +3 -3
- data/app/helpers/rails_admin/application_helper.rb +3 -15
- data/app/helpers/rails_admin/form_builder.rb +10 -9
- data/app/views/layouts/rails_admin/_head.html.erb +10 -27
- data/app/views/layouts/rails_admin/_head_custom.html.erb +3 -0
- data/app/views/layouts/rails_admin/_secondary_navigation.html.erb +1 -1
- data/app/views/rails_admin/main/_dashboard_history.html.erb +1 -1
- data/app/views/rails_admin/main/_delete_notice.html.erb +3 -3
- data/app/views/rails_admin/main/_form_boolean.html.erb +9 -13
- data/app/views/rails_admin/main/_submit_buttons.html.erb +4 -4
- data/app/views/rails_admin/main/bulk_delete.html.erb +2 -2
- data/app/views/rails_admin/main/dashboard.html.erb +3 -3
- data/app/views/rails_admin/main/delete.html.erb +2 -2
- data/app/views/rails_admin/main/export.html.erb +2 -2
- data/app/views/rails_admin/main/history.html.erb +2 -2
- data/app/views/rails_admin/main/index.html.erb +3 -3
- data/config/initializers/active_record_extensions.rb +1 -4
- data/lib/generators/rails_admin/install_generator.rb +37 -92
- data/lib/generators/rails_admin/templates/rails_admin.scss.erb +1 -1
- data/lib/rails_admin/abstract_model.rb +69 -191
- data/lib/rails_admin/adapters/active_record/association.rb +1 -1
- data/lib/rails_admin/adapters/active_record/property.rb +7 -0
- data/lib/rails_admin/adapters/active_record/reflection.rb +87 -0
- data/lib/rails_admin/adapters/active_record/repository.rb +216 -0
- data/lib/rails_admin/adapters/active_record/statement_builder.rb +148 -0
- data/lib/rails_admin/adapters/active_record/where_builder.rb +40 -0
- data/lib/rails_admin/adapters/active_record.rb +7 -356
- data/lib/rails_admin/adapters/mongoid/association.rb +1 -1
- data/lib/rails_admin/adapters/mongoid/extension.rb +16 -6
- data/lib/rails_admin/adapters/mongoid/property.rb +7 -0
- data/lib/rails_admin/adapters/mongoid/reflection.rb +58 -0
- data/lib/rails_admin/adapters/mongoid/repository.rb +237 -0
- data/lib/rails_admin/adapters/mongoid/statement_builder.rb +93 -0
- data/lib/rails_admin/adapters/mongoid.rb +7 -300
- data/lib/rails_admin/adapters/reflection.rb +105 -0
- data/lib/rails_admin/adapters/repository.rb +169 -0
- data/lib/rails_admin/adapters/statement_builder.rb +109 -0
- data/lib/rails_admin/adapters.rb +94 -0
- data/lib/rails_admin/config/actions/base.rb +8 -0
- data/lib/rails_admin/config/actions/bulk_delete.rb +4 -4
- data/lib/rails_admin/config/actions/dashboard.rb +1 -1
- data/lib/rails_admin/config/actions/edit.rb +3 -2
- data/lib/rails_admin/config/actions/index.rb +1 -1
- data/lib/rails_admin/config/actions/new.rb +3 -2
- data/lib/rails_admin/config/actions/show.rb +1 -1
- data/lib/rails_admin/config/configurable.rb +4 -3
- data/lib/rails_admin/config/fields/association.rb +9 -8
- data/lib/rails_admin/config/fields/base.rb +55 -37
- data/lib/rails_admin/config/fields/collection_association.rb +2 -2
- data/lib/rails_admin/config/fields/factories/carrierwave.rb +35 -20
- data/lib/rails_admin/config/fields/factories/dragonfly.rb +30 -18
- data/lib/rails_admin/config/fields/factories/enum.rb +14 -8
- data/lib/rails_admin/config/fields/factories/paperclip.rb +29 -19
- data/lib/rails_admin/config/fields/factories/shrine.rb +19 -8
- data/lib/rails_admin/config/fields/singular_association.rb +6 -1
- data/lib/rails_admin/config/fields/types/active_record_enum.rb +3 -7
- data/lib/rails_admin/config/fields/types/belongs_to_association.rb +8 -4
- data/lib/rails_admin/config/fields/types/bson_object_id.rb +1 -1
- data/lib/rails_admin/config/fields/types/datetime.rb +0 -4
- data/lib/rails_admin/config/fields/types/dragonfly.rb +1 -1
- data/lib/rails_admin/config/fields/types/enum.rb +11 -2
- data/lib/rails_admin/config/fields/types/multiple_file_upload.rb +0 -2
- data/lib/rails_admin/config/fields/types/polymorphic_association.rb +4 -4
- data/lib/rails_admin/config/fields/types/time.rb +1 -1
- data/lib/rails_admin/config/model.rb +27 -5
- data/lib/rails_admin/config/sections/list.rb +0 -8
- data/lib/rails_admin/config.rb +7 -21
- data/lib/rails_admin/criteria/path.rb +66 -0
- data/lib/rails_admin/criteria/period.rb +67 -0
- data/lib/rails_admin/engine.rb +11 -15
- data/lib/rails_admin/extensions/pundit/authorization_adapter.rb +2 -2
- data/lib/rails_admin/extensions/url_for_extension.rb +8 -0
- data/lib/rails_admin/support/asset_source.rb +42 -0
- data/lib/rails_admin/support/composite_keys_serializer.rb +28 -2
- data/lib/rails_admin/support/csv_converter.rb +3 -10
- data/lib/rails_admin/version.rb +7 -6
- data/lib/rails_admin.rb +7 -0
- data/lib/tasks/assets.rake +23 -0
- data/package.json +12 -7
- data/src/rails_admin/base.js +18 -15
- data/src/rails_admin/filter-box.js +7 -4
- data/src/rails_admin/filtering-multiselect.js +1 -1
- data/src/rails_admin/filtering-select.js +8 -8
- data/src/rails_admin/flatpickr.js +14 -0
- data/src/rails_admin/remote-form.js +74 -48
- data/src/rails_admin/styles/base/theming.scss +48 -25
- data/src/rails_admin/styles/base.scss +25 -1
- data/src/rails_admin/styles/filtering-select.scss +23 -62
- data/src/rails_admin/ui.js +1 -1
- data/src/rails_admin/vendor/jquery_nested_form.js +4 -4
- data/src/rails_admin/widgets.js +44 -11
- metadata +32 -128
- data/app/assets/javascripts/rails_admin/application.js.erb +0 -30
- data/app/assets/javascripts/rails_admin/custom/ui.js +0 -1
- data/app/assets/stylesheets/rails_admin/application.scss.erb +0 -35
- data/app/assets/stylesheets/rails_admin/custom/mixins.scss +0 -11
- data/app/assets/stylesheets/rails_admin/custom/theming.scss +0 -13
- data/app/assets/stylesheets/rails_admin/custom/variables.scss +0 -15
- data/lib/generators/rails_admin/importmap_formatter.rb +0 -28
- data/lib/generators/rails_admin/templates/rails_admin.vite.js +0 -2
- data/lib/generators/rails_admin/templates/rails_admin.webpacker.js +0 -2
- data/lib/rails_admin/adapters/active_record/object_extension.rb +0 -13
- data/lib/rails_admin/adapters/active_record.rb.bak +0 -348
- data/lib/rails_admin/adapters/mongoid/object_extension.rb +0 -29
- data/lib/rails_admin/support/es_module_processor.rb +0 -23
- data/vendor/assets/fonts/rails_admin/fa-solid-900.ttf +0 -0
- data/vendor/assets/javascripts/rails_admin/bootstrap.js +0 -5046
- data/vendor/assets/javascripts/rails_admin/flatpickr-with-locales.js +0 -11246
- data/vendor/assets/javascripts/rails_admin/jquery-ui/data.js +0 -41
- data/vendor/assets/javascripts/rails_admin/jquery-ui/effect.js +0 -1637
- data/vendor/assets/javascripts/rails_admin/jquery-ui/ie.js +0 -17
- data/vendor/assets/javascripts/rails_admin/jquery-ui/keycode.js +0 -47
- data/vendor/assets/javascripts/rails_admin/jquery-ui/position.js +0 -500
- data/vendor/assets/javascripts/rails_admin/jquery-ui/safe-active-element.js +0 -42
- data/vendor/assets/javascripts/rails_admin/jquery-ui/scroll-parent.js +0 -47
- data/vendor/assets/javascripts/rails_admin/jquery-ui/unique-id.js +0 -51
- data/vendor/assets/javascripts/rails_admin/jquery-ui/version.js +0 -17
- data/vendor/assets/javascripts/rails_admin/jquery-ui/widget.js +0 -735
- data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/autocomplete.js +0 -689
- data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/menu.js +0 -680
- data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/mouse.js +0 -230
- data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/sortable.js +0 -1561
- data/vendor/assets/javascripts/rails_admin/jquery3.js +0 -10881
- data/vendor/assets/javascripts/rails_admin/popper.js +0 -1974
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_accordion.scss +0 -118
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_alert.scss +0 -57
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_badge.scss +0 -29
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_breadcrumb.scss +0 -28
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_button-group.scss +0 -139
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_buttons.scss +0 -111
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_card.scss +0 -216
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_carousel.scss +0 -229
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_close.scss +0 -40
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_containers.scss +0 -41
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_dropdown.scss +0 -240
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_forms.scss +0 -9
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_functions.scss +0 -302
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_grid.scss +0 -33
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_helpers.scss +0 -9
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_images.scss +0 -42
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_list-group.scss +0 -174
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_mixins.scss +0 -43
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_modal.scss +0 -209
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_nav.scss +0 -139
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_navbar.scss +0 -335
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_offcanvas.scss +0 -83
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_pagination.scss +0 -64
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_placeholders.scss +0 -51
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_popover.scss +0 -158
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_progress.scss +0 -48
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_reboot.scss +0 -625
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_root.scss +0 -54
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_spinners.scss +0 -69
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_tables.scss +0 -155
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_toasts.scss +0 -51
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_tooltip.scss +0 -115
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_transitions.scss +0 -27
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_type.scss +0 -104
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_utilities.scss +0 -630
- data/vendor/assets/stylesheets/rails_admin/bootstrap/_variables.scss +0 -1641
- data/vendor/assets/stylesheets/rails_admin/bootstrap/bootstrap.scss +0 -53
- data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_floating-labels.scss +0 -63
- data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-check.scss +0 -152
- data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-control.scss +0 -219
- data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-range.scss +0 -91
- data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-select.scss +0 -72
- data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-text.scss +0 -11
- data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_input-group.scss +0 -121
- data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_labels.scss +0 -36
- data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_validation.scss +0 -12
- data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_clearfix.scss +0 -3
- data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_colored-links.scss +0 -12
- data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_position.scss +0 -30
- data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_ratio.scss +0 -26
- data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_stacks.scss +0 -15
- data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_stretched-link.scss +0 -15
- data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_text-truncation.scss +0 -7
- data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_visually-hidden.scss +0 -8
- data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_vr.scss +0 -8
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_alert.scss +0 -11
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_backdrop.scss +0 -14
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_border-radius.scss +0 -78
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_box-shadow.scss +0 -18
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_breakpoints.scss +0 -127
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_buttons.scss +0 -133
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_caret.scss +0 -64
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_clearfix.scss +0 -9
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_color-scheme.scss +0 -7
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_container.scss +0 -9
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_deprecate.scss +0 -10
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_forms.scss +0 -144
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_gradients.scss +0 -47
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_grid.scss +0 -151
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_image.scss +0 -16
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_list-group.scss +0 -24
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_lists.scss +0 -7
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_pagination.scss +0 -31
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_reset-text.scss +0 -17
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_resize.scss +0 -6
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_table-variants.scss +0 -21
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_text-truncate.scss +0 -8
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_transition.scss +0 -26
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_utilities.scss +0 -89
- data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_visually-hidden.scss +0 -29
- data/vendor/assets/stylesheets/rails_admin/bootstrap/utilities/_api.scss +0 -47
- data/vendor/assets/stylesheets/rails_admin/bootstrap/vendor/_rfs.scss +0 -354
- data/vendor/assets/stylesheets/rails_admin/flatpickr.css +0 -791
- data/vendor/assets/stylesheets/rails_admin/font-awesome.scss +0 -6351
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'rails_admin/adapters'
|
|
4
|
+
require 'rails_admin/adapters/statement_builder'
|
|
5
|
+
require 'rails_admin/criteria/path'
|
|
6
|
+
require 'rails_admin/criteria/period'
|
|
3
7
|
require 'rails_admin/support/datetime'
|
|
4
8
|
|
|
5
9
|
module RailsAdmin
|
|
6
10
|
class AbstractModel
|
|
11
|
+
# Moved to RailsAdmin::Adapters::StatementBuilder, which is where the
|
|
12
|
+
# adapters that subclass it now live. Kept so that out-of-tree adapters
|
|
13
|
+
# subclassing the old name keep working.
|
|
14
|
+
StatementBuilder = RailsAdmin::Adapters::StatementBuilder
|
|
15
|
+
|
|
7
16
|
cattr_accessor :all
|
|
8
|
-
attr_reader :adapter, :model_name
|
|
17
|
+
attr_reader :adapter, :model_name, :reflection, :repository
|
|
9
18
|
|
|
10
19
|
class << self
|
|
11
20
|
def reset
|
|
@@ -43,16 +52,51 @@ module RailsAdmin
|
|
|
43
52
|
def reset_polymorphic_parents
|
|
44
53
|
@@polymorphic_parents = {}
|
|
45
54
|
end
|
|
55
|
+
|
|
56
|
+
# A classifier answers "what is this attribute, in RailsAdmin's terms" for
|
|
57
|
+
# attributes whose meaning comes from the model layer rather than from the
|
|
58
|
+
# store: attachment libraries, enums and the like. Without it every field
|
|
59
|
+
# factory has to reach past the adapter and inspect the model class itself.
|
|
60
|
+
#
|
|
61
|
+
# The block receives the model class and a property, and returns a Role or
|
|
62
|
+
# nil to defer to the next classifier. Classifiers run last in, first out.
|
|
63
|
+
# A classifier must not ask the property for its role, or it recurses.
|
|
64
|
+
def register_classifier(&block)
|
|
65
|
+
classifiers.unshift(block)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def classifiers
|
|
69
|
+
@classifiers ||= []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def classify(model, property)
|
|
73
|
+
classifiers.each do |classifier|
|
|
74
|
+
role = classifier.call(model, property)
|
|
75
|
+
return role if role
|
|
76
|
+
end
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# What a property turned out to be, beyond its storage type.
|
|
82
|
+
#
|
|
83
|
+
# +kind+ names the thing (:shrine, :paperclip, :enum, ...), +name+ is the
|
|
84
|
+
# field name it should be surfaced under, and +children+ lists the columns
|
|
85
|
+
# that back it and therefore should not be shown on their own.
|
|
86
|
+
Role = Struct.new(:kind, :name, :children, keyword_init: true) do
|
|
87
|
+
def initialize(kind:, name:, children: [])
|
|
88
|
+
super
|
|
89
|
+
end
|
|
46
90
|
end
|
|
47
91
|
|
|
48
92
|
def initialize(model_or_model_name)
|
|
49
93
|
@model_name = model_or_model_name.to_s
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
94
|
+
registration = Adapters.detect(model)
|
|
95
|
+
return unless registration
|
|
96
|
+
|
|
97
|
+
@adapter = registration.name
|
|
98
|
+
@reflection = registration.reflection.new(self)
|
|
99
|
+
@repository = registration.repository.new(self)
|
|
56
100
|
end
|
|
57
101
|
|
|
58
102
|
# do not store a reference to the model, does not play well with ActiveReload/Rails3.2
|
|
@@ -60,13 +104,21 @@ module RailsAdmin
|
|
|
60
104
|
@model_name.constantize
|
|
61
105
|
end
|
|
62
106
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
107
|
+
# What the model is, and what can be done with it, are the two halves of an
|
|
108
|
+
# adapter. AbstractModel is the one object callers talk to; it owns neither
|
|
109
|
+
# answer and forwards both.
|
|
110
|
+
delegate :properties, :associations, :base_class, :primary_key, :primary_keys, :table_name,
|
|
111
|
+
:quoted_table_name, :quote_column_name, :encoding, :embedded?, :cyclic?,
|
|
112
|
+
:adapter_supports_joins?, :belongs_to_required_by_default,
|
|
113
|
+
:pretty_name, :human_attribute_name, :attribute_required?,
|
|
114
|
+
:attribute_length_options, :attribute_enum_values, :dummy_record,
|
|
115
|
+
to: :reflection
|
|
66
116
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
117
|
+
delegate :new, :get, :first, :all, :count, :destroy, :scoped, :where, :read, :save, :each_record,
|
|
118
|
+
:each_associated_children, :format_id, :parse_id,
|
|
119
|
+
:serialize_attribute, :deserialize_attribute,
|
|
120
|
+
:sort_expression, :search_column, :parse_object_id,
|
|
121
|
+
to: :repository
|
|
70
122
|
|
|
71
123
|
def to_s
|
|
72
124
|
model.to_s
|
|
@@ -84,192 +136,18 @@ module RailsAdmin
|
|
|
84
136
|
@model_name.split('::').collect(&:underscore).join('_')
|
|
85
137
|
end
|
|
86
138
|
|
|
87
|
-
def pretty_name
|
|
88
|
-
model.model_name.human
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def where(conditions)
|
|
92
|
-
model.where(conditions)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def each_associated_children(object)
|
|
96
|
-
associations.each do |association|
|
|
97
|
-
case association.type
|
|
98
|
-
when :has_one
|
|
99
|
-
child = object.send(association.name)
|
|
100
|
-
yield(association, [child]) if child
|
|
101
|
-
when :has_many
|
|
102
|
-
children = object.send(association.name)
|
|
103
|
-
yield(association, Array.new(children))
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def format_id(id)
|
|
109
|
-
id
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def parse_id(id)
|
|
113
|
-
id
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
private
|
|
117
|
-
|
|
118
139
|
def initialize_active_record
|
|
119
140
|
@adapter = :active_record
|
|
120
141
|
require 'rails_admin/adapters/active_record'
|
|
121
|
-
|
|
142
|
+
@reflection = Adapters::ActiveRecord::Reflection.new(self)
|
|
143
|
+
@repository = Adapters::ActiveRecord::Repository.new(self)
|
|
122
144
|
end
|
|
123
145
|
|
|
124
146
|
def initialize_mongoid
|
|
125
147
|
@adapter = :mongoid
|
|
126
148
|
require 'rails_admin/adapters/mongoid'
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def parse_field_value(field, value)
|
|
131
|
-
value.is_a?(Array) ? value.map { |v| field.parse_value(v) } : field.parse_value(value)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
class StatementBuilder
|
|
135
|
-
def initialize(column, type, value, operator)
|
|
136
|
-
@column = column
|
|
137
|
-
@type = type
|
|
138
|
-
@value = value
|
|
139
|
-
@operator = operator
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def to_statement
|
|
143
|
-
return if [@operator, @value].any? { |v| v == '_discard' }
|
|
144
|
-
|
|
145
|
-
unary_operators[@operator] || unary_operators[@value] ||
|
|
146
|
-
build_statement_for_type_generic
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
protected
|
|
150
|
-
|
|
151
|
-
def get_filtering_duration
|
|
152
|
-
FilteringDuration.new(@operator, @value).get_duration
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def build_statement_for_type_generic
|
|
156
|
-
build_statement_for_type || begin
|
|
157
|
-
case @type
|
|
158
|
-
when :date
|
|
159
|
-
build_statement_for_date
|
|
160
|
-
when :datetime, :timestamp, :time
|
|
161
|
-
build_statement_for_datetime_or_timestamp
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def build_statement_for_type
|
|
167
|
-
raise 'You must override build_statement_for_type in your StatementBuilder'
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def build_statement_for_integer_decimal_or_float
|
|
171
|
-
case @value
|
|
172
|
-
when Array
|
|
173
|
-
val, range_begin, range_end = *@value.collect do |v|
|
|
174
|
-
next unless v.to_i.to_s == v || v.to_f.to_s == v
|
|
175
|
-
|
|
176
|
-
@type == :integer ? v.to_i : v.to_f
|
|
177
|
-
end
|
|
178
|
-
case @operator
|
|
179
|
-
when 'between'
|
|
180
|
-
range_filter(range_begin, range_end)
|
|
181
|
-
else
|
|
182
|
-
column_for_value(val) if val
|
|
183
|
-
end
|
|
184
|
-
else
|
|
185
|
-
if @value.to_i.to_s == @value || @value.to_f.to_s == @value
|
|
186
|
-
@type == :integer ? column_for_value(@value.to_i) : column_for_value(@value.to_f)
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def build_statement_for_date
|
|
192
|
-
start_date, end_date = get_filtering_duration
|
|
193
|
-
if start_date
|
|
194
|
-
start_date = begin
|
|
195
|
-
start_date.to_date
|
|
196
|
-
rescue StandardError
|
|
197
|
-
nil
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
if end_date
|
|
201
|
-
end_date = begin
|
|
202
|
-
end_date.to_date
|
|
203
|
-
rescue StandardError
|
|
204
|
-
nil
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
range_filter(start_date, end_date)
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def build_statement_for_datetime_or_timestamp
|
|
211
|
-
start_date, end_date = get_filtering_duration
|
|
212
|
-
start_date = start_date.beginning_of_day if start_date.is_a?(Date)
|
|
213
|
-
end_date = end_date.end_of_day if end_date.is_a?(Date)
|
|
214
|
-
range_filter(start_date, end_date)
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def unary_operators
|
|
218
|
-
raise 'You must override unary_operators in your StatementBuilder'
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def range_filter(_min, _max)
|
|
222
|
-
raise 'You must override range_filter in your StatementBuilder'
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
class FilteringDuration
|
|
226
|
-
def initialize(operator, value)
|
|
227
|
-
@value = value
|
|
228
|
-
@operator = operator
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
def get_duration
|
|
232
|
-
case @operator
|
|
233
|
-
when 'between' then between
|
|
234
|
-
when 'today' then today
|
|
235
|
-
when 'yesterday' then yesterday
|
|
236
|
-
when 'this_week' then this_week
|
|
237
|
-
when 'last_week' then last_week
|
|
238
|
-
else default
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
def today
|
|
243
|
-
[Date.today, Date.today]
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def yesterday
|
|
247
|
-
[Date.yesterday, Date.yesterday]
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
def this_week
|
|
251
|
-
[Date.today.beginning_of_week, Date.today.end_of_week]
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def last_week
|
|
255
|
-
[1.week.ago.to_date.beginning_of_week,
|
|
256
|
-
1.week.ago.to_date.end_of_week]
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
def between
|
|
260
|
-
[@value[1], @value[2]]
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
def default
|
|
264
|
-
[default_date, default_date]
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
private
|
|
268
|
-
|
|
269
|
-
def default_date
|
|
270
|
-
Array.wrap(@value).first
|
|
271
|
-
end
|
|
272
|
-
end
|
|
149
|
+
@reflection = Adapters::Mongoid::Reflection.new(self)
|
|
150
|
+
@repository = Adapters::Mongoid::Repository.new(self)
|
|
273
151
|
end
|
|
274
152
|
end
|
|
275
153
|
end
|
|
@@ -43,6 +43,13 @@ module RailsAdmin
|
|
|
43
43
|
false
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
# @see RailsAdmin::AbstractModel.register_classifier
|
|
47
|
+
def role
|
|
48
|
+
return @role if defined?(@role)
|
|
49
|
+
|
|
50
|
+
@role = RailsAdmin::AbstractModel.classify(model, self)
|
|
51
|
+
end
|
|
52
|
+
|
|
46
53
|
def read_only?
|
|
47
54
|
model.readonly_attributes.include? property.name.to_s
|
|
48
55
|
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails_admin/adapters/reflection'
|
|
4
|
+
require 'rails_admin/adapters/active_record/association'
|
|
5
|
+
require 'rails_admin/adapters/active_record/property'
|
|
6
|
+
|
|
7
|
+
module RailsAdmin
|
|
8
|
+
module Adapters
|
|
9
|
+
module ActiveRecord
|
|
10
|
+
class Reflection < RailsAdmin::Adapters::Reflection
|
|
11
|
+
def associations
|
|
12
|
+
model.reflect_on_all_associations.collect do |association|
|
|
13
|
+
Association.new(association, model)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def properties
|
|
18
|
+
columns = model.columns.reject do |c|
|
|
19
|
+
c.type.blank? ||
|
|
20
|
+
DISABLED_COLUMN_TYPES.include?(c.type.to_sym) ||
|
|
21
|
+
c.try(:array)
|
|
22
|
+
end
|
|
23
|
+
columns.collect do |property|
|
|
24
|
+
Property.new(property, model)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def base_class
|
|
29
|
+
model.base_class
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
delegate :primary_key, :table_name, to: :model, prefix: false
|
|
33
|
+
|
|
34
|
+
def quoted_table_name
|
|
35
|
+
model.quoted_table_name
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def quote_column_name(name)
|
|
39
|
+
model.connection.quote_column_name(name)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def encoding
|
|
43
|
+
adapter =
|
|
44
|
+
if ::ActiveRecord::Base.respond_to?(:connection_db_config)
|
|
45
|
+
::ActiveRecord::Base.connection_db_config.configuration_hash[:adapter]
|
|
46
|
+
else
|
|
47
|
+
::ActiveRecord::Base.connection_config[:adapter]
|
|
48
|
+
end
|
|
49
|
+
case adapter
|
|
50
|
+
when 'postgresql'
|
|
51
|
+
::ActiveRecord::Base.connection.select_one("SELECT ''::text AS str;").values.first.encoding
|
|
52
|
+
when 'mysql2'
|
|
53
|
+
if RUBY_ENGINE == 'jruby'
|
|
54
|
+
::ActiveRecord::Base.connection.select_one("SELECT '' AS str;").values.first.encoding
|
|
55
|
+
else
|
|
56
|
+
::ActiveRecord::Base.connection.raw_connection.encoding
|
|
57
|
+
end
|
|
58
|
+
when 'oracle_enhanced'
|
|
59
|
+
::ActiveRecord::Base.connection.select_one('SELECT dummy FROM DUAL').values.first.encoding
|
|
60
|
+
else
|
|
61
|
+
::ActiveRecord::Base.connection.select_one("SELECT '' AS str;").values.first.encoding
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def embedded?
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def cyclic?
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def adapter_supports_joins?
|
|
74
|
+
true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def belongs_to_required_by_default
|
|
78
|
+
model.belongs_to_required_by_default
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def attribute_enum_values(name)
|
|
82
|
+
model.defined_enums[name.to_s]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails_admin/adapters/repository'
|
|
4
|
+
|
|
5
|
+
module RailsAdmin
|
|
6
|
+
module Adapters
|
|
7
|
+
module ActiveRecord
|
|
8
|
+
class Repository < RailsAdmin::Adapters::Repository
|
|
9
|
+
BATCH_SIZE = 1000
|
|
10
|
+
|
|
11
|
+
def new(params = {})
|
|
12
|
+
model.new(params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get(id, scope = scoped)
|
|
16
|
+
primary_key_scope(scope, id).first
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def scoped
|
|
20
|
+
model.all
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def first(options = {}, scope = nil)
|
|
24
|
+
all(options, scope).first
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def all(options = {}, scope = nil)
|
|
28
|
+
scope ||= scoped
|
|
29
|
+
scope = scope.includes(options[:include]) if options[:include]
|
|
30
|
+
scope = scope.limit(options[:limit]) if options[:limit]
|
|
31
|
+
scope = bulk_scope(scope, options) if options[:bulk_ids]
|
|
32
|
+
scope = query_scope(scope, options[:query]) if options[:query]
|
|
33
|
+
scope = filter_scope(scope, options[:filters]) if options[:filters]
|
|
34
|
+
scope = scope.send(Kaminari.config.page_method_name, options[:page]).per(options[:per]) if options[:page] && options[:per]
|
|
35
|
+
scope = sort_scope(scope, options) if options[:sort]
|
|
36
|
+
scope
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def count(options = {}, scope = nil)
|
|
40
|
+
all(options.merge(limit: false, page: false), scope).count(:all)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def destroy(objects)
|
|
44
|
+
Array.wrap(objects).each(&:destroy)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# A relation loads whole when it is iterated, so the records are fetched
|
|
48
|
+
# in batches. find_each does that, but only in primary key order, which is
|
|
49
|
+
# why an export used to come out in a different order from the list.
|
|
50
|
+
#
|
|
51
|
+
# A sort by the primary key alone still goes to find_each, which walks the
|
|
52
|
+
# key and so stays fast however far in it gets. Any other sort is fetched
|
|
53
|
+
# a page at a time by offset, with the primary key added as a tie-break:
|
|
54
|
+
# rows that sort equal are otherwise free to come back in a different
|
|
55
|
+
# order for each page, and stitching the pages together could repeat one
|
|
56
|
+
# row and lose another.
|
|
57
|
+
def each_record(scope, &block)
|
|
58
|
+
return to_enum(:each_record, scope) unless block
|
|
59
|
+
return super unless scope.is_a?(::ActiveRecord::Relation)
|
|
60
|
+
# Already cut down to size, and batching would replace the limit.
|
|
61
|
+
return scope.each(&block) if scope.limit_value || scope.offset_value
|
|
62
|
+
return scope.find_each(batch_size: BATCH_SIZE, &block) if scope.order_values.empty?
|
|
63
|
+
|
|
64
|
+
# find_each in the composite_primary_keys gem does not keep a descending
|
|
65
|
+
# order across a composite key. Only reachable below Rails 7.1, so this
|
|
66
|
+
# goes with support for Rails 7.0, as the gem does.
|
|
67
|
+
direction = primary_key_order(scope) unless primary_keys.many? && defined?(::CompositePrimaryKeys)
|
|
68
|
+
if direction
|
|
69
|
+
scope.reorder(nil).find_each(batch_size: BATCH_SIZE, order: direction, &block)
|
|
70
|
+
else
|
|
71
|
+
each_record_by_offset(scope, &block)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def read(record, name)
|
|
76
|
+
record.has_attribute?(name) ? record.read_attribute(name) : record.send(name)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def format_id(id)
|
|
80
|
+
return id unless primary_keys.many?
|
|
81
|
+
|
|
82
|
+
RailsAdmin.config.composite_keys_serializer.serialize(id)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def parse_id(id)
|
|
86
|
+
return id unless primary_keys.many?
|
|
87
|
+
|
|
88
|
+
ids = RailsAdmin.config.composite_keys_serializer.deserialize(id)
|
|
89
|
+
primary_keys.each_with_index do |key, i|
|
|
90
|
+
ids[i] = model.type_for_attribute(key).cast(ids[i])
|
|
91
|
+
end
|
|
92
|
+
ids
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def serialize_attribute(name, value)
|
|
96
|
+
model.type_for_attribute(name.to_s).serialize(value)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def deserialize_attribute(name, value)
|
|
100
|
+
model.type_for_attribute(name.to_s).deserialize(value)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def sort_expression(order)
|
|
104
|
+
return order.to_s unless order.is_a?(RailsAdmin::Criteria::Path)
|
|
105
|
+
|
|
106
|
+
"#{quoted_path_table_name(order)}.#{quote_column_name(order.attribute)}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Unlike sorting, search statements are assembled unquoted, and the table
|
|
110
|
+
# half is read back out to build the relation's references.
|
|
111
|
+
def search_column(target)
|
|
112
|
+
return target.to_s unless target.is_a?(RailsAdmin::Criteria::Path)
|
|
113
|
+
|
|
114
|
+
"#{path_table_name(target)}.#{target.attribute}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
# The model the path's attribute lives on. Only a single association hop
|
|
120
|
+
# is resolvable, which is all the field configuration can express.
|
|
121
|
+
def path_abstract_model(path)
|
|
122
|
+
return abstract_model if path.root?
|
|
123
|
+
|
|
124
|
+
association = associations.detect { |a| a.name == path.associations.first }
|
|
125
|
+
raise ArgumentError.new("Unknown association in path: #{path}") unless association
|
|
126
|
+
|
|
127
|
+
RailsAdmin::AbstractModel.new(association.klass)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def path_table_name(path)
|
|
131
|
+
path_abstract_model(path).table_name
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def quoted_path_table_name(path)
|
|
135
|
+
path_abstract_model(path).quoted_table_name
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def primary_key_scope(scope, id)
|
|
139
|
+
scope.where(primary_keys.zip(Array(parse_id(id))).to_h)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The direction a scope is sorted in when it is sorted by the primary key
|
|
143
|
+
# and nothing else, or nil. Rather than reading the order back out of the
|
|
144
|
+
# SQL, each way #all could have sorted by the key is rebuilt through
|
|
145
|
+
# #sort_scope and compared, so what is recognised is exactly what #all
|
|
146
|
+
# produces.
|
|
147
|
+
def primary_key_order(scope)
|
|
148
|
+
sorts = [primary_key]
|
|
149
|
+
sorts << RailsAdmin::Criteria::Path[primary_keys.first] unless primary_keys.many?
|
|
150
|
+
%i[desc asc].detect do |direction|
|
|
151
|
+
sorts.any? do |sort|
|
|
152
|
+
sort_scope(scope, sort: sort, sort_reverse: direction == :asc).order_values == scope.order_values
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def each_record_by_offset(scope, &block)
|
|
158
|
+
scope = scope.order(*primary_keys.collect { |key| Arel.sql("#{quoted_table_name}.#{quote_column_name(key)}") })
|
|
159
|
+
(0..).step(BATCH_SIZE) do |offset|
|
|
160
|
+
batch = scope.limit(BATCH_SIZE).offset(offset).to_a
|
|
161
|
+
batch.each(&block)
|
|
162
|
+
break if batch.size < BATCH_SIZE
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# A composite key has to be matched one whole key at a time, which costs
|
|
167
|
+
# an OR per id; a single-column key gets the IN it deserves.
|
|
168
|
+
def bulk_scope(scope, options)
|
|
169
|
+
if primary_keys.many?
|
|
170
|
+
options[:bulk_ids].map { |id| primary_key_scope(scope, id) }.reduce(&:or)
|
|
171
|
+
else
|
|
172
|
+
scope.where(primary_keys.first => options[:bulk_ids])
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def sort_scope(scope, options)
|
|
177
|
+
direction = options[:sort_reverse] ? :asc : :desc
|
|
178
|
+
case options[:sort]
|
|
179
|
+
when RailsAdmin::Criteria::Path, String, Symbol
|
|
180
|
+
scope.reorder("#{sort_expression(options[:sort])} #{direction}")
|
|
181
|
+
when Array
|
|
182
|
+
scope.reorder(options[:sort].zip(Array.new(options[:sort].size) { direction }).to_h)
|
|
183
|
+
when Hash
|
|
184
|
+
scope.reorder(options[:sort].map { |table_name, column| "#{table_name}.#{column}" }.
|
|
185
|
+
zip(Array.new(options[:sort].size) { direction }).to_h)
|
|
186
|
+
else
|
|
187
|
+
raise ArgumentError.new("Unsupported sort value: #{options[:sort]}")
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def query_scope(scope, query, fields = config.list.fields.select(&:queryable?))
|
|
192
|
+
return scope.send(config.list.search_by, query) if config.list.search_by
|
|
193
|
+
|
|
194
|
+
wb = WhereBuilder.new(scope, self)
|
|
195
|
+
# OR all query statements
|
|
196
|
+
each_query_condition(query, fields) { |field, value, operator| wb.add(field, value, operator) }
|
|
197
|
+
wb.build
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def filter_scope(scope, filters, fields = config.list.fields.select(&:filterable?))
|
|
201
|
+
each_filter_condition(filters, fields) do |field, value, operator|
|
|
202
|
+
wb = WhereBuilder.new(scope, self)
|
|
203
|
+
wb.add(field, value, operator)
|
|
204
|
+
# AND current filter statements to other filter statements
|
|
205
|
+
scope = wb.build
|
|
206
|
+
end
|
|
207
|
+
scope
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def build_statement(column, type, value, operator)
|
|
211
|
+
StatementBuilder.new(column, type, value, operator, model.connection.adapter_name).to_statement
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|