brick 1.0.59 → 1.0.62
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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae6fe72ae3ec1d2ba6318f1bb6cb4964b29a1479e3b76e2cb560537cc6a6ff6c
|
4
|
+
data.tar.gz: 608fbd2c50c31dd4eff33a4d7c8a19786c53518e9fc1ff38059bf47ca254b6ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b0c3fcc243cde60b35f2cf07277a53c413b1b805d9a31cf00f4d89676b7eaf0d79ba1ea22bc33190ff8df55eab96e62b0c986f1894dc11e55b8358dbae7cc57
|
7
|
+
data.tar.gz: 32d9c0e8bba6f2a477ed85347680113ab92d1ed52ce044d97bacf734d33fa7c9e7f62bc87ab01b2a027e1f41c62afe802f3601e14f7bdc9d8026b03050337c23
|
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
|
@@ -990,6 +1001,17 @@ class Object
|
|
990
1001
|
self.send(macro, assoc_name, **options)
|
991
1002
|
end
|
992
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
|
+
|
993
1015
|
def build_controller(namespace, class_name, plural_class_name, model, relations)
|
994
1016
|
table_name = ActiveSupport::Inflector.underscore(plural_class_name)
|
995
1017
|
singular_table_name = ActiveSupport::Inflector.singularize(table_name)
|
@@ -1077,27 +1099,9 @@ class Object
|
|
1077
1099
|
# Normal (non-swagger) request
|
1078
1100
|
|
1079
1101
|
# %%% Allow params to define which columns to use for order_by
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
order_default.map { |od_part| order_tbl[od_part] || od_part }
|
1084
|
-
when Symbol
|
1085
|
-
order_tbl[order_default] || order_default
|
1086
|
-
else
|
1087
|
-
pk
|
1088
|
-
end
|
1089
|
-
else
|
1090
|
-
pk # If it's not a custom ORDER BY, just use the key
|
1091
|
-
end
|
1092
|
-
order_by, order_by_txt = model._brick_calculate_ordering(ordering)
|
1093
|
-
if (order_params = params['_brick_order']&.split(',')&.map(&:to_sym)) # Overriding the default by providing a querystring param?
|
1094
|
-
order_by, _ = model._brick_calculate_ordering(order_params, true) # Don't do the txt part
|
1095
|
-
end
|
1096
|
-
|
1097
|
-
code << " def index\n"
|
1098
|
-
code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
|
1099
|
-
code << " @#{table_name}.brick_select(params)\n"
|
1100
|
-
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
|
1101
1105
|
|
1102
1106
|
::Brick.set_db_schema(params)
|
1103
1107
|
if request.format == :csv # Asking for a template?
|
@@ -1128,8 +1132,15 @@ class Object
|
|
1128
1132
|
@_brick_bt_descrip = model._br_bt_descrip
|
1129
1133
|
@_brick_hm_counts = model._br_hm_counts
|
1130
1134
|
@_brick_join_array = join_array
|
1135
|
+
@_brick_erd = params['_brick_erd']&.to_i
|
1131
1136
|
end
|
1132
1137
|
|
1138
|
+
_, order_by_txt = model._brick_calculate_ordering(default_ordering(table_name, pk))
|
1139
|
+
code << " def index\n"
|
1140
|
+
code << " @#{table_name} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
|
1141
|
+
code << " @#{table_name}.brick_select(params)\n"
|
1142
|
+
code << " end\n"
|
1143
|
+
|
1133
1144
|
is_pk_string = nil
|
1134
1145
|
if (pk_col = model&.primary_key)
|
1135
1146
|
code << " def show\n"
|
@@ -1316,7 +1327,7 @@ module ActiveRecord::ConnectionHandling
|
|
1316
1327
|
load apartment_initializer
|
1317
1328
|
apartment_excluded = Apartment.excluded_models
|
1318
1329
|
end
|
1319
|
-
# Only for Postgres
|
1330
|
+
# Only for Postgres (Doesn't work in sqlite3 or MySQL)
|
1320
1331
|
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
1321
1332
|
|
1322
1333
|
is_postgres = nil
|
@@ -1351,7 +1362,7 @@ module ActiveRecord::ConnectionHandling
|
|
1351
1362
|
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
1352
1363
|
end
|
1353
1364
|
|
1354
|
-
::Brick.db_schemas ||=
|
1365
|
+
::Brick.db_schemas ||= {}
|
1355
1366
|
|
1356
1367
|
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1357
1368
|
if (possible_schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
|
@@ -1367,41 +1378,11 @@ module ActiveRecord::ConnectionHandling
|
|
1367
1378
|
# %%% Retrieve internal ActiveRecord table names like this:
|
1368
1379
|
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
1369
1380
|
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
1370
|
-
sql ||= "SELECT t.table_schema AS schema, t.table_name AS relation_name, t.table_type,#{"
|
1371
|
-
pg_catalog.obj_description((t.table_schema || '.' || t.table_name)::regclass, 'pg_class') AS table_description," if is_postgres}
|
1372
|
-
c.column_name, c.data_type,
|
1373
|
-
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
1374
|
-
tc.constraint_type AS const, kcu.constraint_name AS \"key\",
|
1375
|
-
c.is_nullable
|
1376
|
-
FROM INFORMATION_SCHEMA.tables AS t
|
1377
|
-
LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
|
1378
|
-
AND t.table_name = c.table_name
|
1379
|
-
LEFT OUTER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu ON
|
1380
|
-
-- ON kcu.CONSTRAINT_CATALOG = t.table_catalog AND
|
1381
|
-
kcu.CONSTRAINT_SCHEMA = c.table_schema
|
1382
|
-
AND kcu.TABLE_NAME = c.table_name
|
1383
|
-
AND kcu.position_in_unique_constraint IS NULL
|
1384
|
-
AND kcu.ordinal_position = c.ordinal_position
|
1385
|
-
LEFT OUTER JOIN INFORMATION_SCHEMA.table_constraints AS tc
|
1386
|
-
ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
1387
|
-
AND kcu.TABLE_NAME = tc.TABLE_NAME
|
1388
|
-
AND kcu.CONSTRAINT_NAME = tc.constraint_name
|
1389
|
-
WHERE t.table_schema NOT IN ('information_schema', 'pg_catalog')#{"
|
1390
|
-
AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }
|
1391
|
-
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
1392
|
-
AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
|
1393
|
-
ORDER BY 1, t.table_type DESC, c.ordinal_position"
|
1394
1381
|
measures = []
|
1395
1382
|
case ActiveRecord::Base.connection.adapter_name
|
1396
1383
|
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
1397
1384
|
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1398
|
-
|
1399
|
-
ActiveRecord::Base.schema_migrations_table_name
|
1400
|
-
else
|
1401
|
-
'schema_migrations'
|
1402
|
-
end
|
1403
|
-
ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
|
1404
|
-
ActiveRecord::Base.execute_sql(sql, ar_smtn, ar_imtn).each do |r|
|
1385
|
+
ActiveRecord::Base.retrieve_schema_and_tables(sql, is_postgres, schema).each do |r|
|
1405
1386
|
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
1406
1387
|
# is the default schema, usually 'public'.
|
1407
1388
|
schema_name = if ::Brick.config.schema_behavior[:multitenant]
|
@@ -1428,23 +1409,23 @@ module ActiveRecord::ConnectionHandling
|
|
1428
1409
|
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
1429
1410
|
end
|
1430
1411
|
else # MySQL2 acts a little differently, bringing back an array for each row
|
1431
|
-
ActiveRecord::Base.
|
1432
|
-
relation = relations[(relation_name = r[
|
1433
|
-
relation[:isView] = true if r[
|
1434
|
-
col_name = r[
|
1435
|
-
key = case r[
|
1412
|
+
ActiveRecord::Base.retrieve_schema_and_tables(sql).each do |r|
|
1413
|
+
relation = relations[(relation_name = r[1])] # here relation represents a table or view from the database
|
1414
|
+
relation[:isView] = true if r[2] == 'VIEW' # table_type
|
1415
|
+
col_name = r[3]
|
1416
|
+
key = case r[6] # constraint type
|
1436
1417
|
when 'PRIMARY KEY'
|
1437
1418
|
# key
|
1438
|
-
relation[:pkey][r[
|
1419
|
+
relation[:pkey][r[7] || relation_name] ||= []
|
1439
1420
|
when 'UNIQUE'
|
1440
|
-
relation[:ukeys][r[
|
1421
|
+
relation[:ukeys][r[7] || "#{relation_name}.#{col_name}"] ||= []
|
1441
1422
|
# key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
|
1442
1423
|
# key[r['key']]
|
1443
1424
|
end
|
1444
1425
|
key << col_name if key
|
1445
1426
|
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
1446
1427
|
# 'data_type', 'max_length'
|
1447
|
-
cols[col_name] = [r[
|
1428
|
+
cols[col_name] = [r[4], r[5], measures&.include?(col_name)]
|
1448
1429
|
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
1449
1430
|
end
|
1450
1431
|
end
|
@@ -1480,9 +1461,11 @@ module ActiveRecord::ConnectionHandling
|
|
1480
1461
|
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu2
|
1481
1462
|
ON kcu2.CONSTRAINT_CATALOG = rc.UNIQUE_CONSTRAINT_CATALOG
|
1482
1463
|
AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
|
1483
|
-
AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
|
1464
|
+
AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME#{"
|
1465
|
+
AND kcu2.TABLE_NAME = kcu1.REFERENCED_TABLE_NAME
|
1466
|
+
AND kcu2.COLUMN_NAME = kcu1.REFERENCED_COLUMN_NAME" unless is_postgres }
|
1484
1467
|
AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION#{"
|
1485
|
-
WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if schema }"
|
1468
|
+
WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }"
|
1486
1469
|
# AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
|
1487
1470
|
when 'SQLite'
|
1488
1471
|
sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
|
@@ -1498,12 +1481,14 @@ module ActiveRecord::ConnectionHandling
|
|
1498
1481
|
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
1499
1482
|
if apartment_excluded&.include?(fk[1].singularize.camelize)
|
1500
1483
|
fk[0] = Apartment.default_schema
|
1501
|
-
elsif fk[0] == 'public' || (is_multitenant && fk[0] == schema)
|
1484
|
+
elsif is_postgres && (fk[0] == 'public' || (is_multitenant && fk[0] == schema)) ||
|
1485
|
+
!is_postgres && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0])
|
1502
1486
|
fk[0] = nil
|
1503
1487
|
end
|
1504
1488
|
if apartment_excluded&.include?(fk[4].singularize.camelize)
|
1505
1489
|
fk[3] = Apartment.default_schema
|
1506
|
-
elsif fk[3] == 'public' || (is_multitenant && fk[3] == schema)
|
1490
|
+
elsif is_postgres && (fk[3] == 'public' || (is_multitenant && fk[3] == schema)) ||
|
1491
|
+
!is_postgres && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3])
|
1507
1492
|
fk[3] = nil
|
1508
1493
|
end
|
1509
1494
|
::Brick._add_bt_and_hm(fk, relations)
|
@@ -1537,6 +1522,43 @@ module ActiveRecord::ConnectionHandling
|
|
1537
1522
|
|
1538
1523
|
::Brick.load_additional_references if initializer_loaded
|
1539
1524
|
end
|
1525
|
+
|
1526
|
+
def retrieve_schema_and_tables(sql = nil, is_postgres = nil, schema = nil)
|
1527
|
+
sql ||= "SELECT t.table_schema AS \"schema\", t.table_name AS relation_name, t.table_type,#{"
|
1528
|
+
pg_catalog.obj_description((t.table_schema || '.' || t.table_name)::regclass, 'pg_class') AS table_description," if is_postgres}
|
1529
|
+
c.column_name, c.data_type,
|
1530
|
+
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
1531
|
+
tc.constraint_type AS const, kcu.constraint_name AS \"key\",
|
1532
|
+
c.is_nullable
|
1533
|
+
FROM INFORMATION_SCHEMA.tables AS t
|
1534
|
+
LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
|
1535
|
+
AND t.table_name = c.table_name
|
1536
|
+
LEFT OUTER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu ON
|
1537
|
+
-- ON kcu.CONSTRAINT_CATALOG = t.table_catalog AND
|
1538
|
+
kcu.CONSTRAINT_SCHEMA = c.table_schema
|
1539
|
+
AND kcu.TABLE_NAME = c.table_name
|
1540
|
+
AND kcu.position_in_unique_constraint IS NULL
|
1541
|
+
AND kcu.ordinal_position = c.ordinal_position
|
1542
|
+
LEFT OUTER JOIN INFORMATION_SCHEMA.table_constraints AS tc
|
1543
|
+
ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
1544
|
+
AND kcu.TABLE_NAME = tc.TABLE_NAME
|
1545
|
+
AND kcu.CONSTRAINT_NAME = tc.constraint_name
|
1546
|
+
WHERE t.table_schema #{is_postgres ?
|
1547
|
+
"NOT IN ('information_schema', 'pg_catalog')"
|
1548
|
+
:
|
1549
|
+
"= '#{ActiveRecord::Base.connection.current_database.tr("'", "''")}'"}#{"
|
1550
|
+
AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }
|
1551
|
+
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
1552
|
+
AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
|
1553
|
+
ORDER BY 1, t.table_type DESC, 2, c.ordinal_position"
|
1554
|
+
ar_smtn = if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
|
1555
|
+
ActiveRecord::Base.schema_migrations_table_name
|
1556
|
+
else
|
1557
|
+
'schema_migrations'
|
1558
|
+
end
|
1559
|
+
ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
|
1560
|
+
ActiveRecord::Base.execute_sql(sql, ar_smtn, ar_imtn)
|
1561
|
+
end
|
1540
1562
|
end
|
1541
1563
|
|
1542
1564
|
# ==========================================
|
@@ -47,7 +47,10 @@ module Brick
|
|
47
47
|
end
|
48
48
|
|
49
49
|
# After we're initialized and before running the rest of stuff, put our configuration in place
|
50
|
-
ActiveSupport.on_load(:after_initialize) do
|
50
|
+
ActiveSupport.on_load(:after_initialize) do |app|
|
51
|
+
assets_path = File.expand_path("#{__dir__}/../../../../vendor/assets")
|
52
|
+
(app.config.assets.precompile ||= []) << "#{assets_path}/images/brick_erd.png"
|
53
|
+
(app.config.assets.paths ||= []) << assets_path
|
51
54
|
# ====================================
|
52
55
|
# Dynamically create generic templates
|
53
56
|
# ====================================
|
@@ -150,7 +153,6 @@ module Brick
|
|
150
153
|
", nil, #{path_keys(hm_assoc, hm_fk_name, obj_name, pk)}"
|
151
154
|
end
|
152
155
|
hm_entry << ']'
|
153
|
-
puts hm_entry
|
154
156
|
hms_columns << hm_entry
|
155
157
|
when 'show', 'update'
|
156
158
|
hm_stuff << if hm_fk_name
|
@@ -180,6 +182,30 @@ module Brick
|
|
180
182
|
h1, h3 {
|
181
183
|
margin-bottom: 0;
|
182
184
|
}
|
185
|
+
#resourceName {
|
186
|
+
}
|
187
|
+
#imgErd {
|
188
|
+
background-image:url(assets/brick_erd.png);
|
189
|
+
background-size: 100% 100%;
|
190
|
+
width: 2.2em;
|
191
|
+
height: 2.2em;
|
192
|
+
cursor: pointer;
|
193
|
+
}
|
194
|
+
#mermaidErd {
|
195
|
+
position: relative;
|
196
|
+
display: none;
|
197
|
+
}
|
198
|
+
#mermaidErd .exclude {
|
199
|
+
position: absolute;
|
200
|
+
color: red;
|
201
|
+
top: 0;
|
202
|
+
right: 0;
|
203
|
+
cursor: pointer;
|
204
|
+
}
|
205
|
+
.relatedModel {
|
206
|
+
cursor: pointer;
|
207
|
+
}
|
208
|
+
|
183
209
|
#dropper {
|
184
210
|
background-color: #eee;
|
185
211
|
}
|
@@ -197,6 +223,8 @@ table {
|
|
197
223
|
border-collapse: collapse;
|
198
224
|
font-size: 0.9em;
|
199
225
|
font-family: sans-serif;
|
226
|
+
}
|
227
|
+
table.shadow {
|
200
228
|
min-width: 400px;
|
201
229
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
|
202
230
|
}
|
@@ -214,6 +242,7 @@ tr th {
|
|
214
242
|
display: none;
|
215
243
|
top: 0;
|
216
244
|
right: 0;
|
245
|
+
cursor: pointer;
|
217
246
|
}
|
218
247
|
#headerTop tr th:hover {
|
219
248
|
background-color: #18B090;
|
@@ -246,7 +275,7 @@ tr th, tr td {
|
|
246
275
|
color: #80B8D2;
|
247
276
|
}
|
248
277
|
|
249
|
-
table tbody tr {
|
278
|
+
table.shadow tbody tr {
|
250
279
|
border-bottom: thin solid #dddddd;
|
251
280
|
}
|
252
281
|
|
@@ -254,7 +283,7 @@ table tbody tr:nth-of-type(even) {
|
|
254
283
|
background-color: #f3f3f3;
|
255
284
|
}
|
256
285
|
|
257
|
-
table tbody tr:last-of-type {
|
286
|
+
table.shadow tbody tr:last-of-type {
|
258
287
|
border-bottom: 2px solid #009879;
|
259
288
|
}
|
260
289
|
|
@@ -328,6 +357,28 @@ def hide_bcrypt(val, max_len = 200)
|
|
328
357
|
end
|
329
358
|
val
|
330
359
|
end
|
360
|
+
end
|
361
|
+
def display_value(col_type, val)
|
362
|
+
case col_type
|
363
|
+
when 'geometry'
|
364
|
+
if Object.const_defined?('RGeo')
|
365
|
+
@is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2' if @is_mysql.nil?
|
366
|
+
if @is_mysql
|
367
|
+
# MySQL's \"Internal Geometry Format\" is like WKB, but with an initial 4 bytes that indicates the SRID.
|
368
|
+
srid = val[..3].unpack('I')
|
369
|
+
val = val[4..]
|
370
|
+
end
|
371
|
+
RGeo::WKRep::WKBParser.new.parse(val)
|
372
|
+
else
|
373
|
+
'(Add RGeo gem to parse geometry detail)'
|
374
|
+
end
|
375
|
+
else
|
376
|
+
if col_type
|
377
|
+
hide_bcrypt(val)
|
378
|
+
else
|
379
|
+
'?'
|
380
|
+
end
|
381
|
+
end
|
331
382
|
end %>"
|
332
383
|
|
333
384
|
if ['index', 'show', 'update'].include?(args.first)
|
@@ -417,10 +468,11 @@ function changeout(href, param, value, trimAfter) {
|
|
417
468
|
hrefParts[0] = pathParts.join(\"/\");
|
418
469
|
}
|
419
470
|
var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
|
420
|
-
params = params.reduce(function (s, v) { var parts = v.split(\"=\"); s[parts[0]] = parts[1]; return s; }, {});
|
471
|
+
params = params.reduce(function (s, v) { var parts = v.split(\"=\"); if (parts[1] !== null) s[parts[0]] = parts[1]; return s; }, {});
|
421
472
|
if (value === undefined) return params[param];
|
422
473
|
params[param] = value;
|
423
|
-
|
474
|
+
var finalParams = Object.keys(params).reduce(function (s, v) { if (params[v] !== null) s.push(v + \"=\" + params[v]); return s; }, []).join(\"&\");
|
475
|
+
return hrefParts[0] + (finalParams.length > 0 ? \"?\" + finalParams : \"\");
|
424
476
|
}
|
425
477
|
|
426
478
|
// Snag first TR for sticky header
|
@@ -584,7 +636,10 @@ if (headerTop) {
|
|
584
636
|
<p style=\"color: green\"><%= notice %></p>#{"
|
585
637
|
<select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
|
586
638
|
<select id=\"tbl\">#{table_options}</select>
|
587
|
-
<
|
639
|
+
<table id=\"resourceName\"><tr>
|
640
|
+
<td><h1>#{model_plural = model_name.pluralize}</h1></td>
|
641
|
+
<td id=\"imgErd\" title=\"Show ERD\"></td>
|
642
|
+
</tr></table>#{template_link}<%
|
588
643
|
if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
|
589
644
|
description %><br><%
|
590
645
|
end
|
@@ -615,9 +670,41 @@ if (headerTop) {
|
|
615
670
|
});
|
616
671
|
});
|
617
672
|
</script>
|
673
|
+
<% end
|
674
|
+
if true # @_brick_erd
|
675
|
+
%><div id=\"mermaidErd\" class=\"mermaid\">
|
676
|
+
erDiagram
|
677
|
+
<% model_short_name = #{@_brick_model.name.split('::').last.inspect}
|
678
|
+
callbacks = {}
|
679
|
+
@_brick_bt_descrip.each do |bt|
|
680
|
+
bt_full_name = bt[1].first.first.name
|
681
|
+
callbacks[bt_name = bt_full_name.split('::').last] = bt_full_name
|
682
|
+
# binding.pry
|
683
|
+
%> <%= \"#\{model_short_name} #\{'||'}--#\{
|
684
|
+
'o{'} #\{bt_name} : \\\"#\{
|
685
|
+
bt.first unless bt.first.to_s == bt[1].first.first.name.underscore.singularize.tr('/', '_')
|
686
|
+
}\\\"\".html_safe %>
|
687
|
+
<% end %>
|
688
|
+
<% @_brick_hm_counts.each do |hm|
|
689
|
+
hm_full_name = hm.last.klass.name
|
690
|
+
callbacks[hm_name = hm_full_name.split('::').last] = hm_full_name
|
691
|
+
%> <%= \"#\{model_short_name} #\{'}o'}--#\{
|
692
|
+
'||'} #\{hm_name} : \\\"#\{
|
693
|
+
hm.first unless hm.first.to_s == hm_full_name.underscore.pluralize.tr('/', '_')
|
694
|
+
}\\\"\".html_safe %>
|
618
695
|
<% end %>
|
619
|
-
|
620
|
-
|
696
|
+
<% callbacks.keys.each do |cb|
|
697
|
+
%> <%= cb %> {
|
698
|
+
int id
|
699
|
+
}
|
700
|
+
<% end
|
701
|
+
# callback < %= cb_k % > erdClick
|
702
|
+
%>
|
703
|
+
</div>
|
704
|
+
<% end
|
705
|
+
|
706
|
+
%><table id=\"headerTop\"></table>
|
707
|
+
<table id=\"#{table_name}\" class=\"shadow\">
|
621
708
|
<thead><tr>#{"<th x-order=\"#{pk.join(',')}\"></th>" if pk.present?}<%=
|
622
709
|
# Consider getting the name from the association -- hm.first.name -- if a more \"friendly\" alias should be used for a screwy table name
|
623
710
|
cols = {#{hms_keys = []
|
@@ -683,7 +770,7 @@ if (headerTop) {
|
|
683
770
|
<% end
|
684
771
|
elsif (hms_col = hms_cols[col_name])
|
685
772
|
if hms_col.length == 1 %>
|
686
|
-
|
773
|
+
<%= hms_col.first %>
|
687
774
|
<% else
|
688
775
|
klass = (col = cols[col_name])[1]
|
689
776
|
txt = if col[2] == 'HO'
|
@@ -696,8 +783,8 @@ if (headerTop) {
|
|
696
783
|
end %>
|
697
784
|
<%= link_to txt, send(\"#\{klass.name.underscore.tr('/', '_').pluralize}_path\".to_sym, hms_col[2]) unless hms_col[1]&.zero? %>
|
698
785
|
<% end
|
699
|
-
elsif cols
|
700
|
-
|
786
|
+
elsif (col = cols[col_name])
|
787
|
+
%><%= display_value(col&.type || col&.sql_type, val) %><%
|
701
788
|
else # Bad column name!
|
702
789
|
%>?<%
|
703
790
|
end
|
@@ -720,7 +807,7 @@ if (headerTop) {
|
|
720
807
|
<select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
|
721
808
|
<select id=\"tbl\">#{table_options}</select>
|
722
809
|
<h1>Status</h1>
|
723
|
-
<table id=\"status\"><thead><tr>
|
810
|
+
<table id=\"status\" class=\"shadow\"><thead><tr>
|
724
811
|
<th>Resource</th>
|
725
812
|
<th>Table</th>
|
726
813
|
<th>Migration</th>
|
@@ -754,7 +841,7 @@ if (headerTop) {
|
|
754
841
|
%></td>
|
755
842
|
<tr>
|
756
843
|
<% end %>
|
757
|
-
</tbody
|
844
|
+
</tbody></table>
|
758
845
|
#{script}"
|
759
846
|
|
760
847
|
when 'orphans'
|
@@ -796,7 +883,7 @@ end
|
|
796
883
|
# path_options << { '_brick_schema': } if
|
797
884
|
# url = send(:#{model_name.underscore}_path, obj.#{pk})
|
798
885
|
form_for(obj.becomes(#{model_name})) do |f| %>
|
799
|
-
<table>
|
886
|
+
<table class=\"shadow\">
|
800
887
|
<% has_fields = false
|
801
888
|
@#{obj_name}.attributes.each do |k, val|
|
802
889
|
col = #{model_name}.columns_hash[k] %>
|
@@ -853,7 +940,7 @@ end
|
|
853
940
|
<% else
|
854
941
|
html_options = {}
|
855
942
|
html_options[:class] = 'dimmed' unless val
|
856
|
-
case (col_type =
|
943
|
+
case (col_type = col.type || col.sql_type)
|
857
944
|
when :string, :text %>
|
858
945
|
<% if is_bcrypt?(val) # || .readonly? %>
|
859
946
|
<%= hide_bcrypt(val, 1000) %>
|
@@ -883,6 +970,8 @@ end
|
|
883
970
|
# If it's not yet enabled then: create extension ltree;
|
884
971
|
val %>
|
885
972
|
<% when :binary, :primary_key %>
|
973
|
+
<% else %>
|
974
|
+
<%= display_value(col_type, val) %>
|
886
975
|
<% end %>
|
887
976
|
<% end %>
|
888
977
|
</td>
|
@@ -903,7 +992,7 @@ end
|
|
903
992
|
if (pk = hm.first.klass.primary_key)
|
904
993
|
hm_singular_name = (hm_name = hm.first.name.to_s).singularize.underscore
|
905
994
|
obj_pk = (pk.is_a?(Array) ? pk : [pk]).each_with_object([]) { |pk_part, s| s << "#{hm_singular_name}.#{pk_part}" }.join(', ')
|
906
|
-
s << "<table id=\"#{hm_name}\">
|
995
|
+
s << "<table id=\"#{hm_name}\" class=\"shadow\">
|
907
996
|
<tr><th>#{hm[3]}</th></tr>
|
908
997
|
<% collection = @#{obj_name}.#{hm_name}
|
909
998
|
collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection].compact
|
@@ -938,7 +1027,61 @@ flatpickr(\".datetimepicker\", {enableTime: true});
|
|
938
1027
|
flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
|
939
1028
|
</script>
|
940
1029
|
<% end %>
|
1030
|
+
|
1031
|
+
<% if true # @_brick_erd
|
1032
|
+
%>
|
941
1033
|
<script>
|
1034
|
+
var imgErd = document.getElementById(\"imgErd\");
|
1035
|
+
var mermaidErd = document.getElementById(\"mermaidErd\");
|
1036
|
+
var mermaidCode;
|
1037
|
+
var cbs = {<%= callbacks.map { |k, v| \"#\{k}: \\\"#\{v.underscore.pluralize}\\\"\" }.join(', ').html_safe %>};
|
1038
|
+
imgErd.addEventListener(\"click\", showErd);
|
1039
|
+
function showErd() {
|
1040
|
+
imgErd.style.display = \"none\";
|
1041
|
+
mermaidErd.style.display = \"inline-block\";
|
1042
|
+
if (mermaidCode) return; // Cut it short if we've already rendered the diagram
|
1043
|
+
|
1044
|
+
mermaidCode = document.createElement(\"SCRIPT\");
|
1045
|
+
mermaidCode.setAttribute(\"src\", \"https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js\");
|
1046
|
+
mermaidCode.addEventListener(\"load\", function () {
|
1047
|
+
mermaid.initialize({
|
1048
|
+
startOnLoad: true,
|
1049
|
+
securityLevel: \"loose\",
|
1050
|
+
mermaid: {callback: function(objId) {
|
1051
|
+
var svg = document.getElementById(objId);
|
1052
|
+
var cb;
|
1053
|
+
for(cb in cbs) {
|
1054
|
+
var gErd = svg.getElementById(cb);
|
1055
|
+
gErd.setAttribute(\"class\", \"relatedModel\");
|
1056
|
+
gErd.addEventListener(\"click\",
|
1057
|
+
function (evt) {
|
1058
|
+
location.href = changeout(changeout(location.href, null, cbs[this.id]), \"_brick_erd\", \"1\");
|
1059
|
+
}
|
1060
|
+
);
|
1061
|
+
}
|
1062
|
+
}}
|
1063
|
+
});
|
1064
|
+
mermaid.contentLoaded();
|
1065
|
+
// Add <span> at the end
|
1066
|
+
var span = document.createElement(\"SPAN\");
|
1067
|
+
span.className = \"exclude\";
|
1068
|
+
span.innerHTML = \"X\";
|
1069
|
+
span.addEventListener(\"click\", function (e) {
|
1070
|
+
e.stopPropagation();
|
1071
|
+
imgErd.style.display = \"table-cell\";
|
1072
|
+
mermaidErd.style.display = \"none\";
|
1073
|
+
window.history.pushState({}, '', changeout(location.href, '_brick_erd', null));
|
1074
|
+
});
|
1075
|
+
mermaidErd.appendChild(span);
|
1076
|
+
});
|
1077
|
+
document.body.appendChild(mermaidCode);
|
1078
|
+
}
|
1079
|
+
<%= \" showErd();\n\" if (@_brick_erd || 0) > 0
|
1080
|
+
%></script>
|
1081
|
+
|
1082
|
+
<% end
|
1083
|
+
|
1084
|
+
%><script>
|
942
1085
|
<% # Make column headers sort when clicked
|
943
1086
|
# %%% Create a smart javascript routine which can do this client-side %>
|
944
1087
|
[... document.getElementsByTagName(\"TH\")].forEach(function (th) {
|
data/lib/brick/version_number.rb
CHANGED
@@ -21,7 +21,7 @@ module Brick
|
|
21
21
|
'timestamp with time zone' => 'timestamp',
|
22
22
|
'time without time zone' => 'time',
|
23
23
|
'time with time zone' => 'time',
|
24
|
-
'double precision' => 'float',
|
24
|
+
'double precision' => 'float',
|
25
25
|
'smallint' => 'integer' } # %%% Need to put in "limit: 2"
|
26
26
|
# (Still need to find what "inet" and "json" data types map to.)
|
27
27
|
|
@@ -32,6 +32,7 @@ module Brick
|
|
32
32
|
end
|
33
33
|
models = ::Brick.relations.keys.map do |tbl|
|
34
34
|
tbl_parts = tbl.split('.')
|
35
|
+
tbl_parts.shift if [::Brick.default_schema, 'public'].include?(tbl_parts.first)
|
35
36
|
tbl_parts[-1] = tbl_parts[-1].singularize
|
36
37
|
tbl_parts.join('/').camelize
|
37
38
|
end - existing_models.map(&:name)
|
@@ -54,7 +55,7 @@ module Brick
|
|
54
55
|
chosen = gets_list(list: models, chosen: models.dup)
|
55
56
|
relations = ::Brick.relations
|
56
57
|
chosen.each do |model_name|
|
57
|
-
# If we're in a schema then make sure the module file exists
|
58
|
+
# %%% If we're in a schema then make sure the module file exists
|
58
59
|
base_module = if (model_parts = model_name.split('::')).length > 1
|
59
60
|
"::#{model_parts.first}".constantize
|
60
61
|
else
|
@@ -68,7 +69,7 @@ module Brick
|
|
68
69
|
dir << "/#{path_part}"
|
69
70
|
Dir.mkdir(dir) unless Dir.exists?(dir)
|
70
71
|
end
|
71
|
-
File.open("#{dir}/#{path.last}.rb", 'w') { |f| f.write code }
|
72
|
+
File.open("#{dir}/#{path.last}.rb", 'w') { |f| f.write code } unless code.blank?
|
72
73
|
end
|
73
74
|
puts "\n*** Created #{chosen.length} model files under app/models ***"
|
74
75
|
end
|
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.62
|
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-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|