motor-admin-cstham8 0.4.35

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 (167) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +661 -0
  3. data/README.md +230 -0
  4. data/Rakefile +11 -0
  5. data/app/channels/motor/application_cable/channel.rb +14 -0
  6. data/app/channels/motor/application_cable/connection.rb +27 -0
  7. data/app/channels/motor/notes_channel.rb +9 -0
  8. data/app/channels/motor/notifications_channel.rb +9 -0
  9. data/app/controllers/concerns/motor/current_ability.rb +21 -0
  10. data/app/controllers/concerns/motor/current_user_method.rb +18 -0
  11. data/app/controllers/concerns/motor/load_and_authorize_dynamic_resource.rb +73 -0
  12. data/app/controllers/concerns/motor/wrap_io_params.rb +25 -0
  13. data/app/controllers/motor/active_storage_attachments_controller.rb +64 -0
  14. data/app/controllers/motor/alerts_controller.rb +82 -0
  15. data/app/controllers/motor/api_base_controller.rb +33 -0
  16. data/app/controllers/motor/api_configs_controller.rb +54 -0
  17. data/app/controllers/motor/application_controller.rb +8 -0
  18. data/app/controllers/motor/assets_controller.rb +43 -0
  19. data/app/controllers/motor/audits_controller.rb +16 -0
  20. data/app/controllers/motor/auth_tokens_controller.rb +36 -0
  21. data/app/controllers/motor/configs_controller.rb +33 -0
  22. data/app/controllers/motor/dashboards_controller.rb +64 -0
  23. data/app/controllers/motor/data_controller.rb +88 -0
  24. data/app/controllers/motor/forms_controller.rb +61 -0
  25. data/app/controllers/motor/icons_controller.rb +22 -0
  26. data/app/controllers/motor/note_tags_controller.rb +13 -0
  27. data/app/controllers/motor/notes_controller.rb +58 -0
  28. data/app/controllers/motor/notifications_controller.rb +33 -0
  29. data/app/controllers/motor/queries_controller.rb +64 -0
  30. data/app/controllers/motor/reminders_controller.rb +38 -0
  31. data/app/controllers/motor/resource_default_queries_controller.rb +23 -0
  32. data/app/controllers/motor/resource_methods_controller.rb +23 -0
  33. data/app/controllers/motor/resources_controller.rb +26 -0
  34. data/app/controllers/motor/run_api_requests_controller.rb +56 -0
  35. data/app/controllers/motor/run_graphql_requests_controller.rb +48 -0
  36. data/app/controllers/motor/run_queries_controller.rb +77 -0
  37. data/app/controllers/motor/schema_controller.rb +31 -0
  38. data/app/controllers/motor/send_alerts_controller.rb +26 -0
  39. data/app/controllers/motor/sessions_controller.rb +23 -0
  40. data/app/controllers/motor/slack_conversations_controller.rb +11 -0
  41. data/app/controllers/motor/tags_controller.rb +11 -0
  42. data/app/controllers/motor/ui_controller.rb +51 -0
  43. data/app/controllers/motor/users_for_autocomplete_controller.rb +23 -0
  44. data/app/jobs/motor/alert_sending_job.rb +13 -0
  45. data/app/jobs/motor/application_job.rb +6 -0
  46. data/app/jobs/motor/notify_note_mentions_job.rb +9 -0
  47. data/app/jobs/motor/notify_reminder_job.rb +9 -0
  48. data/app/mailers/motor/alerts_mailer.rb +39 -0
  49. data/app/mailers/motor/application_mailer.rb +33 -0
  50. data/app/mailers/motor/notifications_mailer.rb +33 -0
  51. data/app/models/motor/alert.rb +30 -0
  52. data/app/models/motor/alert_lock.rb +7 -0
  53. data/app/models/motor/api_config.rb +28 -0
  54. data/app/models/motor/application_record.rb +18 -0
  55. data/app/models/motor/audit.rb +13 -0
  56. data/app/models/motor/config.rb +13 -0
  57. data/app/models/motor/dashboard.rb +26 -0
  58. data/app/models/motor/form.rb +23 -0
  59. data/app/models/motor/note.rb +18 -0
  60. data/app/models/motor/note_tag.rb +7 -0
  61. data/app/models/motor/note_tag_tag.rb +8 -0
  62. data/app/models/motor/notification.rb +14 -0
  63. data/app/models/motor/query.rb +33 -0
  64. data/app/models/motor/reminder.rb +13 -0
  65. data/app/models/motor/resource.rb +15 -0
  66. data/app/models/motor/tag.rb +7 -0
  67. data/app/models/motor/taggable_tag.rb +8 -0
  68. data/app/views/layouts/motor/application.html.erb +17 -0
  69. data/app/views/layouts/motor/mailer.html.erb +72 -0
  70. data/app/views/motor/alerts_mailer/alert_email.html.erb +54 -0
  71. data/app/views/motor/notifications_mailer/notify_mention_email.html.erb +28 -0
  72. data/app/views/motor/notifications_mailer/notify_reminder_email.html.erb +28 -0
  73. data/app/views/motor/ui/show.html.erb +1 -0
  74. data/config/locales/el.yml +420 -0
  75. data/config/locales/en.yml +340 -0
  76. data/config/locales/es.yml +420 -0
  77. data/config/locales/ja.yml +340 -0
  78. data/config/locales/pt.yml +416 -0
  79. data/config/routes.rb +65 -0
  80. data/lib/generators/motor/install_generator.rb +24 -0
  81. data/lib/generators/motor/install_notes_generator.rb +22 -0
  82. data/lib/generators/motor/migration.rb +17 -0
  83. data/lib/generators/motor/templates/install.rb +271 -0
  84. data/lib/generators/motor/templates/install_api_configs.rb +86 -0
  85. data/lib/generators/motor/templates/install_notes.rb +83 -0
  86. data/lib/generators/motor/templates/upgrade_motor_api_actions.rb +71 -0
  87. data/lib/generators/motor/upgrade_generator.rb +43 -0
  88. data/lib/motor/active_record_utils/action_text_attribute_patch.rb +19 -0
  89. data/lib/motor/active_record_utils/active_record_connection_column_patch.rb +14 -0
  90. data/lib/motor/active_record_utils/active_record_filter.rb +405 -0
  91. data/lib/motor/active_record_utils/active_storage_blob_patch.rb +30 -0
  92. data/lib/motor/active_record_utils/active_storage_links_extension.rb +11 -0
  93. data/lib/motor/active_record_utils/defined_scopes_extension.rb +25 -0
  94. data/lib/motor/active_record_utils/fetch_methods.rb +24 -0
  95. data/lib/motor/active_record_utils/types.rb +64 -0
  96. data/lib/motor/active_record_utils.rb +45 -0
  97. data/lib/motor/admin.rb +141 -0
  98. data/lib/motor/alerts/persistance.rb +97 -0
  99. data/lib/motor/alerts/scheduled_alerts_cache.rb +29 -0
  100. data/lib/motor/alerts/scheduler.rb +30 -0
  101. data/lib/motor/alerts/slack_sender.rb +74 -0
  102. data/lib/motor/alerts.rb +52 -0
  103. data/lib/motor/api_configs.rb +41 -0
  104. data/lib/motor/api_query/apply_scope.rb +44 -0
  105. data/lib/motor/api_query/build_json.rb +171 -0
  106. data/lib/motor/api_query/build_meta.rb +20 -0
  107. data/lib/motor/api_query/filter.rb +125 -0
  108. data/lib/motor/api_query/paginate.rb +19 -0
  109. data/lib/motor/api_query/search.rb +60 -0
  110. data/lib/motor/api_query/sort.rb +64 -0
  111. data/lib/motor/api_query.rb +24 -0
  112. data/lib/motor/assets.rb +62 -0
  113. data/lib/motor/build_schema/active_storage_attachment_schema.rb +125 -0
  114. data/lib/motor/build_schema/adjust_devise_model_schema.rb +60 -0
  115. data/lib/motor/build_schema/apply_permissions.rb +64 -0
  116. data/lib/motor/build_schema/defaults.rb +66 -0
  117. data/lib/motor/build_schema/find_display_column.rb +65 -0
  118. data/lib/motor/build_schema/find_icon.rb +135 -0
  119. data/lib/motor/build_schema/find_searchable_columns.rb +33 -0
  120. data/lib/motor/build_schema/load_from_rails.rb +361 -0
  121. data/lib/motor/build_schema/merge_schema_configs.rb +157 -0
  122. data/lib/motor/build_schema/reorder_schema.rb +88 -0
  123. data/lib/motor/build_schema/utils.rb +31 -0
  124. data/lib/motor/build_schema.rb +125 -0
  125. data/lib/motor/cancan_utils/ability_patch.rb +31 -0
  126. data/lib/motor/cancan_utils/can_manage_all.rb +14 -0
  127. data/lib/motor/cancan_utils.rb +9 -0
  128. data/lib/motor/configs/build_configs_hash.rb +90 -0
  129. data/lib/motor/configs/build_ui_app_tag.rb +177 -0
  130. data/lib/motor/configs/load_from_cache.rb +110 -0
  131. data/lib/motor/configs/sync_from_file.rb +35 -0
  132. data/lib/motor/configs/sync_from_hash.rb +159 -0
  133. data/lib/motor/configs/sync_middleware.rb +72 -0
  134. data/lib/motor/configs/sync_with_remote.rb +47 -0
  135. data/lib/motor/configs/write_to_file.rb +36 -0
  136. data/lib/motor/configs.rb +39 -0
  137. data/lib/motor/dashboards/persistance.rb +73 -0
  138. data/lib/motor/dashboards.rb +8 -0
  139. data/lib/motor/forms/persistance.rb +93 -0
  140. data/lib/motor/forms.rb +8 -0
  141. data/lib/motor/hash_serializer.rb +21 -0
  142. data/lib/motor/net_http_utils.rb +50 -0
  143. data/lib/motor/notes/notify_mentions.rb +71 -0
  144. data/lib/motor/notes/notify_reminder.rb +48 -0
  145. data/lib/motor/notes/persist.rb +36 -0
  146. data/lib/motor/notes/reminders_scheduler.rb +39 -0
  147. data/lib/motor/notes/tags.rb +34 -0
  148. data/lib/motor/notes.rb +12 -0
  149. data/lib/motor/queries/persistance.rb +90 -0
  150. data/lib/motor/queries/postgresql_exec_query.rb +28 -0
  151. data/lib/motor/queries/render_sql_template.rb +61 -0
  152. data/lib/motor/queries/run_query.rb +289 -0
  153. data/lib/motor/queries.rb +11 -0
  154. data/lib/motor/railtie.rb +11 -0
  155. data/lib/motor/resources/custom_sql_columns_cache.rb +17 -0
  156. data/lib/motor/resources/fetch_configured_model.rb +269 -0
  157. data/lib/motor/resources/persist_configs.rb +232 -0
  158. data/lib/motor/resources.rb +19 -0
  159. data/lib/motor/slack/client.rb +62 -0
  160. data/lib/motor/slack.rb +16 -0
  161. data/lib/motor/tags.rb +32 -0
  162. data/lib/motor/tasks/motor.rake +54 -0
  163. data/lib/motor/version.rb +5 -0
  164. data/lib/motor-admin-cstham8.rb +3 -0
  165. data/lib/motor.rb +87 -0
  166. data/ui/dist/manifest.json +1990 -0
  167. metadata +303 -0
@@ -0,0 +1,405 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class UnkownFilterError < NoMethodError
5
+ end
6
+ end
7
+
8
+ module Arel
9
+ module Attributes
10
+ class Relation < Attribute
11
+ attr_accessor :collection, :for_write
12
+
13
+ def initialize(relation, name, collection = false, for_write = false)
14
+ self[:relation] = relation
15
+ self[:name] = name
16
+ @collection = collection
17
+ @for_write = for_write
18
+ end
19
+
20
+ delegate :able_to_type_cast?, to: :relation
21
+
22
+ def table_name
23
+ nil
24
+ end
25
+
26
+ def eql?(other)
27
+ self.class == other.class &&
28
+ relation == other.relation &&
29
+ name == other.name &&
30
+ collection == other.collection
31
+ end
32
+
33
+ delegate :type_cast_for_database, to: :relation
34
+ end
35
+ end
36
+ end
37
+
38
+ module Arel
39
+ module Visitors
40
+ class ToSql
41
+ def visit_Arel_Attributes_Relation(o, collector)
42
+ visit(o.relation, collector)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ module ActiveRecord
49
+ module Associations
50
+ class AliasTracker
51
+ def initialize(connection, aliases)
52
+ @aliases = aliases
53
+ @connection = connection
54
+ @relation_trail = {}
55
+ end
56
+
57
+ def aliased_table_for_relation(trail, arel_table, &block)
58
+ @relation_trail[trail] ||=
59
+ if Rails::VERSION::MAJOR >= 6
60
+ aliased_table_for(arel_table, &block)
61
+ else
62
+ aliased_table_for(arel_table.name, trail.last, nil)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ module ActiveRecord
70
+ class PredicateBuilder # :nodoc:
71
+ def self.filter_joins(klass, filters)
72
+ custom = []
73
+ [build_filter_joins(klass, filters, [], custom), custom]
74
+ end
75
+
76
+ def self.build_filter_joins(klass, filters, relations = [], custom = [])
77
+ case filters
78
+ when Array
79
+ filters.each { |f| build_filter_joins(klass, f, relations, custom) }.compact
80
+ when Hash
81
+ filters.each do |key, value|
82
+ if klass.respond_to?(:filters) && klass.filters.key?(key.to_sym)
83
+ js = klass.filters.dig(key.to_sym, :joins)
84
+
85
+ if js.is_a?(Array)
86
+ js.each do |j|
87
+ if j.is_a?(String)
88
+ custom << j
89
+ else
90
+ relations << j
91
+ end
92
+ end
93
+ elsif js
94
+ if js.is_a?(String)
95
+ custom << js
96
+ else
97
+ relations << js
98
+ end
99
+ end
100
+ elsif reflection = klass._reflections[key.to_s]
101
+ if value.is_a?(Hash)
102
+ relations <<
103
+ if reflection.polymorphic?
104
+ value = value.dup
105
+ join_klass = value.delete(:as).safe_constantize
106
+ right_table = join_klass.arel_table
107
+ left_table = reflection.active_record.arel_table
108
+
109
+ on = right_table[join_klass.primary_key]
110
+ .eq(left_table[reflection.foreign_key])
111
+ .and(left_table[reflection.foreign_type].eq(join_klass.name))
112
+
113
+ cross_boundry_joins = join_klass.left_outer_joins(ActiveRecord::PredicateBuilder.filter_joins(join_klass, value).flatten).send(
114
+ :build_joins, []
115
+ )
116
+
117
+ [
118
+ left_table.join(right_table, Arel::Nodes::OuterJoin).on(on).join_sources,
119
+ cross_boundry_joins
120
+ ]
121
+ else
122
+ {
123
+ key => build_filter_joins(reflection.klass, value, [], custom)
124
+ }
125
+ end
126
+ elsif value.is_a?(Array)
127
+ value.each do |v|
128
+ relations << {
129
+ key => build_filter_joins(reflection.klass, v, [], custom)
130
+ }
131
+ end
132
+ elsif value != true && value != false && value != 'true' && value != 'false' && !value.nil?
133
+ relations << key
134
+ end
135
+ elsif !klass.columns_hash.key?(key.to_s) && key.to_s.end_with?('_ids') && reflection = klass._reflections[key.to_s.gsub(
136
+ /_ids$/, 's'
137
+ )]
138
+ relations << reflection.name
139
+ elsif reflection = klass.reflect_on_all_associations(:has_and_belongs_to_many).find do |r|
140
+ r.join_table == key.to_s && value.keys.first.to_s == r.association_foreign_key.to_s
141
+ end
142
+ reflection = klass._reflections[klass._reflections[reflection.name.to_s].send(:delegate_reflection).options[:through].to_s]
143
+ relations << { reflection.name => build_filter_joins(reflection.klass, value) }
144
+ end
145
+ end
146
+ end
147
+
148
+ relations
149
+ end
150
+
151
+ def build_from_filter_hash(attributes, relation_trail, alias_tracker)
152
+ case attributes
153
+ when Array
154
+ node = build_from_filter_hash(attributes.shift, relation_trail, alias_tracker)
155
+
156
+ n = attributes.shift(2)
157
+ until n.empty?
158
+ n[1] = build_from_filter_hash(n[1], relation_trail, alias_tracker)
159
+ if n[0] == 'AND'
160
+ if node.is_a?(Arel::Nodes::And)
161
+ node.children.push(n[1])
162
+ else
163
+ node = node.and(n[1])
164
+ end
165
+ elsif n[0] == 'OR'
166
+ node = Arel::Nodes::Grouping.new(node).or(Arel::Nodes::Grouping.new(n[1]))
167
+ elsif !n[0].is_a?(String)
168
+ n[0] = build_from_filter_hash(n[0], relation_trail, alias_tracker)
169
+ if node.is_a?(Arel::Nodes::And)
170
+ node.children.push(n[0])
171
+ else
172
+ node = node.and(n[0])
173
+ end
174
+ else
175
+ raise 'lll'
176
+ end
177
+ n = attributes.shift(2)
178
+ end
179
+
180
+ node
181
+ when Hash
182
+ expand_from_filter_hash(attributes, relation_trail, alias_tracker)
183
+ else
184
+ expand_from_filter_hash({ id: attributes }, relation_trail, alias_tracker)
185
+ end
186
+ end
187
+
188
+ def expand_from_filter_hash(attributes, relation_trail, alias_tracker)
189
+ klass = table.send(:klass)
190
+
191
+ children = attributes.flat_map do |key, value|
192
+ if klass.respond_to?(:filters) && custom_filter = klass.filters[key]
193
+ instance_exec(klass, table, key, value, relation_trail, alias_tracker, &custom_filter[:block])
194
+ elsif column = klass.columns_hash[key.to_s] || klass.columns_hash[key.to_s.split('.').first]
195
+ expand_filter_for_column(key, column, value, relation_trail)
196
+ elsif relation = klass.reflect_on_association(key)
197
+ expand_filter_for_relationship(relation, value, relation_trail, alias_tracker)
198
+ elsif key.to_s.end_with?('_ids') && relation = klass.reflect_on_association(key.to_s.gsub(/_ids$/, 's'))
199
+ expand_filter_for_relationship(relation, { id: value }, relation_trail, alias_tracker)
200
+ elsif relation = klass.reflect_on_all_associations(:has_and_belongs_to_many).find do |r|
201
+ r.join_table == key.to_s && value.keys.first.to_s == r.association_foreign_key.to_s
202
+ end
203
+ expand_filter_for_join_table(relation, value, relation_trail, alias_tracker)
204
+ else
205
+ raise ActiveRecord::UnkownFilterError, "Unkown filter \"#{key}\" for #{klass}."
206
+ end
207
+ end
208
+
209
+ children.compact!
210
+ if children.size > 1
211
+ Arel::Nodes::And.new(children)
212
+ else
213
+ children.first
214
+ end
215
+ end
216
+
217
+ def expand_filter_for_column(key, column, value, relation_trail)
218
+ attribute = table.send(:arel_table)[column.name]
219
+ relation_trail.each do |rt|
220
+ attribute = Arel::Attributes::Relation.new(attribute, rt)
221
+ end
222
+
223
+ if column.type == :json || column.type == :jsonb
224
+ names = key.to_s.split('.')
225
+ names.shift
226
+ attribute = attribute[names]
227
+ end
228
+
229
+ if value.is_a?(Hash)
230
+ nodes = value.map do |subkey, subvalue|
231
+ expand_filter_for_arel_attribute(column, attribute, subkey, subvalue)
232
+ end
233
+ nodes.inject { |c, n| c.nil? ? n : c.and(n) }
234
+ elsif value.nil?
235
+ attribute.eq(nil)
236
+ elsif [true, 'true'].include?(value)
237
+ column.type == :boolean ? attribute.eq(true) : attribute.not_eq(nil)
238
+ elsif [false, 'false'].include?(value)
239
+ column.type == :boolean ? attribute.eq(false) : attribute.eq(nil)
240
+ elsif value.is_a?(Array) && !column.array
241
+ attribute.in(value)
242
+ elsif column.type != :json && column.type != :jsonb
243
+ converted_value = column.array ? Array(value) : value
244
+ attribute.eq(converted_value)
245
+ else
246
+ raise ActiveRecord::UnkownFilterError, "Unkown type for #{column}. (type #{value.class})"
247
+ end
248
+ end
249
+
250
+ def expand_filter_for_arel_attribute(column, attribute, key, value)
251
+ case key.to_sym
252
+ when :contains
253
+ attribute.contains(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
254
+ when :contained_by
255
+ attribute.contained_by(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
256
+ when :equal_to, :eq
257
+ attribute.eq(value)
258
+ when :excludes
259
+ attribute.excludes(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
260
+ when :greater_than, :gt
261
+ attribute.gt(value)
262
+ when :greater_than_or_equal_to, :gteq, :gte
263
+ attribute.gteq(value)
264
+ when :has_key
265
+ attribute.has_key(value)
266
+ when :has_keys
267
+ attribute.has_keys(*Array(value).map { |x| Arel::Nodes.build_quoted(x) })
268
+ when :has_any_key
269
+ attribute.has_any_key(*Array(value).map { |x| Arel::Nodes.build_quoted(x) })
270
+ when :in
271
+ attribute.in(value)
272
+ when :less_than, :lt
273
+ attribute.lt(value)
274
+ when :less_than_or_equal_to, :lteq, :lte
275
+ attribute.lteq(value)
276
+ when :like
277
+ attribute.matches(value, nil, true)
278
+ when :ilike
279
+ attribute.matches(value, nil, false)
280
+ when :not, :not_equal, :neq
281
+ attribute.not_eq(value)
282
+ when :not_in
283
+ attribute.not_in(value)
284
+ when :overlaps
285
+ attribute.overlaps(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
286
+ when :not_overlaps
287
+ attribute.not_overlaps(value)
288
+ when :ts_match
289
+ if value.is_a?(Array)
290
+ attribute.ts_query(*value)
291
+ else
292
+ attribute.ts_query(value)
293
+ end
294
+ when :within
295
+ case value
296
+ when String
297
+ if /\A[0-9A-F]*\Z/i.match?(value) && value.start_with?('00', '01')
298
+ attribute.within(Arel::Nodes::HexEncodedBinary.new(value))
299
+ else
300
+ attribute.within(Arel::Nodes.build_quoted(value))
301
+ end
302
+ when Hash
303
+ attribute.within(Arel::Nodes.build_quoted(value))
304
+ else
305
+ raise "Not Supported value for within: #{value.inspect}"
306
+ end
307
+ else
308
+ raise "Not Supported: #{key.to_sym} on column \"#{column.name}\" of type #{column.type}"
309
+ end
310
+ end
311
+
312
+ def expand_filter_for_relationship(relation, value, relation_trail, alias_tracker)
313
+ case relation.macro
314
+ when :has_many
315
+ case value
316
+ when true, 'true'
317
+ counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
318
+ if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
319
+ return table.arel_table[counter_cache_column_name.to_sym].gt(0)
320
+ else
321
+ raise "Not Supported: #{relation.name}"
322
+ end
323
+ when false, 'false'
324
+ counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
325
+ if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
326
+ return table.arel_table[counter_cache_column_name.to_sym].eq(0)
327
+ else
328
+ raise "Not Supported: #{relation.name}"
329
+ end
330
+ end
331
+
332
+ when :belongs_to
333
+ if [true, 'true'].include?(value)
334
+ return table.arel_table[relation.foreign_key].not_eq(nil)
335
+ elsif value == false || value == 'false' || value.nil?
336
+ return table.arel_table[relation.foreign_key].eq(nil)
337
+ end
338
+ end
339
+
340
+ if relation.polymorphic?
341
+ value = value.dup
342
+ klass = value.delete(:as).safe_constantize
343
+
344
+ builder = self.class.new(TableMetadata.new(
345
+ klass,
346
+ alias_tracker.aliased_table_for_relation(relation_trail + ["#{klass.table_name}_as_#{relation.name}"],
347
+ klass.arel_table) do
348
+ klass.arel_table.name
349
+ end,
350
+ relation
351
+ ))
352
+ builder.build_from_filter_hash(value, relation_trail + ["#{klass.table_name}_as_#{relation.name}"],
353
+ alias_tracker)
354
+ else
355
+ builder = self.class.new(TableMetadata.new(
356
+ relation.klass,
357
+ alias_tracker.aliased_table_for_relation(relation_trail + [relation.name],
358
+ relation.klass.arel_table) do
359
+ relation.alias_candidate(table.arel_table.name || relation.klass.arel_table)
360
+ end,
361
+ relation
362
+ ))
363
+ builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
364
+ end
365
+ end
366
+
367
+ def expand_filter_for_join_table(relation, value, relation_trail, alias_tracker)
368
+ relation = relation.active_record._reflections[relation.active_record._reflections[relation.name.to_s].send(:delegate_reflection).options[:through].to_s]
369
+ builder = self.class.new(TableMetadata.new(
370
+ relation.klass,
371
+ alias_tracker.aliased_table_for_relation(relation_trail + [relation.name],
372
+ relation.klass.arel_table) do
373
+ relation.alias_candidate(table.arel_table.name || relation.klass.arel_table)
374
+ end,
375
+ relation
376
+ ))
377
+ builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
378
+ end
379
+ end
380
+ end
381
+
382
+ module ActiveRecord
383
+ class Relation
384
+ class FilterClauseFactory # :nodoc:
385
+ def initialize(klass, predicate_builder)
386
+ @klass = klass
387
+ @predicate_builder = predicate_builder
388
+ end
389
+
390
+ def build(filters, alias_tracker)
391
+ if filters.is_a?(Hash) || filters.is_a?(Array)
392
+ parts = [predicate_builder.build_from_filter_hash(filters, [], alias_tracker)]
393
+ else
394
+ raise ArgumentError, "Unsupported argument type: #{filters.inspect} (#{filters.class})"
395
+ end
396
+
397
+ WhereClause.new(parts)
398
+ end
399
+
400
+ protected
401
+
402
+ attr_reader :klass, :predicate_builder
403
+ end
404
+ end
405
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module ActiveStorageBlobPatch
6
+ KEYWORD_ARGS =
7
+ %i[io filename content_type metadata service_name identify record].freeze
8
+
9
+ def build_after_upload(hash)
10
+ super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
11
+ end
12
+
13
+ def build_after_unfurling(hash)
14
+ super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
15
+ end
16
+
17
+ def create_after_upload!(hash)
18
+ super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
19
+ end
20
+
21
+ def create_after_unfurling!(hash)
22
+ super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
23
+ end
24
+
25
+ def create_and_upload!(hash)
26
+ super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module ActiveStorageLinksExtension
6
+ def path
7
+ Rails.application.routes.url_helpers.rails_blob_path(self, only_path: true)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module DefinedScopesExtension
6
+ def inherited(subclass)
7
+ super
8
+
9
+ subclass.instance_variable_set(:@__scopes__, subclass.superclass.instance_variable_get(:@__scopes__).dup)
10
+ end
11
+
12
+ def scope(name, _body)
13
+ (@__scopes__ ||= []) << name.to_sym
14
+
15
+ super
16
+ end
17
+
18
+ def defined_scopes
19
+ @__scopes__ || []
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ ActiveSupport.on_load(:active_record) { extend Motor::ActiveRecordUtils::DefinedScopesExtension }
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module FetchMethods
6
+ EXCLUDE_METHODS = %i[
7
+ password
8
+ current_password
9
+ password_confirmation
10
+ devise_modules
11
+ ].freeze
12
+
13
+ module_function
14
+
15
+ def call(model)
16
+ (model.instance_methods(false) - model.superclass.instance_methods).reject do |name|
17
+ next true if EXCLUDE_METHODS.include?(name)
18
+ next true if name.to_s.match?(/(:?=|\?|_id)\z/)
19
+ next true if name.to_s.match?(/\A(?:validate|autosave)_associated_records/)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module Types
6
+ MUTEX = Mutex.new
7
+ DEFAULT_TYPE = 'string'
8
+
9
+ UNIFIED_TYPES = {
10
+ 'smallint' => 'integer',
11
+ 'int' => 'integer',
12
+ 'int4' => 'integer',
13
+ 'int8' => 'integer',
14
+ 'int16' => 'integer',
15
+ 'bigint' => 'integer',
16
+ 'numeric' => 'float',
17
+ 'decimal' => 'float',
18
+ 'float4' => 'float',
19
+ 'bpchar' => 'string',
20
+ 'float8' => 'float',
21
+ 'float16' => 'float',
22
+ 'text' => 'string',
23
+ 'citext' => 'string',
24
+ 'jsonb' => 'json',
25
+ 'bool' => 'boolean',
26
+ 'timestamp' => 'datetime',
27
+ 'timestamptz' => 'datetime'
28
+ }.freeze
29
+
30
+ module_function
31
+
32
+ def all
33
+ @all || MUTEX.synchronize do
34
+ @all ||= build_types_hash
35
+ end
36
+ end
37
+
38
+ def find_class_for_name(name)
39
+ all.invert[name.to_s]
40
+ end
41
+
42
+ def find_name_for_type(type)
43
+ name = all[type.subtype.class.to_s] if type.respond_to?(:subtype)
44
+ name ||= all[type.class.to_s]
45
+
46
+ return UNIFIED_TYPES.fetch(name, name) if name
47
+
48
+ nil
49
+ end
50
+
51
+ def build_types_hash
52
+ connection_class = defined?(::ResourceRecord) ? ::ResourceRecord : ActiveRecord::Base
53
+
54
+ type_map = connection_class.connection.send(:type_map)
55
+
56
+ type_map.instance_variable_get(:@mapping).map do |name, type|
57
+ next unless name.is_a?(String)
58
+
59
+ [type.call.class.to_s, name]
60
+ end.compact.to_h
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module_function
6
+
7
+ def reset_id_sequence!(model)
8
+ case ActiveRecord::Base.connection.class.name
9
+ when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
10
+ ActiveRecord::Base.connection.reset_pk_sequence!(model.table_name)
11
+ else
12
+ ActiveRecord::Base.connection.reset_sequence!(model.table_name, 'id')
13
+ end
14
+ end
15
+
16
+ def generate_csv_for_relation(relation, reset_limit: false)
17
+ relation = relation.limit(nil).offset(nil) if reset_limit
18
+
19
+ result = load_query_for_csv(relation)
20
+
21
+ CSV.generate do |csv|
22
+ csv << result.columns
23
+
24
+ result.rows.each { |row| csv << row }
25
+ end
26
+ end
27
+
28
+ def load_query_for_csv(relation)
29
+ model_name = relation.klass.model_name.human(count: :many, default: relation.klass.name.titleize.pluralize)
30
+
31
+ query = Motor::Query.find_by(name: "Export #{model_name}")
32
+
33
+ relation.klass.connection.exec_query(query&.sql_body || relation.to_sql)
34
+ end
35
+ end
36
+ end
37
+
38
+ require_relative 'active_record_utils/types'
39
+ require_relative 'active_record_utils/fetch_methods'
40
+ require_relative 'active_record_utils/defined_scopes_extension'
41
+ require_relative 'active_record_utils/active_storage_links_extension'
42
+ require_relative 'active_record_utils/active_storage_blob_patch'
43
+ require_relative 'active_record_utils/active_record_filter'
44
+ require_relative 'active_record_utils/active_record_connection_column_patch'
45
+ require_relative 'active_record_utils/action_text_attribute_patch'