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
@@ -60,20 +60,39 @@ module RailsAdmin
60
60
  !virtual? || children_fields.first || false
61
61
  end
62
62
 
63
- def sort_column
64
- if sortable == true
65
- "#{abstract_model.quoted_table_name}.#{abstract_model.quote_column_name(name)}"
66
- elsif (sortable.is_a?(String) || sortable.is_a?(Symbol)) && sortable.to_s.include?('.') # just provide sortable, don't do anything smart
67
- sortable
68
- elsif sortable.is_a?(Hash) # just join sortable hash, don't do anything smart
63
+ # What to sort by, as a Criteria::Path wherever the configuration can be
64
+ # read as one. The adapter turns it into whatever its store needs.
65
+ #
66
+ # The table-qualified forms cannot be: their prefix is a table name, not
67
+ # an association name, so they are handed on untouched for the store to
68
+ # interpret.
69
+ def sort_order
70
+ case sortable
71
+ when true
72
+ RailsAdmin::Criteria::Path[name]
73
+ when Hash # <table> => <column>, passed through
74
+ # The key is interpolated as given, so it has to be a table name.
75
+ # searchable resolves a model class here, sortable never has; the
76
+ # asymmetry dates from 845f85ec, which introduced both.
69
77
  "#{sortable.keys.first}.#{sortable.values.first}"
70
- elsif association? # use column on target table
71
- "#{associated_model_config.abstract_model.quoted_table_name}.#{abstract_model.quote_column_name(sortable)}"
72
- else # use described column in the field conf.
73
- "#{abstract_model.quoted_table_name}.#{abstract_model.quote_column_name(sortable)}"
78
+ when String, Symbol
79
+ if sortable.to_s.include?('.') # "table.column", passed through
80
+ sortable
81
+ elsif association? # a column on the associated model
82
+ RailsAdmin::Criteria::Path[name, sortable]
83
+ else
84
+ RailsAdmin::Criteria::Path[sortable]
85
+ end
86
+ else
87
+ sortable
74
88
  end
75
89
  end
76
90
 
91
+ # Deprecated: use #sort_order, which does not commit to a store's syntax.
92
+ def sort_column
93
+ abstract_model.sort_expression(sort_order)
94
+ end
95
+
77
96
  register_instance_option :searchable do
78
97
  !virtual? || children_fields.first || false
79
98
  end
@@ -113,36 +132,39 @@ module RailsAdmin
113
132
  false
114
133
  end
115
134
 
116
- # list of columns I should search for that field [{ column: 'table_name.column', type: field.type }, {..}]
135
+ # What to search for this field: [{column: <target>, type: <field type>}, ..]
136
+ #
137
+ # A target is a Criteria::Path wherever the configuration can be read as
138
+ # one, so that the adapter decides how to reach the attribute. The
139
+ # table-qualified forms cannot be read that way -- their prefix names a
140
+ # table, not an association -- so they stay strings and are handed to the
141
+ # store as they are. Overriding this option with strings keeps working.
117
142
  register_instance_option :searchable_columns do
118
143
  @searchable_columns ||=
119
144
  case searchable
120
145
  when true
121
- [{column: "#{abstract_model.table_name}.#{name}", type: type}]
146
+ [{column: RailsAdmin::Criteria::Path[name], type: type}]
122
147
  when false
123
148
  []
124
149
  when :all # valid only for associations
125
- table_name = associated_model_config.abstract_model.table_name
126
- associated_model_config.list.fields.collect { |f| {column: "#{table_name}.#{f.name}", type: f.type} }
150
+ associated_model_config.list.fields.collect do |f|
151
+ {column: RailsAdmin::Criteria::Path[name, f.name], type: f.type}
152
+ end
127
153
  else
128
154
  [searchable].flatten.collect do |f|
129
- if f.is_a?(String) && f.include?('.') # table_name.column
130
- table_name, column = f.split '.'
131
- type = nil
132
- elsif f.is_a?(Hash) # <Model|table_name> => <attribute|column>
155
+ if f.is_a?(String) && f.include?('.') # table_name.column, passed through
156
+ {column: f, type: :string}
157
+ elsif f.is_a?(Hash) # <Model|table_name> => <attribute|column>, passed through
133
158
  am = AbstractModel.new(f.keys.first) if f.keys.first.is_a?(Class)
134
159
  table_name = am&.table_name || f.keys.first
135
- column = f.values.first
136
160
  property = am&.properties&.detect { |p| p.name == f.values.first.to_sym }
137
- type = property&.type
138
- else # <attribute|column>
161
+ {column: "#{table_name}.#{f.values.first}", type: (property&.type || :string)}
162
+ else # <attribute>
139
163
  am = (association? ? associated_model_config.abstract_model : abstract_model)
140
- table_name = am.table_name
141
- column = f
142
164
  property = am.properties.detect { |p| p.name == f.to_sym }
143
- type = property&.type
165
+ path = association? ? RailsAdmin::Criteria::Path[name, f] : RailsAdmin::Criteria::Path[f]
166
+ {column: path, type: (property&.type || :string)}
144
167
  end
145
- {column: "#{table_name}.#{column}", type: (type || :string)}
146
168
  end
147
169
  end
148
170
  end
@@ -180,7 +202,7 @@ module RailsAdmin
180
202
  #
181
203
  # @see RailsAdmin::AbstractModel.properties
182
204
  register_instance_option :label do
183
- (@label ||= {})[::I18n.locale] ||= abstract_model.model.human_attribute_name name
205
+ (@label ||= {})[::I18n.locale] ||= abstract_model.human_attribute_name name
184
206
  end
185
207
 
186
208
  register_instance_option :hint do
@@ -196,8 +218,11 @@ module RailsAdmin
196
218
 
197
219
  # Accessor for field's length restrictions per validations
198
220
  #
221
+ # Bounds given as a Proc (e.g. Devise's password length validation) are
222
+ # dropped, as they can't be compared against the column limit and are
223
+ # only meant to be resolved at validation time.
199
224
  register_instance_option :valid_length do
200
- @valid_length ||= abstract_model.model.validators_on(name).detect { |v| v.kind == :length }.try(&:options) || {}
225
+ @valid_length ||= abstract_model.attribute_length_options(name).reject { |_, bound| bound.is_a?(Proc) }
201
226
  end
202
227
 
203
228
  register_instance_option :partial do
@@ -215,13 +240,8 @@ module RailsAdmin
215
240
  :nil
216
241
  end
217
242
 
218
- (@required ||= {})[context] ||= !!([name] + children_fields).uniq.detect do |column_name|
219
- abstract_model.model.validators_on(column_name).detect do |v|
220
- !(v.options[:allow_nil] || v.options[:allow_blank]) &&
221
- %i[presence numericality attachment_presence].include?(v.kind) &&
222
- (v.options[:on] == context || v.options[:on].blank?) &&
223
- (v.options[:if].blank? && v.options[:unless].blank?)
224
- end
243
+ (@required ||= {})[context] ||= ([name] + children_fields).uniq.any? do |column_name|
244
+ abstract_model.attribute_required?(column_name, context)
225
245
  end
226
246
  end
227
247
 
@@ -264,8 +284,6 @@ module RailsAdmin
264
284
  false
265
285
  end
266
286
 
267
- register_deprecated_instance_option :eager_load?, :eager_load
268
-
269
287
  def eager_load_values
270
288
  case eager_load
271
289
  when true
@@ -329,7 +347,7 @@ module RailsAdmin
329
347
 
330
348
  # Reader for field's value
331
349
  def value
332
- bindings[:object].safe_send(name)
350
+ abstract_model.read(bindings[:object], name)
333
351
  rescue NoMethodError => e
334
352
  raise e.exception <<~ERROR
335
353
  #{e.message}
@@ -23,7 +23,7 @@ module RailsAdmin
23
23
  i = 0
24
24
  super.sort_by { |a| [selected.index(a[1]) || selected.size, i += 1] }
25
25
  else
26
- value.map { |o| [o.send(associated_object_label_method), format_key(o.send(associated_primary_key))] }
26
+ value.map { |o| [associated_model_config.object_label(o), format_key(o.send(associated_primary_key))] }
27
27
  end
28
28
  end
29
29
 
@@ -40,7 +40,7 @@ module RailsAdmin
40
40
  end
41
41
 
42
42
  def parse_input(params)
43
- return unless associated_model_config.abstract_model.primary_key.is_a?(Array)
43
+ return unless associated_model_config.abstract_model.primary_keys.many?
44
44
 
45
45
  if nested_form
46
46
  params[method_name].each_value do |value|
@@ -4,27 +4,42 @@ require 'rails_admin/config/fields'
4
4
  require 'rails_admin/config/fields/types'
5
5
  require 'rails_admin/config/fields/types/file_upload'
6
6
 
7
+ RailsAdmin::AbstractModel.register_classifier do |model, property|
8
+ next unless defined?(::CarrierWave)
9
+ next unless model.is_a?(CarrierWave::Mount)
10
+
11
+ attachment_name = property.name.to_s.chomp('_file_name').to_sym
12
+ next unless model.uploaders.include?(attachment_name)
13
+
14
+ RailsAdmin::AbstractModel::Role.new(
15
+ kind: :carrierwave,
16
+ name: attachment_name,
17
+ children: [
18
+ model.uploader_options[attachment_name][:mount_on] || attachment_name,
19
+ :"#{attachment_name}_content_type",
20
+ :"#{attachment_name}_file_size",
21
+ ],
22
+ )
23
+ end
24
+
7
25
  RailsAdmin::Config::Fields.register_factory do |parent, properties, fields|
8
- model = parent.abstract_model.model
9
- if defined?(::CarrierWave) && model.is_a?(CarrierWave::Mount) && model.uploaders.include?(attachment_name = properties.name.to_s.chomp('_file_name').to_sym)
10
- columns = [model.uploader_options[attachment_name][:mount_on] || attachment_name, :"#{attachment_name}_content_type", :"#{attachment_name}_file_size"]
11
- field = RailsAdmin::Config::Fields::Types.load(
12
- %i[serialized json].include?(properties.type) ? :multiple_carrierwave : :carrierwave,
13
- ).new(parent, attachment_name, properties)
14
- fields << field
15
- children_fields = []
16
- columns.each do |children_column_name|
17
- child_properties = parent.abstract_model.properties.detect { |p| p.name.to_s == children_column_name.to_s }
18
- next unless child_properties
26
+ role = properties.try(:role)
27
+ next false unless role&.kind == :carrierwave
28
+
29
+ field = RailsAdmin::Config::Fields::Types.load(
30
+ %i[serialized json].include?(properties.type) ? :multiple_carrierwave : :carrierwave,
31
+ ).new(parent, role.name, properties)
32
+ fields << field
33
+ children_fields = []
34
+ role.children.each do |children_column_name|
35
+ child_properties = parent.abstract_model.properties.detect { |p| p.name.to_s == children_column_name.to_s }
36
+ next unless child_properties
19
37
 
20
- children_field = fields.detect { |f| f.name == children_column_name } || RailsAdmin::Config::Fields.default_factory.call(parent, child_properties, fields)
21
- children_field.hide unless field == children_field
22
- children_field.filterable(false) unless field == children_field
23
- children_fields << children_field.name
24
- end
25
- field.children_fields(children_fields)
26
- true
27
- else
28
- false
38
+ children_field = fields.detect { |f| f.name == children_column_name } || RailsAdmin::Config::Fields.default_factory.call(parent, child_properties, fields)
39
+ children_field.hide unless field == children_field
40
+ children_field.filterable(false) unless field == children_field
41
+ children_fields << children_field.name
29
42
  end
43
+ field.children_fields(children_fields)
44
+ true
30
45
  end
@@ -4,25 +4,37 @@ require 'rails_admin/config/fields'
4
4
  require 'rails_admin/config/fields/types'
5
5
  require 'rails_admin/config/fields/types/file_upload'
6
6
 
7
+ RailsAdmin::AbstractModel.register_classifier do |model, property|
8
+ next unless defined?(::Dragonfly)
9
+ next unless (match = /^(.+)_uid$/.match(property.name.to_s))
10
+ next unless model.respond_to?(:dragonfly_attachment_classes)
11
+
12
+ attachment_name = match[1].to_sym
13
+ next unless model.dragonfly_attachment_classes.collect(&:attribute).include?(attachment_name)
14
+
15
+ RailsAdmin::AbstractModel::Role.new(
16
+ kind: :dragonfly,
17
+ name: attachment_name,
18
+ children: %i[name uid].map { |ext| :"#{attachment_name}_#{ext}" },
19
+ )
20
+ end
21
+
7
22
  RailsAdmin::Config::Fields.register_factory do |parent, properties, fields|
8
- extensions = %i[name uid]
9
- if (properties.name.to_s =~ /^(.+)_uid$/) && defined?(::Dragonfly) && parent.abstract_model.model.respond_to?(:dragonfly_attachment_classes) && parent.abstract_model.model.dragonfly_attachment_classes.collect(&:attribute).include?(attachment_name = Regexp.last_match[1].to_sym)
10
- field = RailsAdmin::Config::Fields::Types.load(:dragonfly).new(parent, attachment_name, properties)
11
- children_fields = []
12
- extensions.each do |ext|
13
- children_column_name = :"#{attachment_name}_#{ext}"
14
- child_properties = parent.abstract_model.properties.detect { |p| p.name.to_s == children_column_name.to_s }
15
- next unless child_properties
23
+ role = properties.try(:role)
24
+ next false unless role&.kind == :dragonfly
25
+
26
+ field = RailsAdmin::Config::Fields::Types.load(:dragonfly).new(parent, role.name, properties)
27
+ children_fields = []
28
+ role.children.each do |children_column_name|
29
+ child_properties = parent.abstract_model.properties.detect { |p| p.name.to_s == children_column_name.to_s }
30
+ next unless child_properties
16
31
 
17
- children_field = fields.detect { |f| f.name == children_column_name } || RailsAdmin::Config::Fields.default_factory.call(parent, child_properties, fields)
18
- children_field.hide
19
- children_field.filterable(false)
20
- children_fields << children_field.name
21
- end
22
- field.children_fields(children_fields)
23
- fields << field
24
- true
25
- else
26
- false
32
+ children_field = fields.detect { |f| f.name == children_column_name } || RailsAdmin::Config::Fields.default_factory.call(parent, child_properties, fields)
33
+ children_field.hide
34
+ children_field.filterable(false)
35
+ children_fields << children_field.name
27
36
  end
37
+ field.children_fields(children_fields)
38
+ fields << field
39
+ true
28
40
  end
@@ -4,17 +4,23 @@ require 'rails_admin/config/fields'
4
4
  require 'rails_admin/config/fields/types/enum'
5
5
  require 'rails_admin/config/fields/types/active_record_enum'
6
6
 
7
- RailsAdmin::Config::Fields.register_factory do |parent, properties, fields|
8
- model = parent.abstract_model.model
9
- method_name = "#{properties.name}_enum"
7
+ RailsAdmin::AbstractModel.register_classifier do |model, property|
8
+ method_name = "#{property.name}_enum"
10
9
 
11
- # NOTICE: _method_name could be `to_enum` and this method defined in Object.
12
- if !Object.respond_to?(method_name) && \
13
- (model.respond_to?(method_name) || \
14
- model.method_defined?(method_name))
10
+ # NOTICE: method_name could be `to_enum` and this method defined in Object.
11
+ if !Object.respond_to?(method_name) && (model.respond_to?(method_name) || model.method_defined?(method_name))
12
+ RailsAdmin::AbstractModel::Role.new(kind: :enum, name: property.name)
13
+ elsif model.respond_to?(:defined_enums) && model.defined_enums[property.name.to_s]
14
+ RailsAdmin::AbstractModel::Role.new(kind: :active_record_enum, name: property.name)
15
+ end
16
+ end
17
+
18
+ RailsAdmin::Config::Fields.register_factory do |parent, properties, fields|
19
+ case properties.try(:role)&.kind
20
+ when :enum
15
21
  fields << RailsAdmin::Config::Fields::Types::Enum.new(parent, properties.name, properties)
16
22
  true
17
- elsif model.respond_to?(:defined_enums) && model.defined_enums[properties.name.to_s]
23
+ when :active_record_enum
18
24
  fields << RailsAdmin::Config::Fields::Types::ActiveRecordEnum.new(parent, properties.name, properties)
19
25
  true
20
26
  else
@@ -4,26 +4,36 @@ require 'rails_admin/config/fields'
4
4
  require 'rails_admin/config/fields/types'
5
5
  require 'rails_admin/config/fields/types/file_upload'
6
6
 
7
+ RailsAdmin::AbstractModel.register_classifier do |model, property|
8
+ next unless defined?(::Paperclip)
9
+ next unless (match = /^(.+)_file_name$/.match(property.name.to_s))
10
+
11
+ attachment_name = match[1].to_sym
12
+ next unless model.try(:attachment_definitions)&.key?(attachment_name)
13
+
14
+ RailsAdmin::AbstractModel::Role.new(
15
+ kind: :paperclip,
16
+ name: attachment_name,
17
+ children: %i[file_name content_type file_size updated_at fingerprint].map { |ext| :"#{attachment_name}_#{ext}" },
18
+ )
19
+ end
20
+
7
21
  RailsAdmin::Config::Fields.register_factory do |parent, properties, fields|
8
- extensions = %i[file_name content_type file_size updated_at fingerprint]
9
- model = parent.abstract_model.model
10
- if (properties.name.to_s =~ /^(.+)_file_name$/) && defined?(::Paperclip) && model.try(:attachment_definitions) && model.attachment_definitions.key?(attachment_name = Regexp.last_match[1].to_sym)
11
- field = RailsAdmin::Config::Fields::Types.load(:paperclip).new(parent, attachment_name, properties)
12
- children_fields = []
13
- extensions.each do |ext|
14
- children_column_name = :"#{attachment_name}_#{ext}"
15
- child_properties = parent.abstract_model.properties.detect { |p| p.name.to_s == children_column_name.to_s }
16
- next unless child_properties
22
+ role = properties.try(:role)
23
+ next false unless role&.kind == :paperclip
24
+
25
+ field = RailsAdmin::Config::Fields::Types.load(:paperclip).new(parent, role.name, properties)
26
+ children_fields = []
27
+ role.children.each do |children_column_name|
28
+ child_properties = parent.abstract_model.properties.detect { |p| p.name.to_s == children_column_name.to_s }
29
+ next unless child_properties
17
30
 
18
- children_field = fields.detect { |f| f.name == children_column_name } || RailsAdmin::Config::Fields.default_factory.call(parent, child_properties, fields)
19
- children_field.hide
20
- children_field.filterable(false)
21
- children_fields << children_field.name
22
- end
23
- field.children_fields(children_fields)
24
- fields << field
25
- true
26
- else
27
- false
31
+ children_field = fields.detect { |f| f.name == children_column_name } || RailsAdmin::Config::Fields.default_factory.call(parent, child_properties, fields)
32
+ children_field.hide
33
+ children_field.filterable(false)
34
+ children_fields << children_field.name
28
35
  end
36
+ field.children_fields(children_fields)
37
+ fields << field
38
+ true
29
39
  end
@@ -4,19 +4,30 @@ require 'rails_admin/config/fields'
4
4
  require 'rails_admin/config/fields/types'
5
5
  require 'rails_admin/config/fields/types/file_upload'
6
6
 
7
- RailsAdmin::Config::Fields.register_factory do |parent, properties, fields|
8
- next false unless defined?(::Shrine)
7
+ RailsAdmin::AbstractModel.register_classifier do |model, property|
8
+ next unless defined?(::Shrine)
9
+
10
+ attachment_names = model.ancestors.select { |ancestor| ancestor.is_a?(Shrine::Attachment) }.map { |a| a.instance_variable_get('@name') }
11
+ next if attachment_names.blank?
9
12
 
10
- attachment_names = parent.abstract_model.model.ancestors.select { |m| m.is_a?(Shrine::Attachment) }.map { |a| a.instance_variable_get('@name') }
11
- next false if attachment_names.blank?
13
+ attachment_name = attachment_names.detect { |name| name == property.name.to_s.chomp('_data').to_sym }
14
+ next unless attachment_name
12
15
 
13
- attachment_name = attachment_names.detect { |a| a == properties.name.to_s.chomp('_data').to_sym }
14
- next false unless attachment_name
16
+ RailsAdmin::AbstractModel::Role.new(
17
+ kind: :shrine,
18
+ name: attachment_name,
19
+ children: [:"#{attachment_name}_data"],
20
+ )
21
+ end
22
+
23
+ RailsAdmin::Config::Fields.register_factory do |parent, properties, fields|
24
+ role = properties.try(:role)
25
+ next false unless role&.kind == :shrine
15
26
 
16
- field = RailsAdmin::Config::Fields::Types.load(:shrine).new(parent, attachment_name, properties)
27
+ field = RailsAdmin::Config::Fields::Types.load(:shrine).new(parent, role.name, properties)
17
28
  fields << field
18
29
 
19
- data_field_name = :"#{attachment_name}_data"
30
+ data_field_name = role.children.first
20
31
  child_properties = parent.abstract_model.properties.detect { |p| p.name == data_field_name }
21
32
  next true unless child_properties
22
33
 
@@ -11,7 +11,7 @@ module RailsAdmin
11
11
  end
12
12
 
13
13
  register_instance_option :formatted_value do
14
- (o = value) && o.send(associated_model_config.object_label_method)
14
+ (o = value) && associated_model_config.object_label(o)
15
15
  end
16
16
 
17
17
  register_instance_option :partial do
@@ -38,6 +38,11 @@ module RailsAdmin
38
38
  return unless nested_form && params[method_name].try(:[], :id).present?
39
39
 
40
40
  ids = associated_model_config.abstract_model.parse_id(params[method_name][:id])
41
+ # The composite_primary_keys gem wants its own representation of the key
42
+ # here; ActiveRecord has had composite keys of its own since 7.1 and
43
+ # takes the array as it is. This is the only place the gem is still
44
+ # supported, and it is only reachable below 7.1 -- so it goes when
45
+ # support for Rails 7.0 does.
41
46
  ids = ids.to_composite_keys.to_s if ids.respond_to?(:to_composite_keys)
42
47
  params[method_name][:id] = ids
43
48
  end
@@ -14,7 +14,7 @@ module RailsAdmin
14
14
  end
15
15
 
16
16
  register_instance_option :enum do
17
- abstract_model.model.defined_enums[name.to_s]
17
+ abstract_model.attribute_enum_values(name)
18
18
  end
19
19
 
20
20
  register_instance_option :pretty_value do
@@ -32,7 +32,7 @@ module RailsAdmin
32
32
  def parse_value(value)
33
33
  return unless value.present?
34
34
 
35
- abstract_model.model.attribute_types[name.to_s].serialize(value)
35
+ abstract_model.serialize_attribute(name, value)
36
36
  end
37
37
 
38
38
  def parse_input(params)
@@ -49,11 +49,7 @@ module RailsAdmin
49
49
  private
50
50
 
51
51
  def parse_input_value(value)
52
- abstract_model.model.attribute_types[name.to_s].deserialize(value)
53
- end
54
-
55
- def type_cast_value(value)
56
- abstract_model.model.column_types[name.to_s].type_cast_from_user(value)
52
+ abstract_model.deserialize_attribute(name, value)
57
53
  end
58
54
  end
59
55
  end
@@ -10,7 +10,10 @@ module RailsAdmin
10
10
  RailsAdmin::Config::Fields::Types.register(self)
11
11
 
12
12
  register_instance_option :sortable do
13
- @sortable ||= abstract_model.adapter_supports_joins? && associated_model_config.abstract_model.properties.collect(&:name).include?(associated_model_config.object_label_method) ? associated_model_config.object_label_method : {abstract_model.table_name => method_name}
13
+ # Sort by whatever labels the associated record, when that is an
14
+ # attribute the store knows about. Reaching it may take a join or may
15
+ # not be possible at all, but that is the adapter's problem now.
16
+ @sortable ||= associated_model_config.abstract_model.properties.collect(&:name).include?(associated_model_config.object_label_method) ? associated_model_config.object_label_method : {abstract_model.table_name => method_name}
14
17
  end
15
18
 
16
19
  register_instance_option :searchable do
@@ -27,9 +30,9 @@ module RailsAdmin
27
30
 
28
31
  def selected_id
29
32
  if association.foreign_key.is_a?(Array)
30
- format_key(association.foreign_key.map { |attribute| bindings[:object].safe_send(attribute) })
33
+ format_key(association.foreign_key.map { |attribute| abstract_model.read(bindings[:object], attribute) })
31
34
  else
32
- bindings[:object].safe_send(association.key_accessor)
35
+ abstract_model.read(bindings[:object], association.key_accessor)
33
36
  end
34
37
  end
35
38
 
@@ -37,7 +40,8 @@ module RailsAdmin
37
40
  return super if nested_form
38
41
  return unless params[method_name].present? && association.foreign_key.is_a?(Array)
39
42
 
40
- association.foreign_key.zip(RailsAdmin.config.composite_keys_serializer.deserialize(params.delete(method_name))).each do |key, value|
43
+ keys = associated_model_config.abstract_model.parse_id(params.delete(method_name))
44
+ association.foreign_key.zip(keys).each do |key, value|
41
45
  params[key] = value
42
46
  end
43
47
  end
@@ -11,7 +11,7 @@ module RailsAdmin
11
11
  RailsAdmin::Config::Fields::Types.register(self)
12
12
 
13
13
  register_instance_option :label do
14
- label = ((@label ||= {})[::I18n.locale] ||= abstract_model.model.human_attribute_name name)
14
+ label = ((@label ||= {})[::I18n.locale] ||= abstract_model.human_attribute_name name)
15
15
  label = 'Id' if label == ''
16
16
  label
17
17
  end
@@ -82,10 +82,6 @@ module RailsAdmin
82
82
  :form_datetime
83
83
  end
84
84
 
85
- register_deprecated_instance_option :momentjs_format do
86
- ActiveSupport::Deprecation.warn('The momentjs_format configuration option is deprecated, please use flatpickr_format with corresponding values here: https://flatpickr.js.org/formatting/')
87
- end
88
-
89
85
  def form_value
90
86
  value&.in_time_zone&.strftime('%FT%T') || form_default_value
91
87
  end
@@ -12,7 +12,7 @@ module RailsAdmin
12
12
  RailsAdmin::Config::Fields::Types.register(self)
13
13
 
14
14
  register_instance_option :image? do
15
- if abstract_model.model.new.respond_to?("#{name}_name")
15
+ if abstract_model.dummy_record.respond_to?("#{name}_name")
16
16
  mime_type = Mime::Type.lookup_by_extension(bindings[:object].send("#{name}_name").to_s.split('.').last)
17
17
  mime_type.to_s.match?(/^image/)
18
18
  else
@@ -21,14 +21,23 @@ module RailsAdmin
21
21
  end
22
22
 
23
23
  register_instance_option :enum_method do
24
- @enum_method ||= bindings[:object].class.respond_to?("#{name}_enum") || (bindings[:object] || abstract_model.model.new).respond_to?("#{name}_enum") ? "#{name}_enum" : name
24
+ # Fall back to the model when no object is bound, which is how the
25
+ # filters dropdown renders fields. Asking nil for its class used to
26
+ # look the method up on NilClass, hiding class-level enumerations.
27
+ @enum_method ||=
28
+ if (bindings[:object]&.class || abstract_model.model).respond_to?("#{name}_enum") ||
29
+ (bindings[:object] || abstract_model.dummy_record).respond_to?("#{name}_enum")
30
+ "#{name}_enum"
31
+ else
32
+ name
33
+ end
25
34
  end
26
35
 
27
36
  register_instance_option :enum do
28
37
  if abstract_model.model.respond_to?(enum_method)
29
38
  abstract_model.model.send(enum_method)
30
39
  else
31
- (bindings[:object] || abstract_model.model.new).send(enum_method)
40
+ (bindings[:object] || abstract_model.dummy_record).send(enum_method)
32
41
  end
33
42
  end
34
43
 
@@ -29,8 +29,6 @@ module RailsAdmin
29
29
  nil
30
30
  end
31
31
 
32
- register_deprecated_instance_option :delete_key, :delete_value
33
-
34
32
  register_instance_option :pretty_value do
35
33
  if value.presence
36
34
  v = bindings[:view]
@@ -22,7 +22,7 @@ module RailsAdmin
22
22
  end
23
23
 
24
24
  register_instance_option :formatted_value do
25
- (o = value) && o.send(RailsAdmin.config(o).object_label_method)
25
+ (o = value) && RailsAdmin.config(o).object_label(o)
26
26
  end
27
27
 
28
28
  register_instance_option :sortable do
@@ -69,13 +69,13 @@ module RailsAdmin
69
69
 
70
70
  def type_collection
71
71
  associated_model_config.collect do |config|
72
- [config.label, config.abstract_model.model.name]
72
+ [config.label, config.abstract_model.model_name]
73
73
  end
74
74
  end
75
75
 
76
76
  def type_urls
77
77
  types = associated_model_config.collect do |config|
78
- [config.abstract_model.model.name, config.abstract_model.to_param]
78
+ [config.abstract_model.model_name, config.abstract_model.to_param]
79
79
  end
80
80
  ::Hash[*types.collect { |v| [v[0], bindings[:view].index_path(v[1])] }.flatten]
81
81
  end
@@ -107,7 +107,7 @@ module RailsAdmin
107
107
 
108
108
  def parse_input(params)
109
109
  if (type_value = params[association.foreign_type.to_sym]).present?
110
- config = associated_model_config.find { |c| type_value == c.abstract_model.model.name }
110
+ config = associated_model_config.find { |c| type_value == c.abstract_model.model_name }
111
111
  params[association.foreign_type.to_sym] = config.abstract_model.base_class.name if config
112
112
  end
113
113
  end
@@ -10,7 +10,7 @@ module RailsAdmin
10
10
  RailsAdmin::Config::Fields::Types.register(self)
11
11
 
12
12
  def parse_value(value)
13
- abstract_model.model.type_for_attribute(name.to_s).serialize(super)&.change(year: 2000, month: 1, day: 1)
13
+ abstract_model.serialize_attribute(name, super)&.change(year: 2000, month: 1, day: 1)
14
14
  end
15
15
 
16
16
  register_instance_option :filter_operators do