Module: Motor::BuildSchema::LoadFromRails
- Defined in:
- lib/motor/build_schema/load_from_rails.rb
Constant Summary collapse
- MUTEX =
Mutex.new
- UNIFIED_TYPES =
ActiveRecordUtils::Types::UNIFIED_TYPES
- I18N_SCOPES_KEY =
'activerecord.scopes'
- ACTION_TEXT_REFLECTION_PREFIX =
'rich_text_'
- ACTION_TEXT_COLUMN_SUFFIX =
'_body'
- ACTION_TEXT_SCOPE_PREFIX =
'with_rich_text_'
- ACTIVE_STORAGE_SCOPE_PREFIX =
'with_attached_'
- DEFAULT_CURRENCY_FORMAT_HASH =
{ currency: 'USD', currency_base: 'units' }.freeze
Class Method Summary collapse
-
.build_action_text_column(name, model, ref) ⇒ Object
rubocop:enable Metrics/AbcSize.
- .build_model_schema(model) ⇒ Object
- .build_reference(model, name, reflection) ⇒ Object
-
.build_reflection_column(name, model, ref, default_attrs) ⇒ Object
rubocop:disable Metrics/AbcSize.
- .build_table_column(column, model, default_attrs) ⇒ Object
- .build_validator_hash(validator) ⇒ Object
- .call ⇒ Object
- .eager_load_models! ⇒ Object
-
.fetch_associations(model) ⇒ Object
rubocop:disable Metrics/AbcSize.
- .fetch_column_type(column, model) ⇒ Object
- .fetch_columns(model) ⇒ Object
- .fetch_format_hash(column, model) ⇒ Object
- .fetch_reference_columns(model) ⇒ Object
- .fetch_scopes(model) ⇒ Object
-
.fetch_validators(model, column_name, reflection = nil) ⇒ Object
rubocop:enable Metrics/AbcSize.
- .models ⇒ Object
- .normalize_length_validation_options(options) ⇒ Object
- .valid_reflection?(reflection) ⇒ Boolean
Class Method Details
.build_action_text_column(name, model, ref) ⇒ Object
rubocop:enable Metrics/AbcSize
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 218 def build_action_text_column(name, model, ref) name = name.delete_prefix(ACTION_TEXT_REFLECTION_PREFIX) { name: name + ACTION_TEXT_COLUMN_SUFFIX, display_name: model.human_attribute_name(name), column_type: ColumnTypes::RICHTEXT, column_source: ColumnSources::REFLECTION, access_type: ColumnAccessTypes::READ_WRITE, default_value: '', validators: fetch_validators(model, name, ref), format: {}, reference: nil, virtual: true } end |
.build_model_schema(model) ⇒ Object
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 59 def build_model_schema(model) model_name = model.name return Motor::BuildSchema::ActiveStorageAttachmentSchema.call if model_name == 'ActiveStorage::Attachment' { name: model_name.underscore, slug: Utils.slugify(model), table_name: model.table_name, class_name: model.name, primary_key: model.primary_key, display_name: model.model_name.human(count: :many, default: model_name.titleize.pluralize), display_column: FindDisplayColumn.call(model), columns: fetch_columns(model), associations: fetch_associations(model), icon: Motor::FindIcon.call(model_name), scopes: fetch_scopes(model), actions: BuildSchema::Defaults.actions, tabs: BuildSchema::Defaults.tabs, custom_sql: nil, visible: true, display_primary_key: true }.with_indifferent_access end |
.build_reference(model, name, reflection) ⇒ Object
235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 235 def build_reference(model, name, reflection) { name: name, display_name: model.human_attribute_name(name), model_name: reflection.polymorphic? ? nil : reflection.klass.name.underscore, reference_type: reflection.belongs_to? ? 'belongs_to' : 'has_one', foreign_key: reflection.join_foreign_key, primary_key: reflection.polymorphic? ? 'id' : reflection.join_primary_key, options: reflection..slice(:through, :source), polymorphic: reflection.polymorphic?, virtual: false } end |
.build_reflection_column(name, model, ref, default_attrs) ⇒ Object
rubocop:disable Metrics/AbcSize
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 192 def build_reflection_column(name, model, ref, default_attrs) if !ref.polymorphic? && ref.klass.name == 'ActionText::RichText' return build_action_text_column(name, model, ref) end column_name = ref.belongs_to? ? ref.foreign_key.to_s : name = !ref.polymorphic? && ref.klass.name == 'ActiveStorage::Attachment' access_type = ref.belongs_to? || ? ColumnAccessTypes::READ_WRITE : ColumnAccessTypes::READ_ONLY column_type = ? ColumnTypes::FILE : ColumnTypes::REFERENCE column_source = model.columns_hash[column_name] ? ColumnSources::TABLE : ColumnSources::REFLECTION { name: column_name, display_name: model.human_attribute_name(name), column_type: column_type, column_source: column_source, access_type: access_type, default_value: default_attrs[column_name], validators: fetch_validators(model, column_name, ref), format: {}, reference: build_reference(model, name, ref), virtual: false } end |
.build_table_column(column, model, default_attrs) ⇒ Object
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 118 def build_table_column(column, model, default_attrs) access_type = if model.primary_key == column.name ColumnAccessTypes::READ_ONLY else COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE) end { name: column.name, display_name: Utils.humanize_column_name(model.human_attribute_name(column.name)), column_type: fetch_column_type(column, model), column_source: ColumnSources::TABLE, is_array: column.array?, access_type: access_type, default_value: default_attrs[column.name], validators: fetch_validators(model, column.name), reference: nil, format: fetch_format_hash(column, model), virtual: false } end |
.build_validator_hash(validator) ⇒ Object
295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 295 def build_validator_hash(validator) = validator..reject { |_, v| v.is_a?(Proc) || v.is_a?(Symbol) } case validator when ActiveModel::Validations::InclusionValidator { includes: validator.send(:delimiter) } when ActiveRecord::Validations::PresenceValidator { required: true } when ActiveRecord::Validations::LengthValidator { length: () } when ActiveModel::Validations::NumericalityValidator { numeric: } end end |
.call ⇒ Object
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 23 def call models.map do |model| model = Object.const_get(model.name) next unless model.table_exists? schema = build_model_schema(model) if model.respond_to?(:devise_modules) Motor::BuildSchema::AdjustDeviseModelSchema.call(schema, model.devise_modules) end schema rescue StandardError, NotImplementedError => e Rails.logger.error(e) next end.compact.uniq end |
.eager_load_models! ⇒ Object
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 329 def eager_load_models! MUTEX.synchronize do if Rails::VERSION::MAJOR > 5 && defined?(Zeitwerk::Loader) Zeitwerk::Loader.eager_load_all else Rails.application.eager_load! end ActiveRecord::Base.descendants.each do |model| model.reflections.each do |_, ref| ref.klass rescue StandardError next end end end end |
.fetch_associations(model) ⇒ Object
rubocop:disable Metrics/AbcSize
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 250 def fetch_associations(model) model.reflections.map do |name, ref| next if ref.has_one? || ref.belongs_to? next unless valid_reflection?(ref) model_class = ref.klass next if model_class.name == 'ActiveStorage::Blob' { name: name, display_name: model.human_attribute_name(name), slug: name.underscore, model_name: model_class.name.underscore, foreign_key: ref.join_primary_key, primary_key: ref.join_foreign_key, polymorphic: ref.[:as].present?, icon: Motor::FindIcon.call(name), options: ref..slice(:through, :source), virtual: false, visible: true } end.compact end |
.fetch_column_type(column, model) ⇒ Object
160 161 162 163 164 165 166 167 168 169 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 160 def fetch_column_type(column, model) return ColumnTypes::CURRENCY if column.name == 'price' return ColumnTypes::COLOR if %w[hex color].include?(column.name) return ColumnTypes::TAG if model.defined_enums[column.name] return ColumnTypes::TAG if model.validators_on(column.name).any?(ActiveModel::Validations::InclusionValidator) return ColumnTypes::RICHTEXT if column.name.ends_with?('_html') return ColumnTypes::COLOR if column.name.match?(/_(color|hex)\z/) UNIFIED_TYPES[column.type.to_s] || column.type.to_s end |
.fetch_columns(model) ⇒ Object
103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 103 def fetch_columns(model) default_attrs = model.new.attributes reference_columns = fetch_reference_columns(model) table_columns = model.columns.map do |column| next if reference_columns.find { |c| c[:name] == column.name } build_table_column(column, model, default_attrs) end.compact reference_columns + table_columns end |
.fetch_format_hash(column, model) ⇒ Object
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 141 def fetch_format_hash(column, model) return DEFAULT_CURRENCY_FORMAT_HASH if column.name == 'price' inclusion_validator, = model.validators_on(column.name).grep(ActiveModel::Validations::InclusionValidator) return { select_options: inclusion_validator.send(:delimiter) } if inclusion_validator enum = model.defined_enums[column.name] return { select_options: enum.keys } if enum return {} if column.name == 'year' return { number_format: true } if !column.name.match?(/_(?:id|year)\z/) && %i[integer float].include?(column.type) {} end |
.fetch_reference_columns(model) ⇒ Object
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 171 def fetch_reference_columns(model) default_attrs = model.new.attributes model.reflections.map do |name, ref| next if !ref.has_one? && !ref.belongs_to? unless ref.polymorphic? begin next if ref.klass.name == 'ActiveStorage::Blob' rescue StandardError => e Rails.logger.error(e) next end end build_reflection_column(name, model, ref, default_attrs) end.compact end |
.fetch_scopes(model) ⇒ Object
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 84 def fetch_scopes(model) model.defined_scopes.map do |scope_name| scope_name = scope_name.to_s next if scope_name.starts_with?(ACTIVE_STORAGE_SCOPE_PREFIX) next if scope_name.starts_with?(ACTION_TEXT_SCOPE_PREFIX) { name: scope_name, display_name: I18n.t(scope_name, scope: [I18N_SCOPES_KEY, model.name.underscore].join('.'), default: scope_name.humanize), scope_type: DEFAULT_TYPE, visible: true, preferences: {} } end.compact end |
.fetch_validators(model, column_name, reflection = nil) ⇒ Object
rubocop:enable Metrics/AbcSize
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 276 def fetch_validators(model, column_name, reflection = nil) validators = if reflection&.belongs_to? && !reflection.[:optional] [{ required: true }] else [] end enum = model.defined_enums[column_name] validators += [{ includes: enum.keys }] if enum validators += model.validators_on(column_name).map do |validator| build_validator_hash(validator) end.compact validators.uniq end |
.models ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 43 def models eager_load_models! models = ActiveRecord::Base.descendants.reject { |k| k.abstract_class || k.anonymous? } models -= Motor::ApplicationRecord.descendants models -= [Motor::Audit] models -= [ActiveRecord::SchemaMigration] if defined?(ActiveRecord::SchemaMigration) models -= [ActiveRecord::InternalMetadata] if defined?(ActiveRecord::InternalMetadata) models -= [ActiveStorage::Blob] if defined?(ActiveStorage::Blob) models -= [ActionText::RichText] if defined?(ActionText::RichText) models -= [ActiveStorage::VariantRecord] if defined?(ActiveStorage::VariantRecord) models end |
.normalize_length_validation_options(options) ⇒ Object
310 311 312 313 314 315 316 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 310 def () return if [:in].blank? in_range = [:in] .merge(in: in_range.minmax) end |
.valid_reflection?(reflection) ⇒ Boolean
318 319 320 321 322 323 324 325 326 327 |
# File 'lib/motor/build_schema/load_from_rails.rb', line 318 def valid_reflection?(reflection) reflection.klass reflection.foreign_key true rescue StandardError => e Rails.logger.error(e) false end |