motor-admin-cstham8 0.4.35
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +661 -0
- data/README.md +230 -0
- data/Rakefile +11 -0
- data/app/channels/motor/application_cable/channel.rb +14 -0
- data/app/channels/motor/application_cable/connection.rb +27 -0
- data/app/channels/motor/notes_channel.rb +9 -0
- data/app/channels/motor/notifications_channel.rb +9 -0
- data/app/controllers/concerns/motor/current_ability.rb +21 -0
- data/app/controllers/concerns/motor/current_user_method.rb +18 -0
- data/app/controllers/concerns/motor/load_and_authorize_dynamic_resource.rb +73 -0
- data/app/controllers/concerns/motor/wrap_io_params.rb +25 -0
- data/app/controllers/motor/active_storage_attachments_controller.rb +64 -0
- data/app/controllers/motor/alerts_controller.rb +82 -0
- data/app/controllers/motor/api_base_controller.rb +33 -0
- data/app/controllers/motor/api_configs_controller.rb +54 -0
- data/app/controllers/motor/application_controller.rb +8 -0
- data/app/controllers/motor/assets_controller.rb +43 -0
- data/app/controllers/motor/audits_controller.rb +16 -0
- data/app/controllers/motor/auth_tokens_controller.rb +36 -0
- data/app/controllers/motor/configs_controller.rb +33 -0
- data/app/controllers/motor/dashboards_controller.rb +64 -0
- data/app/controllers/motor/data_controller.rb +88 -0
- data/app/controllers/motor/forms_controller.rb +61 -0
- data/app/controllers/motor/icons_controller.rb +22 -0
- data/app/controllers/motor/note_tags_controller.rb +13 -0
- data/app/controllers/motor/notes_controller.rb +58 -0
- data/app/controllers/motor/notifications_controller.rb +33 -0
- data/app/controllers/motor/queries_controller.rb +64 -0
- data/app/controllers/motor/reminders_controller.rb +38 -0
- data/app/controllers/motor/resource_default_queries_controller.rb +23 -0
- data/app/controllers/motor/resource_methods_controller.rb +23 -0
- data/app/controllers/motor/resources_controller.rb +26 -0
- data/app/controllers/motor/run_api_requests_controller.rb +56 -0
- data/app/controllers/motor/run_graphql_requests_controller.rb +48 -0
- data/app/controllers/motor/run_queries_controller.rb +77 -0
- data/app/controllers/motor/schema_controller.rb +31 -0
- data/app/controllers/motor/send_alerts_controller.rb +26 -0
- data/app/controllers/motor/sessions_controller.rb +23 -0
- data/app/controllers/motor/slack_conversations_controller.rb +11 -0
- data/app/controllers/motor/tags_controller.rb +11 -0
- data/app/controllers/motor/ui_controller.rb +51 -0
- data/app/controllers/motor/users_for_autocomplete_controller.rb +23 -0
- data/app/jobs/motor/alert_sending_job.rb +13 -0
- data/app/jobs/motor/application_job.rb +6 -0
- data/app/jobs/motor/notify_note_mentions_job.rb +9 -0
- data/app/jobs/motor/notify_reminder_job.rb +9 -0
- data/app/mailers/motor/alerts_mailer.rb +39 -0
- data/app/mailers/motor/application_mailer.rb +33 -0
- data/app/mailers/motor/notifications_mailer.rb +33 -0
- data/app/models/motor/alert.rb +30 -0
- data/app/models/motor/alert_lock.rb +7 -0
- data/app/models/motor/api_config.rb +28 -0
- data/app/models/motor/application_record.rb +18 -0
- data/app/models/motor/audit.rb +13 -0
- data/app/models/motor/config.rb +13 -0
- data/app/models/motor/dashboard.rb +26 -0
- data/app/models/motor/form.rb +23 -0
- data/app/models/motor/note.rb +18 -0
- data/app/models/motor/note_tag.rb +7 -0
- data/app/models/motor/note_tag_tag.rb +8 -0
- data/app/models/motor/notification.rb +14 -0
- data/app/models/motor/query.rb +33 -0
- data/app/models/motor/reminder.rb +13 -0
- data/app/models/motor/resource.rb +15 -0
- data/app/models/motor/tag.rb +7 -0
- data/app/models/motor/taggable_tag.rb +8 -0
- data/app/views/layouts/motor/application.html.erb +17 -0
- data/app/views/layouts/motor/mailer.html.erb +72 -0
- data/app/views/motor/alerts_mailer/alert_email.html.erb +54 -0
- data/app/views/motor/notifications_mailer/notify_mention_email.html.erb +28 -0
- data/app/views/motor/notifications_mailer/notify_reminder_email.html.erb +28 -0
- data/app/views/motor/ui/show.html.erb +1 -0
- data/config/locales/el.yml +420 -0
- data/config/locales/en.yml +340 -0
- data/config/locales/es.yml +420 -0
- data/config/locales/ja.yml +340 -0
- data/config/locales/pt.yml +416 -0
- data/config/routes.rb +65 -0
- data/lib/generators/motor/install_generator.rb +24 -0
- data/lib/generators/motor/install_notes_generator.rb +22 -0
- data/lib/generators/motor/migration.rb +17 -0
- data/lib/generators/motor/templates/install.rb +271 -0
- data/lib/generators/motor/templates/install_api_configs.rb +86 -0
- data/lib/generators/motor/templates/install_notes.rb +83 -0
- data/lib/generators/motor/templates/upgrade_motor_api_actions.rb +71 -0
- data/lib/generators/motor/upgrade_generator.rb +43 -0
- data/lib/motor/active_record_utils/action_text_attribute_patch.rb +19 -0
- data/lib/motor/active_record_utils/active_record_connection_column_patch.rb +14 -0
- data/lib/motor/active_record_utils/active_record_filter.rb +405 -0
- data/lib/motor/active_record_utils/active_storage_blob_patch.rb +30 -0
- data/lib/motor/active_record_utils/active_storage_links_extension.rb +11 -0
- data/lib/motor/active_record_utils/defined_scopes_extension.rb +25 -0
- data/lib/motor/active_record_utils/fetch_methods.rb +24 -0
- data/lib/motor/active_record_utils/types.rb +64 -0
- data/lib/motor/active_record_utils.rb +45 -0
- data/lib/motor/admin.rb +141 -0
- data/lib/motor/alerts/persistance.rb +97 -0
- data/lib/motor/alerts/scheduled_alerts_cache.rb +29 -0
- data/lib/motor/alerts/scheduler.rb +30 -0
- data/lib/motor/alerts/slack_sender.rb +74 -0
- data/lib/motor/alerts.rb +52 -0
- data/lib/motor/api_configs.rb +41 -0
- data/lib/motor/api_query/apply_scope.rb +44 -0
- data/lib/motor/api_query/build_json.rb +171 -0
- data/lib/motor/api_query/build_meta.rb +20 -0
- data/lib/motor/api_query/filter.rb +125 -0
- data/lib/motor/api_query/paginate.rb +19 -0
- data/lib/motor/api_query/search.rb +60 -0
- data/lib/motor/api_query/sort.rb +64 -0
- data/lib/motor/api_query.rb +24 -0
- data/lib/motor/assets.rb +62 -0
- data/lib/motor/build_schema/active_storage_attachment_schema.rb +125 -0
- data/lib/motor/build_schema/adjust_devise_model_schema.rb +60 -0
- data/lib/motor/build_schema/apply_permissions.rb +64 -0
- data/lib/motor/build_schema/defaults.rb +66 -0
- data/lib/motor/build_schema/find_display_column.rb +65 -0
- data/lib/motor/build_schema/find_icon.rb +135 -0
- data/lib/motor/build_schema/find_searchable_columns.rb +33 -0
- data/lib/motor/build_schema/load_from_rails.rb +361 -0
- data/lib/motor/build_schema/merge_schema_configs.rb +157 -0
- data/lib/motor/build_schema/reorder_schema.rb +88 -0
- data/lib/motor/build_schema/utils.rb +31 -0
- data/lib/motor/build_schema.rb +125 -0
- data/lib/motor/cancan_utils/ability_patch.rb +31 -0
- data/lib/motor/cancan_utils/can_manage_all.rb +14 -0
- data/lib/motor/cancan_utils.rb +9 -0
- data/lib/motor/configs/build_configs_hash.rb +90 -0
- data/lib/motor/configs/build_ui_app_tag.rb +177 -0
- data/lib/motor/configs/load_from_cache.rb +110 -0
- data/lib/motor/configs/sync_from_file.rb +35 -0
- data/lib/motor/configs/sync_from_hash.rb +159 -0
- data/lib/motor/configs/sync_middleware.rb +72 -0
- data/lib/motor/configs/sync_with_remote.rb +47 -0
- data/lib/motor/configs/write_to_file.rb +36 -0
- data/lib/motor/configs.rb +39 -0
- data/lib/motor/dashboards/persistance.rb +73 -0
- data/lib/motor/dashboards.rb +8 -0
- data/lib/motor/forms/persistance.rb +93 -0
- data/lib/motor/forms.rb +8 -0
- data/lib/motor/hash_serializer.rb +21 -0
- data/lib/motor/net_http_utils.rb +50 -0
- data/lib/motor/notes/notify_mentions.rb +71 -0
- data/lib/motor/notes/notify_reminder.rb +48 -0
- data/lib/motor/notes/persist.rb +36 -0
- data/lib/motor/notes/reminders_scheduler.rb +39 -0
- data/lib/motor/notes/tags.rb +34 -0
- data/lib/motor/notes.rb +12 -0
- data/lib/motor/queries/persistance.rb +90 -0
- data/lib/motor/queries/postgresql_exec_query.rb +28 -0
- data/lib/motor/queries/render_sql_template.rb +61 -0
- data/lib/motor/queries/run_query.rb +289 -0
- data/lib/motor/queries.rb +11 -0
- data/lib/motor/railtie.rb +11 -0
- data/lib/motor/resources/custom_sql_columns_cache.rb +17 -0
- data/lib/motor/resources/fetch_configured_model.rb +269 -0
- data/lib/motor/resources/persist_configs.rb +232 -0
- data/lib/motor/resources.rb +19 -0
- data/lib/motor/slack/client.rb +62 -0
- data/lib/motor/slack.rb +16 -0
- data/lib/motor/tags.rb +32 -0
- data/lib/motor/tasks/motor.rake +54 -0
- data/lib/motor/version.rb +5 -0
- data/lib/motor-admin-cstham8.rb +3 -0
- data/lib/motor.rb +87 -0
- data/ui/dist/manifest.json +1990 -0
- metadata +303 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module BuildSchema
|
5
|
+
module FindDisplayColumn
|
6
|
+
DISPLAY_NAMES = Set.new(
|
7
|
+
%w[
|
8
|
+
name
|
9
|
+
full_name
|
10
|
+
fullname
|
11
|
+
last_name
|
12
|
+
lastname
|
13
|
+
first_name
|
14
|
+
firstname
|
15
|
+
fname
|
16
|
+
lname
|
17
|
+
sname
|
18
|
+
company
|
19
|
+
domain
|
20
|
+
website
|
21
|
+
title
|
22
|
+
phone
|
23
|
+
phone_number
|
24
|
+
email
|
25
|
+
phone
|
26
|
+
filename
|
27
|
+
file_name
|
28
|
+
url
|
29
|
+
make
|
30
|
+
brand
|
31
|
+
manufacturer
|
32
|
+
model
|
33
|
+
address
|
34
|
+
code
|
35
|
+
]
|
36
|
+
).freeze
|
37
|
+
|
38
|
+
DISPLAY_NAME_REGEXP = Regexp.new(Regexp.union(DISPLAY_NAMES.to_a).source, Regexp::IGNORECASE)
|
39
|
+
|
40
|
+
module_function
|
41
|
+
|
42
|
+
def call(model)
|
43
|
+
column_names = fetch_column_names(model)
|
44
|
+
|
45
|
+
select_column_name(column_names)
|
46
|
+
end
|
47
|
+
|
48
|
+
def select_column_name(column_names)
|
49
|
+
name = column_names.find { |column_name| column_name.in?(DISPLAY_NAMES) }
|
50
|
+
name ||= column_names.find { |column_name| column_name.match?(DISPLAY_NAME_REGEXP) }
|
51
|
+
|
52
|
+
name
|
53
|
+
end
|
54
|
+
|
55
|
+
def fetch_column_names(model)
|
56
|
+
model.columns.map do |column|
|
57
|
+
next unless column.type.in?(BuildSchema::SEARCHABLE_COLUMN_TYPES)
|
58
|
+
next if column.try(:array?)
|
59
|
+
|
60
|
+
column.name
|
61
|
+
end.compact
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module FindIcon
|
5
|
+
ICONS_MAP = {
|
6
|
+
'audit' => 'history',
|
7
|
+
'block' => 'ban',
|
8
|
+
'blocked' => 'ban',
|
9
|
+
'blacklisted' => 'ban',
|
10
|
+
'blocklisted' => 'ban',
|
11
|
+
'banned' => 'ban',
|
12
|
+
'website' => 'world',
|
13
|
+
'location' => 'gps',
|
14
|
+
'photo' => 'photo',
|
15
|
+
'image' => 'photo',
|
16
|
+
'screenshot' => 'photo',
|
17
|
+
'picture' => 'photo',
|
18
|
+
'video' => 'video',
|
19
|
+
'file' => 'file',
|
20
|
+
'pdf' => 'file-text',
|
21
|
+
'ad' => 'ad',
|
22
|
+
'shop' => 'building-store',
|
23
|
+
'store' => 'building-store',
|
24
|
+
'seller' => 'building-store',
|
25
|
+
'app' => 'apps',
|
26
|
+
'book' => 'book',
|
27
|
+
'movie' => 'movie',
|
28
|
+
'country' => 'map',
|
29
|
+
'api' => 'tool',
|
30
|
+
'config' => 'tool',
|
31
|
+
'key' => 'key',
|
32
|
+
'phone' => 'phone',
|
33
|
+
'call' => 'phone',
|
34
|
+
'cell' => 'phone',
|
35
|
+
'token' => 'key',
|
36
|
+
'secret' => 'lock',
|
37
|
+
'automation' => 'manual-gearbox',
|
38
|
+
'workflow' => 'manual-gearbox',
|
39
|
+
'relationship' => 'hierarchy',
|
40
|
+
'person' => 'user',
|
41
|
+
'people' => 'users',
|
42
|
+
'mail' => 'mail',
|
43
|
+
'user' => 'user',
|
44
|
+
'rating' => 'star',
|
45
|
+
'article' => 'blockquote',
|
46
|
+
'podcast' => 'headphones',
|
47
|
+
'payment' => 'cash',
|
48
|
+
'log' => 'history',
|
49
|
+
'history' => 'history',
|
50
|
+
'rule' => 'manual-gearbox',
|
51
|
+
'tracking' => 'zoom-question',
|
52
|
+
'github' => 'brand-github',
|
53
|
+
'tag' => 'hash',
|
54
|
+
'category' => 'hash',
|
55
|
+
'label' => 'hash',
|
56
|
+
'twitter' => 'brand-twitter',
|
57
|
+
'facebook' => 'brand-facebook',
|
58
|
+
'google' => 'brand-google',
|
59
|
+
'renewal' => 'repeat',
|
60
|
+
'sales' => 'report-money',
|
61
|
+
'telegram' => 'brand-telegram',
|
62
|
+
'whatsapp' => 'brand-whatsapp',
|
63
|
+
'survey' => 'checkbox',
|
64
|
+
'subcategory' => 'hash',
|
65
|
+
'type' => 'hash',
|
66
|
+
'model' => 'hash',
|
67
|
+
'taxon' => 'hash',
|
68
|
+
'affiliate' => 'affiliate',
|
69
|
+
'chat' => 'message-circle',
|
70
|
+
'message' => 'messages',
|
71
|
+
'poll' => 'messages',
|
72
|
+
'feedpack' => 'messages',
|
73
|
+
'attachment' => 'paperclip',
|
74
|
+
'certificate' => 'certificate',
|
75
|
+
'approval' => 'certificate',
|
76
|
+
'bank' => 'building-bank',
|
77
|
+
'bridge' => 'building-bridge',
|
78
|
+
'car' => 'car',
|
79
|
+
'vehicle' => 'car',
|
80
|
+
'analytic' => 'device-analytics',
|
81
|
+
'stat' => 'device-analytics',
|
82
|
+
'statistic' => 'device-analytics',
|
83
|
+
'link' => 'link',
|
84
|
+
'slug' => 'link',
|
85
|
+
'city' => 'building-skyscraper',
|
86
|
+
'region' => 'map',
|
87
|
+
'district' => 'building-community',
|
88
|
+
'community' => 'building-community',
|
89
|
+
'activity' => 'activity',
|
90
|
+
'invoice' => 'file-invoice',
|
91
|
+
'settlement' => 'file-invoice',
|
92
|
+
'state' => 'map',
|
93
|
+
'note' => 'note',
|
94
|
+
'order' => 'truck-delivery',
|
95
|
+
'lead' => 'building-lighthouse',
|
96
|
+
'error' => 'file-alert',
|
97
|
+
'page' => 'brand-pagekit',
|
98
|
+
'date' => 'calendar-event',
|
99
|
+
'customer' => 'users',
|
100
|
+
'client' => 'users',
|
101
|
+
'ticket' => 'ticket',
|
102
|
+
'contact' => 'users',
|
103
|
+
'member' => 'users',
|
104
|
+
'admin' => 'user-check',
|
105
|
+
'notification' => 'bell',
|
106
|
+
'alert' => 'bell',
|
107
|
+
'team' => 'users',
|
108
|
+
'folder' => 'folder',
|
109
|
+
'subscriber' => 'user-plus',
|
110
|
+
'product' => 'building-store',
|
111
|
+
'html' => 'code',
|
112
|
+
'stripe' => 'brand-stripe',
|
113
|
+
'email' => 'mail',
|
114
|
+
'status' => 'hash'
|
115
|
+
}.freeze
|
116
|
+
|
117
|
+
DEFAULT_ICON = BuildSchema::DEFAULT_ICON
|
118
|
+
|
119
|
+
module_function
|
120
|
+
|
121
|
+
def call(text)
|
122
|
+
text = text.underscore
|
123
|
+
icon = ICONS_MAP[text] || ICONS_MAP[text.singularize]
|
124
|
+
|
125
|
+
icon ||=
|
126
|
+
text.split(%r{[\s_/]}).reverse.reduce(nil) do |_acc, word|
|
127
|
+
found_icon = ICONS_MAP[word] || ICONS_MAP[word.singularize]
|
128
|
+
|
129
|
+
break found_icon if found_icon
|
130
|
+
end
|
131
|
+
|
132
|
+
icon || DEFAULT_ICON
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module BuildSchema
|
5
|
+
module FindSearchableColumns
|
6
|
+
SELECT_COLUMNS_AMOUNT = 2
|
7
|
+
COLUMN_TYPES = BuildSchema::SEARCHABLE_COLUMN_TYPES
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def call(model)
|
12
|
+
columns = find_searchable_columns(model)
|
13
|
+
|
14
|
+
selected_columns =
|
15
|
+
columns.grep(BuildSchema::FindDisplayColumn::DISPLAY_NAME_REGEXP).presence
|
16
|
+
|
17
|
+
selected_columns ||= columns.first(SELECT_COLUMNS_AMOUNT)
|
18
|
+
|
19
|
+
([model.primary_key] + selected_columns).compact.sort
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_searchable_columns(model)
|
23
|
+
model.columns.map do |column|
|
24
|
+
next unless column.type.in?(COLUMN_TYPES)
|
25
|
+
next if column.try(:array?)
|
26
|
+
next if model.validators_on(column.name).any?(ActiveModel::Validations::InclusionValidator)
|
27
|
+
|
28
|
+
column.name
|
29
|
+
end.compact
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,361 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module BuildSchema
|
5
|
+
module LoadFromRails
|
6
|
+
MUTEX = Mutex.new
|
7
|
+
UNIFIED_TYPES = ActiveRecordUtils::Types::UNIFIED_TYPES
|
8
|
+
|
9
|
+
I18N_SCOPES_KEY = 'activerecord.scopes'
|
10
|
+
|
11
|
+
ACTION_TEXT_REFLECTION_PREFIX = 'rich_text_'
|
12
|
+
ACTION_TEXT_COLUMN_SUFFIX = '_body'
|
13
|
+
ACTION_TEXT_SCOPE_PREFIX = 'with_rich_text_'
|
14
|
+
ACTIVE_STORAGE_SCOPE_PREFIX = 'with_attached_'
|
15
|
+
|
16
|
+
DEFAULT_CURRENCY_FORMAT_HASH = {
|
17
|
+
currency: 'USD',
|
18
|
+
currency_base: 'units'
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
module_function
|
22
|
+
|
23
|
+
def call
|
24
|
+
models.map do |model|
|
25
|
+
model = Object.const_get(model.name)
|
26
|
+
|
27
|
+
next unless model.table_exists?
|
28
|
+
|
29
|
+
schema = build_model_schema(model)
|
30
|
+
|
31
|
+
if model.respond_to?(:devise_modules)
|
32
|
+
Motor::BuildSchema::AdjustDeviseModelSchema.call(schema, model.devise_modules)
|
33
|
+
end
|
34
|
+
|
35
|
+
schema
|
36
|
+
rescue StandardError, NotImplementedError => e
|
37
|
+
Rails.logger.error(e)
|
38
|
+
|
39
|
+
next
|
40
|
+
end.compact.uniq
|
41
|
+
end
|
42
|
+
|
43
|
+
def models
|
44
|
+
eager_load_models!
|
45
|
+
|
46
|
+
models = ActiveRecord::Base.descendants.reject { |k| k.abstract_class || k.anonymous? }
|
47
|
+
|
48
|
+
models -= Motor::ApplicationRecord.descendants
|
49
|
+
models -= [Motor::Audit]
|
50
|
+
models -= [ActiveRecord::SchemaMigration] if defined?(ActiveRecord::SchemaMigration)
|
51
|
+
models -= [ActiveRecord::InternalMetadata] if defined?(ActiveRecord::InternalMetadata)
|
52
|
+
models -= [ActiveStorage::Blob] if defined?(ActiveStorage::Blob)
|
53
|
+
models -= [ActionText::RichText] if defined?(ActionText::RichText)
|
54
|
+
models -= [ActiveStorage::VariantRecord] if defined?(ActiveStorage::VariantRecord)
|
55
|
+
|
56
|
+
models
|
57
|
+
end
|
58
|
+
|
59
|
+
# rubocop:disable Metrics/MethodLength
|
60
|
+
def build_model_schema(model)
|
61
|
+
model_name = model.name
|
62
|
+
|
63
|
+
return Motor::BuildSchema::ActiveStorageAttachmentSchema.call if model_name == 'ActiveStorage::Attachment'
|
64
|
+
|
65
|
+
{
|
66
|
+
name: model_name.underscore,
|
67
|
+
slug: Utils.slugify(model),
|
68
|
+
table_name: model.table_name,
|
69
|
+
class_name: model_name,
|
70
|
+
primary_key: model.primary_key,
|
71
|
+
display_name: model.model_name.human(count: :many, default: model_name.titleize.pluralize),
|
72
|
+
display_column: FindDisplayColumn.call(model),
|
73
|
+
columns: fetch_columns(model),
|
74
|
+
associations: fetch_associations(model),
|
75
|
+
icon: Motor::FindIcon.call(model_name),
|
76
|
+
scopes: fetch_scopes(model),
|
77
|
+
actions: BuildSchema::Defaults.actions,
|
78
|
+
tabs: BuildSchema::Defaults.tabs,
|
79
|
+
searchable_columns: FindSearchableColumns.call(model),
|
80
|
+
custom_sql: nil,
|
81
|
+
visible: true,
|
82
|
+
display_primary_key: true,
|
83
|
+
preferences: {
|
84
|
+
display_as: 'table'
|
85
|
+
}
|
86
|
+
}.with_indifferent_access
|
87
|
+
end
|
88
|
+
# rubocop:enable Metrics/MethodLength
|
89
|
+
|
90
|
+
def fetch_scopes(model)
|
91
|
+
model.defined_scopes.map do |scope_name|
|
92
|
+
scope_name = scope_name.to_s
|
93
|
+
|
94
|
+
next if scope_name.starts_with?(ACTIVE_STORAGE_SCOPE_PREFIX)
|
95
|
+
next if scope_name.starts_with?(ACTION_TEXT_SCOPE_PREFIX)
|
96
|
+
|
97
|
+
{
|
98
|
+
name: scope_name,
|
99
|
+
display_name: I18n.t(scope_name,
|
100
|
+
scope: [I18N_SCOPES_KEY, model.name.underscore].join('.'),
|
101
|
+
default: scope_name.humanize),
|
102
|
+
scope_type: DEFAULT_TYPE,
|
103
|
+
visible: true,
|
104
|
+
preferences: {}
|
105
|
+
}
|
106
|
+
end.compact
|
107
|
+
end
|
108
|
+
|
109
|
+
def fetch_columns(model)
|
110
|
+
default_attrs = model.new.attributes
|
111
|
+
|
112
|
+
reference_columns = fetch_reference_columns(model)
|
113
|
+
|
114
|
+
table_columns =
|
115
|
+
model.columns.map do |column|
|
116
|
+
next if reference_columns.find { |c| c[:name] == column.name }
|
117
|
+
|
118
|
+
build_table_column(column, model, default_attrs)
|
119
|
+
end.compact
|
120
|
+
|
121
|
+
reference_columns + table_columns
|
122
|
+
end
|
123
|
+
|
124
|
+
def build_table_column(column, model, default_attrs)
|
125
|
+
access_type =
|
126
|
+
if model.primary_key == column.name
|
127
|
+
ColumnAccessTypes::READ_ONLY
|
128
|
+
else
|
129
|
+
COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE)
|
130
|
+
end
|
131
|
+
|
132
|
+
{
|
133
|
+
name: column.name,
|
134
|
+
display_name: Utils.humanize_column_name(model.human_attribute_name(column.name)),
|
135
|
+
column_type: fetch_column_type(column, model),
|
136
|
+
column_source: ColumnSources::TABLE,
|
137
|
+
is_array: column.try(:array?),
|
138
|
+
access_type: access_type,
|
139
|
+
default_value: default_attrs[column.name],
|
140
|
+
validators: fetch_validators(model, column.name),
|
141
|
+
reference: nil,
|
142
|
+
format: fetch_format_hash(column, model),
|
143
|
+
description: '',
|
144
|
+
virtual: false
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
def fetch_format_hash(column, model)
|
149
|
+
return DEFAULT_CURRENCY_FORMAT_HASH if column.name == 'price'
|
150
|
+
|
151
|
+
inclusion_validator, = model.validators_on(column.name).grep(ActiveModel::Validations::InclusionValidator)
|
152
|
+
|
153
|
+
return { select_options: inclusion_validator.send(:delimiter) } if inclusion_validator
|
154
|
+
|
155
|
+
enum = model.defined_enums[column.name]
|
156
|
+
|
157
|
+
return { select_options: enum.keys } if enum
|
158
|
+
|
159
|
+
return {} if column.name == 'year'
|
160
|
+
|
161
|
+
return { number_format: true } if !column.name == 'id' &&
|
162
|
+
!column.name.match?(/_(?:id|year)\z/) &&
|
163
|
+
%i[integer float].include?(column.type)
|
164
|
+
|
165
|
+
{}
|
166
|
+
end
|
167
|
+
|
168
|
+
def fetch_column_type(column, model)
|
169
|
+
return ColumnTypes::CURRENCY if column.name == 'price'
|
170
|
+
return ColumnTypes::COLOR if %w[hex color].include?(column.name)
|
171
|
+
return ColumnTypes::TAG if model.defined_enums[column.name]
|
172
|
+
return ColumnTypes::TAG if model.validators_on(column.name).any?(ActiveModel::Validations::InclusionValidator)
|
173
|
+
return ColumnTypes::RICHTEXT if column.name.ends_with?('_html')
|
174
|
+
return ColumnTypes::COLOR if column.name.match?(/_(color|hex)\z/)
|
175
|
+
|
176
|
+
UNIFIED_TYPES[column.type.to_s] || column.type.to_s
|
177
|
+
end
|
178
|
+
|
179
|
+
def fetch_reference_columns(model)
|
180
|
+
default_attrs = model.new.attributes
|
181
|
+
|
182
|
+
model.reflections.map do |name, ref|
|
183
|
+
next if !ref.has_one? && !ref.belongs_to?
|
184
|
+
|
185
|
+
unless ref.polymorphic?
|
186
|
+
begin
|
187
|
+
next if ref.klass.name == 'ActiveStorage::Blob'
|
188
|
+
rescue StandardError => e
|
189
|
+
Rails.logger.error(e)
|
190
|
+
|
191
|
+
next
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
build_reflection_column(name, model, ref, default_attrs)
|
196
|
+
end.compact
|
197
|
+
end
|
198
|
+
|
199
|
+
# rubocop:disable Metrics/AbcSize
|
200
|
+
def build_reflection_column(name, model, ref, default_attrs)
|
201
|
+
if !ref.polymorphic? && ref.klass.name == 'ActionText::RichText'
|
202
|
+
return build_action_text_column(name, model, ref)
|
203
|
+
end
|
204
|
+
|
205
|
+
column_name = ref.belongs_to? ? ref.foreign_key.to_s : name
|
206
|
+
is_attachment = !ref.polymorphic? && ref.klass.name == 'ActiveStorage::Attachment'
|
207
|
+
access_type = ref.belongs_to? || is_attachment ? ColumnAccessTypes::READ_WRITE : ColumnAccessTypes::READ_ONLY
|
208
|
+
column_type = is_attachment ? ColumnTypes::FILE : ColumnTypes::REFERENCE
|
209
|
+
column_source = model.columns_hash[column_name] ? ColumnSources::TABLE : ColumnSources::REFLECTION
|
210
|
+
|
211
|
+
{
|
212
|
+
name: column_name,
|
213
|
+
display_name: model.human_attribute_name(name),
|
214
|
+
column_type: column_type,
|
215
|
+
column_source: column_source,
|
216
|
+
access_type: access_type,
|
217
|
+
default_value: default_attrs[column_name],
|
218
|
+
validators: fetch_validators(model, column_name, ref),
|
219
|
+
format: {},
|
220
|
+
reference: build_reference(model, name, ref),
|
221
|
+
virtual: false
|
222
|
+
}
|
223
|
+
end
|
224
|
+
# rubocop:enable Metrics/AbcSize
|
225
|
+
|
226
|
+
def build_action_text_column(name, model, ref)
|
227
|
+
name = name.delete_prefix(ACTION_TEXT_REFLECTION_PREFIX)
|
228
|
+
|
229
|
+
{
|
230
|
+
name: name + ACTION_TEXT_COLUMN_SUFFIX,
|
231
|
+
display_name: model.human_attribute_name(name),
|
232
|
+
column_type: ColumnTypes::RICHTEXT,
|
233
|
+
column_source: ColumnSources::REFLECTION,
|
234
|
+
access_type: ColumnAccessTypes::READ_WRITE,
|
235
|
+
default_value: '',
|
236
|
+
validators: fetch_validators(model, name, ref),
|
237
|
+
format: {},
|
238
|
+
reference: nil,
|
239
|
+
virtual: true
|
240
|
+
}
|
241
|
+
end
|
242
|
+
|
243
|
+
def build_reference(model, name, reflection)
|
244
|
+
primary_key = reflection.polymorphic? ? 'id' : reflection.join_primary_key
|
245
|
+
foreign_key = reflection.join_foreign_key
|
246
|
+
|
247
|
+
primary_key, foreign_key = foreign_key, primary_key if reflection.has_one?
|
248
|
+
|
249
|
+
{
|
250
|
+
name: name,
|
251
|
+
display_name: model.human_attribute_name(name),
|
252
|
+
model_name: reflection.polymorphic? ? nil : reflection.klass.name.underscore,
|
253
|
+
reference_type: reflection.belongs_to? ? 'belongs_to' : 'has_one',
|
254
|
+
foreign_key: foreign_key,
|
255
|
+
primary_key: primary_key,
|
256
|
+
options: reflection.options.slice(:through, :source),
|
257
|
+
polymorphic: reflection.polymorphic?,
|
258
|
+
virtual: false
|
259
|
+
}
|
260
|
+
end
|
261
|
+
|
262
|
+
def fetch_associations(model)
|
263
|
+
model.reflections.map do |name, ref|
|
264
|
+
next if ref.has_one? || ref.belongs_to?
|
265
|
+
next unless valid_reflection?(ref)
|
266
|
+
|
267
|
+
model_class = ref.klass
|
268
|
+
|
269
|
+
next if model_class.name == 'ActiveStorage::Blob'
|
270
|
+
|
271
|
+
{
|
272
|
+
name: name,
|
273
|
+
display_name: model.human_attribute_name(name),
|
274
|
+
slug: name.underscore,
|
275
|
+
model_name: model_class.name.underscore,
|
276
|
+
foreign_key: ref.join_primary_key,
|
277
|
+
primary_key: ref.join_foreign_key,
|
278
|
+
polymorphic: ref.options[:as].present?,
|
279
|
+
icon: Motor::FindIcon.call(name),
|
280
|
+
options: ref.options.slice(:through, :source),
|
281
|
+
virtual: false,
|
282
|
+
visible: true
|
283
|
+
}
|
284
|
+
end.compact
|
285
|
+
end
|
286
|
+
|
287
|
+
def fetch_validators(model, column_name, reflection = nil)
|
288
|
+
validators =
|
289
|
+
if reflection&.belongs_to? && !reflection.options[:optional]
|
290
|
+
[{ required: true }]
|
291
|
+
else
|
292
|
+
[]
|
293
|
+
end
|
294
|
+
|
295
|
+
enum = model.defined_enums[column_name]
|
296
|
+
|
297
|
+
validators += [{ includes: enum.keys }] if enum
|
298
|
+
|
299
|
+
validators += model.validators_on(column_name).map do |validator|
|
300
|
+
next if validator.options[:if] || validator.options[:unless]
|
301
|
+
|
302
|
+
build_validator_hash(validator)
|
303
|
+
end.compact
|
304
|
+
|
305
|
+
validators.uniq
|
306
|
+
end
|
307
|
+
|
308
|
+
def build_validator_hash(validator)
|
309
|
+
options = validator.options.reject { |_, v| v.is_a?(Proc) || v.is_a?(Symbol) }
|
310
|
+
|
311
|
+
case validator
|
312
|
+
when ActiveModel::Validations::InclusionValidator
|
313
|
+
{ includes: validator.send(:delimiter) }
|
314
|
+
when ActiveRecord::Validations::PresenceValidator
|
315
|
+
{ required: true }
|
316
|
+
when ActiveRecord::Validations::LengthValidator
|
317
|
+
{ length: normalize_length_validation_options(options) }
|
318
|
+
when ActiveModel::Validations::NumericalityValidator
|
319
|
+
{ numeric: options }
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def normalize_length_validation_options(options)
|
324
|
+
return options if options[:in].blank?
|
325
|
+
|
326
|
+
in_range = options[:in]
|
327
|
+
|
328
|
+
options.merge(in: in_range.minmax)
|
329
|
+
end
|
330
|
+
|
331
|
+
def valid_reflection?(reflection)
|
332
|
+
reflection.klass
|
333
|
+
reflection.foreign_key
|
334
|
+
|
335
|
+
true
|
336
|
+
rescue StandardError => e
|
337
|
+
Rails.logger.error(e)
|
338
|
+
|
339
|
+
false
|
340
|
+
end
|
341
|
+
|
342
|
+
def eager_load_models!
|
343
|
+
MUTEX.synchronize do
|
344
|
+
if Rails::VERSION::MAJOR > 5 && defined?(Zeitwerk::Loader)
|
345
|
+
Zeitwerk::Loader.eager_load_all
|
346
|
+
else
|
347
|
+
Rails.application.eager_load!
|
348
|
+
end
|
349
|
+
|
350
|
+
ActiveRecord::Base.descendants.each do |model|
|
351
|
+
model.reflections.each_value do |ref|
|
352
|
+
ref.klass
|
353
|
+
rescue StandardError
|
354
|
+
next
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|