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
@@ -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,311 +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
- def first(options = {}, scope = nil)
43
- all(options, scope).first
44
- end
45
-
46
- def all(options = {}, scope = nil)
47
- scope ||= scoped
48
- scope = scope.includes(*options[:include]) if options[:include]
49
- scope = scope.limit(options[:limit]) if options[:limit]
50
- scope = scope.any_in(_id: options[:bulk_ids]) if options[:bulk_ids]
51
- scope = query_scope(scope, options[:query]) if options[:query]
52
- scope = filter_scope(scope, options[:filters]) if options[:filters]
53
- scope = scope.send(Kaminari.config.page_method_name, options[:page]).per(options[:per]) if options[:page] && options[:per]
54
- scope = sort_by(options, scope) if options[:sort]
55
- scope
56
- rescue NoMethodError => e
57
- if /page/.match?(e.message)
58
- e = e.exception <<~ERROR
59
- #{e.message}
60
- If you don't have kaminari-mongoid installed, add `gem 'kaminari-mongoid'` to your Gemfile.
61
- ERROR
62
- end
63
- raise e
64
- end
65
-
66
- def count(options = {}, scope = nil)
67
- all(options.merge(limit: false, page: false), scope).count
68
- end
69
-
70
- def destroy(objects)
71
- Array.wrap(objects).each(&:destroy)
72
- end
73
-
74
- def primary_key
75
- '_id'
76
- end
77
-
78
- def associations
79
- model.relations.values.collect do |association|
80
- Association.new(association, model)
81
- end
82
- end
83
-
84
- def properties
85
- fields = model.fields.reject { |_name, field| DISABLED_COLUMN_TYPES.include?(field.type.to_s) }
86
- fields.collect { |_name, field| Property.new(field, model) }
87
- end
88
-
89
- def base_class
90
- klass = model
91
- klass = klass.superclass while klass.hereditary?
92
- klass
93
- end
94
-
95
- def table_name
96
- model.collection_name.to_s
97
- end
98
-
99
- def encoding
100
- Encoding::UTF_8
101
- end
102
-
103
- def embedded?
104
- associations.detect { |a| a.macro == :embedded_in }
105
- end
106
-
107
- def cyclic?
108
- model.cyclic?
109
- end
110
-
111
- def adapter_supports_joins?
112
- false
113
- end
114
-
115
- private
116
-
117
- def build_statement(column, type, value, operator)
118
- StatementBuilder.new(column, type, value, operator).to_statement
119
- end
120
-
121
- def make_field_conditions(field, value, operator)
122
- conditions_per_collection = {}
123
- field.searchable_columns.each do |column_infos|
124
- collection_name, column_name = parse_collection_name(column_infos[:column])
125
- statement = build_statement(column_name, column_infos[:type], value, operator)
126
- next unless statement
127
-
128
- conditions_per_collection[collection_name] ||= []
129
- conditions_per_collection[collection_name] << statement
130
- end
131
- conditions_per_collection
132
- end
133
-
134
- def query_scope(scope, query, fields = config.list.fields.select(&:queryable?))
135
- if config.list.search_by
136
- scope.send(config.list.search_by, query)
137
- else
138
- statements = []
139
-
140
- fields.each do |field|
141
- value = parse_field_value(field, query)
142
- conditions_per_collection = make_field_conditions(field, value, field.search_operator)
143
- statements.concat make_condition_for_current_collection(field, conditions_per_collection)
144
- end
145
-
146
- scope.where(statements.any? ? {'$or' => statements} : {})
147
- end
148
- end
149
-
150
- # filters example => {"string_field"=>{"0055"=>{"o"=>"like", "v"=>"test_value"}}, ...}
151
- # "0055" is the filter index, no use here. o is the operator, v the value
152
- def filter_scope(scope, filters, fields = config.list.fields.select(&:filterable?))
153
- statements = []
154
-
155
- filters.each_pair do |field_name, filters_dump|
156
- filters_dump.each_value do |filter_dump|
157
- field = fields.detect { |f| f.name.to_s == field_name }
158
- next unless field
159
-
160
- value = parse_field_value(field, filter_dump[:v])
161
- conditions_per_collection = make_field_conditions(field, value, (filter_dump[:o] || 'default'))
162
- field_statements = make_condition_for_current_collection(field, conditions_per_collection)
163
- if field_statements.many?
164
- statements << {'$or' => field_statements}
165
- elsif field_statements.any?
166
- statements << field_statements.first
167
- end
168
- end
169
- end
170
-
171
- scope.where(statements.any? ? {'$and' => statements} : {})
172
- end
173
-
174
- def parse_collection_name(column)
175
- collection_name, column_name = column.split('.')
176
- if associations.detect { |a| a.name == collection_name.to_sym }.try(:embeds?)
177
- [table_name, column]
178
- else
179
- [collection_name, column_name]
180
- end
181
- end
182
-
183
- def make_condition_for_current_collection(target_field, conditions_per_collection)
184
- result = []
185
- conditions_per_collection.each do |collection_name, conditions|
186
- if collection_name == table_name
187
- # conditions referring current model column are passed directly
188
- result.concat conditions
189
- else
190
- # otherwise, collect ids of documents that satisfy search condition
191
- result.concat perform_search_on_associated_collection(target_field.name, conditions)
192
- end
193
- end
194
- result
195
- end
196
-
197
- def perform_search_on_associated_collection(field_name, conditions)
198
- target_association = associations.detect { |a| a.name == field_name }
199
- return [] unless target_association
200
-
201
- model = target_association.klass
202
- case target_association.type
203
- when :belongs_to, :has_and_belongs_to_many
204
- [{target_association.foreign_key.to_s => {'$in' => model.where('$or' => conditions).all.collect { |r| r.send(target_association.primary_key) }}}]
205
- when :has_many, :has_one
206
- [{target_association.primary_key.to_s => {'$in' => model.where('$or' => conditions).all.collect { |r| r.send(target_association.foreign_key) }}}]
207
- end
208
- end
209
-
210
- def sort_by(options, scope)
211
- return scope unless options[:sort]
212
-
213
- case options[:sort]
214
- when String
215
- field_name, collection_name = options[:sort].split('.').reverse
216
- raise 'sorting by associated model column is not supported in Non-Relational databases' if collection_name && collection_name != table_name
217
- when Symbol
218
- field_name = options[:sort].to_s
219
- end
220
- if options[:sort_reverse]
221
- scope.asc field_name
222
- else
223
- scope.desc field_name
224
- end
225
- end
226
-
227
- class StatementBuilder < RailsAdmin::AbstractModel::StatementBuilder
228
- protected
229
-
230
- def unary_operators
231
- {
232
- '_blank' => {@column => {'$in' => [nil, '']}},
233
- '_present' => {@column => {'$nin' => [nil, '']}},
234
- '_null' => {@column => nil},
235
- '_not_null' => {@column => {'$ne' => nil}},
236
- '_empty' => {@column => ''},
237
- '_not_empty' => {@column => {'$ne' => ''}},
238
- }
239
- end
240
-
241
- private
242
-
243
- def build_statement_for_type
244
- case @type
245
- when :boolean then build_statement_for_boolean
246
- when :integer, :decimal, :float then build_statement_for_integer_decimal_or_float
247
- when :string, :text then build_statement_for_string_or_text
248
- when :enum then build_statement_for_enum
249
- when :belongs_to_association, :bson_object_id then build_statement_for_belongs_to_association_or_bson_object_id
250
- end
251
- end
252
-
253
- def build_statement_for_boolean
254
- case @value
255
- when 'false', 'f', '0'
256
- {@column => false}
257
- when 'true', 't', '1'
258
- {@column => true}
259
- end
260
- end
261
-
262
- def column_for_value(value)
263
- {@column => value}
264
- end
265
-
266
- def build_statement_for_string_or_text
267
- return if @value.blank?
268
-
269
- @value =
270
- case @operator
271
- when 'not_like'
272
- Regexp.compile("^((?!#{Regexp.escape(@value)}).)*$", Regexp::IGNORECASE)
273
- when 'default', 'like'
274
- Regexp.compile(Regexp.escape(@value), Regexp::IGNORECASE)
275
- when 'starts_with'
276
- Regexp.compile("^#{Regexp.escape(@value)}", Regexp::IGNORECASE)
277
- when 'ends_with'
278
- Regexp.compile("#{Regexp.escape(@value)}$", Regexp::IGNORECASE)
279
- when 'is', '='
280
- @value.to_s
281
- else
282
- return
283
- end
284
-
285
- {@column => @value}
286
- end
287
-
288
- def build_statement_for_enum
289
- return if @value.blank?
290
-
291
- {@column => {'$in' => Array.wrap(@value)}}
292
- end
293
-
294
- def build_statement_for_belongs_to_association_or_bson_object_id
295
- {@column => @value} if @value
296
- end
297
-
298
- def range_filter(min, max)
299
- if min && max && min == max
300
- {@column => min}
301
- elsif min && max
302
- {@column => {'$gte' => min, '$lte' => max}}
303
- elsif min
304
- {@column => {'$gte' => min}}
305
- elsif max
306
- {@column => {'$lte' => max}}
307
- end
308
- end
309
- end
310
10
  end
311
11
  end
312
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'