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: db41c2288f8bba8f3cb9c5d2d87bacd4806d1f2401ca9709f85de1886b0db377
4
- data.tar.gz: 42b0a7b285a3328ce922daa442e15bf994a382ad0bfc5ed8fe4370ae6b779fbb
3
+ metadata.gz: ae6fe72ae3ec1d2ba6318f1bb6cb4964b29a1479e3b76e2cb560537cc6a6ff6c
4
+ data.tar.gz: 608fbd2c50c31dd4eff33a4d7c8a19786c53518e9fc1ff38059bf47ca254b6ba
5
5
  SHA512:
6
- metadata.gz: b8baacad02f8ba5c0d9bfe11182d8297c817ba46bef250e172f55a0ff949591abcadda790aecc2614515468a4da468415a8a0e62fd2a60232177dcd3cd63ca03
7
- data.tar.gz: e84d2573ec0f95b01dfebf681cec409ec66a1e6052abbb59ebd3130728cad71015c51087f484c55e8e30f3b9a3736d3d9262b7d8cdebf23e5ab702035c0dcbfa
6
+ metadata.gz: 0b0c3fcc243cde60b35f2cf07277a53c413b1b805d9a31cf00f4d89676b7eaf0d79ba1ea22bc33190ff8df55eab96e62b0c986f1894dc11e55b8358dbae7cc57
7
+ data.tar.gz: 32d9c0e8bba6f2a477ed85347680113ab92d1ed52ce044d97bacf734d33fa7c9e7f62bc87ab01b2a027e1f41c62afe802f3601e14f7bdc9d8026b03050337c23
@@ -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
- col_alias = ' AS _class'
402
- end
403
- # Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
404
- cast_as_text = '::text' if is_distinct && Brick.relations[klass.table_name]&.[](:cols)&.[](col.name)&.first&.start_with?('xml')
405
- selects << "\"#{tbl_no_schema}\".\"#{col_name}\"#{cast_as_text}#{col_alias}"
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 << "\"#{field_tbl_name}\".\"#{sel_col.last}\"#{'::text' if is_xml} AS \"#{col_alias}\""
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 << "#{"\"#{tbl_name}\".\"#{id_part}\""} AS \"#{(id_alias = "_brfk_#{v.first}__#{id_part}")}\""
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
- ordering = if (order_tbl = ::Brick.config.order[table_name])
1081
- case (order_default = order_tbl[:_brick_default])
1082
- when Array
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? (Doesn't work in sqlite3)
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
- ar_smtn = if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
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.execute_sql(sql).each do |r|
1432
- relation = relations[(relation_name = r[0])] # here relation represents a table or view from the database
1433
- relation[:isView] = true if r[1] == 'VIEW' # table_type
1434
- col_name = r[2]
1435
- key = case r[5] # constraint type
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[6] || relation_name] ||= []
1419
+ relation[:pkey][r[7] || relation_name] ||= []
1439
1420
  when 'UNIQUE'
1440
- relation[:ukeys][r[6] || "#{relation_name}.#{col_name}"] ||= []
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[3], r[4], measures&.include?(col_name)]
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
- return hrefParts[0] + \"?\" + Object.keys(params).reduce(function (s, v) { s.push(v + \"=\" + params[v]); return s; }, []).join(\"&\");
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
- <h1>#{model_plural = model_name.pluralize}</h1>#{template_link}<%
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
- <table id=\"headerTop\">
620
- <table id=\"#{table_name}\">
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
- <%= hms_col.first %>
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.key?(col_name)
700
- %><%= hide_bcrypt(val) %><%
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><table>
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 = #{model_name}.column_for_attribute(k).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) {
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 59
8
+ TINY = 62
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
@@ -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', # might work with 'double'
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.59
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-14 00:00:00.000000000 Z
11
+ date: 2022-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord