rails_admin 3.3.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 (213) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -3
  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 -14
  8. data/app/helpers/rails_admin/application_helper.rb +3 -15
  9. data/app/helpers/rails_admin/form_builder.rb +6 -6
  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/property.rb +7 -0
  28. data/lib/rails_admin/adapters/active_record/reflection.rb +87 -0
  29. data/lib/rails_admin/adapters/active_record/repository.rb +216 -0
  30. data/lib/rails_admin/adapters/active_record/statement_builder.rb +148 -0
  31. data/lib/rails_admin/adapters/active_record/where_builder.rb +40 -0
  32. data/lib/rails_admin/adapters/active_record.rb +7 -360
  33. data/lib/rails_admin/adapters/mongoid/extension.rb +16 -6
  34. data/lib/rails_admin/adapters/mongoid/property.rb +7 -0
  35. data/lib/rails_admin/adapters/mongoid/reflection.rb +58 -0
  36. data/lib/rails_admin/adapters/mongoid/repository.rb +237 -0
  37. data/lib/rails_admin/adapters/mongoid/statement_builder.rb +93 -0
  38. data/lib/rails_admin/adapters/mongoid.rb +7 -305
  39. data/lib/rails_admin/adapters/reflection.rb +105 -0
  40. data/lib/rails_admin/adapters/repository.rb +169 -0
  41. data/lib/rails_admin/adapters/statement_builder.rb +109 -0
  42. data/lib/rails_admin/adapters.rb +94 -0
  43. data/lib/rails_admin/config/actions/base.rb +8 -0
  44. data/lib/rails_admin/config/actions/bulk_delete.rb +4 -4
  45. data/lib/rails_admin/config/actions/dashboard.rb +1 -1
  46. data/lib/rails_admin/config/actions/edit.rb +3 -2
  47. data/lib/rails_admin/config/actions/export.rb +1 -2
  48. data/lib/rails_admin/config/actions/index.rb +3 -3
  49. data/lib/rails_admin/config/actions/new.rb +3 -2
  50. data/lib/rails_admin/config/actions/show.rb +1 -1
  51. data/lib/rails_admin/config/configurable.rb +3 -2
  52. data/lib/rails_admin/config/fields/association.rb +9 -8
  53. data/lib/rails_admin/config/fields/base.rb +55 -37
  54. data/lib/rails_admin/config/fields/collection_association.rb +2 -2
  55. data/lib/rails_admin/config/fields/factories/carrierwave.rb +35 -20
  56. data/lib/rails_admin/config/fields/factories/dragonfly.rb +30 -18
  57. data/lib/rails_admin/config/fields/factories/enum.rb +14 -8
  58. data/lib/rails_admin/config/fields/factories/paperclip.rb +29 -19
  59. data/lib/rails_admin/config/fields/factories/shrine.rb +19 -8
  60. data/lib/rails_admin/config/fields/singular_association.rb +6 -1
  61. data/lib/rails_admin/config/fields/types/active_record_enum.rb +3 -7
  62. data/lib/rails_admin/config/fields/types/belongs_to_association.rb +8 -4
  63. data/lib/rails_admin/config/fields/types/bson_object_id.rb +1 -1
  64. data/lib/rails_admin/config/fields/types/datetime.rb +0 -4
  65. data/lib/rails_admin/config/fields/types/dragonfly.rb +1 -1
  66. data/lib/rails_admin/config/fields/types/enum.rb +11 -2
  67. data/lib/rails_admin/config/fields/types/multiple_file_upload.rb +0 -2
  68. data/lib/rails_admin/config/fields/types/polymorphic_association.rb +4 -4
  69. data/lib/rails_admin/config/fields/types/time.rb +1 -1
  70. data/lib/rails_admin/config/model.rb +27 -5
  71. data/lib/rails_admin/config/sections/list.rb +0 -8
  72. data/lib/rails_admin/config.rb +7 -21
  73. data/lib/rails_admin/criteria/path.rb +66 -0
  74. data/lib/rails_admin/criteria/period.rb +67 -0
  75. data/lib/rails_admin/engine.rb +11 -15
  76. data/lib/rails_admin/extensions/pundit/authorization_adapter.rb +2 -3
  77. data/lib/rails_admin/extensions/url_for_extension.rb +8 -0
  78. data/lib/rails_admin/support/asset_source.rb +42 -0
  79. data/lib/rails_admin/support/composite_keys_serializer.rb +28 -2
  80. data/lib/rails_admin/support/csv_converter.rb +3 -10
  81. data/lib/rails_admin/version.rb +8 -7
  82. data/lib/rails_admin.rb +7 -1
  83. data/lib/tasks/assets.rake +23 -0
  84. data/package.json +12 -7
  85. data/src/rails_admin/base.js +18 -15
  86. data/src/rails_admin/filter-box.js +7 -4
  87. data/src/rails_admin/filtering-multiselect.js +1 -1
  88. data/src/rails_admin/filtering-select.js +8 -8
  89. data/src/rails_admin/flatpickr.js +14 -0
  90. data/src/rails_admin/remote-form.js +74 -48
  91. data/src/rails_admin/styles/base/theming.scss +48 -25
  92. data/src/rails_admin/styles/base.scss +25 -1
  93. data/src/rails_admin/styles/filtering-select.scss +23 -62
  94. data/src/rails_admin/ui.js +1 -1
  95. data/src/rails_admin/vendor/jquery_nested_form.js +4 -4
  96. data/src/rails_admin/widgets.js +44 -11
  97. metadata +30 -124
  98. data/app/assets/javascripts/rails_admin/application.js.erb +0 -30
  99. data/app/assets/javascripts/rails_admin/custom/ui.js +0 -1
  100. data/app/assets/stylesheets/rails_admin/application.scss.erb +0 -35
  101. data/app/assets/stylesheets/rails_admin/custom/mixins.scss +0 -11
  102. data/app/assets/stylesheets/rails_admin/custom/theming.scss +0 -13
  103. data/app/assets/stylesheets/rails_admin/custom/variables.scss +0 -15
  104. data/lib/generators/rails_admin/importmap_formatter.rb +0 -29
  105. data/lib/generators/rails_admin/templates/rails_admin.vite.js +0 -2
  106. data/lib/generators/rails_admin/templates/rails_admin.webpacker.js +0 -2
  107. data/lib/rails_admin/adapters/active_record/object_extension.rb +0 -13
  108. data/lib/rails_admin/adapters/mongoid/object_extension.rb +0 -29
  109. data/lib/rails_admin/support/es_module_processor.rb +0 -23
  110. data/lib/rails_admin/support/serialization_schema.rb +0 -72
  111. data/vendor/assets/fonts/rails_admin/fa-solid-900.ttf +0 -0
  112. data/vendor/assets/javascripts/rails_admin/bootstrap.js +0 -5046
  113. data/vendor/assets/javascripts/rails_admin/flatpickr-with-locales.js +0 -11246
  114. data/vendor/assets/javascripts/rails_admin/jquery-ui/data.js +0 -41
  115. data/vendor/assets/javascripts/rails_admin/jquery-ui/effect.js +0 -1637
  116. data/vendor/assets/javascripts/rails_admin/jquery-ui/ie.js +0 -17
  117. data/vendor/assets/javascripts/rails_admin/jquery-ui/keycode.js +0 -47
  118. data/vendor/assets/javascripts/rails_admin/jquery-ui/position.js +0 -500
  119. data/vendor/assets/javascripts/rails_admin/jquery-ui/safe-active-element.js +0 -42
  120. data/vendor/assets/javascripts/rails_admin/jquery-ui/scroll-parent.js +0 -47
  121. data/vendor/assets/javascripts/rails_admin/jquery-ui/unique-id.js +0 -51
  122. data/vendor/assets/javascripts/rails_admin/jquery-ui/version.js +0 -17
  123. data/vendor/assets/javascripts/rails_admin/jquery-ui/widget.js +0 -735
  124. data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/autocomplete.js +0 -689
  125. data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/menu.js +0 -680
  126. data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/mouse.js +0 -230
  127. data/vendor/assets/javascripts/rails_admin/jquery-ui/widgets/sortable.js +0 -1561
  128. data/vendor/assets/javascripts/rails_admin/jquery3.js +0 -10881
  129. data/vendor/assets/javascripts/rails_admin/popper.js +0 -1974
  130. data/vendor/assets/stylesheets/rails_admin/bootstrap/_accordion.scss +0 -118
  131. data/vendor/assets/stylesheets/rails_admin/bootstrap/_alert.scss +0 -57
  132. data/vendor/assets/stylesheets/rails_admin/bootstrap/_badge.scss +0 -29
  133. data/vendor/assets/stylesheets/rails_admin/bootstrap/_breadcrumb.scss +0 -28
  134. data/vendor/assets/stylesheets/rails_admin/bootstrap/_button-group.scss +0 -139
  135. data/vendor/assets/stylesheets/rails_admin/bootstrap/_buttons.scss +0 -111
  136. data/vendor/assets/stylesheets/rails_admin/bootstrap/_card.scss +0 -216
  137. data/vendor/assets/stylesheets/rails_admin/bootstrap/_carousel.scss +0 -229
  138. data/vendor/assets/stylesheets/rails_admin/bootstrap/_close.scss +0 -40
  139. data/vendor/assets/stylesheets/rails_admin/bootstrap/_containers.scss +0 -41
  140. data/vendor/assets/stylesheets/rails_admin/bootstrap/_dropdown.scss +0 -240
  141. data/vendor/assets/stylesheets/rails_admin/bootstrap/_forms.scss +0 -9
  142. data/vendor/assets/stylesheets/rails_admin/bootstrap/_functions.scss +0 -302
  143. data/vendor/assets/stylesheets/rails_admin/bootstrap/_grid.scss +0 -33
  144. data/vendor/assets/stylesheets/rails_admin/bootstrap/_helpers.scss +0 -9
  145. data/vendor/assets/stylesheets/rails_admin/bootstrap/_images.scss +0 -42
  146. data/vendor/assets/stylesheets/rails_admin/bootstrap/_list-group.scss +0 -174
  147. data/vendor/assets/stylesheets/rails_admin/bootstrap/_mixins.scss +0 -43
  148. data/vendor/assets/stylesheets/rails_admin/bootstrap/_modal.scss +0 -209
  149. data/vendor/assets/stylesheets/rails_admin/bootstrap/_nav.scss +0 -139
  150. data/vendor/assets/stylesheets/rails_admin/bootstrap/_navbar.scss +0 -335
  151. data/vendor/assets/stylesheets/rails_admin/bootstrap/_offcanvas.scss +0 -83
  152. data/vendor/assets/stylesheets/rails_admin/bootstrap/_pagination.scss +0 -64
  153. data/vendor/assets/stylesheets/rails_admin/bootstrap/_placeholders.scss +0 -51
  154. data/vendor/assets/stylesheets/rails_admin/bootstrap/_popover.scss +0 -158
  155. data/vendor/assets/stylesheets/rails_admin/bootstrap/_progress.scss +0 -48
  156. data/vendor/assets/stylesheets/rails_admin/bootstrap/_reboot.scss +0 -625
  157. data/vendor/assets/stylesheets/rails_admin/bootstrap/_root.scss +0 -54
  158. data/vendor/assets/stylesheets/rails_admin/bootstrap/_spinners.scss +0 -69
  159. data/vendor/assets/stylesheets/rails_admin/bootstrap/_tables.scss +0 -155
  160. data/vendor/assets/stylesheets/rails_admin/bootstrap/_toasts.scss +0 -51
  161. data/vendor/assets/stylesheets/rails_admin/bootstrap/_tooltip.scss +0 -115
  162. data/vendor/assets/stylesheets/rails_admin/bootstrap/_transitions.scss +0 -27
  163. data/vendor/assets/stylesheets/rails_admin/bootstrap/_type.scss +0 -104
  164. data/vendor/assets/stylesheets/rails_admin/bootstrap/_utilities.scss +0 -630
  165. data/vendor/assets/stylesheets/rails_admin/bootstrap/_variables.scss +0 -1641
  166. data/vendor/assets/stylesheets/rails_admin/bootstrap/bootstrap.scss +0 -53
  167. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_floating-labels.scss +0 -63
  168. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-check.scss +0 -152
  169. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-control.scss +0 -219
  170. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-range.scss +0 -91
  171. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-select.scss +0 -72
  172. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_form-text.scss +0 -11
  173. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_input-group.scss +0 -121
  174. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_labels.scss +0 -36
  175. data/vendor/assets/stylesheets/rails_admin/bootstrap/forms/_validation.scss +0 -12
  176. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_clearfix.scss +0 -3
  177. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_colored-links.scss +0 -12
  178. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_position.scss +0 -30
  179. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_ratio.scss +0 -26
  180. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_stacks.scss +0 -15
  181. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_stretched-link.scss +0 -15
  182. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_text-truncation.scss +0 -7
  183. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_visually-hidden.scss +0 -8
  184. data/vendor/assets/stylesheets/rails_admin/bootstrap/helpers/_vr.scss +0 -8
  185. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_alert.scss +0 -11
  186. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_backdrop.scss +0 -14
  187. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_border-radius.scss +0 -78
  188. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_box-shadow.scss +0 -18
  189. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_breakpoints.scss +0 -127
  190. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_buttons.scss +0 -133
  191. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_caret.scss +0 -64
  192. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_clearfix.scss +0 -9
  193. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_color-scheme.scss +0 -7
  194. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_container.scss +0 -9
  195. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_deprecate.scss +0 -10
  196. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_forms.scss +0 -144
  197. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_gradients.scss +0 -47
  198. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_grid.scss +0 -151
  199. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_image.scss +0 -16
  200. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_list-group.scss +0 -24
  201. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_lists.scss +0 -7
  202. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_pagination.scss +0 -31
  203. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_reset-text.scss +0 -17
  204. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_resize.scss +0 -6
  205. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_table-variants.scss +0 -21
  206. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_text-truncate.scss +0 -8
  207. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_transition.scss +0 -26
  208. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_utilities.scss +0 -89
  209. data/vendor/assets/stylesheets/rails_admin/bootstrap/mixins/_visually-hidden.scss +0 -29
  210. data/vendor/assets/stylesheets/rails_admin/bootstrap/utilities/_api.scss +0 -47
  211. data/vendor/assets/stylesheets/rails_admin/bootstrap/vendor/_rfs.scss +0 -354
  212. data/vendor/assets/stylesheets/rails_admin/flatpickr.css +0 -791
  213. data/vendor/assets/stylesheets/rails_admin/font-awesome.scss +0 -6351
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_admin/adapters/repository'
4
+ require 'rails_admin/adapters/mongoid/bson'
5
+
6
+ module RailsAdmin
7
+ module Adapters
8
+ module Mongoid
9
+ class Repository < RailsAdmin::Adapters::Repository
10
+ def parse_object_id(value)
11
+ Bson.parse_object_id(value)
12
+ end
13
+
14
+ def new(params = {})
15
+ model.new(params)
16
+ end
17
+
18
+ def get(id, scope = scoped)
19
+ scope.find(id)
20
+ rescue StandardError => e
21
+ raise e if %w[
22
+ Mongoid::Errors::DocumentNotFound
23
+ Mongoid::Errors::InvalidFind
24
+ Moped::Errors::InvalidObjectId
25
+ BSON::InvalidObjectId
26
+ BSON::Error::InvalidObjectId
27
+ ].exclude?(e.class.to_s)
28
+ end
29
+
30
+ def scoped
31
+ model.scoped
32
+ end
33
+
34
+ # Mongoid's #attributes holds what the document actually carries, so an
35
+ # attribute that was never set is not in there and the method wins.
36
+ def read(record, name)
37
+ record.attributes.key?(name.to_s) ? record.read_attribute(name) : record.send(name)
38
+ end
39
+
40
+ def first(options = {}, scope = nil)
41
+ all(options, scope).first
42
+ end
43
+
44
+ def all(options = {}, scope = nil)
45
+ scope ||= scoped
46
+ scope = scope.includes(*options[:include]) if options[:include]
47
+ scope = scope.limit(options[:limit]) if options[:limit]
48
+ scope = scope.any_in(_id: options[:bulk_ids]) if options[:bulk_ids]
49
+ scope = query_scope(scope, options[:query]) if options[:query]
50
+ scope = filter_scope(scope, options[:filters]) if options[:filters]
51
+ scope = scope.send(Kaminari.config.page_method_name, options[:page]).per(options[:per]) if options[:page] && options[:per]
52
+ scope = sort_by(options, scope) if options[:sort]
53
+ scope
54
+ rescue NoMethodError => e
55
+ if /page/.match?(e.message)
56
+ e = e.exception <<~ERROR
57
+ #{e.message}
58
+ If you don't have kaminari-mongoid installed, add `gem 'kaminari-mongoid'` to your Gemfile.
59
+ ERROR
60
+ end
61
+ raise e
62
+ end
63
+
64
+ def count(options = {}, scope = nil)
65
+ all(options.merge(limit: false, page: false), scope).count
66
+ end
67
+
68
+ def destroy(objects)
69
+ Array.wrap(objects).each(&:destroy)
70
+ end
71
+
72
+ # A document that is not saved yet has no id, so children put on a
73
+ # has_many or has_one have nothing to point back at. Mongoid fills that
74
+ # in on save only where the association is autosave; otherwise the
75
+ # children are still unsaved once the parent finally has an id, so they
76
+ # get a second pass.
77
+ #
78
+ # Embedded documents are stored inside the parent and need none of this,
79
+ # which is why the macro is matched as it is rather than through #type.
80
+ def save(record)
81
+ created = record.new_record?
82
+ record.save.tap { |saved| save_referenced_children(record) if saved && created }
83
+ end
84
+
85
+ private
86
+
87
+ # Read from the record's own class, which for a subclass may say
88
+ # something different about autosave than the model this repository is
89
+ # for.
90
+ def save_referenced_children(record)
91
+ record.class.relations.each_value do |relation|
92
+ association = Association.new(relation, record.class)
93
+ next if association.autosave?
94
+
95
+ case association.macro
96
+ when :has_many
97
+ record.send(association.name).each(&:save)
98
+ when :has_one
99
+ record.send(association.name)&.save
100
+ end
101
+ end
102
+ end
103
+
104
+ def build_statement(column, type, value, operator)
105
+ StatementBuilder.new(column, type, value, operator).to_statement
106
+ end
107
+
108
+ def make_field_conditions(field, value, operator)
109
+ conditions_per_collection = {}
110
+ field.searchable_columns.each do |column_infos|
111
+ collection_name, column_name = parse_collection_name(column_infos[:column])
112
+ statement = build_statement(column_name, column_infos[:type], value, operator)
113
+ next unless statement
114
+
115
+ conditions_per_collection[collection_name] ||= []
116
+ conditions_per_collection[collection_name] << statement
117
+ end
118
+ conditions_per_collection
119
+ end
120
+
121
+ def query_scope(scope, query, fields = config.list.fields.select(&:queryable?))
122
+ return scope.send(config.list.search_by, query) if config.list.search_by
123
+
124
+ statements = []
125
+ each_query_condition(query, fields) do |field, value, operator|
126
+ statements.concat make_condition_for_current_collection(field, make_field_conditions(field, value, operator))
127
+ end
128
+
129
+ scope.where(statements.any? ? {'$or' => statements} : {})
130
+ end
131
+
132
+ def filter_scope(scope, filters, fields = config.list.fields.select(&:filterable?))
133
+ statements = []
134
+
135
+ each_filter_condition(filters, fields) do |field, value, operator|
136
+ field_statements = make_condition_for_current_collection(field, make_field_conditions(field, value, operator))
137
+ if field_statements.many?
138
+ statements << {'$or' => field_statements}
139
+ elsif field_statements.any?
140
+ statements << field_statements.first
141
+ end
142
+ end
143
+
144
+ scope.where(statements.any? ? {'$and' => statements} : {})
145
+ end
146
+
147
+ # Without joins an attribute on an associated document cannot be sorted
148
+ # on. For a belongs_to the local foreign key is the closest thing
149
+ # available, and is what the field configuration used to ask for on its
150
+ # own once it had checked whether the adapter supported joins.
151
+ def sort_field_name(path)
152
+ return path.attribute.to_s if path.root?
153
+
154
+ association = associations.detect { |a| a.name == path.associations.first }
155
+ foreign_key = association.foreign_key if association&.type == :belongs_to
156
+ raise 'sorting by associated model column is not supported in Non-Relational databases' unless foreign_key
157
+
158
+ foreign_key.to_s
159
+ end
160
+
161
+ # Where a search target lives, as [collection, field] -- the collection is
162
+ # only used to group conditions and to tell apart what can be asked of
163
+ # this collection directly from what needs a second lookup.
164
+ def parse_collection_name(target)
165
+ return parse_collection_name_from_string(target.to_s) unless target.is_a?(RailsAdmin::Criteria::Path)
166
+
167
+ return [table_name, target.attribute.to_s] if target.root?
168
+
169
+ association = associations.detect { |a| a.name == target.associations.first }
170
+ if association.try(:embeds?)
171
+ # Embedded documents are reached by their field path, not by collection.
172
+ [table_name, target.to_s]
173
+ elsif association
174
+ [RailsAdmin::AbstractModel.new(association.klass).table_name, target.attribute.to_s]
175
+ else
176
+ [target.associations.first.to_s, target.attribute.to_s]
177
+ end
178
+ end
179
+
180
+ def parse_collection_name_from_string(column)
181
+ collection_name, column_name = column.split('.')
182
+ if associations.detect { |a| a.name == collection_name.to_sym }.try(:embeds?)
183
+ [table_name, column]
184
+ else
185
+ [collection_name, column_name]
186
+ end
187
+ end
188
+
189
+ def make_condition_for_current_collection(target_field, conditions_per_collection)
190
+ result = []
191
+ conditions_per_collection.each do |collection_name, conditions|
192
+ if collection_name == table_name
193
+ # conditions referring current model column are passed directly
194
+ result.concat conditions
195
+ else
196
+ # otherwise, collect ids of documents that satisfy search condition
197
+ result.concat perform_search_on_associated_collection(target_field.name, conditions)
198
+ end
199
+ end
200
+ result
201
+ end
202
+
203
+ def perform_search_on_associated_collection(field_name, conditions)
204
+ target_association = associations.detect { |a| a.name == field_name }
205
+ return [] unless target_association
206
+
207
+ model = target_association.klass
208
+ case target_association.type
209
+ when :belongs_to, :has_and_belongs_to_many
210
+ [{target_association.foreign_key.to_s => {'$in' => model.where('$or' => conditions).all.collect { |r| r.send(target_association.primary_key) }}}]
211
+ when :has_many, :has_one
212
+ [{target_association.primary_key.to_s => {'$in' => model.where('$or' => conditions).all.collect { |r| r.send(target_association.foreign_key) }}}]
213
+ end
214
+ end
215
+
216
+ def sort_by(options, scope)
217
+ return scope unless options[:sort]
218
+
219
+ case options[:sort]
220
+ when RailsAdmin::Criteria::Path
221
+ field_name = sort_field_name(options[:sort])
222
+ when String
223
+ field_name, collection_name = options[:sort].split('.').reverse
224
+ raise 'sorting by associated model column is not supported in Non-Relational databases' if collection_name && collection_name != table_name
225
+ when Symbol
226
+ field_name = options[:sort].to_s
227
+ end
228
+ if options[:sort_reverse]
229
+ scope.asc field_name
230
+ else
231
+ scope.desc field_name
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_admin/adapters/statement_builder'
4
+
5
+ module RailsAdmin
6
+ module Adapters
7
+ module Mongoid
8
+ class StatementBuilder < RailsAdmin::Adapters::StatementBuilder
9
+ protected
10
+
11
+ def unary_operators
12
+ {
13
+ '_blank' => {@column => {'$in' => [nil, '']}},
14
+ '_present' => {@column => {'$nin' => [nil, '']}},
15
+ '_null' => {@column => nil},
16
+ '_not_null' => {@column => {'$ne' => nil}},
17
+ '_empty' => {@column => ''},
18
+ '_not_empty' => {@column => {'$ne' => ''}},
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ def build_statement_for_type
25
+ case @type
26
+ when :boolean then build_statement_for_boolean
27
+ when :integer, :decimal, :float then build_statement_for_integer_decimal_or_float
28
+ when :string, :text then build_statement_for_string_or_text
29
+ when :enum then build_statement_for_enum
30
+ when :belongs_to_association, :bson_object_id then build_statement_for_belongs_to_association_or_bson_object_id
31
+ end
32
+ end
33
+
34
+ def build_statement_for_boolean
35
+ case @value
36
+ when 'false', 'f', '0'
37
+ {@column => false}
38
+ when 'true', 't', '1'
39
+ {@column => true}
40
+ end
41
+ end
42
+
43
+ def column_for_value(value)
44
+ {@column => value}
45
+ end
46
+
47
+ def build_statement_for_string_or_text
48
+ return if @value.blank?
49
+
50
+ @value =
51
+ case @operator
52
+ when 'not_like'
53
+ Regexp.compile("^((?!#{Regexp.escape(@value)}).)*$", Regexp::IGNORECASE)
54
+ when 'default', 'like'
55
+ Regexp.compile(Regexp.escape(@value), Regexp::IGNORECASE)
56
+ when 'starts_with'
57
+ Regexp.compile("^#{Regexp.escape(@value)}", Regexp::IGNORECASE)
58
+ when 'ends_with'
59
+ Regexp.compile("#{Regexp.escape(@value)}$", Regexp::IGNORECASE)
60
+ when 'is', '='
61
+ @value.to_s
62
+ else
63
+ return
64
+ end
65
+
66
+ {@column => @value}
67
+ end
68
+
69
+ def build_statement_for_enum
70
+ return if @value.blank?
71
+
72
+ {@column => {'$in' => Array.wrap(@value)}}
73
+ end
74
+
75
+ def build_statement_for_belongs_to_association_or_bson_object_id
76
+ {@column => @value} if @value
77
+ end
78
+
79
+ def range_filter(min, max)
80
+ if min && max && min == max
81
+ {@column => min}
82
+ elsif min && max
83
+ {@column => {'$gte' => min, '$lte' => max}}
84
+ elsif min
85
+ {@column => {'$gte' => min}}
86
+ elsif max
87
+ {@column => {'$lte' => max}}
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -2,316 +2,18 @@
2
2
 
3
3
  require 'mongoid'
4
4
  require 'rails_admin/config/sections/list'
5
- require 'rails_admin/adapters/mongoid/association'
6
- require 'rails_admin/adapters/mongoid/object_extension'
7
- require 'rails_admin/adapters/mongoid/property'
8
- require 'rails_admin/adapters/mongoid/bson'
9
5
 
10
6
  module RailsAdmin
11
7
  module Adapters
12
8
  module Mongoid
13
9
  DISABLED_COLUMN_TYPES = %w[Range Moped::BSON::Binary BSON::Binary Mongoid::Geospatial::Point].freeze
14
-
15
- def parse_object_id(value)
16
- Bson.parse_object_id(value)
17
- end
18
-
19
- def new(params = {})
20
- model.new(params).extend(ObjectExtension)
21
- end
22
-
23
- def get(id, scope = scoped)
24
- object = scope.find(id)
25
- return nil unless object
26
-
27
- object.extend(ObjectExtension)
28
- rescue StandardError => e
29
- raise e if %w[
30
- Mongoid::Errors::DocumentNotFound
31
- Mongoid::Errors::InvalidFind
32
- Moped::Errors::InvalidObjectId
33
- BSON::InvalidObjectId
34
- BSON::Error::InvalidObjectId
35
- ].exclude?(e.class.to_s)
36
- end
37
-
38
- def scoped
39
- model.scoped
40
- end
41
-
42
- # Criteria#none is lost when merged into another criteria
43
- def none
44
- scoped.in(_id: [])
45
- end
46
-
47
- def first(options = {}, scope = nil)
48
- all(options, scope).first
49
- end
50
-
51
- def all(options = {}, scope = nil)
52
- scope ||= scoped
53
- scope = scope.includes(*options[:include]) if options[:include]
54
- scope = scope.limit(options[:limit]) if options[:limit]
55
- scope = scope.any_in(_id: options[:bulk_ids]) if options[:bulk_ids]
56
- scope = query_scope(scope, options[:query]) if options[:query]
57
- scope = filter_scope(scope, options[:filters]) if options[:filters]
58
- scope = scope.send(Kaminari.config.page_method_name, options[:page]).per(options[:per]) if options[:page] && options[:per]
59
- scope = sort_by(options, scope) if options[:sort]
60
- scope
61
- rescue NoMethodError => e
62
- if /page/.match?(e.message)
63
- e = e.exception <<~ERROR
64
- #{e.message}
65
- If you don't have kaminari-mongoid installed, add `gem 'kaminari-mongoid'` to your Gemfile.
66
- ERROR
67
- end
68
- raise e
69
- end
70
-
71
- def count(options = {}, scope = nil)
72
- all(options.merge(limit: false, page: false), scope).count
73
- end
74
-
75
- def destroy(objects)
76
- Array.wrap(objects).each(&:destroy)
77
- end
78
-
79
- def primary_key
80
- '_id'
81
- end
82
-
83
- def associations
84
- model.relations.values.collect do |association|
85
- Association.new(association, model)
86
- end
87
- end
88
-
89
- def properties
90
- fields = model.fields.reject { |_name, field| DISABLED_COLUMN_TYPES.include?(field.type.to_s) }
91
- fields.collect { |_name, field| Property.new(field, model) }
92
- end
93
-
94
- def base_class
95
- klass = model
96
- klass = klass.superclass while klass.hereditary?
97
- klass
98
- end
99
-
100
- def table_name
101
- model.collection_name.to_s
102
- end
103
-
104
- def encoding
105
- Encoding::UTF_8
106
- end
107
-
108
- def embedded?
109
- associations.detect { |a| a.macro == :embedded_in }
110
- end
111
-
112
- def cyclic?
113
- model.cyclic?
114
- end
115
-
116
- def adapter_supports_joins?
117
- false
118
- end
119
-
120
- private
121
-
122
- def build_statement(column, type, value, operator)
123
- StatementBuilder.new(column, type, value, operator).to_statement
124
- end
125
-
126
- def make_field_conditions(field, value, operator)
127
- conditions_per_collection = {}
128
- field.searchable_columns.each do |column_infos|
129
- collection_name, column_name = parse_collection_name(column_infos[:column])
130
- statement = build_statement(column_name, column_infos[:type], value, operator)
131
- next unless statement
132
-
133
- conditions_per_collection[collection_name] ||= []
134
- conditions_per_collection[collection_name] << statement
135
- end
136
- conditions_per_collection
137
- end
138
-
139
- def query_scope(scope, query, fields = config.list.fields.select(&:queryable?))
140
- if config.list.search_by
141
- scope.send(config.list.search_by, query)
142
- else
143
- statements = []
144
-
145
- fields.each do |field|
146
- value = parse_field_value(field, query)
147
- conditions_per_collection = make_field_conditions(field, value, field.search_operator)
148
- statements.concat make_condition_for_current_collection(field, conditions_per_collection)
149
- end
150
-
151
- scope.where(statements.any? ? {'$or' => statements} : {})
152
- end
153
- end
154
-
155
- # filters example => {"string_field"=>{"0055"=>{"o"=>"like", "v"=>"test_value"}}, ...}
156
- # "0055" is the filter index, no use here. o is the operator, v the value
157
- def filter_scope(scope, filters, fields = config.list.fields.select(&:filterable?))
158
- statements = []
159
-
160
- filters.each_pair do |field_name, filters_dump|
161
- filters_dump.each_value do |filter_dump|
162
- field = fields.detect { |f| f.name.to_s == field_name }
163
- next unless field
164
-
165
- value = parse_field_value(field, filter_dump[:v])
166
- conditions_per_collection = make_field_conditions(field, value, (filter_dump[:o] || 'default'))
167
- field_statements = make_condition_for_current_collection(field, conditions_per_collection)
168
- if field_statements.many?
169
- statements << {'$or' => field_statements}
170
- elsif field_statements.any?
171
- statements << field_statements.first
172
- end
173
- end
174
- end
175
-
176
- scope.where(statements.any? ? {'$and' => statements} : {})
177
- end
178
-
179
- def parse_collection_name(column)
180
- collection_name, column_name = column.split('.')
181
- if associations.detect { |a| a.name == collection_name.to_sym }.try(:embeds?)
182
- [table_name, column]
183
- else
184
- [collection_name, column_name]
185
- end
186
- end
187
-
188
- def make_condition_for_current_collection(target_field, conditions_per_collection)
189
- result = []
190
- conditions_per_collection.each do |collection_name, conditions|
191
- if collection_name == table_name
192
- # conditions referring current model column are passed directly
193
- result.concat conditions
194
- else
195
- # otherwise, collect ids of documents that satisfy search condition
196
- result.concat perform_search_on_associated_collection(target_field.name, conditions)
197
- end
198
- end
199
- result
200
- end
201
-
202
- def perform_search_on_associated_collection(field_name, conditions)
203
- target_association = associations.detect { |a| a.name == field_name }
204
- return [] unless target_association
205
-
206
- model = target_association.klass
207
- case target_association.type
208
- when :belongs_to, :has_and_belongs_to_many
209
- [{target_association.foreign_key.to_s => {'$in' => model.where('$or' => conditions).all.collect { |r| r.send(target_association.primary_key) }}}]
210
- when :has_many, :has_one
211
- [{target_association.primary_key.to_s => {'$in' => model.where('$or' => conditions).all.collect { |r| r.send(target_association.foreign_key) }}}]
212
- end
213
- end
214
-
215
- def sort_by(options, scope)
216
- return scope unless options[:sort]
217
-
218
- case options[:sort]
219
- when String
220
- field_name, collection_name = options[:sort].split('.').reverse
221
- raise 'sorting by associated model column is not supported in Non-Relational databases' if collection_name && collection_name != table_name
222
- when Symbol
223
- field_name = options[:sort].to_s
224
- end
225
- if options[:sort_reverse]
226
- scope.asc field_name
227
- else
228
- scope.desc field_name
229
- end
230
- end
231
-
232
- class StatementBuilder < RailsAdmin::AbstractModel::StatementBuilder
233
- protected
234
-
235
- def unary_operators
236
- {
237
- '_blank' => {@column => {'$in' => [nil, '']}},
238
- '_present' => {@column => {'$nin' => [nil, '']}},
239
- '_null' => {@column => nil},
240
- '_not_null' => {@column => {'$ne' => nil}},
241
- '_empty' => {@column => ''},
242
- '_not_empty' => {@column => {'$ne' => ''}},
243
- }
244
- end
245
-
246
- private
247
-
248
- def build_statement_for_type
249
- case @type
250
- when :boolean then build_statement_for_boolean
251
- when :integer, :decimal, :float then build_statement_for_integer_decimal_or_float
252
- when :string, :text then build_statement_for_string_or_text
253
- when :enum then build_statement_for_enum
254
- when :belongs_to_association, :bson_object_id then build_statement_for_belongs_to_association_or_bson_object_id
255
- end
256
- end
257
-
258
- def build_statement_for_boolean
259
- case @value
260
- when 'false', 'f', '0'
261
- {@column => false}
262
- when 'true', 't', '1'
263
- {@column => true}
264
- end
265
- end
266
-
267
- def column_for_value(value)
268
- {@column => value}
269
- end
270
-
271
- def build_statement_for_string_or_text
272
- return if @value.blank?
273
-
274
- @value =
275
- case @operator
276
- when 'not_like'
277
- Regexp.compile("^((?!#{Regexp.escape(@value)}).)*$", Regexp::IGNORECASE)
278
- when 'default', 'like'
279
- Regexp.compile(Regexp.escape(@value), Regexp::IGNORECASE)
280
- when 'starts_with'
281
- Regexp.compile("^#{Regexp.escape(@value)}", Regexp::IGNORECASE)
282
- when 'ends_with'
283
- Regexp.compile("#{Regexp.escape(@value)}$", Regexp::IGNORECASE)
284
- when 'is', '='
285
- @value.to_s
286
- else
287
- return
288
- end
289
-
290
- {@column => @value}
291
- end
292
-
293
- def build_statement_for_enum
294
- return if @value.blank?
295
-
296
- {@column => {'$in' => Array.wrap(@value)}}
297
- end
298
-
299
- def build_statement_for_belongs_to_association_or_bson_object_id
300
- {@column => @value} if @value
301
- end
302
-
303
- def range_filter(min, max)
304
- if min && max && min == max
305
- {@column => min}
306
- elsif min && max
307
- {@column => {'$gte' => min, '$lte' => max}}
308
- elsif min
309
- {@column => {'$gte' => min}}
310
- elsif max
311
- {@column => {'$lte' => max}}
312
- end
313
- end
314
- end
315
10
  end
316
11
  end
317
12
  end
13
+
14
+ require 'rails_admin/adapters/mongoid/association'
15
+ require 'rails_admin/adapters/mongoid/bson'
16
+ require 'rails_admin/adapters/mongoid/property'
17
+ require 'rails_admin/adapters/mongoid/reflection'
18
+ require 'rails_admin/adapters/mongoid/repository'
19
+ require 'rails_admin/adapters/mongoid/statement_builder'