brick 1.0.155 → 1.0.157
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/brick/config.rb +8 -0
- data/lib/brick/extensions.rb +93 -71
- data/lib/brick/frameworks/rails/engine.rb +20 -9
- data/lib/brick/frameworks/rails/form_builder.rb +3 -3
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +30 -19
- data/lib/generators/brick/migrations_generator.rb +10 -8
- data/lib/generators/brick/models_generator.rb +3 -0
- data/lib/generators/brick/seeds_generator.rb +79 -15
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 995a300b0971af02a39ed75857ce349c99720068555cbb1d6311a75d9a13f2b8
|
4
|
+
data.tar.gz: e9153dac19552fc8dd581774f89b9ed682ac040b5a4dc0d47f4b28fe079ca36d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c0c8553d5ef266aa6e947d6f0dd9ee4ae23d64118cd3c69565345747be0afa4de63be999976bd8e4fed55ebec2f3551aa87a4f4b84dee0a3dced188d23260f6
|
7
|
+
data.tar.gz: a7e0dbdd27c5d0905d7acf0a7a3f6fb5e254556ef65a0633a3021c395664c65f152afd3cecc53992c0f5da0095a1246b10d14898c34d82c60fc1cec298e62a68
|
data/lib/brick/config.rb
CHANGED
@@ -332,6 +332,14 @@ module Brick
|
|
332
332
|
@mutex.synchronize { @table_name_prefixes = value }
|
333
333
|
end
|
334
334
|
|
335
|
+
def treat_as_module
|
336
|
+
@mutex.synchronize { @treat_as_module || [] }
|
337
|
+
end
|
338
|
+
|
339
|
+
def treat_as_module=(mod_names)
|
340
|
+
@mutex.synchronize { @treat_as_module = mod_names }
|
341
|
+
end
|
342
|
+
|
335
343
|
def order
|
336
344
|
@mutex.synchronize { @order || {} }
|
337
345
|
end
|
data/lib/brick/extensions.rb
CHANGED
@@ -59,7 +59,7 @@ module ActiveRecord
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def is_brick?
|
62
|
-
instance_variables.include?(:@
|
62
|
+
instance_variables.include?(:@_brick_relation) && instance_variable_get(:@_brick_relation)
|
63
63
|
end
|
64
64
|
|
65
65
|
def _assoc_names
|
@@ -544,11 +544,9 @@ module ActiveRecord
|
|
544
544
|
wheres = {}
|
545
545
|
params.each do |k, v|
|
546
546
|
k = k.to_s # Rails < 4.2 comes in as a symbol
|
547
|
-
next
|
548
|
-
'_brick_erd', '_brick_exclude', '_brick_unexclude',
|
549
|
-
'_brick_page', '_brick_page_size', '_brick_offset', '_brick_limit',
|
550
|
-
'_brick_is_api', 'controller', 'action'].include?(k)
|
547
|
+
next unless k.start_with?('__')
|
551
548
|
|
549
|
+
k = k[2..-1] # Take off leading "__"
|
552
550
|
if (where_col = (ks = k.split('.')).last)[-1] == '!'
|
553
551
|
where_col = where_col[0..-2]
|
554
552
|
end
|
@@ -1163,6 +1161,9 @@ if ActiveSupport::Dependencies.respond_to?(:autoload_module!) # %%% Only works w
|
|
1163
1161
|
end
|
1164
1162
|
|
1165
1163
|
::Brick::ADD_CONST_MISSING = lambda do
|
1164
|
+
return if @_brick_const_missing_done
|
1165
|
+
|
1166
|
+
@_brick_const_missing_done = true
|
1166
1167
|
alias _brick_const_missing const_missing
|
1167
1168
|
def const_missing(*args)
|
1168
1169
|
requested = args.first.to_s
|
@@ -1187,9 +1188,13 @@ end
|
|
1187
1188
|
end
|
1188
1189
|
base_module = if self < ActiveRecord::Migration || !self.name
|
1189
1190
|
brick_root || Object
|
1190
|
-
elsif
|
1191
|
+
elsif split_self_name&.length&.> 1 # Classic mode
|
1191
1192
|
begin
|
1192
|
-
|
1193
|
+
base = self
|
1194
|
+
unless (base_goal = requested.split('::')[0..-2].join('::')).empty?
|
1195
|
+
base = base.parent while base.name != base_goal && base != Object
|
1196
|
+
end
|
1197
|
+
return base._brick_const_missing(*args)
|
1193
1198
|
|
1194
1199
|
rescue NameError # %%% Avoid the error "____ cannot be autoloaded from an anonymous class or module"
|
1195
1200
|
return self.const_get(args.first) if self.const_defined?(args.first)
|
@@ -1294,10 +1299,16 @@ end
|
|
1294
1299
|
# Build out a module for the schema if it's namespaced
|
1295
1300
|
# schema_name = schema_name.camelize
|
1296
1301
|
base_module.const_set(schema_name.to_sym, (built_module = Module.new))
|
1297
|
-
|
1298
1302
|
[built_module, "module #{schema_name}; end\n"]
|
1299
1303
|
# %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied
|
1300
1304
|
|
1305
|
+
# MODULE (overrides from "treat_as_module")
|
1306
|
+
elsif (::Brick.enable_models? || ::Brick.enable_controllers?) &&
|
1307
|
+
(possible_module = (base_module == Object ? '' : "#{base_module.name}::") + class_name) &&
|
1308
|
+
::Brick.config.treat_as_module.include?(possible_module)
|
1309
|
+
base_module.const_set(class_name.to_sym, (built_module = Module.new))
|
1310
|
+
[built_module, "module #{possible_module}; end\n"]
|
1311
|
+
|
1301
1312
|
# AVO Resource
|
1302
1313
|
elsif base_module == Object && Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) && requested.end_with?('Resource') &&
|
1303
1314
|
# Expect that anything called MotorResource or SpinaResource could be from those administrative gems
|
@@ -1464,12 +1475,12 @@ class Object
|
|
1464
1475
|
rescue StandardError => ex
|
1465
1476
|
::ActiveRecord::Base
|
1466
1477
|
end))
|
1467
|
-
|
1468
1478
|
end
|
1469
1479
|
hmts = nil
|
1470
1480
|
code = +"class #{full_name} < #{base_model.name}\n"
|
1471
1481
|
built_model = Class.new(base_model) do |new_model_class|
|
1472
1482
|
(schema_module || Object).const_set((chosen_name = (inheritable_name || model_name)).to_sym, new_model_class)
|
1483
|
+
@_brick_relation = relation
|
1473
1484
|
if inheritable_name
|
1474
1485
|
new_model_class.define_singleton_method :inherited do |subclass|
|
1475
1486
|
super(subclass)
|
@@ -1479,7 +1490,7 @@ class Object
|
|
1479
1490
|
puts "should be \"class #{model_name} < #{inheritable_name}\"\n (not \"#{subclass.name} < #{inheritable_name}\")"
|
1480
1491
|
end
|
1481
1492
|
end
|
1482
|
-
|
1493
|
+
new_model_class.abstract_class = true
|
1483
1494
|
code << " self.abstract_class = true\n"
|
1484
1495
|
elsif Object.const_defined?('BCrypt') && relation[:cols].include?('password_digest') &&
|
1485
1496
|
!instance_methods.include?(:password) && respond_to?(:has_secure_password)
|
@@ -1489,6 +1500,10 @@ class Object
|
|
1489
1500
|
end
|
1490
1501
|
# Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
|
1491
1502
|
code << " self.table_name = '#{self.table_name = matching}'\n" if inheritable_name || self.table_name != matching
|
1503
|
+
if (inh_col = ::Brick.config.sti_type_column.find { |_k, v| v.include?(matching) }&.first)
|
1504
|
+
new_model_class.inheritance_column = inh_col
|
1505
|
+
code << " self.inheritance_column = '#{inh_col}'\n"
|
1506
|
+
end
|
1492
1507
|
|
1493
1508
|
# Override models backed by a view so they return true for #is_view?
|
1494
1509
|
# (Dynamically-created controllers and view templates for such models will then act in a read-only way)
|
@@ -1539,19 +1554,19 @@ class Object
|
|
1539
1554
|
unless is_sti
|
1540
1555
|
fks = relation[:fks] || {}
|
1541
1556
|
# Do the bulk of the has_many / belongs_to processing, and store details about HMT so they can be done at the very last
|
1542
|
-
fks.each_with_object(Hash.new { |h, k| h[k] = [] }) do |fk,
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1557
|
+
hmts = fks.each_with_object(Hash.new { |h, k| h[k] = [] }) do |fk, hmts2|
|
1558
|
+
# The key in each hash entry (fk.first) is the constraint name
|
1559
|
+
inverse_assoc_name = (assoc = fk.last)[:inverse]&.fetch(:assoc_name, nil)
|
1560
|
+
if (invs = assoc[:inverse_table]).is_a?(Array)
|
1561
|
+
if assoc[:is_bt]
|
1562
|
+
invs = invs.first # Just do the first one of what would be multiple identical polymorphic belongs_to
|
1563
|
+
else
|
1564
|
+
invs.each { |inv| build_bt_or_hm(full_name, relations, relation, hmts2, assoc, inverse_assoc_name, inv, code) }
|
1565
|
+
end
|
1566
|
+
else
|
1567
|
+
build_bt_or_hm(full_name, relations, relation, hmts2, assoc, inverse_assoc_name, invs, code)
|
1568
|
+
end
|
1569
|
+
end
|
1555
1570
|
# # Not NULLables
|
1556
1571
|
# # %%% For the minute we've had to pull this out because it's been troublesome implementing the NotNull validator
|
1557
1572
|
# relation[:cols].each do |col, datatype|
|
@@ -1569,58 +1584,54 @@ class Object
|
|
1569
1584
|
# self.broadcasts_to ->(model) { (model&.class&.name || chosen_name).underscore.pluralize.to_sym }
|
1570
1585
|
# code << " broadcasts_to ->(#{chosen_name}) { #{chosen_name}&.class&.name&.underscore&.pluralize&.to_sym }\n"
|
1571
1586
|
# end
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
hms.
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1605
|
-
|
1606
|
-
|
1607
|
-
options[:
|
1608
|
-
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1612
|
-
# options[:foreign_key] = hm.first[:fk].to_sym
|
1613
|
-
far_assoc = relations[hm.first[:inverse_table]][:fks].find { |_k, v| v[:assoc_name] == hm.last }
|
1614
|
-
options[:class_name] = far_assoc.last[:inverse_table].singularize.camelize
|
1615
|
-
options[:foreign_key] = far_assoc.last[:fk].to_sym
|
1616
|
-
end
|
1617
|
-
options[:source] ||= hm.last.to_sym unless hmt_name.singularize == hm.last
|
1618
|
-
code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
|
1619
|
-
self.send(:has_many, hmt_name.to_sym, **options)
|
1587
|
+
|
1588
|
+
hmts&.each do |hmt_fk, hms|
|
1589
|
+
hmt_fk = hmt_fk.tr('.', '_')
|
1590
|
+
hms.each do |hm|
|
1591
|
+
# %%% Need to confirm that HMTs work when they are built from has_manys with custom names
|
1592
|
+
through = ::Brick.config.schema_behavior[:multitenant] ? hm.first[:assoc_name] : hm.first[:inverse_table].tr('.', '_').pluralize
|
1593
|
+
options = {}
|
1594
|
+
hmt_name = if hms.length > 1
|
1595
|
+
if hms[0].first[:inverse][:assoc_name] == hms[1].first[:inverse][:assoc_name] # Same BT names pointing back to us? (Most common scenario)
|
1596
|
+
"#{hmt_fk}_through_#{hm.first[:assoc_name]}"
|
1597
|
+
else # Use BT names to provide uniqueness
|
1598
|
+
if self.name.underscore.singularize == hm.first[:alternate_name]
|
1599
|
+
# Has previously been:
|
1600
|
+
# # If it folds back on itself then look at the other side
|
1601
|
+
# # (At this point just infer the source be the inverse of the first has_many that
|
1602
|
+
# # we find that is not ourselves. If there are more than two then uh oh, can't
|
1603
|
+
# # yet handle that rare circumstance!)
|
1604
|
+
# other = hms.find { |hm1| hm1 != hm } # .first[:fk]
|
1605
|
+
# options[:source] = other.first[:inverse][:assoc_name].to_sym
|
1606
|
+
# And also has been:
|
1607
|
+
# hm.first[:inverse][:assoc_name].to_sym
|
1608
|
+
options[:source] = hm.last.to_sym
|
1609
|
+
else
|
1610
|
+
through = hm.first.fetch(:alternate_chosen_name, hm.first[:alternate_name])
|
1611
|
+
end
|
1612
|
+
singular_assoc_name = hm.first[:inverse][:assoc_name].singularize
|
1613
|
+
"#{singular_assoc_name}_#{hmt_fk}"
|
1614
|
+
end
|
1615
|
+
else
|
1616
|
+
hmt_fk
|
1617
|
+
end
|
1618
|
+
options[:through] = through.to_sym
|
1619
|
+
if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
|
1620
|
+
hmt_name = "#{hmt_name.singularize}_#{hm.first[:assoc_name]}"
|
1621
|
+
# Was:
|
1622
|
+
# options[:class_name] = hm.first[:inverse_table].singularize.camelize
|
1623
|
+
# options[:foreign_key] = hm.first[:fk].to_sym
|
1624
|
+
far_assoc = relations[hm.first[:inverse_table]][:fks].find { |_k, v| v[:assoc_name] == hm.last }
|
1625
|
+
options[:class_name] = far_assoc.last[:inverse_table].singularize.camelize
|
1626
|
+
options[:foreign_key] = far_assoc.last[:fk].to_sym
|
1620
1627
|
end
|
1628
|
+
options[:source] ||= hm.last.to_sym unless hmt_name.singularize == hm.last
|
1629
|
+
code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
|
1630
|
+
new_model_class.send(:has_many, hmt_name.to_sym, **options)
|
1621
1631
|
end
|
1622
1632
|
end
|
1623
1633
|
code << "end # model #{full_name}\n"
|
1634
|
+
end # model class definition
|
1624
1635
|
[built_model, code]
|
1625
1636
|
end
|
1626
1637
|
|
@@ -2147,6 +2158,17 @@ class Object
|
|
2147
2158
|
new_obj.send("#{k}=", ActiveStorage::Filename.new('')) if v.is_a?(ActiveStorage::Filename) && !v.instance_variable_get(:@filename)
|
2148
2159
|
end if Object.const_defined?('ActiveStorage')
|
2149
2160
|
end
|
2161
|
+
new_obj.attribute_names.each do |a|
|
2162
|
+
if (val = params["__#{a}"])
|
2163
|
+
# val = case new_obj.class.column_for_attribute(a).type
|
2164
|
+
# when :datetime, :date, :time, :timestamp
|
2165
|
+
# val.
|
2166
|
+
# else
|
2167
|
+
# val
|
2168
|
+
# end
|
2169
|
+
new_obj.send("#{a}=", val)
|
2170
|
+
end
|
2171
|
+
end
|
2150
2172
|
instance_variable_set("@#{singular_table_name}".to_sym, new_obj)
|
2151
2173
|
add_csp_hash
|
2152
2174
|
end
|
@@ -750,12 +750,12 @@ window.addEventListener(\"popstate\", linkSchemas);
|
|
750
750
|
(hm_fk_name.is_a?(String) && hm_fk_name.include?('.')) # HMT? (Could do a better check for this)
|
751
751
|
predicates = path_keys(hm_assoc, hm_fk_name, pk).map do |k, v|
|
752
752
|
if v == '[sti_type]'
|
753
|
-
"'#{k}': (@#{obj_name}.#{hm_assoc.active_record.inheritance_column})&.constantize&.base_class&.name"
|
753
|
+
"'__#{k}': (@#{obj_name}.#{hm_assoc.active_record.inheritance_column})&.constantize&.base_class&.name"
|
754
754
|
else
|
755
|
-
v.is_a?(String) ? "'#{k}': '#{v}'" : "'#{k}': @#{obj_name}.#{v}"
|
755
|
+
v.is_a?(String) ? "'__#{k}': '#{v}'" : "'__#{k}': @#{obj_name}.#{v}"
|
756
756
|
end
|
757
757
|
end.join(', ')
|
758
|
-
"<%= link_to '#{assoc_name}', #{hm_assoc.klass._brick_index}_path({ #{predicates} }) %>\n"
|
758
|
+
"<%= link_to '#{assoc_name}', #{hm_assoc.klass._brick_index}_path(predicates = { #{predicates} }) %>\n"
|
759
759
|
else
|
760
760
|
puts "Warning: has_many :#{hm_assoc.name} in model #{hm_assoc.active_record.name} currently looks for a foreign key called \"#{hm_assoc.foreign_key}\". "\
|
761
761
|
"Instead it should use the clause \"foreign_key: :#{hm_assoc.inverse_of&.foreign_key}\"."
|
@@ -897,6 +897,9 @@ tr th {
|
|
897
897
|
tr th a {
|
898
898
|
color: #80FFB8;
|
899
899
|
}
|
900
|
+
.add-hm-related {
|
901
|
+
float: right;
|
902
|
+
}
|
900
903
|
|
901
904
|
tr th, tr td {
|
902
905
|
padding: 0.2em 0.5em;
|
@@ -1169,7 +1172,10 @@ if (window.brickFontFamily) {
|
|
1169
1172
|
x.style.fontFamily = brickFontFamily.toString();
|
1170
1173
|
});
|
1171
1174
|
}
|
1172
|
-
</script>
|
1175
|
+
</script>
|
1176
|
+
<% if (apartment_default_schema = ::Brick.apartment_multitenant && ::Brick.apartment_default_tenant)
|
1177
|
+
Apartment::Tenant.switch!(apartment_default_schema)
|
1178
|
+
end %>"
|
1173
1179
|
|
1174
1180
|
erd_markup = if @_brick_model
|
1175
1181
|
"<div id=\"mermaidErd\" class=\"mermaid\">
|
@@ -1658,7 +1664,7 @@ end
|
|
1658
1664
|
# path_options = [obj.#{pk}]
|
1659
1665
|
# path_options << { '_brick_schema': } if
|
1660
1666
|
options = {}
|
1661
|
-
if ::Brick.config.path_prefix
|
1667
|
+
if ::Brick.config.path_prefix || (obj.class.table_name.singularize == obj.class.table_name)
|
1662
1668
|
path_helper = obj.new_record? ? #{model_name}._brick_index : #{model_name}._brick_index(:singular)
|
1663
1669
|
options[:url] = send(\"#\{path_helper}_path\".to_sym, obj)
|
1664
1670
|
end
|
@@ -1763,7 +1769,14 @@ end
|
|
1763
1769
|
end"}"
|
1764
1770
|
end
|
1765
1771
|
s << "<table id=\"#{hm_name}\" class=\"shadow\">
|
1766
|
-
<tr><th>#{hm[1]}#{' poly' if hm[0].options[:as]} #{hm[3]}
|
1772
|
+
<tr><th>#{hm[1]}#{' poly' if hm[0].options[:as]} #{hm[3]}
|
1773
|
+
<span class = \"add-hm-related\"><%=
|
1774
|
+
pk_val = (obj_pk = model.primary_key).is_a?(String) ? obj.send(obj_pk) : obj_pk.map { |pk_part| obj.send(pk_part) }
|
1775
|
+
pk_val_arr = [pk_val] unless pk_val.is_a?(Array)
|
1776
|
+
link_to('<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"#fff\" d=\"M24 10h-10v-10h-4v10h-10v4h10v10h4v-10h10z\"/></svg>'.html_safe,
|
1777
|
+
new_#{hm.first.klass._brick_index(:singular)}_path(predicates))
|
1778
|
+
%></span>
|
1779
|
+
</th></tr>
|
1767
1780
|
<% if (assoc = @#{obj_name}.class.reflect_on_association(:#{hm_name})).macro == :has_one &&
|
1768
1781
|
assoc.options&.fetch(:through, nil).nil?
|
1769
1782
|
# In order to apply DSL properly, evaluate this HO the other way around as if it were as a BT
|
@@ -2034,9 +2047,7 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
|
|
2034
2047
|
alias _brick_render_template render_template
|
2035
2048
|
def render_template(view, template, layout_name, *args)
|
2036
2049
|
layout_name = nil if (is_brick = template.instance_variable_get(:@is_brick)) && layout_name.is_a?(Proc)
|
2037
|
-
|
2038
|
-
Apartment::Tenant.switch!(::Brick.apartment_default_tenant) if is_brick && ::Brick.apartment_multitenant
|
2039
|
-
result
|
2050
|
+
_brick_render_template(view, template, layout_name, *args)
|
2040
2051
|
end
|
2041
2052
|
end # TemplateRenderer
|
2042
2053
|
end
|
@@ -71,11 +71,11 @@ module Brick::Rails::FormBuilder
|
|
71
71
|
# Postgres naturally uses the +uuid_generate_v4()+ function from the uuid-ossp extension
|
72
72
|
# If it's not yet enabled then: create extension \"uuid-ossp\";
|
73
73
|
# ActiveUUID gem created a new :uuid type
|
74
|
-
out << val
|
74
|
+
out << val if val
|
75
75
|
when :ltree
|
76
76
|
# In Postgres labels of data stored in a hierarchical tree-like structure
|
77
77
|
# If it's not yet enabled then: create extension ltree;
|
78
|
-
out << val
|
78
|
+
out << val if val
|
79
79
|
when :binary
|
80
80
|
is_revert = false
|
81
81
|
if val
|
@@ -95,7 +95,7 @@ module Brick::Rails::FormBuilder
|
|
95
95
|
else
|
96
96
|
eheij = ActiveSupport::JSON::Encoding.escape_html_entities_in_json
|
97
97
|
ActiveSupport::JSON::Encoding.escape_html_entities_in_json = false if eheij
|
98
|
-
val_str = val
|
98
|
+
val_str = val&.to_json
|
99
99
|
ActiveSupport::JSON::Encoding.escape_html_entities_in_json = eheij
|
100
100
|
end
|
101
101
|
# Because there are so danged many quotes in JSON, escape them specially by converting to backticks.
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -113,21 +113,23 @@ module Brick
|
|
113
113
|
def set_db_schema(params = nil)
|
114
114
|
# If Apartment::Tenant.current is not still the default (usually 'public') then an elevator has brought us into
|
115
115
|
# a different tenant. If so then don't allow schema navigation.
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
116
|
+
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' && apartment_multitenant
|
117
|
+
current_schema = (ActiveRecord::Base.execute_sql('SELECT current_schemas(true)')
|
118
|
+
.first['current_schemas'][1..-2]
|
119
|
+
.split(',') - ['pg_catalog', 'pg_toast', 'heroku_ext']).first
|
120
|
+
is_show_schema_list = current_schema == ::Brick.default_schema
|
121
|
+
schema = (is_show_schema_list && params && params['_brick_schema']) || ::Brick.default_schema
|
122
|
+
chosen = if is_show_schema_list && ::Brick.db_schemas&.key?(schema)
|
123
|
+
Apartment::Tenant.switch!(schema)
|
124
|
+
schema
|
125
|
+
elsif ::Brick.test_schema
|
126
|
+
is_show_schema_list = true
|
127
|
+
Apartment::Tenant.switch!(::Brick.test_schema)
|
128
|
+
::Brick.test_schema
|
129
|
+
else
|
130
|
+
current_schema # Just return the current schema
|
131
|
+
end
|
132
|
+
end
|
131
133
|
[chosen == ::Brick.default_schema ? nil : chosen, is_show_schema_list]
|
132
134
|
end
|
133
135
|
|
@@ -416,6 +418,12 @@ module Brick
|
|
416
418
|
Brick.config.table_name_prefixes = value
|
417
419
|
end
|
418
420
|
|
421
|
+
# @api public
|
422
|
+
# Causes Decidim to work with this line: Brick.treat_as_module = ['Decidim::ContentBlocks']
|
423
|
+
def treat_as_module=(value)
|
424
|
+
Brick.config.treat_as_module = value
|
425
|
+
end
|
426
|
+
|
419
427
|
# @api public
|
420
428
|
def metadata_columns=(value)
|
421
429
|
Brick.config.metadata_columns = value
|
@@ -1147,8 +1155,11 @@ ActiveSupport.on_load(:active_record) do
|
|
1147
1155
|
class << self
|
1148
1156
|
def execute_sql(sql, *param_array)
|
1149
1157
|
param_array = param_array.first if param_array.length == 1 && param_array.first.is_a?(Array)
|
1150
|
-
|
1158
|
+
case ActiveRecord::Base.connection.adapter_name
|
1159
|
+
when 'OracleEnhanced', 'SQLServer'
|
1151
1160
|
connection.exec_query(send(:sanitize_sql_array, [sql] + param_array)).rows
|
1161
|
+
when 'Trilogy'
|
1162
|
+
connection.execute(send(:sanitize_sql_array, [sql] + param_array)).rows
|
1152
1163
|
else
|
1153
1164
|
connection.execute(send(:sanitize_sql_array, [sql] + param_array))
|
1154
1165
|
end
|
@@ -1159,8 +1170,7 @@ ActiveSupport.on_load(:active_record) do
|
|
1159
1170
|
# :singleton-method:
|
1160
1171
|
# Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
|
1161
1172
|
# dates and times from the database. This is set to :utc by default.
|
1162
|
-
unless respond_to?(:default_timezone)
|
1163
|
-
puts "ADDING!!! 4.w"
|
1173
|
+
unless ::ActiveRecord.respond_to?(:default_timezone) || respond_to?(:default_timezone)
|
1164
1174
|
mattr_accessor :default_timezone, instance_writer: false
|
1165
1175
|
self.default_timezone = :utc
|
1166
1176
|
end
|
@@ -1453,7 +1463,8 @@ ActiveSupport.on_load(:active_record) do
|
|
1453
1463
|
class << self
|
1454
1464
|
alias _original_load load
|
1455
1465
|
def load(yaml, *args, **kwargs)
|
1456
|
-
if kwargs[:aliases].nil? && caller[0..4].any? { |line| line.end_with?("`database_configuration'")
|
1466
|
+
if kwargs[:aliases].nil? && caller[0..4].any? { |line| line.end_with?("`database_configuration'") ||
|
1467
|
+
line.end_with?("`secrets'") }
|
1457
1468
|
kwargs[:aliases] = true
|
1458
1469
|
end
|
1459
1470
|
_original_load(yaml, *args, **kwargs)
|
@@ -23,7 +23,8 @@ 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
|
-
|
26
|
+
'ARRAY' => 'string', # Note that we'll also add ", array: true"
|
27
|
+
# Oracle data types
|
27
28
|
'VARCHAR2' => 'string',
|
28
29
|
'CHAR' => 'string',
|
29
30
|
['NUMBER', 22] => 'integer',
|
@@ -54,8 +55,8 @@ module Brick
|
|
54
55
|
def brick_migrations
|
55
56
|
# If Apartment is active, see if a default schema to analyse is indicated
|
56
57
|
|
57
|
-
|
58
|
-
|
58
|
+
::Brick.mode = :on
|
59
|
+
ActiveRecord::Base.establish_connection
|
59
60
|
|
60
61
|
if (tables = ::Brick.relations.reject { |k, v| v.key?(:isView) && v[:isView] == true }.map(&:first).sort).empty?
|
61
62
|
puts "No tables found in database #{ActiveRecord::Base.connection.current_database}."
|
@@ -176,7 +177,7 @@ module Brick
|
|
176
177
|
# Support missing primary key (by adding: , id: false)
|
177
178
|
id_option = if pk_is_also_fk || !pkey_cols&.present?
|
178
179
|
needs_serial_col = true
|
179
|
-
', id: false'
|
180
|
+
+', id: false'
|
180
181
|
elsif ((pkey_col_first = (col_def = relation[:cols][pkey_cols&.first])&.first) &&
|
181
182
|
(pkey_col_first = SQL_TYPES[pkey_col_first] || SQL_TYPES[col_def&.[](0..1)] ||
|
182
183
|
SQL_TYPES.find { |r| r.first.is_a?(Regexp) && pkey_col_first =~ r.first }&.last ||
|
@@ -185,16 +186,16 @@ module Brick
|
|
185
186
|
)
|
186
187
|
case pkey_col_first
|
187
188
|
when 'integer'
|
188
|
-
', id: :serial'
|
189
|
+
+', id: :serial'
|
189
190
|
when 'bigint'
|
190
|
-
', id: :bigserial'
|
191
|
+
+', id: :bigserial'
|
191
192
|
else
|
192
|
-
", id: :#{pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
|
193
|
+
+", id: :#{pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
|
193
194
|
end +
|
194
195
|
(pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '')
|
195
196
|
end
|
196
197
|
if !id_option && pkey_cols.sort != arpk
|
197
|
-
id_option = ", primary_key: :#{pkey_cols.first}"
|
198
|
+
id_option = +", primary_key: :#{pkey_cols.first}"
|
198
199
|
end
|
199
200
|
if !is_4x_rails && (comment = relation&.fetch(:description, nil))&.present?
|
200
201
|
(id_option ||= +'') << ", comment: #{comment.inspect}"
|
@@ -223,6 +224,7 @@ module Brick
|
|
223
224
|
SQL_TYPES.find { |r| r.first.is_a?(Regexp) && col_type.first =~ r.first }&.last ||
|
224
225
|
col_type.first
|
225
226
|
suffix = col_type[3] || pkey_cols&.include?(col) ? +', null: false' : +''
|
227
|
+
suffix << ', array: true' if (col_type.first == 'ARRAY')
|
226
228
|
if !is_4x_rails && klass && (comment = klass.columns_hash.fetch(col, nil)&.comment)&.present?
|
227
229
|
suffix << ", comment: #{comment.inspect}"
|
228
230
|
end
|
@@ -9,29 +9,50 @@ module Brick
|
|
9
9
|
|
10
10
|
desc 'Auto-generates a seeds file from existing data.'
|
11
11
|
|
12
|
+
SeedModel = Struct.new(:table_name, :klass, :is_brick)
|
13
|
+
SeedModel.define_method(:to_s) do
|
14
|
+
"#{klass.name}#{' (brick-generated)' if is_brick}"
|
15
|
+
end
|
16
|
+
|
12
17
|
def brick_seeds
|
13
18
|
# %%% If Apartment is active and there's no schema_to_analyse, ask which schema they want
|
14
19
|
|
15
20
|
::Brick.mode = :on
|
16
21
|
ActiveRecord::Base.establish_connection
|
17
22
|
|
18
|
-
|
19
|
-
|
23
|
+
# Load all models
|
24
|
+
::Brick.eager_load_classes
|
25
|
+
|
26
|
+
# Generate a list of viable models that can be chosen
|
27
|
+
# First start with any existing models that have been defined ...
|
28
|
+
existing_models = ActiveRecord::Base.descendants.each_with_object({}) do |m, s|
|
29
|
+
s[m.table_name] = SeedModel.new(m.table_name, m, false) if !m.abstract_class? && m.table_exists?
|
30
|
+
end
|
31
|
+
models = (existing_models.values +
|
32
|
+
# ... then add models which can be auto-built by Brick
|
33
|
+
::Brick.relations.reject do |k, v|
|
34
|
+
(v.key?(:isView) && v[:isView] == true) || existing_models.key?(k)
|
35
|
+
end.map { |k, v| SeedModel.new(k, v[:class_name].constantize, true) }
|
36
|
+
).sort { |a, b| a.to_s <=> b.to_s }
|
37
|
+
if models.empty?
|
38
|
+
puts "No viable models found for database #{ActiveRecord::Base.connection.current_database}."
|
20
39
|
return
|
21
40
|
end
|
22
41
|
|
23
42
|
if File.exist?(seed_file_path = "#{::Rails.root}/db/seeds.rb")
|
24
|
-
puts "WARNING: seeds file #{seed_file_path} appears to already be present
|
43
|
+
puts "WARNING: seeds file #{seed_file_path} appears to already be present.\nOverwrite?"
|
44
|
+
return unless gets_list(list: ['No', 'Yes']) == 'Yes'
|
45
|
+
|
46
|
+
puts "\n"
|
25
47
|
end
|
26
48
|
|
27
|
-
|
28
|
-
chosen = gets_list(list: tables, chosen: tables.dup)
|
49
|
+
chosen = gets_list(list: models, chosen: models.dup)
|
29
50
|
schemas = chosen.each_with_object({}) do |v, s|
|
30
|
-
if (v_parts = v.split('.')).length > 1
|
51
|
+
if (v_parts = v.table_name.split('.')).length > 1
|
31
52
|
s[v_parts.first] = nil unless [::Brick.default_schema, 'public'].include?(v_parts.first)
|
32
53
|
end
|
33
54
|
end
|
34
|
-
seeds = +"# Seeds file for #{ActiveRecord::Base.connection.current_database}
|
55
|
+
seeds = +"# Seeds file for #{ActiveRecord::Base.connection.current_database}:\n"
|
35
56
|
done = []
|
36
57
|
fks = {}
|
37
58
|
stuck = {}
|
@@ -40,12 +61,13 @@ module Brick
|
|
40
61
|
# Start by making entries for fringe models (those with no foreign keys).
|
41
62
|
# Continue layer by layer, creating entries for models that reference ones already done, until
|
42
63
|
# no more entries can be created. (At that point hopefully all models are accounted for.)
|
43
|
-
while (fringe = chosen.reject do |
|
64
|
+
while (fringe = chosen.reject do |seed_model|
|
65
|
+
tbl = seed_model.table_name
|
44
66
|
snag_fks = []
|
45
67
|
snags = ::Brick.relations.fetch(tbl, nil)&.fetch(:fks, nil)&.select do |_k, v|
|
46
68
|
v[:is_bt] && !v[:polymorphic] &&
|
47
69
|
tbl != v[:inverse_table] && # Ignore self-referencing associations (stuff like "parent_id")
|
48
|
-
!done.
|
70
|
+
!done.any? { |done_seed_model| done_seed_model.table_name == v[:inverse_table] } &&
|
49
71
|
::Brick.config.ignore_migration_fks.exclude?(snag_fk = "#{tbl}.#{v[:fk]}") &&
|
50
72
|
snag_fks << snag_fk
|
51
73
|
end
|
@@ -53,12 +75,14 @@ module Brick
|
|
53
75
|
# puts snag_fks.inspect
|
54
76
|
stuck[tbl] = snags
|
55
77
|
end
|
56
|
-
end
|
78
|
+
end
|
79
|
+
).present?
|
57
80
|
seeds << "\n"
|
58
|
-
fringe.each do |
|
81
|
+
fringe.each do |seed_model|
|
82
|
+
tbl = seed_model.table_name
|
59
83
|
next unless ::Brick.config.exclude_tables.exclude?(tbl) &&
|
60
84
|
(relation = ::Brick.relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present? &&
|
61
|
-
(klass =
|
85
|
+
(klass = seed_model.klass).table_exists?
|
62
86
|
|
63
87
|
pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ar_base.primary_key].flatten.sort)
|
64
88
|
# In case things aren't as standard
|
@@ -82,10 +106,14 @@ module Brick
|
|
82
106
|
fkeys = relation[:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
|
83
107
|
# Refer to this table name as a symbol or dotted string as appropriate
|
84
108
|
# tbl_code = tbl_parts.length == 1 ? ":#{tbl_parts.first}" : "'#{tbl}'"
|
85
|
-
seeds << " # #{class_name}\n"
|
86
109
|
|
110
|
+
has_rows = false
|
87
111
|
is_empty = true
|
88
112
|
klass.order(*pkey_cols).each do |obj|
|
113
|
+
unless has_rows
|
114
|
+
has_rows = true
|
115
|
+
seeds << " puts 'Seeding: #{seed_model.klass.name}'\n"
|
116
|
+
end
|
89
117
|
is_empty = false
|
90
118
|
pk_val = obj.send(pkey_cols.first)
|
91
119
|
fk_vals = []
|
@@ -98,19 +126,55 @@ module Brick
|
|
98
126
|
val = val.to_s
|
99
127
|
end
|
100
128
|
if fk
|
101
|
-
|
129
|
+
inv_tbl = fk[:inverse_table].gsub('.', '__')
|
130
|
+
fk_vals << "#{fk[:assoc_name]}: #{inv_tbl}_#{brick_escape(val)}" if val
|
102
131
|
else
|
103
132
|
data << "#{col}: #{val.inspect}"
|
104
133
|
end
|
105
134
|
end
|
106
|
-
seeds << "#{tbl}_#{pk_val} = #{
|
135
|
+
seeds << "#{tbl.gsub('.', '__')}_#{brick_escape(pk_val)} = #{seed_model.klass.name}.create(#{(fk_vals + data).join(', ')})\n"
|
107
136
|
end
|
137
|
+
seeds << " # (Skipping #{seed_model.klass.name} as it has no rows)\n" unless has_rows
|
108
138
|
File.open(seed_file_path, "w") { |f| f.write seeds }
|
109
139
|
end
|
110
140
|
done.concat(fringe)
|
111
141
|
chosen -= done
|
112
142
|
end
|
143
|
+
stuck_counts = Hash.new { |h, k| h[k] = 0 }
|
144
|
+
chosen.each do |leftover|
|
145
|
+
puts "Can't do #{leftover.klass.name} because:\n #{stuck[leftover.table_name].map do |snag|
|
146
|
+
stuck_counts[snag.last[:inverse_table]] += 1
|
147
|
+
snag.last[:assoc_name]
|
148
|
+
end.join(', ')}"
|
149
|
+
end
|
113
150
|
puts "\n*** Created seeds for #{done.length} models in db/seeds.rb ***"
|
151
|
+
if (stuck_sorted = stuck_counts.to_a.sort { |a, b| b.last <=> a.last }).length.positive?
|
152
|
+
puts "-----------------------------------------"
|
153
|
+
puts "Unable to create migrations for #{stuck_sorted.length} tables#{
|
154
|
+
". Here's the top 5 blockers" if stuck_sorted.length > 5
|
155
|
+
}:"
|
156
|
+
pp stuck_sorted[0..4]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def brick_escape(val)
|
163
|
+
val = val.to_s if val.is_a?(Date) || val.is_a?(Time) # Accommodate when for whatever reason a primary key is a date or time
|
164
|
+
case val
|
165
|
+
when String
|
166
|
+
ret = +''
|
167
|
+
val.each_char do |ch|
|
168
|
+
if ch < '0' || (ch > '9' && ch < 'A') || ch > 'Z'
|
169
|
+
ret << (ch == '_' ? ch : "x#{'K'.unpack('H*')[0]}")
|
170
|
+
else
|
171
|
+
ret << ch
|
172
|
+
end
|
173
|
+
end
|
174
|
+
ret
|
175
|
+
else
|
176
|
+
val
|
177
|
+
end
|
114
178
|
end
|
115
179
|
end
|
116
180
|
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.157
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-07-
|
11
|
+
date: 2023-07-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|