brick 1.0.68 → 1.0.70
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/extensions.rb +191 -69
- data/lib/brick/frameworks/rails/engine.rb +12 -7
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +22 -3
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af28a866a39817e9daa3a54b8f091c790d9280571cec94f9a3a317606e239fd9
|
4
|
+
data.tar.gz: a2cfe0b823c8b364c8f842852e631a48efe7f9dde8d506a27fbc85ebb74e58eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0cc18926c7c449e7dfba8d919d41b2490cb4d4a14692073f98f96903da075a31ff029f7a5f5931a845c7caf8f00b51cdba9ab0711dfa626d9e445d9122350b2b
|
7
|
+
data.tar.gz: 84373ca68edb9ebb88b8ccf2963bbf2e416c35c41840df1c1cd6922ef757cac204453aeb1e15881316681558a53ce17e32692e16320bb1b53c608bc41401920d
|
data/lib/brick/extensions.rb
CHANGED
@@ -266,18 +266,27 @@ module ActiveRecord
|
|
266
266
|
quoted_table_name = table_name.split('.').map { |x| "\"#{x}\"" }.join('.')
|
267
267
|
order_by_txt = [] if is_do_txt
|
268
268
|
ordering = [ordering] if ordering && !ordering.is_a?(Array)
|
269
|
-
order_by = ordering&.
|
269
|
+
order_by = ordering&.each_with_object([]) do |ord_part, s| # %%% If a term is also used as an eqi-condition in the WHERE clause, it can be omitted from ORDER BY
|
270
270
|
case ord_part
|
271
271
|
when String
|
272
272
|
ord_expr = ord_part.gsub('^^^', quoted_table_name)
|
273
273
|
order_by_txt&.<<("Arel.sql(#{ord_expr})")
|
274
|
-
Arel.sql(ord_expr)
|
274
|
+
s << Arel.sql(ord_expr)
|
275
275
|
else # Expecting only Symbol
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
276
|
+
if _br_hm_counts.key?(ord_part)
|
277
|
+
ord_part = "\"b_r_#{ord_part}_ct\""
|
278
|
+
elsif !_br_bt_descrip.key?(ord_part) && !column_names.include?(ord_part.to_s)
|
279
|
+
# Disallow ordering by a bogus column
|
280
|
+
# %%% Note this bogus entry so that Javascript can remove any bogus _brick_order
|
281
|
+
# parameter from the querystring, pushing it into the browser history.
|
282
|
+
ord_part = nil
|
283
|
+
end
|
284
|
+
if ord_part
|
285
|
+
# Retain any reference to a bt_descrip as being a symbol
|
286
|
+
# Was: "#{quoted_table_name}.\"#{ord_part}\""
|
287
|
+
order_by_txt&.<<(_br_bt_descrip.key?(ord_part) ? ord_part : ord_part.inspect)
|
288
|
+
s << ord_part
|
289
|
+
end
|
281
290
|
end
|
282
291
|
end
|
283
292
|
[order_by, order_by_txt]
|
@@ -373,7 +382,9 @@ module ActiveRecord
|
|
373
382
|
end
|
374
383
|
|
375
384
|
def brick_select(params, selects = nil, order_by = nil, translations = {}, join_array = ::Brick::JoinArray.new)
|
385
|
+
is_postgres = ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
376
386
|
is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2'
|
387
|
+
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
|
377
388
|
is_distinct = nil
|
378
389
|
wheres = {}
|
379
390
|
params.each do |k, v|
|
@@ -398,13 +409,15 @@ module ActiveRecord
|
|
398
409
|
if selects&.empty? # Default to all columns
|
399
410
|
tbl_no_schema = table.name.split('.').last
|
400
411
|
columns.each do |col|
|
401
|
-
col_alias = " AS
|
412
|
+
col_alias = " AS #{col.name}_" if (col_name = col.name) == 'class'
|
402
413
|
selects << if is_mysql
|
403
414
|
"`#{tbl_no_schema}`.`#{col_name}`#{col_alias}"
|
404
|
-
|
415
|
+
elsif is_postgres || is_mssql
|
405
416
|
# Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
|
406
417
|
cast_as_text = '::text' if is_distinct && Brick.relations[klass.table_name]&.[](:cols)&.[](col.name)&.first&.start_with?('xml')
|
407
418
|
"\"#{tbl_no_schema}\".\"#{col_name}\"#{cast_as_text}#{col_alias}"
|
419
|
+
else # Sqlite or Oracle
|
420
|
+
"#{tbl_no_schema}.#{col_name}#{col_alias}"
|
408
421
|
end
|
409
422
|
end
|
410
423
|
end
|
@@ -430,13 +443,17 @@ module ActiveRecord
|
|
430
443
|
# Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
|
431
444
|
is_xml = is_distinct && Brick.relations[sel_col.first.table_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
|
432
445
|
# If it's not unique then also include the belongs_to association name before the column name
|
433
|
-
if used_col_aliases.key?(col_alias = "
|
434
|
-
col_alias = "
|
446
|
+
if used_col_aliases.key?(col_alias = "br_fk_#{v.first}__#{sel_col.last}")
|
447
|
+
col_alias = "br_fk_#{v.first}__#{v1[idx][-2..-1].map(&:to_s).join('__')}"
|
435
448
|
end
|
436
449
|
selects << if is_mysql
|
437
450
|
"`#{field_tbl_name}`.`#{sel_col.last}` AS `#{col_alias}`"
|
438
|
-
|
451
|
+
elsif is_postgres
|
439
452
|
"\"#{field_tbl_name}\".\"#{sel_col.last}\"#{'::text' if is_xml} AS \"#{col_alias}\""
|
453
|
+
elsif is_mssql
|
454
|
+
"\"#{field_tbl_name}\".\"#{sel_col.last}\" AS \"#{col_alias}\""
|
455
|
+
else
|
456
|
+
"#{field_tbl_name}.#{sel_col.last} AS \"#{col_alias}\""
|
440
457
|
end
|
441
458
|
used_col_aliases[col_alias] = nil
|
442
459
|
v1[idx] << col_alias
|
@@ -447,9 +464,11 @@ module ActiveRecord
|
|
447
464
|
((id_col = k1.primary_key).is_a?(Array) ? id_col : [id_col]).each do |id_part|
|
448
465
|
id_for_tables[v.first] << if id_part
|
449
466
|
selects << if is_mysql
|
450
|
-
"#{"`#{tbl_name}`.`#{id_part}`"} AS `#{(id_alias = "
|
467
|
+
"#{"`#{tbl_name}`.`#{id_part}`"} AS `#{(id_alias = "br_fk_#{v.first}__#{id_part}")}`"
|
468
|
+
elsif is_postgres || is_mssql
|
469
|
+
"#{"\"#{tbl_name}\".\"#{id_part}\""} AS \"#{(id_alias = "br_fk_#{v.first}__#{id_part}")}\""
|
451
470
|
else
|
452
|
-
"#{"
|
471
|
+
"#{"#{tbl_name}.#{id_part}"} AS \"#{(id_alias = "br_fk_#{v.first}__#{id_part}")}\""
|
453
472
|
end
|
454
473
|
id_alias
|
455
474
|
end
|
@@ -479,9 +498,22 @@ module ActiveRecord
|
|
479
498
|
end
|
480
499
|
next unless count_column # %%% Would be able to remove this when multiple foreign keys to same destination becomes bulletproof
|
481
500
|
|
482
|
-
tbl_alias = is_mysql
|
501
|
+
tbl_alias = if is_mysql
|
502
|
+
"`b_r_#{hm.name}`"
|
503
|
+
elsif is_postgres
|
504
|
+
"\"b_r_#{hm.name}\""
|
505
|
+
else
|
506
|
+
"b_r_#{hm.name}"
|
507
|
+
end
|
483
508
|
pri_tbl = hm.active_record
|
484
509
|
pri_tbl_name = is_mysql ? "`#{pri_tbl.table_name}`" : "\"#{pri_tbl.table_name.gsub('.', '"."')}\""
|
510
|
+
pri_tbl_name = if is_mysql
|
511
|
+
"`#{pri_tbl.table_name}`"
|
512
|
+
elsif is_postgres || is_mssql
|
513
|
+
"\"#{pri_tbl.table_name.gsub('.', '"."')}\""
|
514
|
+
else
|
515
|
+
pri_tbl.table_name
|
516
|
+
end
|
485
517
|
on_clause = []
|
486
518
|
if fk_col.is_a?(Array) # Composite key?
|
487
519
|
fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{tbl_alias}.#{fk_col_part} = #{pri_tbl_name}.#{pri_tbl.primary_key[idx]}" }
|
@@ -494,22 +526,29 @@ module ActiveRecord
|
|
494
526
|
selects << poly_type
|
495
527
|
on_clause << "#{tbl_alias}.#{poly_type} = '#{name}'"
|
496
528
|
end
|
497
|
-
hm_table_name = is_mysql
|
529
|
+
hm_table_name = if is_mysql
|
530
|
+
"`#{associative&.table_name || hm.klass.table_name}`"
|
531
|
+
elsif is_postgres || is_mssql
|
532
|
+
"\"#{(associative&.table_name || hm.klass.table_name).gsub('.', '"."')}\""
|
533
|
+
else
|
534
|
+
associative&.table_name || hm.klass.table_name
|
535
|
+
end
|
536
|
+
group_bys = ::Brick.is_oracle || is_mssql ? selects : (1..selects.length).to_a
|
498
537
|
join_clause = "LEFT OUTER
|
499
538
|
JOIN (SELECT #{selects.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}#{count_column
|
500
|
-
}) AS
|
539
|
+
}) AS c_t_ FROM #{hm_table_name} GROUP BY #{group_bys.join(', ')}) #{tbl_alias}"
|
501
540
|
joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
|
502
541
|
end
|
503
542
|
where!(wheres) unless wheres.empty?
|
504
543
|
# Must parse the order_by and see if there are any symbols which refer to BT associations
|
505
|
-
# as they must be expanded to find the corresponding
|
544
|
+
# as they must be expanded to find the corresponding b_r_model__column naming for each.
|
506
545
|
if order_by.present?
|
507
546
|
final_order_by = *order_by.each_with_object([]) do |v, s|
|
508
547
|
if v.is_a?(Symbol)
|
509
548
|
# Add the ordered series of columns derived from the BT based on its DSL
|
510
549
|
if (bt_cols = klass._br_bt_descrip[v])
|
511
550
|
bt_cols.values.each do |v1|
|
512
|
-
v1.each { |v2| s << v2.last if v2.length > 1 }
|
551
|
+
v1.each { |v2| s << "\"#{v2.last}\"" if v2.length > 1 }
|
513
552
|
end
|
514
553
|
else
|
515
554
|
s << v
|
@@ -625,7 +664,7 @@ Module.class_exec do
|
|
625
664
|
)
|
626
665
|
return possible
|
627
666
|
end
|
628
|
-
class_name = args.first.to_s
|
667
|
+
class_name = ::Brick.namify(args.first.to_s)
|
629
668
|
# self.name is nil when a model name is requested in an .erb file
|
630
669
|
base_module = (self < ActiveRecord::Migration || !self.name) ? Object : self
|
631
670
|
# See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
|
@@ -737,7 +776,7 @@ class Object
|
|
737
776
|
schema_name = Apartment.default_schema
|
738
777
|
end
|
739
778
|
# Maybe, just maybe there's a database table that will satisfy this need
|
740
|
-
if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
|
779
|
+
if (matching = [table_name, singular_table_name, plural_class_name, model_name, table_name.titleize].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
|
741
780
|
build_model_worker(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
|
742
781
|
end
|
743
782
|
end
|
@@ -1020,6 +1059,7 @@ class Object
|
|
1020
1059
|
table_name = ActiveSupport::Inflector.underscore(plural_class_name)
|
1021
1060
|
singular_table_name = ActiveSupport::Inflector.singularize(table_name)
|
1022
1061
|
pk = model&._brick_primary_key(relations.fetch(table_name, nil))
|
1062
|
+
is_postgres = ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1023
1063
|
is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2'
|
1024
1064
|
|
1025
1065
|
namespace_name = "#{namespace.name}::" if namespace
|
@@ -1125,7 +1165,13 @@ class Object
|
|
1125
1165
|
# %%% Add custom HM count columns
|
1126
1166
|
# %%% What happens when the PK is composite?
|
1127
1167
|
counts = model._br_hm_counts.each_with_object([]) do |v, s|
|
1128
|
-
s <<
|
1168
|
+
s << if is_mysql
|
1169
|
+
"`b_r_#{v.first}`.c_t_ AS \"b_r_#{v.first}_ct\""
|
1170
|
+
elsif is_postgres
|
1171
|
+
"\"b_r_#{v.first}\".c_t_ AS \"b_r_#{v.first}_ct\""
|
1172
|
+
else
|
1173
|
+
"b_r_#{v.first}.c_t_ AS \"b_r_#{v.first}_ct\""
|
1174
|
+
end
|
1129
1175
|
end
|
1130
1176
|
instance_variable_set("@#{table_name}".to_sym, ar_relation.dup._select!(*selects, *counts))
|
1131
1177
|
if namespace && (idx = lookup_context.prefixes.index(table_name))
|
@@ -1297,7 +1343,7 @@ class Object
|
|
1297
1343
|
# hm_assoc[:assoc_name] = new_alt_name
|
1298
1344
|
[new_alt_name, true]
|
1299
1345
|
else
|
1300
|
-
assoc_name = hm_assoc[:inverse_table].pluralize
|
1346
|
+
assoc_name = ::Brick.namify(hm_assoc[:inverse_table]).pluralize
|
1301
1347
|
# hm_assoc[:assoc_name] = assoc_name
|
1302
1348
|
[assoc_name, assoc_name.include?('.')]
|
1303
1349
|
end
|
@@ -1340,12 +1386,20 @@ module ActiveRecord::ConnectionHandling
|
|
1340
1386
|
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
1341
1387
|
|
1342
1388
|
is_postgres = nil
|
1389
|
+
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
|
1343
1390
|
case ActiveRecord::Base.connection.adapter_name
|
1344
|
-
when 'PostgreSQL'
|
1345
|
-
is_postgres =
|
1391
|
+
when 'PostgreSQL', 'SQLServer'
|
1392
|
+
is_postgres = !is_mssql
|
1346
1393
|
db_schemas = ActiveRecord::Base.execute_sql('SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;')
|
1347
1394
|
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
1348
|
-
row =
|
1395
|
+
row = case row
|
1396
|
+
when String
|
1397
|
+
row
|
1398
|
+
when Array
|
1399
|
+
row.first
|
1400
|
+
else
|
1401
|
+
row['table_schema']
|
1402
|
+
end
|
1349
1403
|
# Remove any system schemas
|
1350
1404
|
s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
|
1351
1405
|
end
|
@@ -1357,6 +1411,11 @@ module ActiveRecord::ConnectionHandling
|
|
1357
1411
|
end
|
1358
1412
|
when 'Mysql2'
|
1359
1413
|
::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
|
1414
|
+
when 'OracleEnhanced'
|
1415
|
+
# ActiveRecord::Base.connection.current_database will be something like "XEPDB1"
|
1416
|
+
::Brick.default_schema = schema = ActiveRecord::Base.connection.raw_connection.username
|
1417
|
+
::Brick.db_schemas = {}
|
1418
|
+
execute_oracle("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] = nil }
|
1360
1419
|
when 'SQLite'
|
1361
1420
|
sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
|
1362
1421
|
p.name AS column_name, p.type AS data_type,
|
@@ -1389,7 +1448,7 @@ module ActiveRecord::ConnectionHandling
|
|
1389
1448
|
case ActiveRecord::Base.connection.adapter_name
|
1390
1449
|
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
1391
1450
|
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1392
|
-
ActiveRecord::Base.retrieve_schema_and_tables(sql, is_postgres, schema).each do |r|
|
1451
|
+
ActiveRecord::Base.retrieve_schema_and_tables(sql, is_postgres, is_mssql, schema).each do |r|
|
1393
1452
|
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
1394
1453
|
# is the default schema, usually 'public'.
|
1395
1454
|
schema_name = if ::Brick.config.schema_behavior[:multitenant]
|
@@ -1415,9 +1474,32 @@ module ActiveRecord::ConnectionHandling
|
|
1415
1474
|
cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name), r['is_nullable'] == 'NO']
|
1416
1475
|
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
1417
1476
|
end
|
1418
|
-
else # MySQL2
|
1419
|
-
ActiveRecord::Base.
|
1420
|
-
|
1477
|
+
else # MySQL2 and OracleEnhanced act a little differently, bringing back an array for each row
|
1478
|
+
schema_and_tables = case ActiveRecord::Base.connection.adapter_name
|
1479
|
+
when 'OracleEnhanced'
|
1480
|
+
sql =
|
1481
|
+
"SELECT c.owner AS schema, c.table_name AS relation_name, 'BASE_TABLE' AS table_type,
|
1482
|
+
LOWER(c.column_name) AS column_name, c.data_type,
|
1483
|
+
COALESCE(c.data_length, c.data_precision) AS max_length,
|
1484
|
+
CASE ac.constraint_type WHEN 'P' THEN 'PRIMARY KEY' END AS const,
|
1485
|
+
ac.constraint_name AS \"key\",
|
1486
|
+
CASE c.nullable WHEN 'Y' THEN 'YES' ELSE 'NO' END AS is_nullable
|
1487
|
+
FROM all_tab_cols c
|
1488
|
+
LEFT OUTER JOIN all_cons_columns acc ON acc.owner = c.owner AND acc.table_name = c.table_name AND acc.column_name = c.column_name
|
1489
|
+
LEFT OUTER JOIN all_constraints ac ON ac.owner = acc.owner AND ac.table_name = acc.table_name AND ac.constraint_name = acc.constraint_name AND constraint_type = 'P'
|
1490
|
+
WHERE c.owner IN (#{::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')})
|
1491
|
+
ORDER BY 1, 2, c.internal_column_id, acc.position"
|
1492
|
+
execute_oracle(sql, *ar_tables)
|
1493
|
+
else
|
1494
|
+
ActiveRecord::Base.retrieve_schema_and_tables(sql)
|
1495
|
+
end
|
1496
|
+
schema_and_tables.each do |r|
|
1497
|
+
next if r[1].index('$') # Oracle can have goofy table names with $
|
1498
|
+
|
1499
|
+
if (relation_name = r[1]) =~ /^[A-Z_]+$/
|
1500
|
+
relation_name.downcase!
|
1501
|
+
end
|
1502
|
+
relation = relations[relation_name] # here relation represents a table or view from the database
|
1421
1503
|
relation[:isView] = true if r[2] == 'VIEW' # table_type
|
1422
1504
|
col_name = r[3]
|
1423
1505
|
key = case r[6] # constraint type
|
@@ -1457,9 +1539,9 @@ module ActiveRecord::ConnectionHandling
|
|
1457
1539
|
# end
|
1458
1540
|
# schema = ::Brick.default_schema # Reset back for this next round of fun
|
1459
1541
|
case ActiveRecord::Base.connection.adapter_name
|
1460
|
-
when 'PostgreSQL', 'Mysql2'
|
1542
|
+
when 'PostgreSQL', 'Mysql2', 'SQLServer'
|
1461
1543
|
sql = "SELECT kcu1.CONSTRAINT_SCHEMA, kcu1.TABLE_NAME, kcu1.COLUMN_NAME,
|
1462
|
-
|
1544
|
+
kcu2.CONSTRAINT_SCHEMA AS primary_schema, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME AS CONSTRAINT_SCHEMA_FK
|
1463
1545
|
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
|
1464
1546
|
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
|
1465
1547
|
ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
|
@@ -1470,36 +1552,55 @@ module ActiveRecord::ConnectionHandling
|
|
1470
1552
|
AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
|
1471
1553
|
AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME#{"
|
1472
1554
|
AND kcu2.TABLE_NAME = kcu1.REFERENCED_TABLE_NAME
|
1473
|
-
AND kcu2.COLUMN_NAME = kcu1.REFERENCED_COLUMN_NAME" unless is_postgres }
|
1555
|
+
AND kcu2.COLUMN_NAME = kcu1.REFERENCED_COLUMN_NAME" unless is_postgres || is_mssql }
|
1474
1556
|
AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION#{"
|
1475
1557
|
WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }"
|
1476
1558
|
# AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
|
1559
|
+
fk_references = ActiveRecord::Base.execute_sql(sql)
|
1477
1560
|
when 'SQLite'
|
1478
1561
|
sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
|
1479
1562
|
FROM sqlite_master m
|
1480
1563
|
INNER JOIN pragma_foreign_key_list(m.name) fkl ON m.type = 'table'
|
1481
1564
|
ORDER BY m.name, fkl.seq"
|
1482
|
-
|
1565
|
+
fk_references = ActiveRecord::Base.execute_sql(sql)
|
1566
|
+
when 'OracleEnhanced'
|
1567
|
+
schemas = ::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')
|
1568
|
+
sql =
|
1569
|
+
"SELECT -- fk
|
1570
|
+
ac.owner AS constraint_schema, acc_fk.table_name, LOWER(acc_fk.column_name),
|
1571
|
+
-- referenced pk
|
1572
|
+
ac.r_owner AS primary_schema, acc_pk.table_name AS primary_table, acc_fk.constraint_name AS constraint_schema_fk
|
1573
|
+
-- , LOWER(acc_pk.column_name)
|
1574
|
+
FROM all_cons_columns acc_fk
|
1575
|
+
INNER JOIN all_constraints ac ON acc_fk.owner = ac.owner
|
1576
|
+
AND acc_fk.constraint_name = ac.constraint_name
|
1577
|
+
INNER JOIN all_cons_columns acc_pk ON ac.r_owner = acc_pk.owner
|
1578
|
+
AND ac.r_constraint_name = acc_pk.constraint_name
|
1579
|
+
WHERE ac.constraint_type = 'R'
|
1580
|
+
AND ac.owner IN (#{schemas})
|
1581
|
+
AND ac.r_owner IN (#{schemas})"
|
1582
|
+
fk_references = ActiveRecord::Base.execute_oracle(sql)
|
1483
1583
|
end
|
1484
|
-
if
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
end
|
1495
|
-
if apartment_excluded&.include?(fk[4].singularize.camelize)
|
1496
|
-
fk[3] = Apartment.default_schema
|
1497
|
-
elsif is_postgres && (fk[3] == 'public' || (is_multitenant && fk[3] == schema)) ||
|
1498
|
-
!is_postgres && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3])
|
1499
|
-
fk[3] = nil
|
1500
|
-
end
|
1501
|
-
::Brick._add_bt_and_hm(fk, relations)
|
1584
|
+
::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
|
1585
|
+
# ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1586
|
+
fk_references&.each do |fk|
|
1587
|
+
fk = fk.values unless fk.is_a?(Array)
|
1588
|
+
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
1589
|
+
if apartment_excluded&.include?(::Brick.namify(fk[1]).singularize.camelize)
|
1590
|
+
fk[0] = Apartment.default_schema
|
1591
|
+
elsif is_postgres && (fk[0] == 'public' || (is_multitenant && fk[0] == schema)) ||
|
1592
|
+
!is_postgres && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0])
|
1593
|
+
fk[0] = nil
|
1502
1594
|
end
|
1595
|
+
if apartment_excluded&.include?(fk[4].singularize.camelize)
|
1596
|
+
fk[3] = Apartment.default_schema
|
1597
|
+
elsif is_postgres && (fk[3] == 'public' || (is_multitenant && fk[3] == schema)) ||
|
1598
|
+
!is_postgres && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3])
|
1599
|
+
fk[3] = nil
|
1600
|
+
end
|
1601
|
+
fk[1].downcase! if ::Brick.is_oracle && fk[1] =~ /^[A-Z_]+$/
|
1602
|
+
fk[4].downcase! if ::Brick.is_oracle && fk[4] =~ /^[A-Z_]+$/
|
1603
|
+
::Brick._add_bt_and_hm(fk, relations)
|
1503
1604
|
end
|
1504
1605
|
end
|
1505
1606
|
|
@@ -1530,29 +1631,35 @@ module ActiveRecord::ConnectionHandling
|
|
1530
1631
|
::Brick.load_additional_references if initializer_loaded
|
1531
1632
|
end
|
1532
1633
|
|
1533
|
-
def retrieve_schema_and_tables(sql = nil, is_postgres = nil, schema = nil)
|
1634
|
+
def retrieve_schema_and_tables(sql = nil, is_postgres = nil, is_mssql = nil, schema = nil)
|
1635
|
+
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if is_mssql.nil?
|
1534
1636
|
sql ||= "SELECT t.table_schema AS \"schema\", t.table_name AS relation_name, t.table_type,#{"
|
1535
1637
|
pg_catalog.obj_description(
|
1536
1638
|
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass, 'pg_class'
|
1537
1639
|
) AS table_description," if is_postgres}
|
1538
1640
|
c.column_name, c.data_type,
|
1539
1641
|
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
1540
|
-
|
1642
|
+
kcu.constraint_type AS const, kcu.constraint_name AS \"key\",
|
1541
1643
|
c.is_nullable
|
1542
1644
|
FROM INFORMATION_SCHEMA.tables AS t
|
1543
1645
|
LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
|
1544
1646
|
AND t.table_name = c.table_name
|
1545
|
-
|
1546
|
-
|
1647
|
+
LEFT OUTER JOIN
|
1648
|
+
(SELECT kcu1.constraint_schema, kcu1.table_name, kcu1.ordinal_position,
|
1649
|
+
tc.constraint_type, kcu1.constraint_name
|
1650
|
+
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
|
1651
|
+
INNER JOIN INFORMATION_SCHEMA.table_constraints AS tc
|
1652
|
+
ON kcu1.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
1653
|
+
AND kcu1.TABLE_NAME = tc.TABLE_NAME
|
1654
|
+
AND kcu1.CONSTRAINT_NAME = tc.constraint_name
|
1655
|
+
AND tc.constraint_type != 'FOREIGN KEY' -- For MSSQL
|
1656
|
+
) AS kcu ON
|
1657
|
+
-- kcu.CONSTRAINT_CATALOG = t.table_catalog AND
|
1547
1658
|
kcu.CONSTRAINT_SCHEMA = c.table_schema
|
1548
|
-
AND kcu.TABLE_NAME = c.table_name
|
1549
|
-
|
1659
|
+
AND kcu.TABLE_NAME = c.table_name#{"
|
1660
|
+
-- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}
|
1550
1661
|
AND kcu.ordinal_position = c.ordinal_position
|
1551
|
-
|
1552
|
-
ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
1553
|
-
AND kcu.TABLE_NAME = tc.TABLE_NAME
|
1554
|
-
AND kcu.CONSTRAINT_NAME = tc.constraint_name
|
1555
|
-
WHERE t.table_schema #{is_postgres ?
|
1662
|
+
WHERE t.table_schema #{is_postgres || is_mssql ?
|
1556
1663
|
"NOT IN ('information_schema', 'pg_catalog')"
|
1557
1664
|
:
|
1558
1665
|
"= '#{ActiveRecord::Base.connection.current_database.tr("'", "''")}'"}#{"
|
@@ -1560,13 +1667,27 @@ module ActiveRecord::ConnectionHandling
|
|
1560
1667
|
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
1561
1668
|
AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
|
1562
1669
|
ORDER BY 1, t.table_type DESC, 2, c.ordinal_position"
|
1670
|
+
ActiveRecord::Base.execute_sql(sql, *ar_tables)
|
1671
|
+
end
|
1672
|
+
|
1673
|
+
def ar_tables
|
1563
1674
|
ar_smtn = if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
|
1564
1675
|
ActiveRecord::Base.schema_migrations_table_name
|
1565
1676
|
else
|
1566
1677
|
'schema_migrations'
|
1567
1678
|
end
|
1568
1679
|
ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
|
1569
|
-
|
1680
|
+
[ar_smtn, ar_imtn]
|
1681
|
+
end
|
1682
|
+
|
1683
|
+
def execute_oracle(*sql_args)
|
1684
|
+
cursor = ActiveRecord::Base.execute_sql(*sql_args)
|
1685
|
+
result = []
|
1686
|
+
while row = cursor.fetch()
|
1687
|
+
result << row
|
1688
|
+
end
|
1689
|
+
cursor.close
|
1690
|
+
result
|
1570
1691
|
end
|
1571
1692
|
end
|
1572
1693
|
|
@@ -1594,22 +1715,23 @@ module Brick
|
|
1594
1715
|
|
1595
1716
|
class << self
|
1596
1717
|
def _add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false)
|
1597
|
-
bt_assoc_name = fk[2]
|
1718
|
+
bt_assoc_name = ::Brick.namify(fk[2])
|
1598
1719
|
unless is_polymorphic
|
1599
1720
|
bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id')
|
1600
|
-
bt_assoc_name[0..-4]
|
1721
|
+
bt_assoc_name[-3] == '_' ? bt_assoc_name[0..-4] : bt_assoc_name[0..-3]
|
1601
1722
|
elsif bt_assoc_name.downcase.end_with?('id') && bt_assoc_name.exclude?('_')
|
1602
1723
|
bt_assoc_name[0..-3] # Make the bold assumption that we can just peel off any final ID part
|
1603
1724
|
else
|
1604
1725
|
"#{bt_assoc_name}_bt"
|
1605
1726
|
end
|
1606
1727
|
end
|
1607
|
-
bt_assoc_name = "
|
1728
|
+
bt_assoc_name = "#{bt_assoc_name}_" if bt_assoc_name == 'attribute'
|
1608
1729
|
|
1609
1730
|
# %%% Temporary schema patch
|
1610
1731
|
for_tbl = fk[1]
|
1732
|
+
fk_namified = ::Brick.namify(fk[1])
|
1611
1733
|
apartment = Object.const_defined?('Apartment') && Apartment
|
1612
|
-
fk[0] = Apartment.default_schema if apartment && apartment.excluded_models.include?(
|
1734
|
+
fk[0] = Apartment.default_schema if apartment && apartment.excluded_models.include?(fk_namified.singularize.camelize)
|
1613
1735
|
fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
|
1614
1736
|
bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
|
1615
1737
|
|
@@ -1708,7 +1830,7 @@ module Brick
|
|
1708
1830
|
else
|
1709
1831
|
fk[1]
|
1710
1832
|
end
|
1711
|
-
assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name:
|
1833
|
+
assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: fk_namified.pluralize, alternate_name: bt_assoc_name,
|
1712
1834
|
inverse_table: inv_tbl, inverse: assoc_bt }
|
1713
1835
|
assoc_hm[:polymorphic] = true if is_polymorphic
|
1714
1836
|
hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
|
@@ -49,8 +49,10 @@ module Brick
|
|
49
49
|
# After we're initialized and before running the rest of stuff, put our configuration in place
|
50
50
|
ActiveSupport.on_load(:after_initialize) do |app|
|
51
51
|
assets_path = File.expand_path("#{__dir__}/../../../../vendor/assets")
|
52
|
-
(app.config.assets
|
53
|
-
|
52
|
+
if (app_config = app.config).respond_to?(:assets)
|
53
|
+
(app_config.assets.precompile ||= []) << "#{assets_path}/images/brick_erd.png"
|
54
|
+
(app.config.assets.paths ||= []) << assets_path
|
55
|
+
end
|
54
56
|
# ====================================
|
55
57
|
# Dynamically create generic templates
|
56
58
|
# ====================================
|
@@ -145,7 +147,7 @@ module Brick
|
|
145
147
|
'nil'
|
146
148
|
else
|
147
149
|
# Postgres column names are limited to 63 characters
|
148
|
-
"#{obj_name}.#{"
|
150
|
+
"#{obj_name}.#{"b_r_#{assoc_name}_ct"[0..62]} || 0"
|
149
151
|
end
|
150
152
|
", #{set_ct}, #{path_keys(hm_assoc, hm_fk_name, obj_name, pk)}"
|
151
153
|
end
|
@@ -565,7 +567,7 @@ erDiagram
|
|
565
567
|
end %>
|
566
568
|
<% end
|
567
569
|
def dt_lookup(dt)
|
568
|
-
{ 'integer' => 'int', }[dt] || dt
|
570
|
+
{ 'integer' => 'int', }[dt] || dt&.tr(' ', '_') || 'int'
|
569
571
|
end
|
570
572
|
callbacks.merge({model_short_name => #{@_brick_model.name}}).each do |cb_k, cb_class|
|
571
573
|
cb_relation = ::Brick.relations[cb_class.table_name]
|
@@ -581,8 +583,9 @@ erDiagram
|
|
581
583
|
fk.each do |fk_part| %>
|
582
584
|
<%= \"#\{dt_lookup(cols[fk_part].first)} #\{fk_part} \\\" fk\\\"\".html_safe unless pkeys&.include?(fk_part) %><%
|
583
585
|
end
|
584
|
-
else
|
585
|
-
|
586
|
+
else # %%% Does not yet accommodate polymorphic BTs
|
587
|
+
%>
|
588
|
+
<%= \"#\{dt_lookup(cols[fk]&.first)} #\{fk} \\\" fk\\\"\".html_safe unless pkeys&.include?(fk) %><%
|
586
589
|
end
|
587
590
|
end %>
|
588
591
|
}
|
@@ -1096,7 +1099,9 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
|
|
1096
1099
|
gErd.setAttribute(\"class\", \"relatedModel\");
|
1097
1100
|
gErd.addEventListener(\"click\",
|
1098
1101
|
function (evt) {
|
1099
|
-
location.href = changeout(changeout(
|
1102
|
+
location.href = changeout(changeout(
|
1103
|
+
changeout(location.href, '_brick_order', null), // Remove any ordering
|
1104
|
+
-1, cbs[this.id]), \"_brick_erd\", \"1\");
|
1100
1105
|
}
|
1101
1106
|
);
|
1102
1107
|
}
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -123,7 +123,7 @@ module Brick
|
|
123
123
|
end
|
124
124
|
|
125
125
|
class << self
|
126
|
-
attr_accessor :default_schema, :db_schemas, :routes_done
|
126
|
+
attr_accessor :default_schema, :db_schemas, :routes_done, :is_oracle
|
127
127
|
|
128
128
|
def set_db_schema(params)
|
129
129
|
schema = params['_brick_schema'] || 'public'
|
@@ -151,6 +151,20 @@ module Brick
|
|
151
151
|
@pending_models ||= {}
|
152
152
|
end
|
153
153
|
|
154
|
+
# Convert spaces to underscores if the second character and onwards is mixed case
|
155
|
+
def namify(name)
|
156
|
+
if name.include?(' ')
|
157
|
+
# All uppers or all lowers?
|
158
|
+
if name[1..-1] =~ /^[A-Z0-9_]+$/ || name[1..-1] =~ /^[a-z0-9_]+$/
|
159
|
+
name.titleize.tr(' ', '_')
|
160
|
+
else # Mixed uppers and lowers -- just remove existing spaces
|
161
|
+
name.tr(' ', '')
|
162
|
+
end
|
163
|
+
else
|
164
|
+
name
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
154
168
|
def get_bts_and_hms(model)
|
155
169
|
bts, hms = model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
|
156
170
|
next if !const_defined?(a.name.to_s.singularize.camelize) && ::Brick.config.exclude_tables.include?(a.plural_name)
|
@@ -469,9 +483,10 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
469
483
|
# %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
|
470
484
|
# If auto-controllers and auto-models are both enabled then this makes sense:
|
471
485
|
::Brick.relations.each do |rel_name, v|
|
472
|
-
rel_name = rel_name.split('.').map(
|
486
|
+
rel_name = rel_name.split('.').map { |x| ::Brick.namify(x).underscore }
|
473
487
|
schema_names = rel_name[0..-2]
|
474
488
|
schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == Apartment.default_schema
|
489
|
+
# %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
|
475
490
|
k = rel_name.last
|
476
491
|
unless existing_controllers.key?(controller_name = k.pluralize)
|
477
492
|
options = {}
|
@@ -545,7 +560,11 @@ ActiveSupport.on_load(:active_record) do
|
|
545
560
|
class << self
|
546
561
|
def execute_sql(sql, *param_array)
|
547
562
|
param_array = param_array.first if param_array.length == 1 && param_array.first.is_a?(Array)
|
548
|
-
connection.
|
563
|
+
if ActiveRecord::Base.connection.adapter_name == 'SQLServer'
|
564
|
+
connection.exec_query(send(:sanitize_sql_array, [sql] + param_array)).rows
|
565
|
+
else
|
566
|
+
connection.execute(send(:sanitize_sql_array, [sql] + param_array))
|
567
|
+
end
|
549
568
|
end
|
550
569
|
end
|
551
570
|
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.70
|
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-09-
|
11
|
+
date: 2022-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '3.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '3.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: fancy_gets
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -264,5 +264,5 @@ requirements: []
|
|
264
264
|
rubygems_version: 3.1.6
|
265
265
|
signing_key:
|
266
266
|
specification_version: 4
|
267
|
-
summary:
|
267
|
+
summary: Create a Rails app from data alone
|
268
268
|
test_files: []
|