brick 1.0.57 → 1.0.60
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.
- checksums.yaml +4 -4
- data/lib/brick/config.rb +5 -0
- data/lib/brick/extensions.rb +187 -113
- data/lib/brick/frameworks/rails/engine.rb +53 -4
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +3 -0
- data/lib/generators/brick/migrations_generator.rb +31 -14
- data/lib/generators/brick/{model_generator.rb → models_generator.rb} +41 -32
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 741384ed503092151ed20f360abd8819fdf3b25fc4042e5aa953dae928acab6d
|
4
|
+
data.tar.gz: 11e88b4b7bdc51b9237e095fdaae1297741e1c315737e18d7194c3c84e937797
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: daa2950ea3ff1354c642949797a4ed7e7ed5c14cfcba4e5560572e997e4df43b537ecb12e84dc2d710b3e067870a91c69519ee41735ea7e9ada0d110ae2bd906
|
7
|
+
data.tar.gz: 3e74b2d21d72d84ea7286a7b8959b0abb293c50844b5e3ba4fce73cc4f8b5dd68fa7a50dc9753901f0b82b6a3196ab3fd282c4e709d0ae82af37e5a4e68488a9
|
data/lib/brick/config.rb
CHANGED
@@ -230,6 +230,11 @@ module Brick
|
|
230
230
|
@mutex.synchronize { @not_nullables = columns }
|
231
231
|
end
|
232
232
|
|
233
|
+
# Add status page showing all resources and what files have been built out for them
|
234
|
+
def add_status
|
235
|
+
true
|
236
|
+
end
|
237
|
+
|
233
238
|
# Add a special page to show references to non-existent records ("orphans")
|
234
239
|
def add_orphans
|
235
240
|
true
|
data/lib/brick/extensions.rb
CHANGED
@@ -373,6 +373,7 @@ module ActiveRecord
|
|
373
373
|
end
|
374
374
|
|
375
375
|
def brick_select(params, selects = nil, order_by = nil, translations = {}, join_array = ::Brick::JoinArray.new)
|
376
|
+
is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2'
|
376
377
|
is_distinct = nil
|
377
378
|
wheres = {}
|
378
379
|
params.each do |k, v|
|
@@ -397,12 +398,14 @@ module ActiveRecord
|
|
397
398
|
if selects&.empty? # Default to all columns
|
398
399
|
tbl_no_schema = table.name.split('.').last
|
399
400
|
columns.each do |col|
|
400
|
-
if (col_name = col.name) == 'class'
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
401
|
+
col_alias = ' AS _class' if (col_name = col.name) == 'class'
|
402
|
+
selects << if is_mysql
|
403
|
+
"`#{tbl_no_schema}`.`#{col_name}`#{col_alias}"
|
404
|
+
else
|
405
|
+
# Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
|
406
|
+
cast_as_text = '::text' if is_distinct && Brick.relations[klass.table_name]&.[](:cols)&.[](col.name)&.first&.start_with?('xml')
|
407
|
+
"\"#{tbl_no_schema}\".\"#{col_name}\"#{cast_as_text}#{col_alias}"
|
408
|
+
end
|
406
409
|
end
|
407
410
|
end
|
408
411
|
|
@@ -430,7 +433,11 @@ module ActiveRecord
|
|
430
433
|
if used_col_aliases.key?(col_alias = "_brfk_#{v.first}__#{sel_col.last}")
|
431
434
|
col_alias = "_brfk_#{v.first}__#{v1[idx][-2..-1].map(&:to_s).join('__')}"
|
432
435
|
end
|
433
|
-
selects <<
|
436
|
+
selects << if is_mysql
|
437
|
+
"`#{field_tbl_name}`.`#{sel_col.last}` AS `#{col_alias}`"
|
438
|
+
else
|
439
|
+
"\"#{field_tbl_name}\".\"#{sel_col.last}\"#{'::text' if is_xml} AS \"#{col_alias}\""
|
440
|
+
end
|
434
441
|
used_col_aliases[col_alias] = nil
|
435
442
|
v1[idx] << col_alias
|
436
443
|
end
|
@@ -439,7 +446,11 @@ module ActiveRecord
|
|
439
446
|
# Accommodate composite primary key by allowing id_col to come in as an array
|
440
447
|
((id_col = k1.primary_key).is_a?(Array) ? id_col : [id_col]).each do |id_part|
|
441
448
|
id_for_tables[v.first] << if id_part
|
442
|
-
selects <<
|
449
|
+
selects << if is_mysql
|
450
|
+
"#{"`#{tbl_name}`.`#{id_part}`"} AS `#{(id_alias = "_brfk_#{v.first}__#{id_part}")}`"
|
451
|
+
else
|
452
|
+
"#{"\"#{tbl_name}\".\"#{id_part}\""} AS \"#{(id_alias = "_brfk_#{v.first}__#{id_part}")}\""
|
453
|
+
end
|
443
454
|
id_alias
|
444
455
|
end
|
445
456
|
end
|
@@ -646,7 +657,7 @@ Module.class_exec do
|
|
646
657
|
full_class_name << "::#{self.name}" unless self == Object
|
647
658
|
full_class_name << "::#{plural_class_name.underscore.singularize.camelize}"
|
648
659
|
if (plural_class_name == 'BrickSwagger' ||
|
649
|
-
(::Brick.config.add_orphans && plural_class_name == 'BrickGem') ||
|
660
|
+
((::Brick.config.add_status || ::Brick.config.add_orphans) && plural_class_name == 'BrickGem') ||
|
650
661
|
model = self.const_get(full_class_name))
|
651
662
|
# if it's a controller and no match or a model doesn't really use the same table name, eager load all models and try to find a model class of the right name.
|
652
663
|
Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
|
@@ -667,45 +678,11 @@ Module.class_exec do
|
|
667
678
|
elsif ::Brick.enable_models?
|
668
679
|
# Custom inheritable Brick base model?
|
669
680
|
class_name = (inheritable_name = class_name)[5..-1] if class_name.start_with?('Brick')
|
670
|
-
|
671
|
-
# checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
|
672
|
-
|
673
|
-
if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
|
674
|
-
base_module != Object # ... or otherwise already in some namespace?
|
675
|
-
schema_name = [(singular_schema_name = name.underscore),
|
676
|
-
(schema_name = singular_schema_name.pluralize),
|
677
|
-
name,
|
678
|
-
name.pluralize].find { |s| Brick.db_schemas.include?(s) }
|
679
|
-
end
|
680
|
-
plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
|
681
|
-
# If it's namespaced then we turn the first part into what would be a schema name
|
682
|
-
singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('/', '.')
|
683
|
-
|
684
|
-
if base_model
|
685
|
-
schema_name = name.underscore # For the auto-STI namespace models
|
686
|
-
table_name = base_model.table_name
|
687
|
-
Object.send(:build_model, base_module, inheritable_name, model_name, singular_table_name, table_name, relations, table_name)
|
688
|
-
else
|
689
|
-
# Adjust for STI if we know of a base model for the requested model name
|
690
|
-
# %%% Does not yet work with namespaced model names. Perhaps prefix with plural_class_name when doing the lookups here.
|
691
|
-
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
692
|
-
base_model.table_name
|
693
|
-
else
|
694
|
-
ActiveSupport::Inflector.pluralize(singular_table_name)
|
695
|
-
end
|
696
|
-
if ::Brick.apartment_multitenant &&
|
697
|
-
Apartment.excluded_models.include?(table_name.singularize.camelize)
|
698
|
-
schema_name = Apartment.default_schema
|
699
|
-
end
|
700
|
-
# Maybe, just maybe there's a database table that will satisfy this need
|
701
|
-
if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
|
702
|
-
Object.send(:build_model, schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
703
|
-
end
|
704
|
-
end
|
681
|
+
Object.send(:build_model, relations, base_module, name, class_name, inheritable_name)
|
705
682
|
end
|
706
683
|
if result
|
707
684
|
built_class, code = result
|
708
|
-
puts "\n#{code}"
|
685
|
+
puts "\n#{code}\n"
|
709
686
|
built_class
|
710
687
|
elsif ::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}") && !schema_name
|
711
688
|
# module_prefixes = type_name.split('::')
|
@@ -727,7 +704,42 @@ class Object
|
|
727
704
|
|
728
705
|
private
|
729
706
|
|
730
|
-
def build_model(
|
707
|
+
def build_model(relations, base_module, base_name, class_name, inheritable_name = nil)
|
708
|
+
if (base_model = ::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil)&.constantize) || # Are we part of an auto-STI namespace? ...
|
709
|
+
base_module != Object # ... or otherwise already in some namespace?
|
710
|
+
schema_name = [(singular_schema_name = base_name.underscore),
|
711
|
+
(schema_name = singular_schema_name.pluralize),
|
712
|
+
base_name,
|
713
|
+
base_name.pluralize].find { |s| Brick.db_schemas.include?(s) }
|
714
|
+
end
|
715
|
+
plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
|
716
|
+
# If it's namespaced then we turn the first part into what would be a schema name
|
717
|
+
singular_table_name = ActiveSupport::Inflector.underscore(model_name).gsub('/', '.')
|
718
|
+
|
719
|
+
if base_model
|
720
|
+
schema_name = base_name.underscore # For the auto-STI namespace models
|
721
|
+
table_name = base_model.table_name
|
722
|
+
build_model_worker(base_module, inheritable_name, model_name, singular_table_name, table_name, relations, table_name)
|
723
|
+
else
|
724
|
+
# Adjust for STI if we know of a base model for the requested model name
|
725
|
+
# %%% Does not yet work with namespaced model names. Perhaps prefix with plural_class_name when doing the lookups here.
|
726
|
+
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
727
|
+
base_model.table_name
|
728
|
+
else
|
729
|
+
ActiveSupport::Inflector.pluralize(singular_table_name)
|
730
|
+
end
|
731
|
+
if ::Brick.apartment_multitenant &&
|
732
|
+
Apartment.excluded_models.include?(table_name.singularize.camelize)
|
733
|
+
schema_name = Apartment.default_schema
|
734
|
+
end
|
735
|
+
# Maybe, just maybe there's a database table that will satisfy this need
|
736
|
+
if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
|
737
|
+
build_model_worker(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
738
|
+
end
|
739
|
+
end
|
740
|
+
end
|
741
|
+
|
742
|
+
def build_model_worker(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
731
743
|
if ::Brick.apartment_multitenant &&
|
732
744
|
schema_name == Apartment.default_schema
|
733
745
|
relation = relations["#{schema_name}.#{matching}"]
|
@@ -893,7 +905,7 @@ class Object
|
|
893
905
|
end
|
894
906
|
end
|
895
907
|
end
|
896
|
-
code << "end # model #{full_name}\n
|
908
|
+
code << "end # model #{full_name}\n"
|
897
909
|
[built_model, code]
|
898
910
|
end
|
899
911
|
|
@@ -989,6 +1001,17 @@ class Object
|
|
989
1001
|
self.send(macro, assoc_name, **options)
|
990
1002
|
end
|
991
1003
|
|
1004
|
+
def default_ordering(table_name, pk)
|
1005
|
+
case (order_tbl = ::Brick.config.order[table_name]) && (order_default = order_tbl[:_brick_default])
|
1006
|
+
when Array
|
1007
|
+
order_default.map { |od_part| order_tbl[od_part] || od_part }
|
1008
|
+
when Symbol
|
1009
|
+
order_tbl[order_default] || order_default
|
1010
|
+
else
|
1011
|
+
pk.map(&:to_sym) # If it's not a custom ORDER BY, just use the key
|
1012
|
+
end
|
1013
|
+
end
|
1014
|
+
|
992
1015
|
def build_controller(namespace, class_name, plural_class_name, model, relations)
|
993
1016
|
table_name = ActiveSupport::Inflector.underscore(plural_class_name)
|
994
1017
|
singular_table_name = ActiveSupport::Inflector.singularize(table_name)
|
@@ -1002,6 +1025,9 @@ class Object
|
|
1002
1025
|
# Brick-specific pages
|
1003
1026
|
case plural_class_name
|
1004
1027
|
when 'BrickGem'
|
1028
|
+
self.define_method :status do
|
1029
|
+
instance_variable_set(:@resources, ::Brick.get_status_of_resources)
|
1030
|
+
end
|
1005
1031
|
self.define_method :orphans do
|
1006
1032
|
instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params)))
|
1007
1033
|
end
|
@@ -1073,27 +1099,9 @@ class Object
|
|
1073
1099
|
# Normal (non-swagger) request
|
1074
1100
|
|
1075
1101
|
# %%% Allow params to define which columns to use for order_by
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
order_default.map { |od_part| order_tbl[od_part] || od_part }
|
1080
|
-
when Symbol
|
1081
|
-
order_tbl[order_default] || order_default
|
1082
|
-
else
|
1083
|
-
pk
|
1084
|
-
end
|
1085
|
-
else
|
1086
|
-
pk # If it's not a custom ORDER BY, just use the key
|
1087
|
-
end
|
1088
|
-
order_by, order_by_txt = model._brick_calculate_ordering(ordering)
|
1089
|
-
if (order_params = params['_brick_order']&.split(',')&.map(&:to_sym)) # Overriding the default by providing a querystring param?
|
1090
|
-
order_by, _ = model._brick_calculate_ordering(order_params, true) # Don't do the txt part
|
1091
|
-
end
|
1092
|
-
|
1093
|
-
code << " def index\n"
|
1094
|
-
code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
|
1095
|
-
code << " @#{table_name}.brick_select(params)\n"
|
1096
|
-
code << " end\n"
|
1102
|
+
# Overriding the default by providing a querystring param?
|
1103
|
+
ordering = params['_brick_order']&.split(',')&.map(&:to_sym) || Object.send(:default_ordering, table_name, pk)
|
1104
|
+
order_by, _ = model._brick_calculate_ordering(ordering, true) # Don't do the txt part
|
1097
1105
|
|
1098
1106
|
::Brick.set_db_schema(params)
|
1099
1107
|
if request.format == :csv # Asking for a template?
|
@@ -1126,6 +1134,12 @@ class Object
|
|
1126
1134
|
@_brick_join_array = join_array
|
1127
1135
|
end
|
1128
1136
|
|
1137
|
+
_, order_by_txt = model._brick_calculate_ordering(default_ordering(table_name, pk))
|
1138
|
+
code << " def index\n"
|
1139
|
+
code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
|
1140
|
+
code << " @#{table_name}.brick_select(params)\n"
|
1141
|
+
code << " end\n"
|
1142
|
+
|
1129
1143
|
is_pk_string = nil
|
1130
1144
|
if (pk_col = model&.primary_key)
|
1131
1145
|
code << " def show\n"
|
@@ -1255,7 +1269,7 @@ class Object
|
|
1255
1269
|
# Get column names for params from relations[model.table_name][:cols].keys
|
1256
1270
|
end
|
1257
1271
|
# end
|
1258
|
-
code << "end # #{namespace_name}#{class_name}\n
|
1272
|
+
code << "end # #{namespace_name}#{class_name}\n"
|
1259
1273
|
end # class definition
|
1260
1274
|
[built_controller, code]
|
1261
1275
|
end
|
@@ -1312,7 +1326,7 @@ module ActiveRecord::ConnectionHandling
|
|
1312
1326
|
load apartment_initializer
|
1313
1327
|
apartment_excluded = Apartment.excluded_models
|
1314
1328
|
end
|
1315
|
-
# Only for Postgres
|
1329
|
+
# Only for Postgres (Doesn't work in sqlite3 or MySQL)
|
1316
1330
|
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
1317
1331
|
|
1318
1332
|
is_postgres = nil
|
@@ -1347,7 +1361,7 @@ module ActiveRecord::ConnectionHandling
|
|
1347
1361
|
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
1348
1362
|
end
|
1349
1363
|
|
1350
|
-
::Brick.db_schemas ||=
|
1364
|
+
::Brick.db_schemas ||= {}
|
1351
1365
|
|
1352
1366
|
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1353
1367
|
if (possible_schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
|
@@ -1363,41 +1377,11 @@ module ActiveRecord::ConnectionHandling
|
|
1363
1377
|
# %%% Retrieve internal ActiveRecord table names like this:
|
1364
1378
|
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
1365
1379
|
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
1366
|
-
sql ||= "SELECT t.table_schema AS schema, t.table_name AS relation_name, t.table_type,#{"
|
1367
|
-
pg_catalog.obj_description((t.table_schema || '.' || t.table_name)::regclass, 'pg_class') AS table_description," if is_postgres}
|
1368
|
-
c.column_name, c.data_type,
|
1369
|
-
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
1370
|
-
tc.constraint_type AS const, kcu.constraint_name AS \"key\",
|
1371
|
-
c.is_nullable
|
1372
|
-
FROM INFORMATION_SCHEMA.tables AS t
|
1373
|
-
LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
|
1374
|
-
AND t.table_name = c.table_name
|
1375
|
-
LEFT OUTER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu ON
|
1376
|
-
-- ON kcu.CONSTRAINT_CATALOG = t.table_catalog AND
|
1377
|
-
kcu.CONSTRAINT_SCHEMA = c.table_schema
|
1378
|
-
AND kcu.TABLE_NAME = c.table_name
|
1379
|
-
AND kcu.position_in_unique_constraint IS NULL
|
1380
|
-
AND kcu.ordinal_position = c.ordinal_position
|
1381
|
-
LEFT OUTER JOIN INFORMATION_SCHEMA.table_constraints AS tc
|
1382
|
-
ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
1383
|
-
AND kcu.TABLE_NAME = tc.TABLE_NAME
|
1384
|
-
AND kcu.CONSTRAINT_NAME = tc.constraint_name
|
1385
|
-
WHERE t.table_schema NOT IN ('information_schema', 'pg_catalog')#{"
|
1386
|
-
AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }
|
1387
|
-
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
1388
|
-
AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
|
1389
|
-
ORDER BY 1, t.table_type DESC, c.ordinal_position"
|
1390
1380
|
measures = []
|
1391
1381
|
case ActiveRecord::Base.connection.adapter_name
|
1392
1382
|
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
1393
1383
|
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1394
|
-
|
1395
|
-
ActiveRecord::Base.schema_migrations_table_name
|
1396
|
-
else
|
1397
|
-
'schema_migrations'
|
1398
|
-
end
|
1399
|
-
ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
|
1400
|
-
ActiveRecord::Base.execute_sql(sql, ar_smtn, ar_imtn).each do |r|
|
1384
|
+
ActiveRecord::Base.retrieve_schema_and_tables(sql, is_postgres, schema).each do |r|
|
1401
1385
|
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
1402
1386
|
# is the default schema, usually 'public'.
|
1403
1387
|
schema_name = if ::Brick.config.schema_behavior[:multitenant]
|
@@ -1424,23 +1408,23 @@ module ActiveRecord::ConnectionHandling
|
|
1424
1408
|
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
1425
1409
|
end
|
1426
1410
|
else # MySQL2 acts a little differently, bringing back an array for each row
|
1427
|
-
ActiveRecord::Base.
|
1428
|
-
relation = relations[(relation_name = r[
|
1429
|
-
relation[:isView] = true if r[
|
1430
|
-
col_name = r[
|
1431
|
-
key = case r[
|
1411
|
+
ActiveRecord::Base.retrieve_schema_and_tables(sql).each do |r|
|
1412
|
+
relation = relations[(relation_name = r[1])] # here relation represents a table or view from the database
|
1413
|
+
relation[:isView] = true if r[2] == 'VIEW' # table_type
|
1414
|
+
col_name = r[3]
|
1415
|
+
key = case r[6] # constraint type
|
1432
1416
|
when 'PRIMARY KEY'
|
1433
1417
|
# key
|
1434
|
-
relation[:pkey][r[
|
1418
|
+
relation[:pkey][r[7] || relation_name] ||= []
|
1435
1419
|
when 'UNIQUE'
|
1436
|
-
relation[:ukeys][r[
|
1420
|
+
relation[:ukeys][r[7] || "#{relation_name}.#{col_name}"] ||= []
|
1437
1421
|
# key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
|
1438
1422
|
# key[r['key']]
|
1439
1423
|
end
|
1440
1424
|
key << col_name if key
|
1441
1425
|
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
1442
1426
|
# 'data_type', 'max_length'
|
1443
|
-
cols[col_name] = [r[
|
1427
|
+
cols[col_name] = [r[4], r[5], measures&.include?(col_name)]
|
1444
1428
|
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
1445
1429
|
end
|
1446
1430
|
end
|
@@ -1478,7 +1462,7 @@ module ActiveRecord::ConnectionHandling
|
|
1478
1462
|
AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
|
1479
1463
|
AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
|
1480
1464
|
AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION#{"
|
1481
|
-
WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }"
|
1465
|
+
WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }"
|
1482
1466
|
# AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
|
1483
1467
|
when 'SQLite'
|
1484
1468
|
sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
|
@@ -1494,12 +1478,14 @@ module ActiveRecord::ConnectionHandling
|
|
1494
1478
|
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
1495
1479
|
if apartment_excluded&.include?(fk[1].singularize.camelize)
|
1496
1480
|
fk[0] = Apartment.default_schema
|
1497
|
-
elsif fk[0] == 'public' || (is_multitenant && fk[0] == schema)
|
1481
|
+
elsif is_postgres && (fk[0] == 'public' || (is_multitenant && fk[0] == schema)) ||
|
1482
|
+
!is_postgres && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0])
|
1498
1483
|
fk[0] = nil
|
1499
1484
|
end
|
1500
1485
|
if apartment_excluded&.include?(fk[4].singularize.camelize)
|
1501
1486
|
fk[3] = Apartment.default_schema
|
1502
|
-
elsif fk[3] == 'public' || (is_multitenant && fk[3] == schema)
|
1487
|
+
elsif is_postgres && (fk[3] == 'public' || (is_multitenant && fk[3] == schema)) ||
|
1488
|
+
!is_postgres && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3])
|
1503
1489
|
fk[3] = nil
|
1504
1490
|
end
|
1505
1491
|
::Brick._add_bt_and_hm(fk, relations)
|
@@ -1533,6 +1519,41 @@ module ActiveRecord::ConnectionHandling
|
|
1533
1519
|
|
1534
1520
|
::Brick.load_additional_references if initializer_loaded
|
1535
1521
|
end
|
1522
|
+
|
1523
|
+
def retrieve_schema_and_tables(sql = nil, is_postgres = nil, schema = nil)
|
1524
|
+
sql ||= "SELECT t.table_schema AS \"schema\", t.table_name AS relation_name, t.table_type,#{"
|
1525
|
+
pg_catalog.obj_description((t.table_schema || '.' || t.table_name)::regclass, 'pg_class') AS table_description," if is_postgres}
|
1526
|
+
c.column_name, c.data_type,
|
1527
|
+
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
1528
|
+
tc.constraint_type AS const, kcu.constraint_name AS \"key\",
|
1529
|
+
c.is_nullable
|
1530
|
+
FROM INFORMATION_SCHEMA.tables AS t
|
1531
|
+
LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
|
1532
|
+
AND t.table_name = c.table_name
|
1533
|
+
LEFT OUTER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu ON
|
1534
|
+
-- ON kcu.CONSTRAINT_CATALOG = t.table_catalog AND
|
1535
|
+
kcu.CONSTRAINT_SCHEMA = c.table_schema
|
1536
|
+
AND kcu.TABLE_NAME = c.table_name
|
1537
|
+
AND kcu.position_in_unique_constraint IS NULL
|
1538
|
+
AND kcu.ordinal_position = c.ordinal_position
|
1539
|
+
LEFT OUTER JOIN INFORMATION_SCHEMA.table_constraints AS tc
|
1540
|
+
ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
1541
|
+
AND kcu.TABLE_NAME = tc.TABLE_NAME
|
1542
|
+
AND kcu.CONSTRAINT_NAME = tc.constraint_name
|
1543
|
+
WHERE t.table_schema NOT IN ('information_schema', #{
|
1544
|
+
is_postgres ? "'pg_catalog'" : "'mysql', 'performance_schema', 'sys'"})#{"
|
1545
|
+
AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }
|
1546
|
+
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
1547
|
+
AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
|
1548
|
+
ORDER BY 1, t.table_type DESC, 2, c.ordinal_position"
|
1549
|
+
ar_smtn = if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
|
1550
|
+
ActiveRecord::Base.schema_migrations_table_name
|
1551
|
+
else
|
1552
|
+
'schema_migrations'
|
1553
|
+
end
|
1554
|
+
ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
|
1555
|
+
ActiveRecord::Base.execute_sql(sql, ar_smtn, ar_imtn)
|
1556
|
+
end
|
1536
1557
|
end
|
1537
1558
|
|
1538
1559
|
# ==========================================
|
@@ -1680,6 +1701,59 @@ module Brick
|
|
1680
1701
|
assoc_bt[:inverse] = assoc_hm
|
1681
1702
|
end
|
1682
1703
|
|
1704
|
+
# Identify built out routes, migrations, models,
|
1705
|
+
# (and also soon controllers and views!)
|
1706
|
+
# for each resource
|
1707
|
+
def get_status_of_resources
|
1708
|
+
rails_root = ::Rails.root.to_s
|
1709
|
+
migrations = if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{rails_root}/db/migrate")
|
1710
|
+
Dir["#{mig_path}/**/*.rb"].each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
|
1711
|
+
File.read(v).split("\n").each do |line|
|
1712
|
+
# For all non-commented lines, look for any that have "create_table", "alter_table", or "drop_table"
|
1713
|
+
if !line.lstrip.start_with?('#') &&
|
1714
|
+
(idx = (line.index('create_table ') || line.index('create_table('))&.+(13)) ||
|
1715
|
+
(idx = (line.index('alter_table ') || line.index('alter_table('))&.+(12)) ||
|
1716
|
+
(idx = (line.index('drop_table ') || line.index('drop_table('))&.+(11))
|
1717
|
+
tbl = line[idx..-1].match(/([:'"\w\.]+)/)&.captures&.first
|
1718
|
+
if tbl
|
1719
|
+
s[tbl.tr(':\'"', '').pluralize] << v
|
1720
|
+
break
|
1721
|
+
end
|
1722
|
+
end
|
1723
|
+
end
|
1724
|
+
end
|
1725
|
+
end
|
1726
|
+
if ::ActiveSupport.version < ::Gem::Version.new('6') ||
|
1727
|
+
::Rails.configuration.instance_variable_get(:@autoloader) == :classic
|
1728
|
+
Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
|
1729
|
+
else
|
1730
|
+
Zeitwerk::Loader.eager_load_all
|
1731
|
+
end
|
1732
|
+
abstract_activerecord_bases = ActiveRecord::Base.descendants.select { |ar| ar.abstract_class? }.map(&:name)
|
1733
|
+
# abstract_activerecord_bases << ActiveRecord::Base
|
1734
|
+
models = if Dir.exist?(model_path = "#{rails_root}/app/models")
|
1735
|
+
Dir["#{model_path}/**/*.rb"].each_with_object({}) do |v, s|
|
1736
|
+
File.read(v).split("\n").each do |line|
|
1737
|
+
# For all non-commented lines, look for any that start with "class " and also "< ApplicationRecord"
|
1738
|
+
if line.lstrip.start_with?('class') && (idx = line.index('class'))
|
1739
|
+
model = line[idx + 5..-1].match(/[\s:]+([\w:]+)/)&.captures&.first
|
1740
|
+
if model && abstract_activerecord_bases.exclude?(model)
|
1741
|
+
klass = begin
|
1742
|
+
model.constantize
|
1743
|
+
rescue
|
1744
|
+
end
|
1745
|
+
s[model.underscore.tr('/', '.').pluralize] = [
|
1746
|
+
v.start_with?(rails_root) ? v[rails_root.length + 1..-1] : v,
|
1747
|
+
klass
|
1748
|
+
]
|
1749
|
+
end
|
1750
|
+
end
|
1751
|
+
end
|
1752
|
+
end
|
1753
|
+
end
|
1754
|
+
::Brick.relations.keys.map { |v| [(r = v.pluralize), (model = models[r])&.last&.table_name || v, migrations&.fetch(r, nil), model&.first] }
|
1755
|
+
end
|
1756
|
+
|
1683
1757
|
# Locate orphaned records
|
1684
1758
|
def find_orphans(multi_schema)
|
1685
1759
|
is_default_schema = multi_schema&.==(Apartment.default_schema)
|
@@ -56,6 +56,7 @@ module Brick
|
|
56
56
|
# Used by Rails 5.0 and above
|
57
57
|
alias :_brick_template_exists? :template_exists?
|
58
58
|
def template_exists?(*args, **options)
|
59
|
+
(::Brick.config.add_status && args.first == 'status') ||
|
59
60
|
(::Brick.config.add_orphans && args.first == 'orphans') ||
|
60
61
|
_brick_template_exists?(*args, **options) ||
|
61
62
|
# Do not auto-create a template when it's searching for an application.html.erb, which comes in like: ["edit", ["games", "application"]]
|
@@ -96,11 +97,12 @@ module Brick
|
|
96
97
|
@_brick_model ||
|
97
98
|
(ActionView.version < ::Gem::Version.new('5.0') && args[1].is_a?(Array) ? set_brick_model(args) : nil)
|
98
99
|
)&.name) ||
|
100
|
+
(is_status = ::Brick.config.add_status && args[0..1] == ['status', ['brick_gem']]) ||
|
99
101
|
(is_orphans = ::Brick.config.add_orphans && args[0..1] == ['orphans', ['brick_gem']])
|
100
102
|
return _brick_find_template(*args, **options)
|
101
103
|
end
|
102
104
|
|
103
|
-
unless is_orphans
|
105
|
+
unless is_status || is_orphans
|
104
106
|
pk = @_brick_model._brick_primary_key(::Brick.relations.fetch(model_name, nil))
|
105
107
|
obj_name = model_name.split('::').last.underscore
|
106
108
|
path_obj_name = model_name.underscore.tr('/', '_')
|
@@ -148,7 +150,6 @@ module Brick
|
|
148
150
|
", nil, #{path_keys(hm_assoc, hm_fk_name, obj_name, pk)}"
|
149
151
|
end
|
150
152
|
hm_entry << ']'
|
151
|
-
puts hm_entry
|
152
153
|
hms_columns << hm_entry
|
153
154
|
when 'show', 'update'
|
154
155
|
hm_stuff << if hm_fk_name
|
@@ -613,8 +614,9 @@ if (headerTop) {
|
|
613
614
|
});
|
614
615
|
});
|
615
616
|
</script>
|
616
|
-
<% end
|
617
|
-
|
617
|
+
<% end
|
618
|
+
|
619
|
+
%><table id=\"headerTop\">
|
618
620
|
<table id=\"#{table_name}\">
|
619
621
|
<thead><tr>#{"<th x-order=\"#{pk.join(',')}\"></th>" if pk.present?}<%=
|
620
622
|
# Consider getting the name from the association -- hm.first.name -- if a more \"friendly\" alias should be used for a screwy table name
|
@@ -708,6 +710,53 @@ if (headerTop) {
|
|
708
710
|
|
709
711
|
#{"<hr><%= link_to \"New #{obj_name}\", new_#{path_obj_name}_path %>" unless @_brick_model.is_view?}
|
710
712
|
#{script}"
|
713
|
+
|
714
|
+
when 'status'
|
715
|
+
# Status page - list of all resources and 5 things they do or don't have present, and what is turned on and off
|
716
|
+
# Must load all models, and then find what table names are represented
|
717
|
+
# Easily could be multiple files involved (STI for instance)
|
718
|
+
+"#{css}
|
719
|
+
<p style=\"color: green\"><%= notice %></p>#{"
|
720
|
+
<select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
|
721
|
+
<select id=\"tbl\">#{table_options}</select>
|
722
|
+
<h1>Status</h1>
|
723
|
+
<table id=\"status\"><thead><tr>
|
724
|
+
<th>Resource</th>
|
725
|
+
<th>Table</th>
|
726
|
+
<th>Migration</th>
|
727
|
+
<th>Model</th>
|
728
|
+
<th>Route</th>
|
729
|
+
<th>Controller</th>
|
730
|
+
<th>Views</th>
|
731
|
+
</tr></thead>
|
732
|
+
<tbody>
|
733
|
+
<% # (listing in schema.rb)
|
734
|
+
# Solid colour if file or route entry is present
|
735
|
+
@resources.each do |r|
|
736
|
+
%>
|
737
|
+
<tr>
|
738
|
+
<td><%= link_to(r[0], \"/#\{r[0].tr('.', '/')}\") %></td>
|
739
|
+
<td<%= if r[1]
|
740
|
+
' class=\"orphan\"' unless ::Brick.relations.key?(r[1])
|
741
|
+
else
|
742
|
+
' class=\"dimmed\"'
|
743
|
+
end&.html_safe %>><%= # Table
|
744
|
+
r[1] %></td>
|
745
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless r[2] %>><%= # Migration
|
746
|
+
r[2]&.join('<br>')&.html_safe %></td>
|
747
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless r[3] %>><%= # Model
|
748
|
+
r[3] %></td>
|
749
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless r[4] %>><%= # Route
|
750
|
+
%></td>
|
751
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless r[5] %>><%= # Controller
|
752
|
+
%></td>
|
753
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless r[6] %>><%= # Views
|
754
|
+
%></td>
|
755
|
+
<tr>
|
756
|
+
<% end %>
|
757
|
+
</tbody><table>
|
758
|
+
#{script}"
|
759
|
+
|
711
760
|
when 'orphans'
|
712
761
|
if is_orphans
|
713
762
|
+"#{css}
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -484,6 +484,9 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
484
484
|
send(:resources, controller_name.to_sym, **options)
|
485
485
|
end
|
486
486
|
end
|
487
|
+
if ::Brick.config.add_status && instance_variable_get(:@set).named_routes.names.exclude?(:brick_status)
|
488
|
+
get('/brick_status', to: 'brick_gem#status', as: 'brick_status')
|
489
|
+
end
|
487
490
|
if ::Brick.config.add_orphans && instance_variable_get(:@set).named_routes.names.exclude?(:brick_orphans)
|
488
491
|
get('/brick_orphans', to: 'brick_gem#orphans', as: 'brick_orphans')
|
489
492
|
end
|
@@ -84,8 +84,13 @@ module Brick
|
|
84
84
|
|
85
85
|
# Generate a list of tables that can be chosen
|
86
86
|
chosen = gets_list(list: tables, chosen: tables.dup)
|
87
|
+
schemas = chosen.each_with_object({}) do |v, s|
|
88
|
+
if (v_parts = v.split('.')).length > 1
|
89
|
+
s[v_parts.first] = nil unless [::Brick.default_schema, 'public'].include?(v_parts.first)
|
90
|
+
end
|
91
|
+
end
|
87
92
|
# Start the timestamps back the same number of minutes from now as expected number of migrations to create
|
88
|
-
current_mig_time = Time.now - chosen.length.minutes
|
93
|
+
current_mig_time = Time.now - (schemas.length + chosen.length).minutes
|
89
94
|
done = []
|
90
95
|
fks = {}
|
91
96
|
stuck = {}
|
@@ -124,9 +129,14 @@ module Brick
|
|
124
129
|
tbl_parts.first
|
125
130
|
end
|
126
131
|
end
|
132
|
+
unless schema.blank? || built_schemas.key?(schema)
|
133
|
+
mig = +" def change\n create_schema(:#{schema}) unless schema_exists?(:#{schema})\n end\n"
|
134
|
+
migration_file_write(mig_path, "create_db_schema_#{schema}", current_mig_time += 1.minute, ar_version, mig)
|
135
|
+
built_schemas[schema] = nil
|
136
|
+
end
|
137
|
+
|
127
138
|
# %%% For the moment we're skipping polymorphics
|
128
139
|
fkey_cols = relation[:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
|
129
|
-
mig = +"class Create#{(full_table_name = tbl_parts.join('_')).camelize} < ActiveRecord::Migration#{ar_version}\n"
|
130
140
|
# If the primary key is also used as a foreign key, will need to do id: false and then build out
|
131
141
|
# a column definition which includes :primary_key -- %%% also using a data type of bigserial or serial
|
132
142
|
# if this one has come in as bigint or integer.
|
@@ -163,8 +173,7 @@ module Brick
|
|
163
173
|
end
|
164
174
|
# Refer to this table name as a symbol or dotted string as appropriate
|
165
175
|
tbl_code = tbl_parts.length == 1 ? ":#{tbl_parts.first}" : "'#{tbl}'"
|
166
|
-
mig
|
167
|
-
mig << " create_schema :#{schema} unless reverting? || schema_exists?(:#{schema})\n" if schema
|
176
|
+
mig = +" def change\n return unless reverting? || !table_exists?(#{tbl_code})\n\n"
|
168
177
|
mig << " create_table #{tbl_code}#{id_option} do |t|\n"
|
169
178
|
possible_ts = [] # Track possible generic timestamps
|
170
179
|
add_fks = [] # Track foreign keys to add after table creation
|
@@ -217,7 +226,11 @@ module Brick
|
|
217
226
|
possible_ts.each { |ts| emit_column('timestamp', ts.first, nil) }
|
218
227
|
end
|
219
228
|
mig << " end\n"
|
220
|
-
|
229
|
+
if pk_is_also_fk
|
230
|
+
mig << " reversible do |dir|\n"
|
231
|
+
mig << " dir.up { execute('ALTER TABLE #{tbl} ADD PRIMARY KEY (#{pk_is_also_fk})') }\n"
|
232
|
+
mig << " end\n"
|
233
|
+
end
|
221
234
|
add_fks.each do |add_fk|
|
222
235
|
is_commented = false
|
223
236
|
# add_fk[2] holds the inverse relation
|
@@ -227,21 +240,16 @@ module Brick
|
|
227
240
|
# No official PK, but if coincidentally there's a column of the same name, take a chance on it
|
228
241
|
pk = (add_fk[2][:cols].key?(add_fk[1]) && add_fk[1]) || '???'
|
229
242
|
end
|
230
|
-
#
|
243
|
+
# to_table column
|
231
244
|
mig << " #{'# ' if is_commented}add_foreign_key #{tbl_code}, #{add_fk[0]}, column: :#{add_fk[1]}, primary_key: :#{pk}\n"
|
232
245
|
end
|
233
|
-
|
234
|
-
|
235
|
-
built_schemas[schema] = nil
|
236
|
-
end
|
237
|
-
mig << " end\nend\n"
|
238
|
-
current_mig_time += 1.minute
|
239
|
-
versions_to_create << (version = current_mig_time.strftime('%Y%m%d%H%M00')).split('_').first
|
240
|
-
File.open("#{mig_path}/#{version}_create_#{full_table_name}.rb", "w") { |f| f.write mig }
|
246
|
+
mig << " end\n"
|
247
|
+
versions_to_create << migration_file_write(mig_path, "create_#{tbl_parts.join('_')}", current_mig_time += 1.minute, ar_version, mig)
|
241
248
|
end
|
242
249
|
done.concat(fringe)
|
243
250
|
chosen -= done
|
244
251
|
end
|
252
|
+
|
245
253
|
stuck_counts = Hash.new { |h, k| h[k] = 0 }
|
246
254
|
chosen.each do |leftover|
|
247
255
|
puts "Can't do #{leftover} because:\n #{stuck[leftover].map do |snag|
|
@@ -285,5 +293,14 @@ module Brick
|
|
285
293
|
def emit_column(type, name, suffix)
|
286
294
|
" t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}#{suffix}\n"
|
287
295
|
end
|
296
|
+
|
297
|
+
def migration_file_write(mig_path, name, current_mig_time, ar_version, mig)
|
298
|
+
File.open("#{mig_path}/#{version = current_mig_time.strftime('%Y%m%d%H%M00')}_#{name}.rb", "w") do |f|
|
299
|
+
f.write "class #{name.camelize} < ActiveRecord::Migration#{ar_version}\n"
|
300
|
+
f.write mig
|
301
|
+
f.write "end\n"
|
302
|
+
end
|
303
|
+
version
|
304
|
+
end
|
288
305
|
end
|
289
306
|
end
|
@@ -6,63 +6,72 @@ require 'fancy_gets'
|
|
6
6
|
|
7
7
|
module Brick
|
8
8
|
# Auto-generates models, controllers, or views
|
9
|
-
class
|
9
|
+
class ModelsGenerator < ::Rails::Generators::Base
|
10
10
|
include FancyGets
|
11
11
|
# include ::Rails::Generators::Migration
|
12
12
|
|
13
|
-
# # source_root File.expand_path('templates', __dir__)
|
14
|
-
# class_option(
|
15
|
-
# :with_changes,
|
16
|
-
# type: :boolean,
|
17
|
-
# default: false,
|
18
|
-
# desc: 'Add IMPORT_TEMPLATE to model'
|
19
|
-
# )
|
20
|
-
|
21
13
|
desc 'Auto-generates models, controllers, or views.'
|
22
14
|
|
23
|
-
def
|
24
|
-
# %%% If Apartment is active, ask which schema they want
|
15
|
+
def brick_models
|
16
|
+
# %%% If Apartment is active and there's no schema_to_analyse, ask which schema they want
|
25
17
|
|
26
18
|
# Load all models
|
27
|
-
|
19
|
+
if ::ActiveSupport.version < ::Gem::Version.new('6') ||
|
20
|
+
::Rails.configuration.instance_variable_get(:@autoloader) == :classic
|
21
|
+
Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
|
22
|
+
else
|
23
|
+
Zeitwerk::Loader.eager_load_all
|
24
|
+
end
|
28
25
|
|
29
26
|
# Generate a list of viable models that can be chosen
|
30
27
|
longest_length = 0
|
31
28
|
model_info = Hash.new { |h, k| h[k] = {} }
|
32
29
|
tableless = Hash.new { |h, k| h[k] = [] }
|
33
|
-
|
34
|
-
|
35
|
-
true
|
36
|
-
elsif !m.table_exists?
|
37
|
-
tableless[m.table_name] << m.name
|
38
|
-
' (No Table)'
|
39
|
-
else
|
40
|
-
this_f_keys = (model_info[m][:f_keys] = m.reflect_on_all_associations.select { |a| a.macro == :belongs_to }) || []
|
41
|
-
column_names = (model_info[m][:column_names] = m.columns.map(&:name) - [m.primary_key, 'created_at', 'updated_at', 'deleted_at'] - this_f_keys.map(&:foreign_key))
|
42
|
-
if column_names.empty? && this_f_keys && !this_f_keys.empty?
|
43
|
-
fk_message = ", although #{this_f_keys.length} foreign keys"
|
44
|
-
" (No columns#{fk_message})"
|
45
|
-
end
|
46
|
-
end
|
47
|
-
# puts "#{m.name}#{trouble}" if trouble&.is_a?(String)
|
48
|
-
trouble
|
30
|
+
existing_models = ActiveRecord::Base.descendants.reject do |m|
|
31
|
+
m.abstract_class? || !m.table_exists? || ::Brick.relations.key?(m.table_name)
|
49
32
|
end
|
33
|
+
models = ::Brick.relations.keys.map do |tbl|
|
34
|
+
tbl_parts = tbl.split('.')
|
35
|
+
tbl_parts.shift if [::Brick.default_schema, 'public'].include?(tbl_parts.first)
|
36
|
+
tbl_parts[-1] = tbl_parts[-1].singularize
|
37
|
+
tbl_parts.join('/').camelize
|
38
|
+
end - existing_models.map(&:name)
|
50
39
|
models.sort! do |a, b| # Sort first to separate namespaced stuff from the rest, then alphabetically
|
51
|
-
is_a_namespaced = a.
|
52
|
-
is_b_namespaced = b.
|
40
|
+
is_a_namespaced = a.include?('::')
|
41
|
+
is_b_namespaced = b.include?('::')
|
53
42
|
if is_a_namespaced && !is_b_namespaced
|
54
43
|
1
|
55
44
|
elsif !is_a_namespaced && is_b_namespaced
|
56
45
|
-1
|
57
46
|
else
|
58
|
-
a
|
47
|
+
a <=> b
|
59
48
|
end
|
60
49
|
end
|
61
50
|
models.each do |m| # Find longest name in the list for future use to show lists on the right side of the screen
|
62
|
-
if longest_length < (len = m.
|
51
|
+
if longest_length < (len = m.length)
|
63
52
|
longest_length = len
|
64
53
|
end
|
65
54
|
end
|
55
|
+
chosen = gets_list(list: models, chosen: models.dup)
|
56
|
+
relations = ::Brick.relations
|
57
|
+
chosen.each do |model_name|
|
58
|
+
# %%% If we're in a schema then make sure the module file exists
|
59
|
+
base_module = if (model_parts = model_name.split('::')).length > 1
|
60
|
+
"::#{model_parts.first}".constantize
|
61
|
+
else
|
62
|
+
Object
|
63
|
+
end
|
64
|
+
_built_model, code = Object.send(:build_model, relations, base_module, base_module.name, model_parts.last)
|
65
|
+
path = ['models']
|
66
|
+
path.concat(model_parts.map(&:underscore))
|
67
|
+
dir = +"#{::Rails.root}/app"
|
68
|
+
path[0..-2].each do |path_part|
|
69
|
+
dir << "/#{path_part}"
|
70
|
+
Dir.mkdir(dir) unless Dir.exists?(dir)
|
71
|
+
end
|
72
|
+
File.open("#{dir}/#{path.last}.rb", 'w') { |f| f.write code } unless code.blank?
|
73
|
+
end
|
74
|
+
puts "\n*** Created #{chosen.length} model files under app/models ***"
|
66
75
|
end
|
67
76
|
|
68
77
|
private
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.60
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -239,7 +239,7 @@ files:
|
|
239
239
|
- lib/generators/brick/USAGE
|
240
240
|
- lib/generators/brick/install_generator.rb
|
241
241
|
- lib/generators/brick/migrations_generator.rb
|
242
|
-
- lib/generators/brick/
|
242
|
+
- lib/generators/brick/models_generator.rb
|
243
243
|
- lib/generators/brick/templates/add_object_changes_to_versions.rb.erb
|
244
244
|
- lib/generators/brick/templates/create_versions.rb.erb
|
245
245
|
homepage: https://github.com/lorint/brick
|