rails_admin 3.2.1 → 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.
Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -6
  3. data/README.md +11 -11
  4. data/Rakefile +3 -11
  5. data/app/assets/builds/rails_admin.css +10 -0
  6. data/app/assets/builds/rails_admin.js +34 -0
  7. data/app/controllers/rails_admin/main_controller.rb +3 -3
  8. data/app/helpers/rails_admin/application_helper.rb +3 -15
  9. data/app/helpers/rails_admin/form_builder.rb +10 -9
  10. data/app/views/layouts/rails_admin/_head.html.erb +10 -27
  11. data/app/views/layouts/rails_admin/_head_custom.html.erb +3 -0
  12. data/app/views/layouts/rails_admin/_secondary_navigation.html.erb +1 -1
  13. data/app/views/rails_admin/main/_dashboard_history.html.erb +1 -1
  14. data/app/views/rails_admin/main/_delete_notice.html.erb +3 -3
  15. data/app/views/rails_admin/main/_form_boolean.html.erb +9 -13
  16. data/app/views/rails_admin/main/_submit_buttons.html.erb +4 -4
  17. data/app/views/rails_admin/main/bulk_delete.html.erb +2 -2
  18. data/app/views/rails_admin/main/dashboard.html.erb +3 -3
  19. data/app/views/rails_admin/main/delete.html.erb +2 -2
  20. data/app/views/rails_admin/main/export.html.erb +2 -2
  21. data/app/views/rails_admin/main/history.html.erb +2 -2
  22. data/app/views/rails_admin/main/index.html.erb +3 -3
  23. data/config/initializers/active_record_extensions.rb +1 -4
  24. data/lib/generators/rails_admin/install_generator.rb +37 -92
  25. data/lib/generators/rails_admin/templates/rails_admin.scss.erb +1 -1
  26. data/lib/rails_admin/abstract_model.rb +69 -191
  27. data/lib/rails_admin/adapters/active_record/association.rb +1 -1
  28. data/lib/rails_admin/adapters/active_record/property.rb +7 -0
  29. data/lib/rails_admin/adapters/active_record/reflection.rb +87 -0
  30. data/lib/rails_admin/adapters/active_record/repository.rb +216 -0
  31. data/lib/rails_admin/adapters/active_record/statement_builder.rb +148 -0
  32. data/lib/rails_admin/adapters/active_record/where_builder.rb +40 -0
  33. data/lib/rails_admin/adapters/active_record.rb +7 -356
  34. data/lib/rails_admin/adapters/mongoid/association.rb +1 -1
  35. data/lib/rails_admin/adapters/mongoid/extension.rb +16 -6
  36. data/lib/rails_admin/adapters/mongoid/property.rb +7 -0
  37. data/lib/rails_admin/adapters/mongoid/reflection.rb +58 -0
  38. data/lib/rails_admin/adapters/mongoid/repository.rb +237 -0
  39. data/lib/rails_admin/adapters/mongoid/statement_builder.rb +93 -0
  40. data/lib/rails_admin/adapters/mongoid.rb +7 -300
  41. data/lib/rails_admin/adapters/reflection.rb +105 -0
  42. data/lib/rails_admin/adapters/repository.rb +169 -0
  43. data/lib/rails_admin/adapters/statement_builder.rb +109 -0
  44. data/lib/rails_admin/adapters.rb +94 -0
  45. data/lib/rails_admin/config/actions/base.rb +8 -0
  46. data/lib/rails_admin/config/actions/bulk_delete.rb +4 -4
  47. data/lib/rails_admin/config/actions/dashboard.rb +1 -1
  48. data/lib/rails_admin/config/actions/edit.rb +3 -2
  49. data/lib/rails_admin/config/actions/index.rb +1 -1
  50. data/lib/rails_admin/config/actions/new.rb +3 -2
  51. data/lib/rails_admin/config/actions/show.rb +1 -1
  52. data/lib/rails_admin/config/configurable.rb +4 -3
  53. data/lib/rails_admin/config/fields/association.rb +9 -8
  54. data/lib/rails_admin/config/fields/base.rb +55 -37
  55. data/lib/rails_admin/config/fields/collection_association.rb +2 -2
  56. data/lib/rails_admin/config/fields/factories/carrierwave.rb +35 -20
  57. data/lib/rails_admin/config/fields/factories/dragonfly.rb +30 -18
  58. data/lib/rails_admin/config/fields/factories/enum.rb +14 -8
  59. data/lib/rails_admin/config/fields/factories/paperclip.rb +29 -19
  60. data/lib/rails_admin/config/fields/factories/shrine.rb +19 -8
  61. data/lib/rails_admin/config/fields/singular_association.rb +6 -1
  62. data/lib/rails_admin/config/fields/types/active_record_enum.rb +3 -7
  63. data/lib/rails_admin/config/fields/types/belongs_to_association.rb +8 -4
  64. data/lib/rails_admin/config/fields/types/bson_object_id.rb +1 -1
  65. data/lib/rails_admin/config/fields/types/datetime.rb +0 -4
  66. data/lib/rails_admin/config/fields/types/dragonfly.rb +1 -1
  67. data/lib/rails_admin/config/fields/types/enum.rb +11 -2
  68. data/lib/rails_admin/config/fields/types/multiple_file_upload.rb +0 -2
  69. data/lib/rails_admin/config/fields/types/polymorphic_association.rb +4 -4
  70. data/lib/rails_admin/config/fields/types/time.rb +1 -1
  71. data/lib/rails_admin/config/model.rb +27 -5
  72. data/lib/rails_admin/config/sections/list.rb +0 -8
  73. data/lib/rails_admin/config.rb +7 -21
  74. data/lib/rails_admin/criteria/path.rb +66 -0
  75. data/lib/rails_admin/criteria/period.rb +67 -0
  76. data/lib/rails_admin/engine.rb +11 -15
  77. data/lib/rails_admin/extensions/pundit/authorization_adapter.rb +2 -2
  78. data/lib/rails_admin/extensions/url_for_extension.rb +8 -0
  79. data/lib/rails_admin/support/asset_source.rb +42 -0
  80. data/lib/rails_admin/support/composite_keys_serializer.rb +28 -2
  81. data/lib/rails_admin/support/csv_converter.rb +3 -10
  82. data/lib/rails_admin/version.rb +8 -7
  83. data/lib/rails_admin.rb +7 -0
  84. data/lib/tasks/assets.rake +23 -0
  85. data/package.json +12 -7
  86. data/src/rails_admin/base.js +18 -15
  87. data/src/rails_admin/filter-box.js +7 -4
  88. data/src/rails_admin/filtering-multiselect.js +1 -1
  89. data/src/rails_admin/filtering-select.js +8 -8
  90. data/src/rails_admin/flatpickr.js +14 -0
  91. data/src/rails_admin/remote-form.js +74 -48
  92. data/src/rails_admin/styles/base/theming.scss +48 -25
  93. data/src/rails_admin/styles/base.scss +25 -1
  94. data/src/rails_admin/styles/filtering-select.scss +23 -62
  95. data/src/rails_admin/ui.js +1 -1
  96. data/src/rails_admin/vendor/jquery_nested_form.js +4 -4
  97. data/src/rails_admin/widgets.js +44 -11
  98. metadata +34 -130
  99. data/app/assets/javascripts/rails_admin/application.js.erb +0 -30
  100. data/app/assets/javascripts/rails_admin/custom/ui.js +0 -1
  101. data/app/assets/stylesheets/rails_admin/application.scss.erb +0 -35
  102. data/app/assets/stylesheets/rails_admin/custom/mixins.scss +0 -11
  103. data/app/assets/stylesheets/rails_admin/custom/theming.scss +0 -13
  104. data/app/assets/stylesheets/rails_admin/custom/variables.scss +0 -15
  105. data/lib/generators/rails_admin/importmap_formatter.rb +0 -28
  106. data/lib/generators/rails_admin/templates/rails_admin.vite.js +0 -2
  107. data/lib/generators/rails_admin/templates/rails_admin.webpacker.js +0 -2
  108. data/lib/rails_admin/adapters/active_record/object_extension.rb +0 -13
  109. data/lib/rails_admin/adapters/active_record.rb.bak +0 -348
  110. data/lib/rails_admin/adapters/mongoid/object_extension.rb +0 -29
  111. data/lib/rails_admin/support/es_module_processor.rb +0 -23
  112. data/vendor/assets/fonts/rails_admin/fa-solid-900.ttf +0 -0
  113. data/vendor/assets/javascripts/rails_admin/bootstrap.js +0 -5046
  114. data/vendor/assets/javascripts/rails_admin/flatpickr-with-locales.js +0 -11246
  115. data/vendor/assets/javascripts/rails_admin/jquery-ui/data.js +0 -41
  116. data/vendor/assets/javascripts/rails_admin/jquery-ui/effect.js +0 -1637
  117. data/vendor/assets/javascripts/rails_admin/jquery-ui/ie.js +0 -17
  118. data/vendor/assets/javascripts/rails_admin/jquery-ui/keycode.js +0 -47
  119. data/vendor/assets/javascripts/rails_admin/jquery-ui/position.js +0 -500
  120. data/vendor/assets/javascripts/rails_admin/jquery-ui/safe-active-element.js +0 -42
  121. data/vendor/assets/javascripts/rails_admin/jquery-ui/scroll-parent.js +0 -47
  122. data/vendor/assets/javascripts/rails_admin/jquery-ui/unique-id.js +0 -51
  123. data/vendor/assets/javascripts/rails_admin/jquery-ui/version.js +0 -17
  124. data/vendor/assets/javascripts/rails_admin/jquery-ui/widget.js +0 -735
  125. data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/autocomplete.js +0 -689
  126. data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/menu.js +0 -680
  127. data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/mouse.js +0 -230
  128. data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/sortable.js +0 -1561
  129. data/vendor/assets/javascripts/rails_admin/jquery3.js +0 -10881
  130. data/vendor/assets/javascripts/rails_admin/popper.js +0 -1974
  131. data/vendor/assets/stylesheets/rails_admin/bootstrap/_accordion.scss +0 -118
  132. data/vendor/assets/stylesheets/rails_admin/bootstrap/_alert.scss +0 -57
  133. data/vendor/assets/stylesheets/rails_admin/bootstrap/_badge.scss +0 -29
  134. data/vendor/assets/stylesheets/rails_admin/bootstrap/_breadcrumb.scss +0 -28
  135. data/vendor/assets/stylesheets/rails_admin/bootstrap/_button-group.scss +0 -139
  136. data/vendor/assets/stylesheets/rails_admin/bootstrap/_buttons.scss +0 -111
  137. data/vendor/assets/stylesheets/rails_admin/bootstrap/_card.scss +0 -216
  138. data/vendor/assets/stylesheets/rails_admin/bootstrap/_carousel.scss +0 -229
  139. data/vendor/assets/stylesheets/rails_admin/bootstrap/_close.scss +0 -40
  140. data/vendor/assets/stylesheets/rails_admin/bootstrap/_containers.scss +0 -41
  141. data/vendor/assets/stylesheets/rails_admin/bootstrap/_dropdown.scss +0 -240
  142. data/vendor/assets/stylesheets/rails_admin/bootstrap/_forms.scss +0 -9
  143. data/vendor/assets/stylesheets/rails_admin/bootstrap/_functions.scss +0 -302
  144. data/vendor/assets/stylesheets/rails_admin/bootstrap/_grid.scss +0 -33
  145. data/vendor/assets/stylesheets/rails_admin/bootstrap/_helpers.scss +0 -9
  146. data/vendor/assets/stylesheets/rails_admin/bootstrap/_images.scss +0 -42
  147. data/vendor/assets/stylesheets/rails_admin/bootstrap/_list-group.scss +0 -174
  148. data/vendor/assets/stylesheets/rails_admin/bootstrap/_mixins.scss +0 -43
  149. data/vendor/assets/stylesheets/rails_admin/bootstrap/_modal.scss +0 -209
  150. data/vendor/assets/stylesheets/rails_admin/bootstrap/_nav.scss +0 -139
  151. data/vendor/assets/stylesheets/rails_admin/bootstrap/_navbar.scss +0 -335
  152. data/vendor/assets/stylesheets/rails_admin/bootstrap/_offcanvas.scss +0 -83
  153. data/vendor/assets/stylesheets/rails_admin/bootstrap/_pagination.scss +0 -64
  154. data/vendor/assets/stylesheets/rails_admin/bootstrap/_placeholders.scss +0 -51
  155. data/vendor/assets/stylesheets/rails_admin/bootstrap/_popover.scss +0 -158
  156. data/vendor/assets/stylesheets/rails_admin/bootstrap/_progress.scss +0 -48
  157. data/vendor/assets/stylesheets/rails_admin/bootstrap/_reboot.scss +0 -625
  158. data/vendor/assets/stylesheets/rails_admin/bootstrap/_root.scss +0 -54
  159. data/vendor/assets/stylesheets/rails_admin/bootstrap/_spinners.scss +0 -69
  160. data/vendor/assets/stylesheets/rails_admin/bootstrap/_tables.scss +0 -155
  161. data/vendor/assets/stylesheets/rails_admin/bootstrap/_toasts.scss +0 -51
  162. data/vendor/assets/stylesheets/rails_admin/bootstrap/_tooltip.scss +0 -115
  163. data/vendor/assets/stylesheets/rails_admin/bootstrap/_transitions.scss +0 -27
  164. data/vendor/assets/stylesheets/rails_admin/bootstrap/_type.scss +0 -104
  165. data/vendor/assets/stylesheets/rails_admin/bootstrap/_utilities.scss +0 -630
  166. data/vendor/assets/stylesheets/rails_admin/bootstrap/_variables.scss +0 -1641
  167. data/vendor/assets/stylesheets/rails_admin/bootstrap/bootstrap.scss +0 -53
  168. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_floating-labels.scss +0 -63
  169. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-check.scss +0 -152
  170. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-control.scss +0 -219
  171. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-range.scss +0 -91
  172. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-select.scss +0 -72
  173. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-text.scss +0 -11
  174. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_input-group.scss +0 -121
  175. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_labels.scss +0 -36
  176. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_validation.scss +0 -12
  177. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_clearfix.scss +0 -3
  178. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_colored-links.scss +0 -12
  179. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_position.scss +0 -30
  180. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_ratio.scss +0 -26
  181. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_stacks.scss +0 -15
  182. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_stretched-link.scss +0 -15
  183. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_text-truncation.scss +0 -7
  184. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_visually-hidden.scss +0 -8
  185. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_vr.scss +0 -8
  186. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_alert.scss +0 -11
  187. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_backdrop.scss +0 -14
  188. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_border-radius.scss +0 -78
  189. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_box-shadow.scss +0 -18
  190. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_breakpoints.scss +0 -127
  191. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_buttons.scss +0 -133
  192. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_caret.scss +0 -64
  193. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_clearfix.scss +0 -9
  194. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_color-scheme.scss +0 -7
  195. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_container.scss +0 -9
  196. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_deprecate.scss +0 -10
  197. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_forms.scss +0 -144
  198. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_gradients.scss +0 -47
  199. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_grid.scss +0 -151
  200. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_image.scss +0 -16
  201. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_list-group.scss +0 -24
  202. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_lists.scss +0 -7
  203. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_pagination.scss +0 -31
  204. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_reset-text.scss +0 -17
  205. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_resize.scss +0 -6
  206. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_table-variants.scss +0 -21
  207. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_text-truncate.scss +0 -8
  208. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_transition.scss +0 -26
  209. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_utilities.scss +0 -89
  210. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_visually-hidden.scss +0 -29
  211. data/vendor/assets/stylesheets/rails_admin/bootstrap/utilities/_api.scss +0 -47
  212. data/vendor/assets/stylesheets/rails_admin/bootstrap/vendor/_rfs.scss +0 -354
  213. data/vendor/assets/stylesheets/rails_admin/flatpickr.css +0 -791
  214. 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
- ancestors = model.ancestors.collect(&:to_s)
51
- if ancestors.include?('ActiveRecord::Base') && !model.abstract_class? && model.table_exists?
52
- initialize_active_record
53
- elsif ancestors.include?('Mongoid::Document')
54
- initialize_mongoid
55
- end
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
- def quoted_table_name
64
- table_name
65
- end
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
- def quote_column_name(name)
68
- name
69
- end
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
- extend Adapters::ActiveRecord
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
- extend Adapters::Mongoid
128
- end
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
@@ -105,7 +105,7 @@ module RailsAdmin
105
105
  end
106
106
 
107
107
  def inverse_of
108
- options[:inverse_of].try :to_sym
108
+ options[:inverse_of].try(:to_sym) || as
109
109
  end
110
110
 
111
111
  def read_only?
@@ -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