brick 1.0.70 → 1.0.72

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: af28a866a39817e9daa3a54b8f091c790d9280571cec94f9a3a317606e239fd9
4
- data.tar.gz: a2cfe0b823c8b364c8f842852e631a48efe7f9dde8d506a27fbc85ebb74e58eb
3
+ metadata.gz: 06c3efed47ee7086f01a4f59c177f362343f376b1d6534f86e61ee38001b0d7b
4
+ data.tar.gz: 8b521c56eb5afc09b06aec0adf0085136cb9e94d2c1a93355a8a2fc79da0316b
5
5
  SHA512:
6
- metadata.gz: 0cc18926c7c449e7dfba8d919d41b2490cb4d4a14692073f98f96903da075a31ff029f7a5f5931a845c7caf8f00b51cdba9ab0711dfa626d9e445d9122350b2b
7
- data.tar.gz: 84373ca68edb9ebb88b8ccf2963bbf2e416c35c41840df1c1cd6922ef757cac204453aeb1e15881316681558a53ce17e32692e16320bb1b53c608bc41401920d
6
+ metadata.gz: 1566ddaa89e45496b752b64ec218456e05dca50cc81a722d5a88e4d4bbe19b28ca981eeb253688f44e55a8ef9949d195bdc5a61f5ba7980b2507c315277b4d4d
7
+ data.tar.gz: 7643f78bf92721c89bdb0065f112592cba26023ea7745ba34551ce39d9378cd286f50b3ea1491c813e10f6d93521ef16c54fa70edc5dbcd4d70cb5ad6a859ad3
@@ -68,13 +68,14 @@ module ActiveRecord
68
68
  end
69
69
 
70
70
  def self._brick_primary_key(relation = nil)
71
- return instance_variable_get(:@_brick_primary_key) if instance_variable_defined?(:@_brick_primary_key)
71
+ return @_brick_primary_key if instance_variable_defined?(:@_brick_primary_key)
72
72
 
73
73
  pk = begin
74
74
  primary_key.is_a?(String) ? [primary_key] : primary_key || []
75
75
  rescue
76
76
  []
77
77
  end
78
+ pk.map! { |pk_part| pk_part =~ /^[A-Z0-9_]+$/ ? pk_part.downcase : pk_part } unless connection.adapter_name == 'MySQL2'
78
79
  # Just return [] if we're missing any part of the primary key. (PK is usually just "id")
79
80
  if relation && pk.present?
80
81
  @_brick_primary_key ||= pk.any? { |pk_part| !relation[:cols].key?(pk_part) } ? [] : pk
@@ -99,13 +100,14 @@ module ActiveRecord
99
100
  dsl
100
101
  end
101
102
 
102
- def self.brick_parse_dsl(build_array = nil, prefix = [], translations = {}, is_polymorphic = false)
103
+ def self.brick_parse_dsl(build_array = nil, prefix = [], translations = {}, emit_dsl = false, is_polymorphic = false)
103
104
  build_array = ::Brick::JoinArray.new.tap { |ary| ary.replace([build_array]) } if build_array.is_a?(::Brick::JoinHash)
104
105
  build_array = ::Brick::JoinArray.new unless build_array.nil? || build_array.is_a?(Array)
105
106
  members = []
106
107
  bracket_name = nil
107
108
  prefix = [prefix] unless prefix.is_a?(Array)
108
109
  if (dsl = ::Brick.config.model_descrips[name] || brick_get_dsl)
110
+ dsl2 = +''
109
111
  klass = nil
110
112
  dsl.each_char do |ch|
111
113
  if bracket_name
@@ -125,7 +127,15 @@ module ActiveRecord
125
127
  end
126
128
  translations[parts[0..-2].join('.')] = klass
127
129
  end
128
- members << parts
130
+ if klass.column_names.exclude?(parts.last) &&
131
+ (klass = (orig_class = klass).reflect_on_association(possible_dsl = parts.pop.to_sym)&.klass)
132
+ members2, dsl2a = klass.brick_parse_dsl(build_array, prefix + [possible_dsl], translations, true)
133
+ members += members2
134
+ dsl2 << dsl2a
135
+ else
136
+ dsl2 << "[#{bracket_name}]"
137
+ members << parts
138
+ end
129
139
  bracket_name = nil
130
140
  else
131
141
  bracket_name << ch
@@ -133,13 +143,17 @@ module ActiveRecord
133
143
  elsif ch == '['
134
144
  bracket_name = +''
135
145
  klass = self
146
+ else
147
+ dsl2 << ch
136
148
  end
137
149
  end
150
+ # Rewrite the DSL in case it's now different from having to expand it
151
+ ::Brick.config.model_descrips[name] = dsl2 unless emit_dsl
138
152
  else # With no DSL available, still put this prefix into the JoinArray so we can get primary key (ID) info from this table
139
153
  x = prefix.each_with_object(build_array) { |v, s| s[v.to_sym] }
140
154
  x[prefix.last] = nil unless prefix.empty? # Using []= will "hydrate" any missing part(s) in our whole series
141
155
  end
142
- members
156
+ emit_dsl ? [members, dsl2] : members
143
157
  end
144
158
 
145
159
  # If available, parse simple DSL attached to a model in order to provide a friendlier name.
@@ -169,9 +183,12 @@ module ActiveRecord
169
183
  this_obj = caches.fetch(obj_name) { caches[obj_name] = this_obj&.send(part.to_sym) }
170
184
  break if this_obj.nil?
171
185
  end
186
+ if this_obj.is_a?(ActiveRecord::Base) && (obj_descrip = this_obj.class.brick_descrip(this_obj))
187
+ this_obj = obj_descrip
188
+ end
172
189
  this_obj&.to_s || ''
173
190
  end
174
- is_brackets_have_content = true unless (datum).blank?
191
+ is_brackets_have_content = true unless datum.blank?
175
192
  output << (datum || '')
176
193
  bracket_name = nil
177
194
  else
@@ -244,7 +261,8 @@ module ActiveRecord
244
261
 
245
262
  # join_array will receive this relation name when calling #brick_parse_dsl
246
263
  _br_bt_descrip[bt.first] = if bt[1].is_a?(Array)
247
- bt[1].each_with_object({}) { |bt_class, s| s[bt_class] = bt_class.brick_parse_dsl(join_array, bt.first, translations, true) }
264
+ # Last two params here: "false" is for don't emit DSL, and "true" is for yes, we are polymorphic
265
+ bt[1].each_with_object({}) { |bt_class, s| s[bt_class] = bt_class.brick_parse_dsl(join_array, bt.first, translations, false, true) }
248
266
  else
249
267
  { bt.last => bt[1].brick_parse_dsl(join_array, bt.first, translations) }
250
268
  end
@@ -303,7 +321,7 @@ module ActiveRecord
303
321
  end
304
322
 
305
323
  class Relation
306
- attr_reader :_brick_chains
324
+ attr_reader :_brick_chains, :_arel_applied_aliases
307
325
 
308
326
  # CLASS STUFF
309
327
  def _recurse_arel(piece, prefix = '')
@@ -341,7 +359,7 @@ module ActiveRecord
341
359
  # parent = piece.left == on.left.relation ? on.right.relation : on.left.relation
342
360
  # binding.pry if piece.left.is_a?(Arel::Nodes::TableAlias)
343
361
  if table.is_a?(Arel::Nodes::TableAlias)
344
- alias_name = table.right
362
+ @_arel_applied_aliases << (alias_name = table.right)
345
363
  table = table.left
346
364
  end
347
365
  (_brick_chains[table._arel_table_type] ||= []) << (alias_name || table.table_alias || table.name)
@@ -367,6 +385,7 @@ module ActiveRecord
367
385
 
368
386
  # INSTANCE STUFF
369
387
  def _arel_alias_names
388
+ @_arel_applied_aliases = []
370
389
  # %%% If with Rails 3.1 and older you get "NoMethodError: undefined method `eq' for nil:NilClass"
371
390
  # when trying to call relation.arel, then somewhere along the line while navigating a has_many
372
391
  # relationship it can't find the proper foreign key.
@@ -414,10 +433,17 @@ module ActiveRecord
414
433
  "`#{tbl_no_schema}`.`#{col_name}`#{col_alias}"
415
434
  elsif is_postgres || is_mssql
416
435
  # Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
417
- cast_as_text = '::text' if is_distinct && Brick.relations[klass.table_name]&.[](:cols)&.[](col.name)&.first&.start_with?('xml')
436
+ cast_as_text = '::text' if is_distinct && Brick.relations[klass.table_name]&.[](:cols)&.[](col_name)&.first&.start_with?('xml')
418
437
  "\"#{tbl_no_schema}\".\"#{col_name}\"#{cast_as_text}#{col_alias}"
419
- else # Sqlite or Oracle
420
- "#{tbl_no_schema}.#{col_name}#{col_alias}"
438
+ elsif col.type # Could be Sqlite or Oracle
439
+ if col_alias || !(/^[a-z0-9_]+$/ =~ col_name)
440
+ "#{tbl_no_schema}.\"#{col_name}\"#{col_alias}"
441
+ else
442
+ "#{tbl_no_schema}.#{col_name}"
443
+ end
444
+ else # Oracle with a custom data type
445
+ typ = col.sql_type
446
+ "'<#{typ.end_with?('_TYP') ? typ[0..-5] : typ}>' AS #{col.name}"
421
447
  end
422
448
  end
423
449
  end
@@ -436,9 +462,14 @@ module ActiveRecord
436
462
  next if chains[k1].nil?
437
463
 
438
464
  tbl_name = (field_tbl_names[v.first][k1] ||= shift_or_first(chains[k1])).split('.').last
465
+ # If it's Oracle, quote any AREL aliases that had been applied
466
+ tbl_name = "\"#{tbl_name}\"" if ::Brick.is_oracle && rel_dupe._arel_applied_aliases.include?(tbl_name)
439
467
  field_tbl_name = nil
440
468
  v1.map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
469
+ # binding.pry if chains[sel_col.first].nil?
441
470
  field_tbl_name = (field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])).split('.').last
471
+ # If it's Oracle, quote any AREL aliases that had been applied
472
+ field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe._arel_applied_aliases.include?(field_tbl_name)
442
473
 
443
474
  # Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
444
475
  is_xml = is_distinct && Brick.relations[sel_col.first.table_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
@@ -709,9 +740,11 @@ Module.class_exec do
709
740
  base_module == Object && # %%% This works for Person::Person -- but also limits us to not being able to allow more than one level of namespacing
710
741
  (schema_name = [(singular_table_name = class_name.underscore),
711
742
  (table_name = singular_table_name.pluralize),
712
- class_name,
743
+ ::Brick.is_oracle ? class_name.upcase : class_name,
713
744
  (plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas.include?(s) }&.camelize ||
714
745
  (::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name))
746
+ return self.const_get(schema_name) if self.const_defined?(schema_name)
747
+
715
748
  # Build out a module for the schema if it's namespaced
716
749
  # schema_name = schema_name.camelize
717
750
  base_module.const_set(schema_name.to_sym, (built_module = Module.new))
@@ -1344,8 +1377,13 @@ class Object
1344
1377
  [new_alt_name, true]
1345
1378
  else
1346
1379
  assoc_name = ::Brick.namify(hm_assoc[:inverse_table]).pluralize
1380
+ if (needs_class = assoc_name.include?('.')) # If there is a schema name present, use a downcased version for the :has_many
1381
+ assoc_parts = assoc_name.split('.')
1382
+ assoc_parts[0].downcase! if assoc_parts[0] =~ /^[A-Z0-9_]+$/
1383
+ assoc_name = assoc_parts.join('.')
1384
+ end
1347
1385
  # hm_assoc[:assoc_name] = assoc_name
1348
- [assoc_name, assoc_name.include?('.')]
1386
+ [assoc_name, needs_class]
1349
1387
  end
1350
1388
  end
1351
1389
  end
@@ -1401,7 +1439,8 @@ module ActiveRecord::ConnectionHandling
1401
1439
  row['table_schema']
1402
1440
  end
1403
1441
  # Remove any system schemas
1404
- s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
1442
+ s[row] = nil unless ['information_schema', 'pg_catalog',
1443
+ 'INFORMATION_SCHEMA', 'sys'].include?(row)
1405
1444
  end
1406
1445
  if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
1407
1446
  (sta = multitenancy[:schema_to_analyse]) != 'public') &&
@@ -1415,7 +1454,7 @@ module ActiveRecord::ConnectionHandling
1415
1454
  # ActiveRecord::Base.connection.current_database will be something like "XEPDB1"
1416
1455
  ::Brick.default_schema = schema = ActiveRecord::Base.connection.raw_connection.username
1417
1456
  ::Brick.db_schemas = {}
1418
- execute_oracle("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] = nil }
1457
+ ActiveRecord::Base.execute_sql("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] = nil }
1419
1458
  when 'SQLite'
1420
1459
  sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
1421
1460
  p.name AS column_name, p.type AS data_type,
@@ -1445,6 +1484,7 @@ module ActiveRecord::ConnectionHandling
1445
1484
  # ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
1446
1485
  # For if it's not SQLite -- so this is the Postgres and MySQL version
1447
1486
  measures = []
1487
+ ::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
1448
1488
  case ActiveRecord::Base.connection.adapter_name
1449
1489
  when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
1450
1490
  # schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
@@ -1478,8 +1518,9 @@ module ActiveRecord::ConnectionHandling
1478
1518
  schema_and_tables = case ActiveRecord::Base.connection.adapter_name
1479
1519
  when 'OracleEnhanced'
1480
1520
  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,
1521
+ "SELECT c.owner AS schema, c.table_name AS relation_name,
1522
+ CASE WHEN v.owner IS NULL THEN 'BASE_TABLE' ELSE 'VIEW' END AS table_type,
1523
+ c.column_name, c.data_type,
1483
1524
  COALESCE(c.data_length, c.data_precision) AS max_length,
1484
1525
  CASE ac.constraint_type WHEN 'P' THEN 'PRIMARY KEY' END AS const,
1485
1526
  ac.constraint_name AS \"key\",
@@ -1487,21 +1528,29 @@ module ActiveRecord::ConnectionHandling
1487
1528
  FROM all_tab_cols c
1488
1529
  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
1530
  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'
1531
+ LEFT OUTER JOIN all_views v ON c.owner = v.owner AND c.table_name = v.view_name
1490
1532
  WHERE c.owner IN (#{::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')})
1533
+ AND c.table_name NOT IN (?, ?)
1491
1534
  ORDER BY 1, 2, c.internal_column_id, acc.position"
1492
- execute_oracle(sql, *ar_tables)
1535
+ ActiveRecord::Base.execute_sql(sql, *ar_tables)
1493
1536
  else
1494
1537
  ActiveRecord::Base.retrieve_schema_and_tables(sql)
1495
1538
  end
1539
+
1496
1540
  schema_and_tables.each do |r|
1497
1541
  next if r[1].index('$') # Oracle can have goofy table names with $
1498
1542
 
1499
- if (relation_name = r[1]) =~ /^[A-Z_]+$/
1543
+ if (relation_name = r[1]) =~ /^[A-Z0-9_]+$/
1500
1544
  relation_name.downcase!
1501
1545
  end
1546
+ # Expect the default schema for SQL Server to be 'dbo'.
1547
+ if (::Brick.is_oracle && r[0] != schema) || (is_mssql && r[0] != 'dbo')
1548
+ relation_name = "#{r[0]}.#{relation_name}"
1549
+ end
1550
+
1502
1551
  relation = relations[relation_name] # here relation represents a table or view from the database
1503
1552
  relation[:isView] = true if r[2] == 'VIEW' # table_type
1504
- col_name = r[3]
1553
+ col_name = ::Brick.is_oracle ? connection.send(:oracle_downcase, r[3]) : r[3]
1505
1554
  key = case r[6] # constraint type
1506
1555
  when 'PRIMARY KEY'
1507
1556
  # key
@@ -1513,8 +1562,8 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
1513
1562
  end
1514
1563
  key << col_name if key
1515
1564
  cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
1516
- # 'data_type', 'max_length'
1517
- cols[col_name] = [r[4], r[5], measures&.include?(col_name)]
1565
+ # 'data_type', 'max_length', measure, 'is_nullable'
1566
+ cols[col_name] = [r[4], r[5], measures&.include?(col_name), r[8] == 'NO']
1518
1567
  # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
1519
1568
  end
1520
1569
  end
@@ -1567,10 +1616,10 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
1567
1616
  schemas = ::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')
1568
1617
  sql =
1569
1618
  "SELECT -- fk
1570
- ac.owner AS constraint_schema, acc_fk.table_name, LOWER(acc_fk.column_name),
1619
+ ac.owner AS constraint_schema, acc_fk.table_name, acc_fk.column_name,
1571
1620
  -- referenced pk
1572
1621
  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)
1622
+ -- , acc_pk.column_name
1574
1623
  FROM all_cons_columns acc_fk
1575
1624
  INNER JOIN all_constraints ac ON acc_fk.owner = ac.owner
1576
1625
  AND acc_fk.constraint_name = ac.constraint_name
@@ -1579,7 +1628,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
1579
1628
  WHERE ac.constraint_type = 'R'
1580
1629
  AND ac.owner IN (#{schemas})
1581
1630
  AND ac.r_owner IN (#{schemas})"
1582
- fk_references = ActiveRecord::Base.execute_oracle(sql)
1631
+ fk_references = ActiveRecord::Base.execute_sql(sql)
1583
1632
  end
1584
1633
  ::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
1585
1634
  # ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
@@ -1588,49 +1637,71 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
1588
1637
  # Multitenancy makes things a little more general overall, except for non-tenanted tables
1589
1638
  if apartment_excluded&.include?(::Brick.namify(fk[1]).singularize.camelize)
1590
1639
  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])
1640
+ elsif (is_postgres && (fk[0] == 'public' || (is_multitenant && fk[0] == schema))) ||
1641
+ (::Brick.is_oracle && fk[0] == schema) ||
1642
+ (is_mssql && fk[0] == 'dbo') ||
1643
+ (!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0]))
1593
1644
  fk[0] = nil
1594
1645
  end
1595
1646
  if apartment_excluded&.include?(fk[4].singularize.camelize)
1596
1647
  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])
1648
+ elsif (is_postgres && (fk[3] == 'public' || (is_multitenant && fk[3] == schema))) ||
1649
+ (::Brick.is_oracle && fk[3] == schema) ||
1650
+ (is_mssql && fk[3] == 'dbo') ||
1651
+ (!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3]))
1599
1652
  fk[3] = nil
1600
1653
  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_]+$/
1654
+ if ::Brick.is_oracle
1655
+ fk[1].downcase! if fk[1] =~ /^[A-Z0-9_]+$/
1656
+ fk[4].downcase! if fk[4] =~ /^[A-Z0-9_]+$/
1657
+ fk[2] = connection.send(:oracle_downcase, fk[2])
1658
+ end
1603
1659
  ::Brick._add_bt_and_hm(fk, relations)
1604
1660
  end
1605
1661
  end
1606
1662
 
1607
1663
  tables = []
1608
1664
  views = []
1665
+ table_class_length = 0
1666
+ view_class_length = 0
1609
1667
  relations.each do |k, v|
1610
1668
  name_parts = k.split('.')
1611
1669
  idx = 1
1612
1670
  name_parts = name_parts.map do |x|
1613
- ((idx += 1) < name_parts.length ? x.singularize : x).camelize
1671
+ (idx += 1) < name_parts.length ? x : x.singularize
1614
1672
  end
1673
+ name_parts.shift if apartment && name_parts.length > 1 && name_parts.first == Apartment.default_schema
1674
+ class_name = name_parts.map(&:camelize).join('::')
1615
1675
  if v.key?(:isView)
1676
+ view_class_length = class_name.length if class_name.length > view_class_length
1616
1677
  views
1617
1678
  else
1618
- name_parts.shift if apartment && name_parts.length > 1 && name_parts.first == Apartment.default_schema
1679
+ table_class_length = class_name.length if class_name.length > table_class_length
1619
1680
  tables
1620
- end << name_parts.join('::')
1681
+ end << [class_name, name_parts]
1621
1682
  end
1622
- unless tables.empty?
1623
- puts "\nClasses that can be built from tables:"
1624
- tables.sort.each { |x| puts x }
1683
+ puts "\n" if tables.present? || views.present?
1684
+ if tables.present?
1685
+ puts "Classes that can be built from tables:"
1686
+ display_classes(tables, table_class_length)
1625
1687
  end
1626
- unless views.empty?
1627
- puts "\nClasses that can be built from views:"
1628
- views.sort.each { |x| puts x }
1688
+ if views.present?
1689
+ puts "Classes that can be built from views:"
1690
+ display_classes(views, view_class_length)
1629
1691
  end
1630
1692
 
1631
1693
  ::Brick.load_additional_references if initializer_loaded
1632
1694
  end
1633
1695
 
1696
+ def display_classes(rels, max_length)
1697
+ rels.sort.each do |rel|
1698
+ rel_link = rel.last.dup.map(&:underscore)
1699
+ rel_link[-1] = rel_link[-1].pluralize
1700
+ puts "#{rel.first}#{' ' * (max_length - rel.first.length)} /#{rel_link.join('/')}"
1701
+ end
1702
+ puts "\n"
1703
+ end
1704
+
1634
1705
  def retrieve_schema_and_tables(sql = nil, is_postgres = nil, is_mssql = nil, schema = nil)
1635
1706
  is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if is_mssql.nil?
1636
1707
  sql ||= "SELECT t.table_schema AS \"schema\", t.table_name AS relation_name, t.table_type,#{"
@@ -1660,7 +1731,8 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
1660
1731
  -- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}
1661
1732
  AND kcu.ordinal_position = c.ordinal_position
1662
1733
  WHERE t.table_schema #{is_postgres || is_mssql ?
1663
- "NOT IN ('information_schema', 'pg_catalog')"
1734
+ "NOT IN ('information_schema', 'pg_catalog',
1735
+ 'INFORMATION_SCHEMA', 'sys')"
1664
1736
  :
1665
1737
  "= '#{ActiveRecord::Base.connection.current_database.tr("'", "''")}'"}#{"
1666
1738
  AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }
@@ -1679,16 +1751,6 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
1679
1751
  ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
1680
1752
  [ar_smtn, ar_imtn]
1681
1753
  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
1691
- end
1692
1754
  end
1693
1755
 
1694
1756
  # ==========================================
@@ -1715,7 +1777,7 @@ module Brick
1715
1777
 
1716
1778
  class << self
1717
1779
  def _add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false)
1718
- bt_assoc_name = ::Brick.namify(fk[2])
1780
+ bt_assoc_name = ::Brick.namify(fk[2], true)
1719
1781
  unless is_polymorphic
1720
1782
  bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id')
1721
1783
  bt_assoc_name[-3] == '_' ? bt_assoc_name[0..-4] : bt_assoc_name[0..-3]
@@ -87,9 +87,9 @@ module Brick
87
87
 
88
88
  def path_keys(hm_assoc, fk_name, obj_name, pk)
89
89
  keys = if fk_name.is_a?(Array) && pk.is_a?(Array) # Composite keys?
90
- fk_name.zip(pk.map { |pk_part| "#{obj_name}.#{pk_part}" })
90
+ fk_name.zip(pk.map { |fk_part| "#{obj_name}.#{fk_part}" })
91
91
  else
92
- pk = pk.each_with_object([]) { |pk_part, s| s << "#{obj_name}.#{pk_part}" }
92
+ pk = pk.map { |pk_part| "#{obj_name}.#{pk_part}" }
93
93
  [[fk_name, pk.length == 1 ? pk.first : pk.inspect]]
94
94
  end
95
95
  keys << [hm_assoc.inverse_of.foreign_type, hm_assoc.active_record.name] if hm_assoc.options.key?(:as)
@@ -106,8 +106,8 @@ module Brick
106
106
  return _brick_find_template(*args, **options)
107
107
  end
108
108
 
109
- if @_brick_model
110
- pk = @_brick_model._brick_primary_key(::Brick.relations.fetch(model_name, nil))
109
+ if @_brick_model
110
+ pk = @_brick_model._brick_primary_key(::Brick.relations.fetch(@_brick_model&.table_name, nil))
111
111
  obj_name = model_name.split('::').last.underscore
112
112
  path_obj_name = model_name.underscore.tr('/', '_')
113
113
  table_name = obj_name.pluralize
@@ -1090,9 +1090,9 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1090
1090
  mermaid.initialize({
1091
1091
  startOnLoad: true,
1092
1092
  securityLevel: \"loose\",
1093
+ er: { useMaxWidth: false },
1093
1094
  mermaid: {callback: function(objId) {
1094
1095
  var svg = document.getElementById(objId);
1095
- svg.removeAttribute(\"width\");
1096
1096
  var cb;
1097
1097
  for(cb in cbs) {
1098
1098
  var gErd = svg.getElementById(cb);
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 70
8
+ TINY = 72
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
data/lib/brick.rb CHANGED
@@ -152,7 +152,8 @@ module Brick
152
152
  end
153
153
 
154
154
  # Convert spaces to underscores if the second character and onwards is mixed case
155
- def namify(name)
155
+ def namify(name, attempt_downcase = false)
156
+ name.downcase! if attempt_downcase && name =~ /^[A-Z0-9_]+$/
156
157
  if name.include?(' ')
157
158
  # All uppers or all lowers?
158
159
  if name[1..-1] =~ /^[A-Z0-9_]+$/ || name[1..-1] =~ /^[a-z0-9_]+$/
@@ -560,7 +561,7 @@ ActiveSupport.on_load(:active_record) do
560
561
  class << self
561
562
  def execute_sql(sql, *param_array)
562
563
  param_array = param_array.first if param_array.length == 1 && param_array.first.is_a?(Array)
563
- if ActiveRecord::Base.connection.adapter_name == 'SQLServer'
564
+ if ['OracleEnhanced', 'SQLServer'].include?(ActiveRecord::Base.connection.adapter_name)
564
565
  connection.exec_query(send(:sanitize_sql_array, [sql] + param_array)).rows
565
566
  else
566
567
  connection.execute(send(:sanitize_sql_array, [sql] + param_array))
@@ -726,6 +727,63 @@ ActiveSupport.on_load(:active_record) do
726
727
  end
727
728
  end
728
729
  end
730
+
731
+ # Migration stuff
732
+ module ConnectionAdapters
733
+ # Override the downcasing implementation from the OracleEnhanced gem as it has bad regex
734
+ if const_defined?(:OracleEnhanced)
735
+ module OracleEnhanced::Quoting
736
+ private
737
+
738
+ def oracle_downcase(column_name)
739
+ return nil if column_name.nil?
740
+
741
+ /^[A-Za-z0-9_]+$/ =~ column_name ? column_name.downcase : column_name
742
+ end
743
+ end
744
+ end
745
+ if const_defined?(:SQLServerAdapter)
746
+ class SQLServer::TableDefinition
747
+ alias _brick_new_column_definition new_column_definition
748
+ def new_column_definition(name, type, **options)
749
+ case type
750
+ when :serial
751
+ type = :integer
752
+ options[:is_identity] = true
753
+ when :bigserial
754
+ type = :bigint
755
+ options[:is_identity] = true
756
+ end
757
+ _brick_new_column_definition(name, type, **options)
758
+ end
759
+ def serial(*args)
760
+ options = args.extract_options!
761
+ options[:is_identity] = true
762
+ args.each { |name| column(name, 'integer', options) }
763
+ end
764
+ def bigserial(*args)
765
+ options = args.extract_options!
766
+ options[:is_identity] = true
767
+ args.each { |name| column(name, 'bigint', options) }
768
+ end
769
+ # Seems that geography gets used a fair bit in MSSQL
770
+ def geography(*args)
771
+ options = args.extract_options!
772
+ # options[:precision] ||= 8
773
+ # options[:scale] ||= 2
774
+ args.each { |name| column(name, 'geography', options) }
775
+ end
776
+ end
777
+ class SQLServerAdapter
778
+ unless respond_to?(:schema_exists?)
779
+ def schema_exists?(schema)
780
+ schema_sql = 'SELECT 1 FROM sys.schemas WHERE name = ?'
781
+ ActiveRecord::Base.execute_sql(schema_sql, schema).present?
782
+ end
783
+ end
784
+ end
785
+ end
786
+ end
729
787
  end
730
788
  # rubocop:enable Lint/ConstantDefinitionInBlock
731
789
 
@@ -23,6 +23,21 @@ module Brick
23
23
  'time with time zone' => 'time',
24
24
  'double precision' => 'float',
25
25
  'smallint' => 'integer', # %%% Need to put in "limit: 2"
26
+ # # Oracle data types
27
+ 'VARCHAR2' => 'string',
28
+ 'CHAR' => 'string',
29
+ ['NUMBER', 22] => 'integer',
30
+ /^INTERVAL / => 'string', # Time interval stuff like INTERVAL YEAR(2) TO MONTH, INTERVAL '999' DAY(3), etc
31
+ 'XMLTYPE' => 'xml',
32
+ 'RAW' => 'binary',
33
+ 'SDO_GEOMETRY' => 'geometry',
34
+ # MSSQL data types
35
+ 'int' => 'integer',
36
+ 'nvarchar' => 'string',
37
+ 'nchar' => 'string',
38
+ 'datetime2' => 'timestamp',
39
+ 'bit' => 'boolean',
40
+ 'varbinary' => 'binary',
26
41
  # Sqlite data types
27
42
  'TEXT' => 'text',
28
43
  '' => 'string',
@@ -122,7 +137,7 @@ module Brick
122
137
  fringe.each do |tbl|
123
138
  next unless (relation = ::Brick.relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present?
124
139
 
125
- pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ActiveRecord::Base.primary_key].flatten)
140
+ pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ApplicationRecord.primary_key].flatten.sort)
126
141
  # In case things aren't as standard
127
142
  if pkey_cols.empty?
128
143
  pkey_cols = if rpk.empty? && relation[:cols][arpk.first]&.first == key_type
@@ -141,7 +156,7 @@ module Brick
141
156
  end
142
157
  unless schema.blank? || built_schemas.key?(schema)
143
158
  mig = +" def change\n create_schema(:#{schema}) unless schema_exists?(:#{schema})\n end\n"
144
- migration_file_write(mig_path, "create_db_schema_#{schema}", current_mig_time += 1.minute, ar_version, mig)
159
+ migration_file_write(mig_path, "create_db_schema_#{schema.underscore}", current_mig_time += 1.minute, ar_version, mig)
145
160
  built_schemas[schema] = nil
146
161
  end
147
162
 
@@ -151,11 +166,16 @@ module Brick
151
166
  # a column definition which includes :primary_key -- %%% also using a data type of bigserial or serial
152
167
  # if this one has come in as bigint or integer.
153
168
  pk_is_also_fk = fkey_cols.any? { |assoc| pkey_cols&.first == assoc[:fk] } ? pkey_cols&.first : nil
154
- # Support missing primary key (by adding: ,id: false)
169
+ # Support missing primary key (by adding: , id: false)
155
170
  id_option = if pk_is_also_fk || !pkey_cols&.present?
171
+ needs_serial_col = true
156
172
  ', id: false'
157
- elsif ((pkey_col_first = relation[:cols][pkey_cols&.first]&.first) &&
158
- (pkey_col_first = SQL_TYPES[pkey_col_first] || pkey_col_first) != key_type)
173
+ elsif ((pkey_col_first = (col_def = relation[:cols][pkey_cols&.first])&.first) &&
174
+ (pkey_col_first = SQL_TYPES[pkey_col_first] || SQL_TYPES[col_def&.[](0..1)] ||
175
+ SQL_TYPES.find { |r| r.first.is_a?(Regexp) && pkey_col_first =~ r.first }&.last ||
176
+ pkey_col_first
177
+ ) != key_type
178
+ )
159
179
  case pkey_col_first
160
180
  when 'integer'
161
181
  ', id: :serial'
@@ -164,9 +184,14 @@ module Brick
164
184
  else
165
185
  ", id: :#{pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
166
186
  end +
167
- (pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '') +
168
- (!is_4x_rails && (comment = relation&.fetch(:description, nil))&.present? ? ", comment: #{comment.inspect}" : '')
187
+ (pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '')
169
188
  end
189
+ if !id_option && pkey_cols.sort != arpk
190
+ id_option = ", primary_key: :#{pkey_cols.first}"
191
+ end
192
+ if !is_4x_rails && (comment = relation&.fetch(:description, nil))&.present?
193
+ (id_option ||= +'') << ", comment: #{comment.inspect}"
194
+ end
170
195
  # Find the ActiveRecord class in order to see if the columns have comments
171
196
  unless is_4x_rails
172
197
  klass = begin
@@ -187,18 +212,24 @@ module Brick
187
212
  possible_ts = [] # Track possible generic timestamps
188
213
  add_fks = [] # Track foreign keys to add after table creation
189
214
  relation[:cols].each do |col, col_type|
190
- sql_type = SQL_TYPES[col_type.first] || col_type.first
191
- suffix = col_type[3] ? +', null: false' : +''
215
+ sql_type = SQL_TYPES[col_type.first] || SQL_TYPES[col_type[0..1]] ||
216
+ SQL_TYPES.find { |r| r.first.is_a?(Regexp) && col_type.first =~ r.first }&.last ||
217
+ col_type.first
218
+ suffix = col_type[3] || pkey_cols&.include?(col) ? +', null: false' : +''
192
219
  if !is_4x_rails && klass && (comment = klass.columns_hash.fetch(col, nil)&.comment)&.present?
193
220
  suffix << ", comment: #{comment.inspect}"
194
221
  end
195
222
  # Determine if this column is used as part of a foreign key
196
- if fk = fkey_cols.find { |assoc| col == assoc[:fk] }
223
+ if (fk = fkey_cols.find { |assoc| col == assoc[:fk] })
197
224
  to_table = fk[:inverse_table].split('.')
198
225
  to_table = to_table.length == 1 ? ":#{to_table.first}" : "'#{fk[:inverse_table]}'"
226
+ if needs_serial_col && pkey_cols&.include?(col) && (new_serial_type = {'integer' => 'serial', 'bigint' => 'bigserial'}[sql_type])
227
+ sql_type = new_serial_type
228
+ needs_serial_col = false
229
+ end
199
230
  if fk[:fk] != "#{fk[:assoc_name].singularize}_id" # Need to do our own foreign_key tricks, not use references?
200
231
  column = fk[:fk]
201
- mig << " t.#{sql_type} :#{column}#{suffix}\n"
232
+ mig << emit_column(sql_type, column, suffix)
202
233
  add_fks << [to_table, column, ::Brick.relations[fk[:inverse_table]]]
203
234
  else
204
235
  suffix << ", type: :#{sql_type}" unless sql_type == key_type
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.70
4
+ version: 1.0.72
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-13 00:00:00.000000000 Z
11
+ date: 2022-09-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord