brick 1.0.156 → 1.0.157

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e001ce24602c8fef817c24a0eb88e014e990d98828474acd66d868b50dd68692
4
- data.tar.gz: 8378593754641ffc86059672fe6148ba767d91a867361b21ec8377a82903af6b
3
+ metadata.gz: 995a300b0971af02a39ed75857ce349c99720068555cbb1d6311a75d9a13f2b8
4
+ data.tar.gz: e9153dac19552fc8dd581774f89b9ed682ac040b5a4dc0d47f4b28fe079ca36d
5
5
  SHA512:
6
- metadata.gz: 84fa7d9831dfca26b9b08ef46bd852c3d4f96e1f8420ff086526f97964090b3f95d71b5d66d9ff7ba47ec5f74e50938c9714731d21d0c59ac2365305ab8826bc
7
- data.tar.gz: cf790bbb66852142fe07cb1619517043dc3ef8c47b0f020b22bc635cd902fface9b52631507b2fdd9520b604c98eae00b58a1bad29ace185c8d342e79e99a062
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
@@ -59,7 +59,7 @@ module ActiveRecord
59
59
  end
60
60
 
61
61
  def is_brick?
62
- instance_variables.include?(:@_brick_built) && instance_variable_get(:@_brick_built)
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 if ['_brick_schema', '_brick_order',
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 (split_self_name || self.name.split('::')).length > 1 # Classic mode
1191
+ elsif split_self_name&.length&.> 1 # Classic mode
1191
1192
  begin
1192
- return self._brick_const_missing(*args)
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
@@ -1469,6 +1480,7 @@ class Object
1469
1480
  code = +"class #{full_name} < #{base_model.name}\n"
1470
1481
  built_model = Class.new(base_model) do |new_model_class|
1471
1482
  (schema_module || Object).const_set((chosen_name = (inheritable_name || model_name)).to_sym, new_model_class)
1483
+ @_brick_relation = relation
1472
1484
  if inheritable_name
1473
1485
  new_model_class.define_singleton_method :inherited do |subclass|
1474
1486
  super(subclass)
@@ -1478,7 +1490,7 @@ class Object
1478
1490
  puts "should be \"class #{model_name} < #{inheritable_name}\"\n (not \"#{subclass.name} < #{inheritable_name}\")"
1479
1491
  end
1480
1492
  end
1481
- self.abstract_class = true
1493
+ new_model_class.abstract_class = true
1482
1494
  code << " self.abstract_class = true\n"
1483
1495
  elsif Object.const_defined?('BCrypt') && relation[:cols].include?('password_digest') &&
1484
1496
  !instance_methods.include?(:password) && respond_to?(:has_secure_password)
@@ -1489,7 +1501,7 @@ class Object
1489
1501
  # Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
1490
1502
  code << " self.table_name = '#{self.table_name = matching}'\n" if inheritable_name || self.table_name != matching
1491
1503
  if (inh_col = ::Brick.config.sti_type_column.find { |_k, v| v.include?(matching) }&.first)
1492
- self.inheritance_column = inh_col
1504
+ new_model_class.inheritance_column = inh_col
1493
1505
  code << " self.inheritance_column = '#{inh_col}'\n"
1494
1506
  end
1495
1507
 
@@ -1542,19 +1554,19 @@ class Object
1542
1554
  unless is_sti
1543
1555
  fks = relation[:fks] || {}
1544
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
1545
- fks.each_with_object(Hash.new { |h, k| h[k] = [] }) do |fk, hmts|
1546
- # The key in each hash entry (fk.first) is the constraint name
1547
- inverse_assoc_name = (assoc = fk.last)[:inverse]&.fetch(:assoc_name, nil)
1548
- if (invs = assoc[:inverse_table]).is_a?(Array)
1549
- if assoc[:is_bt]
1550
- invs = invs.first # Just do the first one of what would be multiple identical polymorphic belongs_to
1551
- else
1552
- invs.each { |inv| build_bt_or_hm(full_name, relations, relation, hmts, assoc, inverse_assoc_name, inv, code) }
1553
- end
1554
- end
1555
- build_bt_or_hm(full_name, relations, relation, hmts, assoc, inverse_assoc_name, invs, code) unless invs.is_a?(Array)
1556
- hmts
1557
- end
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
1558
1570
  # # Not NULLables
1559
1571
  # # %%% For the minute we've had to pull this out because it's been troublesome implementing the NotNull validator
1560
1572
  # relation[:cols].each do |col, datatype|
@@ -1572,58 +1584,54 @@ class Object
1572
1584
  # self.broadcasts_to ->(model) { (model&.class&.name || chosen_name).underscore.pluralize.to_sym }
1573
1585
  # code << " broadcasts_to ->(#{chosen_name}) { #{chosen_name}&.class&.name&.underscore&.pluralize&.to_sym }\n"
1574
1586
  # end
1575
- end # class definition
1576
- # Having this separate -- will this now work out better?
1577
- built_model.class_exec do
1578
- @_brick_built = true
1579
- @_brick_relation = relation
1580
- hmts&.each do |hmt_fk, hms|
1581
- hmt_fk = hmt_fk.tr('.', '_')
1582
- hms.each do |hm|
1583
- # %%% Need to confirm that HMTs work when they are built from has_manys with custom names
1584
- through = ::Brick.config.schema_behavior[:multitenant] ? hm.first[:assoc_name] : hm.first[:inverse_table].tr('.', '_').pluralize
1585
- options = {}
1586
- hmt_name = if hms.length > 1
1587
- if hms[0].first[:inverse][:assoc_name] == hms[1].first[:inverse][:assoc_name] # Same BT names pointing back to us? (Most common scenario)
1588
- "#{hmt_fk}_through_#{hm.first[:assoc_name]}"
1589
- else # Use BT names to provide uniqueness
1590
- if self.name.underscore.singularize == hm.first[:alternate_name]
1591
- # Has previously been:
1592
- # # If it folds back on itself then look at the other side
1593
- # # (At this point just infer the source be the inverse of the first has_many that
1594
- # # we find that is not ourselves. If there are more than two then uh oh, can't
1595
- # # yet handle that rare circumstance!)
1596
- # other = hms.find { |hm1| hm1 != hm } # .first[:fk]
1597
- # options[:source] = other.first[:inverse][:assoc_name].to_sym
1598
- # And also has been:
1599
- # hm.first[:inverse][:assoc_name].to_sym
1600
- options[:source] = hm.last.to_sym
1601
- else
1602
- through = hm.first.fetch(:alternate_chosen_name, hm.first[:alternate_name])
1603
- end
1604
- singular_assoc_name = hm.first[:inverse][:assoc_name].singularize
1605
- "#{singular_assoc_name}_#{hmt_fk}"
1606
- end
1607
- else
1608
- hmt_fk
1609
- end
1610
- options[:through] = through.to_sym
1611
- if relation[:fks].any? { |k, v| v[:assoc_name] == hmt_name }
1612
- hmt_name = "#{hmt_name.singularize}_#{hm.first[:assoc_name]}"
1613
- # Was:
1614
- # options[:class_name] = hm.first[:inverse_table].singularize.camelize
1615
- # options[:foreign_key] = hm.first[:fk].to_sym
1616
- far_assoc = relations[hm.first[:inverse_table]][:fks].find { |_k, v| v[:assoc_name] == hm.last }
1617
- options[:class_name] = far_assoc.last[:inverse_table].singularize.camelize
1618
- options[:foreign_key] = far_assoc.last[:fk].to_sym
1619
- end
1620
- options[:source] ||= hm.last.to_sym unless hmt_name.singularize == hm.last
1621
- code << " has_many :#{hmt_name}#{options.map { |opt| ", #{opt.first}: #{opt.last.inspect}" }.join}\n"
1622
- 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
1623
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)
1624
1631
  end
1625
1632
  end
1626
1633
  code << "end # model #{full_name}\n"
1634
+ end # model class definition
1627
1635
  [built_model, code]
1628
1636
  end
1629
1637
 
@@ -2150,6 +2158,17 @@ class Object
2150
2158
  new_obj.send("#{k}=", ActiveStorage::Filename.new('')) if v.is_a?(ActiveStorage::Filename) && !v.instance_variable_get(:@filename)
2151
2159
  end if Object.const_defined?('ActiveStorage')
2152
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
2153
2172
  instance_variable_set("@#{singular_table_name}".to_sym, new_obj)
2154
2173
  add_csp_hash
2155
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]}</th></tr>
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
- result = _brick_render_template(view, template, layout_name, *args)
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.to_json
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.
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 156
8
+ TINY = 157
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
@@ -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
- chosen = if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' &&
117
- (current_schema = (ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2]
118
- .split(',') - ['pg_catalog', 'pg_toast', 'heroku_ext']).first) &&
119
- (is_show_schema_list = (apartment_multitenant && current_schema == ::Brick.default_schema)) &&
120
- (schema = (params ? params['_brick_schema'] : ::Brick.default_schema)) &&
121
- ::Brick.db_schemas&.key?(schema)
122
- Apartment::Tenant.switch!(schema)
123
- schema
124
- elsif ::Brick.test_schema
125
- is_show_schema_list = true
126
- Apartment::Tenant.switch!(::Brick.test_schema)
127
- ::Brick.test_schema
128
- else
129
- current_schema # Just return the current schema
130
- end
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
- if ['OracleEnhanced', 'SQLServer'].include?(ActiveRecord::Base.connection.adapter_name)
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
@@ -1452,7 +1463,8 @@ ActiveSupport.on_load(:active_record) do
1452
1463
  class << self
1453
1464
  alias _original_load load
1454
1465
  def load(yaml, *args, **kwargs)
1455
- 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'") }
1456
1468
  kwargs[:aliases] = true
1457
1469
  end
1458
1470
  _original_load(yaml, *args, **kwargs)
@@ -55,8 +55,8 @@ module Brick
55
55
  def brick_migrations
56
56
  # If Apartment is active, see if a default schema to analyse is indicated
57
57
 
58
- # # Load all models
59
- # ::Brick.eager_load_classes
58
+ ::Brick.mode = :on
59
+ ActiveRecord::Base.establish_connection
60
60
 
61
61
  if (tables = ::Brick.relations.reject { |k, v| v.key?(:isView) && v[:isView] == true }.map(&:first).sort).empty?
62
62
  puts "No tables found in database #{ActiveRecord::Base.connection.current_database}."
@@ -15,6 +15,9 @@ module Brick
15
15
  def brick_models
16
16
  # %%% If Apartment is active and there's no schema_to_analyse, ask which schema they want
17
17
 
18
+ ::Brick.mode = :on
19
+ ActiveRecord::Base.establish_connection
20
+
18
21
  # Load all models
19
22
  ::Brick.eager_load_classes
20
23
 
@@ -9,25 +9,46 @@ 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
- if (tables = ::Brick.relations.reject { |k, v| v.key?(:isView) && v[:isView] == true }.map(&:first).sort).empty?
19
- puts "No tables found in database #{ActiveRecord::Base.connection.current_database}."
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
- # Generate a list of tables that can be chosen
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
@@ -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 |tbl|
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.include?(v[:inverse_table]) &&
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).present?
78
+ end
79
+ ).present?
57
80
  seeds << "\n"
58
- fringe.each do |tbl|
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 = Object.const_get(class_name = relation[:class_name])).table_exists?
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
@@ -88,7 +112,7 @@ module Brick
88
112
  klass.order(*pkey_cols).each do |obj|
89
113
  unless has_rows
90
114
  has_rows = true
91
- seeds << " puts 'Seeding: #{class_name}'\n"
115
+ seeds << " puts 'Seeding: #{seed_model.klass.name}'\n"
92
116
  end
93
117
  is_empty = false
94
118
  pk_val = obj.send(pkey_cols.first)
@@ -108,9 +132,9 @@ module Brick
108
132
  data << "#{col}: #{val.inspect}"
109
133
  end
110
134
  end
111
- seeds << "#{tbl.gsub('.', '__')}_#{brick_escape(pk_val)} = #{class_name}.create(#{(fk_vals + data).join(', ')})\n"
135
+ seeds << "#{tbl.gsub('.', '__')}_#{brick_escape(pk_val)} = #{seed_model.klass.name}.create(#{(fk_vals + data).join(', ')})\n"
112
136
  end
113
- seeds << " # (Skipping #{class_name} as it has no rows)\n" unless has_rows
137
+ seeds << " # (Skipping #{seed_model.klass.name} as it has no rows)\n" unless has_rows
114
138
  File.open(seed_file_path, "w") { |f| f.write seeds }
115
139
  end
116
140
  done.concat(fringe)
@@ -118,7 +142,7 @@ module Brick
118
142
  end
119
143
  stuck_counts = Hash.new { |h, k| h[k] = 0 }
120
144
  chosen.each do |leftover|
121
- puts "Can't do #{leftover} because:\n #{stuck[leftover].map do |snag|
145
+ puts "Can't do #{leftover.klass.name} because:\n #{stuck[leftover.table_name].map do |snag|
122
146
  stuck_counts[snag.last[:inverse_table]] += 1
123
147
  snag.last[:assoc_name]
124
148
  end.join(', ')}"
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.156
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 00:00:00.000000000 Z
11
+ date: 2023-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord