brick 1.0.155 → 1.0.157
Sign up to get free protection for your applications and to get access to all the features.
- 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
|