brick 1.0.77 → 1.0.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e113470d473585f716cfa5b835f503448a0d160b60de20f884bf3ce2cc14c08
4
- data.tar.gz: 40064f980dd6893cb4e7e47861e00b184983d1a4bbc11b7cbd16fd6caddfab87
3
+ metadata.gz: eedc546e7e9693459cf228ee376650cdf53c0325e55d50ef24bbf46d421f8738
4
+ data.tar.gz: c4feda4b5db8334363d05f3418dc172f0dbf408c21192222008a5b9a174ae448
5
5
  SHA512:
6
- metadata.gz: 4219dfee478a845a949b1ac9b03d0a135504723a892b571ca068af70c2b58ca52025f5e1f2fedb6a97a5b2dcd97280c5f54132b854e285e6daf7bc95d0c2d357
7
- data.tar.gz: 67ae65b0de3801bbb2a1817d16b4023116ee7d41491b6d45927bd8197a8f800151f8ce79de34732a55f3dfcabbe301dc69b276899cfd544604d6a64e61220c99
6
+ metadata.gz: 0c9713099c057b9c9041121576a4f38d5949213926a47d597f88f4a9021ce14cd3df2a30c14b8b929b92ba4549ec4492bdfd8de13cb0a4f710e911c00eab67b6
7
+ data.tar.gz: 27ca4aa6d40e165aaca5b09cf5b2ce97737b26adc8424b1d25d81b9813100679e9e06a61279c4fd8b96d750a89a4c4cb8eafbfe2d425a4d4c6868644820f297d
@@ -258,7 +258,7 @@ module ActiveRecord
258
258
 
259
259
  def self._brick_index(mode = nil)
260
260
  tbl_parts = ((mode == :singular) ? table_name.singularize : table_name).split('.')
261
- tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.first == Apartment.default_schema
261
+ tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == Apartment.default_schema
262
262
  tbl_parts.unshift(::Brick.config.path_prefix) if ::Brick.config.path_prefix
263
263
  index = tbl_parts.map(&:underscore).join('_')
264
264
  # Rails applies an _index suffix to that route when the resource name is singular
@@ -555,7 +555,10 @@ module ActiveRecord
555
555
  tbl_name = "\"#{tbl_name}\"" if ::Brick.is_oracle && rel_dupe._arel_applied_aliases.include?(tbl_name)
556
556
  field_tbl_name = nil
557
557
  v1.map { |x| [translations[x[0..-2].map(&:to_s).join('.')], x.last] }.each_with_index do |sel_col, idx|
558
- # binding.pry if chains[sel_col.first].nil?
558
+ # unless chains[sel_col.first]
559
+ # puts 'You might have some bogus DSL in your brick.rb file'
560
+ # next
561
+ # end
559
562
  field_tbl_name = (field_tbl_names[v.first][sel_col.first] ||= shift_or_first(chains[sel_col.first])).split('.').last
560
563
  # If it's Oracle, quote any AREL aliases that had been applied
561
564
  field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe._arel_applied_aliases.include?(field_tbl_name)
@@ -606,19 +609,51 @@ module ActiveRecord
606
609
  end
607
610
  end
608
611
  # Add derived table JOIN for the has_many counts
612
+ nix = []
609
613
  klass._br_hm_counts.each do |k, hm|
610
- associative = nil
611
614
  count_column = if hm.options[:through]
612
- if (fk_col = (associative = klass._br_associatives&.[](hm.name))&.foreign_key)
613
- if hm.source_reflection.macro == :belongs_to # Traditional HMT using an associative table
614
- hm.foreign_key
615
- else # A HMT that goes HM -> HM, something like Categories -> Products -> LineItems
616
- hm.source_reflection.active_record.primary_key
617
- end
615
+ # Build the chain of JOINs going to the final destination HMT table
616
+ # (Usually just one JOIN, but could be many.)
617
+ hmt_assoc = hm
618
+ x = []
619
+ x.unshift(hmt_assoc) while hmt_assoc.options[:through] && (hmt_assoc = klass.reflect_on_association(hmt_assoc.options[:through]))
620
+ from_clause = +"#{x.first.table_name} br_t0"
621
+ fk_col = x.shift.foreign_key
622
+ link_back = [klass.primary_key] # %%% Inverse path back to the original object -- used to build out a link with a filter
623
+ idx = 0
624
+ bail_out = nil
625
+ x.map do |a|
626
+ from_clause << "\n LEFT OUTER JOIN #{a.table_name} br_t#{idx += 1} "
627
+ from_clause << if (src_ref = a.source_reflection).macro == :belongs_to
628
+ "ON br_t#{idx}.id = br_t#{idx - 1}.#{a.foreign_key}"
629
+ elsif src_ref.options[:as]
630
+ "ON br_t#{idx}.#{src_ref.type} = '#{src_ref.active_record.name}'" + # "polymorphable_type"
631
+ " AND br_t#{idx}.#{src_ref.foreign_key} = br_t#{idx - 1}.id"
632
+ elsif src_ref.options[:source_type]
633
+ print "Skipping #{hm.name} --HMT-> #{hm.source_reflection.name} as it uses source_type which is not supported"
634
+ nix << k
635
+ bail_out = true
636
+ break
637
+ else # Standard has_many
638
+ "ON br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.id"
639
+ end
640
+ link_back.unshift(a.source_reflection.name)
641
+ [a.table_name, a.foreign_key, a.source_reflection.macro]
642
+ end
643
+ next if bail_out
644
+ # count_column is determined from the originating HMT member
645
+ if (src_ref = hm.source_reflection).nil?
646
+ puts "*** Warning: Could not determine destination model for this HMT association in model #{klass.name}:\n has_many :#{hm.name}, through: :#{hm.options[:through]}"
647
+ nix << k
648
+ bail_out = true
649
+ elsif src_ref.macro == :belongs_to # Traditional HMT using an associative table
650
+ "br_t#{idx}.#{hm.foreign_key}"
651
+ else # A HMT that goes HM -> HM, something like Categories -> Products -> LineItems
652
+ "br_t#{idx}.#{src_ref.active_record.primary_key}"
618
653
  end
619
654
  else
620
- fk_col = hm.foreign_key
621
- poly_type = hm.inverse_of.foreign_type if hm.options.key?(:as)
655
+ fk_col = (inv = hm.inverse_of)&.foreign_key || hm.foreign_key
656
+ poly_type = inv.foreign_type if hm.options.key?(:as)
622
657
  pk = hm.klass.primary_key
623
658
  (pk.is_a?(Array) ? pk.first : pk) || '*'
624
659
  end
@@ -652,20 +687,38 @@ module ActiveRecord
652
687
  selects << poly_type
653
688
  on_clause << "#{tbl_alias}.#{poly_type} = '#{name}'"
654
689
  end
655
- hm_table_name = if is_mysql
656
- "`#{associative&.table_name || hm.klass.table_name}`"
657
- elsif is_postgres || is_mssql
658
- "\"#{(associative&.table_name || hm.klass.table_name).gsub('.', '"."')}\""
659
- else
660
- associative&.table_name || hm.klass.table_name
661
- end
690
+ unless from_clause
691
+ hm_table_name = if is_mysql
692
+ "`#{hm.table_name}`"
693
+ elsif is_postgres || is_mssql
694
+ "\"#{(hm.table_name).gsub('.', '"."')}\""
695
+ else
696
+ hm.table_name
697
+ end
698
+ end
662
699
  group_bys = ::Brick.is_oracle || is_mssql ? selects : (1..selects.length).to_a
663
700
  join_clause = "LEFT OUTER
664
- JOIN (SELECT #{selects.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}#{count_column
665
- }) AS c_t_ FROM #{hm_table_name} GROUP BY #{group_bys.join(', ')}) #{tbl_alias}"
701
+ JOIN (SELECT #{selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}#{count_column
702
+ }) AS c_t_ FROM #{from_clause || hm_table_name} GROUP BY #{group_bys.join(', ')}) #{tbl_alias}"
666
703
  joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
667
704
  end
668
- where!(wheres) unless wheres.empty?
705
+ while (n = nix.pop)
706
+ klass._br_hm_counts.delete(n)
707
+ end
708
+
709
+ unless wheres.empty?
710
+ # Rewrite the wheres to reference table and correlation names built out by AREL
711
+ wheres2 = wheres.each_with_object({}) do |v, s|
712
+ if (v_parts = v.first.split('.')).length == 1
713
+ s[v.first] = v.last
714
+ else
715
+ k1 = klass.reflect_on_association(v_parts.first)&.klass
716
+ tbl_name = (field_tbl_names[v_parts.first][k1] ||= shift_or_first(chains[k1])).split('.').last
717
+ s["#{tbl_name}.#{v_parts.last}"] = v.last
718
+ end
719
+ end
720
+ where!(wheres2)
721
+ end
669
722
  # Must parse the order_by and see if there are any symbols which refer to BT associations
670
723
  # or custom columns as they must be expanded to find the corresponding b_r_model__column
671
724
  # or br_cc_column naming for each.
@@ -805,12 +858,30 @@ Module.class_exec do
805
858
  end
806
859
  base_module = if self < ActiveRecord::Migration || !self.name
807
860
  brick_root || Object
808
- elsif (split_self_name || self.name.split('::')).length > 1
809
- return self._brick_const_missing(*args)
861
+ elsif (split_self_name || self.name.split('::')).length > 1 # Classic mode
862
+ begin
863
+ return self._brick_const_missing(*args)
864
+
865
+ rescue NameError # %%% Avoid the error "____ cannot be autoloaded from an anonymous class or module"
866
+ return self.const_get(args.first) if self.const_defined?(args.first)
867
+
868
+ # unless self == (prnt = (respond_to?(:parent) ? parent : module_parent))
869
+ unless self == Object
870
+ begin
871
+ return Object._brick_const_missing(*args)
872
+
873
+ rescue NameError
874
+ return Object.const_get(args.first) if Object.const_defined?(args.first)
875
+
876
+ end
877
+ end
878
+ end
879
+ Object
810
880
  else
811
881
  self
812
882
  end
813
- desired_classname = (self == Object) ? requested : "#{name}::#{requested}"
883
+ # puts "#{self.name} - #{args.first}"
884
+ desired_classname = (self == Object || !name) ? requested : "#{name}::#{requested}"
814
885
  if ((is_defined = self.const_defined?(args.first)) && (possible = self.const_get(args.first)) && possible.name == desired_classname) ||
815
886
  # Try to require the respective Ruby file
816
887
  ((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore) ||
@@ -822,9 +893,7 @@ Module.class_exec do
822
893
  # then return what we've found.
823
894
  (is_defined && !::Brick.is_eager_loading) # Used to also have: && possible != self
824
895
  if (!brick_root && (filename || possible.instance_of?(Class))) ||
825
- (possible.instance_of?(Module) &&
826
- ((possible.respond_to?(:module_parent) ? possible.module_parent : possible.parent) == self)
827
- ) ||
896
+ (possible.instance_of?(Module) && possible.module_parent == self) ||
828
897
  (possible.instance_of?(Class) && possible == self) # Are we simply searching for ourselves?
829
898
  return possible
830
899
  end
@@ -881,9 +950,21 @@ Module.class_exec do
881
950
  base_module._brick_const_missing(*args)
882
951
  # elsif base_module != Object
883
952
  # module_parent.const_missing(*args)
884
- else
885
- puts "MISSING! #{base_module.name} #{args.inspect} #{table_name}"
886
- base_module._brick_const_missing(*args)
953
+ elsif Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ...
954
+ (Rails::Autoloaders.respond_to?(:zeitwerk_enabled?) ? Rails::Autoloaders.zeitwerk_enabled? : true)
955
+ self._brick_const_missing(*args) # ... rely solely on Zeitwerk.
956
+ else # Classic mode
957
+ unless (found = base_module._brick_const_missing(*args))
958
+ puts "MISSING! #{base_module.name} #{args.inspect} #{table_name}"
959
+ end
960
+ found
961
+ end
962
+ end
963
+
964
+ # Support Rails < 6.0 which adds #parent instead of #module_parent
965
+ unless respond_to?(:module_parent)
966
+ def module_parent # Weirdly for Grape::API does NOT come in with the proper class, but some anonymous Class thing
967
+ parent
887
968
  end
888
969
  end
889
970
  end
@@ -940,9 +1021,7 @@ class Object
940
1021
  schema_name
941
1022
  else
942
1023
  matching = "#{schema_name}.#{matching}"
943
- # %%% Coming up with integers when tables are in schemas
944
- # ::Brick.db_schemas[schema_name] ||= self.const_get(schema_name.camelize.to_sym)
945
- self.const_get(schema_name.camelize)
1024
+ (::Brick.db_schemas[schema_name] || {})[:class] ||= self.const_get(schema_name.camelize.to_sym)
946
1025
  end
947
1026
  "#{schema_module&.name}::#{inheritable_name || model_name}"
948
1027
  end
@@ -952,10 +1031,10 @@ class Object
952
1031
 
953
1032
  # Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
954
1033
  if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
955
- unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.camelize}::")
956
- puts "Warning: Class name for a model that references table \"#{matching
957
- }\" should be \"#{ActiveSupport::Inflector.singularize(inheritable_name || model_name)}\"."
958
- end
1034
+ # unless ::Brick.config.sti_namespace_prefixes&.key?("::#{singular_table_name.camelize}::")
1035
+ # puts "Warning: Class name for a model that references table \"#{matching
1036
+ # }\" should be \"#{ActiveSupport::Inflector.singularize(inheritable_name || model_name)}\"."
1037
+ # end
959
1038
  return
960
1039
  end
961
1040
 
@@ -1309,7 +1388,7 @@ class Object
1309
1388
  ordering = params['_brick_order']&.split(',')&.map(&:to_sym) || Object.send(:default_ordering, table_name, pk)
1310
1389
  order_by, _ = model._brick_calculate_ordering(ordering, true) # Don't do the txt part
1311
1390
 
1312
- @_brick_params = (ar_relation = model.all).brick_select(params, (selects = []), nil,
1391
+ @_brick_params = (ar_relation = model.all).brick_select(params, (selects = []), order_by,
1313
1392
  translations = {},
1314
1393
  join_array = ::Brick::JoinArray.new)
1315
1394
  # %%% Add custom HM count columns
@@ -1393,7 +1472,7 @@ class Object
1393
1472
  end
1394
1473
 
1395
1474
  if pk.present?
1396
- # if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.include?(schema)
1475
+ # if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.key?(schema)
1397
1476
  # ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
1398
1477
  # end
1399
1478
 
@@ -1574,6 +1653,7 @@ module ActiveRecord::ConnectionHandling
1574
1653
  # return if ActiveRecord::Base.connection.current_database == 'postgres'
1575
1654
 
1576
1655
  initializer_loaded = false
1656
+ orig_schema = nil
1577
1657
  if (relations = ::Brick.relations).empty?
1578
1658
  # If there's schema things configured then we only expect our initializer to be named exactly this
1579
1659
  if File.exist?(brick_initializer = ::Rails.root.join('config/initializers/brick.rb'))
@@ -1583,7 +1663,10 @@ module ActiveRecord::ConnectionHandling
1583
1663
  # .default_schema are specified then we can work with non-tenanted models more appropriately
1584
1664
  if (apartment = Object.const_defined?('Apartment')) &&
1585
1665
  File.exist?(apartment_initializer = ::Rails.root.join('config/initializers/apartment.rb'))
1586
- load apartment_initializer
1666
+ unless @_apartment_loaded
1667
+ load apartment_initializer
1668
+ @_apartment_loaded = true
1669
+ end
1587
1670
  apartment_excluded = Apartment.excluded_models
1588
1671
  end
1589
1672
  # Only for Postgres (Doesn't work in sqlite3 or MySQL)
@@ -1607,12 +1690,14 @@ module ActiveRecord::ConnectionHandling
1607
1690
  [row['table_schema'], row['dt']]
1608
1691
  end
1609
1692
  # Remove any system schemas
1610
- s[row.first] = row.last unless ['information_schema', 'pg_catalog', 'pg_toast',
1611
- 'INFORMATION_SCHEMA', 'sys'].include?(row.first)
1693
+ s[row.first] = { dt: row.last } unless ['information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
1694
+ 'INFORMATION_SCHEMA', 'sys'].include?(row.first)
1612
1695
  end
1613
1696
  if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
1614
1697
  (sta = multitenancy[:schema_to_analyse]) != 'public') &&
1615
- ::Brick.db_schemas.include?(sta)
1698
+ ::Brick.db_schemas.key?(sta)
1699
+ # Take note of the current schema so we can go back to it at the end of all this
1700
+ orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
1616
1701
  ::Brick.default_schema = schema = sta
1617
1702
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1618
1703
  end
@@ -1622,7 +1707,7 @@ module ActiveRecord::ConnectionHandling
1622
1707
  # ActiveRecord::Base.connection.current_database will be something like "XEPDB1"
1623
1708
  ::Brick.default_schema = schema = ActiveRecord::Base.connection.raw_connection.username
1624
1709
  ::Brick.db_schemas = {}
1625
- ActiveRecord::Base.execute_sql("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] = nil }
1710
+ ActiveRecord::Base.execute_sql("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] = {} }
1626
1711
  when 'SQLite'
1627
1712
  sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
1628
1713
  p.name AS column_name, p.type AS data_type,
@@ -1641,10 +1726,12 @@ module ActiveRecord::ConnectionHandling
1641
1726
  if (possible_schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
1642
1727
  if ::Brick.db_schemas.key?(possible_schema)
1643
1728
  ::Brick.default_schema = schema = possible_schema
1729
+ orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
1644
1730
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1645
1731
  elsif Rails.env == 'test' # When testing, just find the most recently-created schema
1646
- ::Brick.default_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last <=> a.last }.first.first
1647
- puts "While running tests, had noticed that in the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\" which does not exist. Reverting to instead use the most recently-created schema, #{schema}."
1732
+ ::Brick.default_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
1733
+ puts "While running tests, had noticed in the brick.rb initializer that the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\" which does not exist. Reading table structure from the most recently-created schema, #{schema}."
1734
+ orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
1648
1735
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1649
1736
  else
1650
1737
  puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\". This schema does not exist. ***"
@@ -1848,6 +1935,11 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
1848
1935
  v[:class_name] = (schema_names + [rel_name.last.singularize]).map(&:camelize).join('::')
1849
1936
  end
1850
1937
  ::Brick.load_additional_references if initializer_loaded
1938
+
1939
+ if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'heroku_ext']).first)
1940
+ puts "Now switching back to \"#{orig_schema}\" schema."
1941
+ ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", orig_schema)
1942
+ end
1851
1943
  end
1852
1944
 
1853
1945
  def retrieve_schema_and_tables(sql = nil, is_postgres = nil, is_mssql = nil, schema = nil)
@@ -2134,7 +2226,12 @@ module Brick
2134
2226
  end
2135
2227
  end
2136
2228
  end
2137
- ::Brick.relations.keys.map { |v| [(model = models[v])&.last, model&.last&.table_name, migrations&.fetch(v, nil), model&.first] }
2229
+ ::Brick.relations.keys.map do |v|
2230
+ tbl_parts = v.split('.')
2231
+ tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == Apartment.default_schema
2232
+ res = tbl_parts.join('.')
2233
+ [v, (model = models[res])&.last&.table_name, migrations&.fetch(res, nil), model&.first]
2234
+ end
2138
2235
  end
2139
2236
 
2140
2237
  def ensure_unique(name, *sources)
@@ -125,8 +125,8 @@ module Brick
125
125
  "H#{hm_assoc.macro == :has_one ? 'O' : 'M'}#{'T' if hm_assoc.options[:through]}",
126
126
  (assoc_name = hm.first)]
127
127
  hm_fk_name = if (through = hm_assoc.options[:through])
128
- # %%% How to deal with weird self_ref type has_many -> has_one polymorphic stuff?
129
- # (or perhaps we don't need to!)
128
+ next unless @_brick_model._br_hm_counts.key?(hm_assoc.name) # Skip any weird HMTs that go through a HM with a source_type
129
+
130
130
  next unless @_brick_model.instance_methods.include?(through) &&
131
131
  (associative = @_brick_model._br_associatives.fetch(hm.first, nil))
132
132
 
@@ -353,11 +353,8 @@ def hide_bcrypt(val, max_len = 200)
353
353
  '(hidden)'
354
354
  else
355
355
  if val.is_a?(String)
356
- if val.length > max_len
357
- val = val[0...max_len]
358
- val << '...'
359
- end
360
- val.force_encoding('UTF-8') unless val.encoding.name == 'UTF-8'
356
+ val = \"#\{val[0...max_len]}...\" if val.length > max_len
357
+ val = val.dup.force_encoding('UTF-8') unless val.encoding.name == 'UTF-8'
361
358
  end
362
359
  val
363
360
  end
@@ -372,7 +369,7 @@ def display_value(col_type, val)
372
369
  if @is_mysql || @is_mssql
373
370
  # MySQL's \"Internal Geometry Format\" and MSSQL's Geography are like WKB, but with an initial 4 bytes that indicates the SRID.
374
371
  if (srid = val&.[](0..3)&.unpack('I'))
375
- val = val.force_encoding('BINARY')[4..-1].bytes
372
+ val = val.dup.force_encoding('BINARY')[4..-1].bytes
376
373
 
377
374
  # MSSQL spatial bitwise flags, often 0C for a point:
378
375
  # xxxx xxx1 = HasZValues
@@ -409,9 +406,8 @@ def display_value(col_type, val)
409
406
  end
410
407
  end
411
408
  # Accommodate composite primary keys that include strings with forward-slash characters
412
- def slashify(val)
413
- val = [val] unless val.is_a?(Array)
414
- val.map { |val_part| val_part.is_a?(String) ? val_part.gsub('/', '^^sl^^') : val_part }
409
+ def slashify(*vals)
410
+ vals.map { |val_part| val_part.is_a?(String) ? val_part.gsub('/', '^^sl^^') : val_part }
415
411
  end
416
412
  callbacks = {} %>"
417
413
 
@@ -673,7 +669,7 @@ erDiagram
673
669
  inline = case args.first
674
670
  when 'index'
675
671
  obj_pk = if pk&.is_a?(Array) # Composite primary key?
676
- "[#{pk.map { |pk_part| "#{obj_name}.#{pk_part}" }.join(', ')}]" unless pk.empty?
672
+ "#{pk.map { |pk_part| "#{obj_name}.#{pk_part}" }.join(', ')}" unless pk.empty?
677
673
  elsif pk
678
674
  "#{obj_name}.#{pk}"
679
675
  end
@@ -787,7 +783,7 @@ erDiagram
787
783
  origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
788
784
  if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| fk[:fk] == key_parts.last }) &&
789
785
  (obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
790
- <h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination._brick_index\}_path\".to_sym, id) %></h3><%
786
+ <h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination._brick_index(:singular)\}_path\".to_sym, id) %></h3><%
791
787
  end
792
788
  end %>
793
789
  (<%= link_to 'See all #{model_name.split('::').last.pluralize}', #{@_brick_model._brick_index}_path %>)
@@ -817,6 +813,24 @@ erDiagram
817
813
  hms_keys << (assoc_name = (assoc = hm.first).name.to_s)
818
814
  "#{assoc_name.inspect} => [#{(assoc.options[:through] && !assoc.through_reflection).inspect}, #{assoc.klass.name}, #{hm[1].inspect}, #{hm[2].inspect}]"
819
815
  end.join(', ')}}
816
+ # If the resource is missing, has the user simply created an inappropriately pluralised name for a table?
817
+ @#{table_name} ||= if dym_list = instance_variables.reject do |entry|
818
+ entry.to_s.start_with?('@_') ||
819
+ ['@cache_hit', '@marked_for_same_origin_verification', '@view_renderer', '@view_flow', '@output_buffer', '@virtual_path'].include?(entry.to_s)
820
+ end
821
+ msg = \"Can't find resource \\\"#{table_name}\\\".\"
822
+ # Can't be sure otherwise of what is up, so check DidYouMean and offer a suggestion.
823
+ if (dym = DidYouMean::SpellChecker.new(dictionary: dym_list).correct('@#{table_name}')).present?
824
+ msg << \"\nIf you meant \\\"#\{found_dym = dym.first[1..-1]}\\\" then to avoid this message add this entry into inflections.rb:\n\"
825
+ msg << \" inflect.singular('#\{found_dym}', '#{obj_name}')\"
826
+ puts
827
+ puts \"WARNING: #\{msg}\"
828
+ puts
829
+ @#{table_name} = instance_variable_get(dym.first.to_sym)
830
+ else
831
+ raise ActiveRecord::RecordNotFound.new(msg)
832
+ end
833
+ end
820
834
  col_keys = @#{table_name}.columns.each_with_object([]) do |col, s|
821
835
  col_name = col.name
822
836
  next if @_brick_incl&.exclude?(col_name) ||
@@ -940,7 +954,11 @@ erDiagram
940
954
  @resources.each do |r|
941
955
  %>
942
956
  <tr>
943
- <td><%= link_to(r[0], r[0] && send(\"#\{r[0]&._brick_index}_path\".to_sym)) %></td>
957
+ <td><%= begin
958
+ kls = Object.const_get(::Brick.relations[r[0]].fetch(:class_name, nil))
959
+ rescue
960
+ end
961
+ kls ? link_to(r[0], send(\"#\{kls._brick_index}_path\".to_sym)) : r[0] %></td>
944
962
  <td<%= if r[1]
945
963
  ' class=\"orphan\"' unless ::Brick.relations.key?(r[1])
946
964
  else
@@ -1277,6 +1295,9 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
1277
1295
  }
1278
1296
  });
1279
1297
  </script>"
1298
+ # puts "==============="
1299
+ # puts inline
1300
+ # puts "==============="
1280
1301
  # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
1281
1302
  keys = options.has_key?(:locals) ? options[:locals].keys : []
1282
1303
  handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 77
8
+ TINY = 79
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
@@ -126,10 +126,15 @@ module Brick
126
126
  attr_accessor :default_schema, :db_schemas, :routes_done, :is_oracle, :is_eager_loading
127
127
 
128
128
  def set_db_schema(params = nil)
129
- schema = (params ? params['_brick_schema'] : ::Brick.default_schema) || 'public'
130
- if schema && ::Brick.db_schemas&.include?(schema)
129
+ schema = (params ? params['_brick_schema'] : ::Brick.default_schema)
130
+ if schema && ::Brick.db_schemas&.key?(schema)
131
131
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
132
132
  schema
133
+ elsif ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
134
+ # Just return the current schema
135
+ orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
136
+ # ::Brick.apartment_multitenant && tbl_parts.first == Apartment.default_schema
137
+ (orig_schema - ['pg_catalog']).first
133
138
  end
134
139
  end
135
140
 
@@ -174,13 +179,21 @@ module Brick
174
179
 
175
180
  case a.macro
176
181
  when :belongs_to
177
- s.first[a.foreign_key] = if a.polymorphic?
178
- primary_tables = relations[model.table_name][:fks].find { |_k, fk| fk[:assoc_name] == a.name.to_s }&.last&.fetch(:inverse_table, [])
179
- models = primary_tables&.map { |table| table.singularize.camelize.constantize }
180
- [a.name, models, true]
181
- else
182
- [a.name, a.klass]
183
- end
182
+ if a.polymorphic?
183
+ rel_poly_bt = relations[model.table_name][:fks].find { |_k, fk| fk[:assoc_name] == a.name.to_s }
184
+ if (primary_tables = rel_poly_bt&.last&.fetch(:inverse_table, [])).is_a?(Array)
185
+ models = primary_tables&.map { |table| table.singularize.camelize.constantize }
186
+ s.first[a.foreign_key] = [a.name, models, true]
187
+ else
188
+ # This will come up when using Devise invitable when invited_by_class_name is not
189
+ # specified because in that circumstance it adds a polymorphic :invited_by association,
190
+ # along with appropriate invited_by_type and invited_by_id columns.
191
+ puts "Missing any real indication as to which models \"has_many\" this polymorphic BT in model #{a.active_record.name}:"
192
+ puts " belongs_to :#{a.name}, polymorphic: true"
193
+ end
194
+ else
195
+ s.first[a.foreign_key] = [a.name, a.klass]
196
+ end
184
197
  when :has_many, :has_one # This gets has_many as well as has_many :through
185
198
  # %%% weed out ones that don't have an available model to reference
186
199
  s.last[a.name] = a
@@ -435,7 +448,7 @@ module Brick
435
448
  end
436
449
  end
437
450
  if (polys = ::Brick.config.polymorphics)
438
- if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.include?(schema)
451
+ if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.key?(schema)
439
452
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
440
453
  end
441
454
  missing_stis = {}
@@ -525,9 +538,9 @@ In config/initializers/brick.rb appropriate entries would look something like:
525
538
  abstract_ar_bases
526
539
  end
527
540
 
528
- def display_classes(rels, max_length)
541
+ def display_classes(prefix, rels, max_length)
529
542
  rels.sort.each do |rel|
530
- puts "#{rel.first}#{' ' * (max_length - rel.first.length)} /#{rel.last}"
543
+ puts "#{rel.first}#{' ' * (max_length - rel.first.length)} /#{prefix}#{rel.last}"
531
544
  end
532
545
  puts "\n"
533
546
  end
@@ -556,13 +569,13 @@ In config/initializers/brick.rb appropriate entries would look something like:
556
569
 
557
570
  # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
558
571
  # If auto-controllers and auto-models are both enabled then this makes sense:
572
+ controller_prefix = (::Brick.config.path_prefix ? "#{::Brick.config.path_prefix}/" : '')
559
573
  ::Brick.relations.each do |k, v|
560
574
  unless !(controller_name = v.fetch(:resource, nil)&.pluralize) || existing_controllers.key?(controller_name)
561
575
  options = {}
562
576
  options[:only] = [:index, :show] if v.key?(:isView)
563
577
  # First do the API routes
564
578
  full_resource = nil
565
- controller_prefix = (::Brick.config.path_prefix ? "#{::Brick.config.path_prefix}/" : '')
566
579
  if (schema_name = v.fetch(:schema, nil))
567
580
  full_resource = "#{schema_name}/#{v[:resource]}"
568
581
  send(:get, "#{::Brick.api_root}#{full_resource}", { to: "#{controller_prefix}#{schema_name}/#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
@@ -595,19 +608,19 @@ In config/initializers/brick.rb appropriate entries would look something like:
595
608
  if tables.present?
596
609
  puts "Classes that can be built from tables:#{' ' * (table_class_length - 38)} Path:"
597
610
  puts "======================================#{' ' * (table_class_length - 38)} ====="
598
- ::Brick.display_classes(tables, table_class_length)
611
+ ::Brick.display_classes(controller_prefix, tables, table_class_length)
599
612
  end
600
613
  if views.present?
601
614
  puts "Classes that can be built from views:#{' ' * (view_class_length - 37)} Path:"
602
615
  puts "=====================================#{' ' * (view_class_length - 37)} ====="
603
- ::Brick.display_classes(views, view_class_length)
616
+ ::Brick.display_classes(controller_prefix, views, view_class_length)
604
617
  end
605
618
 
606
619
  if ::Brick.config.add_status && instance_variable_get(:@set).named_routes.names.exclude?(:brick_status)
607
- get('/brick_status', to: 'brick_gem#status', as: 'brick_status')
620
+ get("/#{controller_prefix}brick_status", to: 'brick_gem#status', as: 'brick_status')
608
621
  end
609
622
  if ::Brick.config.add_orphans && instance_variable_get(:@set).named_routes.names.exclude?(:brick_orphans)
610
- get('/brick_orphans', to: 'brick_gem#orphans', as: 'brick_orphans')
623
+ get("/#{controller_prefix}brick_orphans", to: 'brick_gem#orphans', as: 'brick_orphans')
611
624
  end
612
625
  if Object.const_defined?('Rswag::Ui') && doc_endpoint = Rswag::Ui.config.config_object[:urls].last
613
626
  # Serves JSON swagger info from a path such as '/api-docs/v1/swagger.json'
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.77
4
+ version: 1.0.79
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-10 00:00:00.000000000 Z
11
+ date: 2022-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord