brick 1.0.190 → 1.0.192

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: 3e7a12940f4ac57eea3c53bb3b437472f90bbaa857bd6611d3e871463add46f2
4
- data.tar.gz: 8afb6e47cebd99a9b4172aa82d9447b71dd66e6df750249063b66b9b9ba939a9
3
+ metadata.gz: 148c8f46702d06322322c04487a1045e6d7a3927611394a84ffe138be78adb5e
4
+ data.tar.gz: 716bfaae50dc99809bc3aee4eecf5ff1c44325bca54271db225a765cdd97fb56
5
5
  SHA512:
6
- metadata.gz: d5379ddb6c61675b5606730a771a3f95638829b8359b42fd78c0509836635a93d36b787b09f21a0b9321623c917a149508b643a68ff4474baf7b40739ad5eeed
7
- data.tar.gz: 40b6c6dc4fd31b10632fafb572608ec1c6473b6efa1dab346f1c82fc4f9daa1faba6d3675b336a780291595e25ace20358a95205a9284e7c42ddf450efe1d0a4
6
+ metadata.gz: d97d94ed8d951718b34d1fc0d3ee8d023b3765bafc759fd41161155c927014ddec54a67e371363bbb17b18a614f4be16862d9cb51be313438050a3ce6e3356a2
7
+ data.tar.gz: efbd8b062eccd5d5441aa8e9fe6162bce076e188761f5c7877f9ca454899fc13873fa6cdf4db21cd2154e24c824487f56a085f41402432bd99486e17213e7f00
data/lib/brick/config.rb CHANGED
@@ -399,6 +399,14 @@ module Brick
399
399
  @mutex.synchronize { @not_nullables = columns }
400
400
  end
401
401
 
402
+ def omit_empty_tables_in_dropdown
403
+ @mutex.synchronize { @omit_empty_tables_in_dropdown }
404
+ end
405
+
406
+ def omit_empty_tables_in_dropdown=(field_set)
407
+ @mutex.synchronize { @omit_empty_tables_in_dropdown = field_set }
408
+ end
409
+
402
410
  def always_load_fields
403
411
  @mutex.synchronize { @always_load_fields || {} }
404
412
  end
@@ -82,8 +82,10 @@ module ActiveRecord
82
82
 
83
83
  def json_column?(col)
84
84
  col.type == :json || ::Brick.config.json_columns[table_name]&.include?(col.name) ||
85
- (respond_to?(:attribute_types) && (attr_types = attribute_types[col.name]).respond_to?(:coder) &&
86
- (attr_types.coder.is_a?(Class) ? attr_types.coder : attr_types.coder&.class)&.name&.end_with?('JSON'))
85
+ (
86
+ respond_to?(:attribute_types) && (attr_types = attribute_types[col.name]).respond_to?(:coder) &&
87
+ (attr_types.coder.is_a?(Class) ? attr_types.coder : attr_types.coder&.class)&.name&.end_with?('JSON')
88
+ )
87
89
  end
88
90
 
89
91
  def brick_foreign_type(assoc)
@@ -284,7 +286,10 @@ module ActiveRecord
284
286
  end
285
287
  this_obj&.to_s || ''
286
288
  end
287
- is_brackets_have_content = true unless datum.blank?
289
+ begin
290
+ is_brackets_have_content = true unless datum.blank?
291
+ rescue
292
+ end
288
293
  output << (datum || '')
289
294
  bracket_name = nil
290
295
  else
@@ -329,27 +334,10 @@ module ActiveRecord
329
334
  end
330
335
 
331
336
  # Providing a relation object allows auto-modules built from table name prefixes to work
332
- def self._brick_index(mode = nil, separator = '_', relation = nil)
337
+ def self._brick_index(mode = nil, separator = nil, relation = nil)
333
338
  return if abstract_class?
334
339
 
335
- tbl_parts = ((mode == :singular) ? table_name.singularize : table_name).split('.')
336
- tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
337
- if (aps = relation&.fetch(:auto_prefixed_schema, nil)) && tbl_parts.last.start_with?(aps)
338
- last_part = tbl_parts.last[aps.length..-1]
339
- aps = aps[0..-2] if aps[-1] == '_'
340
- tbl_parts[-1] = aps
341
- tbl_parts << last_part
342
- end
343
- path_prefix = []
344
- if ::Brick.config.path_prefix
345
- tbl_parts.unshift(::Brick.config.path_prefix)
346
- path_prefix << ::Brick.config.path_prefix
347
- end
348
- index = tbl_parts.map(&:underscore).join(separator)
349
- # Rails applies an _index suffix to that route when the resource name isn't something plural
350
- index << '_index' if mode != :singular && separator == '_' &&
351
- index == (path_prefix + [name&.underscore&.tr('/', '_') || '_']).join(separator)
352
- index
340
+ ::Brick._brick_index(table_name, mode, separator, relation)
353
341
  end
354
342
 
355
343
  def self.brick_import_template
@@ -686,7 +674,7 @@ module ActiveRecord
686
674
  (cust_col_override || klass._br_cust_cols).each do |k, cc|
687
675
  if rel_dupe.respond_to?(k) # Name already taken?
688
676
  # %%% Use ensure_unique here in this kind of fashion:
689
- # cnstr_name = ensure_unique(+"(brick) #{for_tbl}_#{pri_tbl}", bts, hms)
677
+ # cnstr_name = ensure_unique(+"(brick) #{for_tbl}_#{pri_tbl}", nil, bts, hms)
690
678
  # binding.pry
691
679
  next
692
680
  end
@@ -806,6 +794,7 @@ module ActiveRecord
806
794
 
807
795
  # Add derived table JOIN for the has_many counts
808
796
  nix = []
797
+ previous = []
809
798
  klass._br_hm_counts.each do |k, hm|
810
799
  count_column = if hm.options[:through]
811
800
  # Build the chain of JOINs going to the final destination HMT table
@@ -832,7 +821,7 @@ module ActiveRecord
832
821
  through_sources.push(this_hm = src_ref.active_record.reflect_on_association(thr))
833
822
  end
834
823
  through_sources.push(src_ref) unless src_ref.belongs_to?
835
- from_clause = +"#{through_sources.first.table_name} br_t0"
824
+ from_clause = +"#{_br_quoted_name(through_sources.first.table_name)} br_t0"
836
825
  fk_col = through_sources.shift.foreign_key
837
826
 
838
827
  idx = 0
@@ -900,7 +889,7 @@ module ActiveRecord
900
889
  next
901
890
  end
902
891
 
903
- tbl_alias = "b_r_#{hm.name}"
892
+ tbl_alias = unique63("b_r_#{hm.name}", previous)
904
893
  on_clause = []
905
894
  hm_selects = if fk_col.is_a?(Array) # Composite key?
906
895
  fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{tbl_alias}.#{fk_col_part} = #{pri_tbl.table_name}.#{pri_key[idx]}" }
@@ -1105,6 +1094,19 @@ Might want to add this in your brick.rb:
1105
1094
  def shift_or_first(ary)
1106
1095
  ary.length > 1 ? ary.shift : ary.first
1107
1096
  end
1097
+
1098
+ def unique63(name, previous)
1099
+ name = name[0..62] if name.length > 63
1100
+ unique_num = 1
1101
+ loop do
1102
+ break unless previous.include?(name)
1103
+
1104
+ unique_suffix = "_#{unique_num += 1}"
1105
+ name = "#{name[0..name.length - unique_suffix.length - 1]}#{unique_suffix}"
1106
+ end
1107
+ previous << name
1108
+ name
1109
+ end
1108
1110
  end
1109
1111
 
1110
1112
  module Inheritance
@@ -1360,7 +1362,7 @@ end
1360
1362
  end
1361
1363
  rescue NameError # If the const_get for the model has failed...
1362
1364
  skip_controller = true
1363
- # ... then just fall through and allow it to fail when trying to loading the ____Controller class normally.
1365
+ # ... then just fall through and allow it to fail when trying to load the ____Controller class normally.
1364
1366
  end
1365
1367
  end
1366
1368
  unless skip_controller
@@ -1715,7 +1717,8 @@ class Object
1715
1717
  # options[:class_name] = hm.first[:inverse_table].singularize.camelize
1716
1718
  # options[:foreign_key] = hm.first[:fk].to_sym
1717
1719
  far_assoc = relations[hm.first[:inverse_table]][:fks].find { |_k, v| v[:assoc_name] == hm[1] }
1718
- options[:class_name] = ::Brick.namify(far_assoc.last[:inverse_table], :underscore).camelize
1720
+ # Was: ::Brick.namify(far_assoc.last[:inverse_table], :underscore).camelize
1721
+ options[:class_name] = relations[far_assoc.last[:inverse_table]][:class_name]
1719
1722
  options[:foreign_key] = far_assoc.last[:fk].to_sym
1720
1723
  end
1721
1724
  options[:source] ||= hm[1].to_sym unless hmt_name.singularize == hm[1]
@@ -1744,7 +1747,7 @@ class Object
1744
1747
  options[:optional] = true if assoc.key?(:optional)
1745
1748
  if assoc.key?(:polymorphic) ||
1746
1749
  # If a polymorphic association is missing but could be established then go ahead and put it into place.
1747
- relations[assoc[:inverse_table]][:class_name].constantize.reflect_on_all_associations.find { |inv_assoc| !inv_assoc.belongs_to? && inv_assoc.options[:as].to_s == assoc[:assoc_name] }
1750
+ relations.fetch(assoc[:inverse_table], nil)&.fetch(:class_name, nil)&.constantize&.reflect_on_all_associations&.find { |inv_assoc| !inv_assoc.belongs_to? && inv_assoc.options[:as].to_s == assoc[:assoc_name] }
1748
1751
  assoc[:polymorphic] ||= true
1749
1752
  options[:polymorphic] = true
1750
1753
  else
@@ -1802,9 +1805,9 @@ class Object
1802
1805
  ::Brick.config.schema_behavior[:multitenant] && singular_table_parts.first == 'public'
1803
1806
  singular_table_parts.shift
1804
1807
  end
1805
- options[:class_name] = "::#{assoc[:primary_class]&.name ||
1806
- singular_table_parts.map { |p| ::Brick.namify(p, :underscore).camelize}.join('::')
1807
- }" if need_class_name
1808
+ if need_class_name
1809
+ options[:class_name] = "::#{assoc[:primary_class]&.name || ::Brick.relations[inverse_table][:class_name]}"
1810
+ end
1808
1811
  if need_fk # Funky foreign key?
1809
1812
  options_fk_key = :foreign_key
1810
1813
  if assoc[:fk].is_a?(Array)
@@ -2559,6 +2562,8 @@ class Object
2559
2562
  assoc_name = assoc_parts.join('.')
2560
2563
  else
2561
2564
  class_name_parts = ::Brick.namify(hm_assoc[:inverse_table], :underscore).split('.')
2565
+ last_idx = class_name_parts.length - 1
2566
+ class_name_parts[last_idx] = class_name_parts[last_idx].singularize
2562
2567
  real_name = class_name_parts.map(&:camelize).join('::')
2563
2568
  needs_class = (real_name != hm_assoc[:inverse_table].camelize)
2564
2569
  end
@@ -2645,6 +2650,10 @@ end.class_exec do
2645
2650
  orig_schema = nil
2646
2651
  if (relations = ::Brick.relations).keys == [:db_name]
2647
2652
  ::Brick.remove_instance_variable(:@_additional_references_loaded) if ::Brick.instance_variable_defined?(:@_additional_references_loaded)
2653
+
2654
+ # --------------------------------------------
2655
+ # 1. Load three initializers early
2656
+ # (inflectsions.rb, brick.rb, apartment.rb)
2648
2657
  # Very first thing, load inflections since we'll be using .pluralize and .singularize on table and model names
2649
2658
  if File.exist?(inflections = ::Rails.root&.join('config/initializers/inflections.rb') || '')
2650
2659
  load inflections
@@ -2695,6 +2704,8 @@ end.class_exec do
2695
2704
  # Only for Postgres (Doesn't work in sqlite3 or MySQL)
2696
2705
  # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
2697
2706
 
2707
+ # ---------------------------
2708
+ # 2. Figure out schema things
2698
2709
  is_postgres = nil
2699
2710
  is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
2700
2711
  case ActiveRecord::Base.connection.adapter_name
@@ -2767,6 +2778,8 @@ end.class_exec do
2767
2778
 
2768
2779
  ::Brick.db_schemas ||= {}
2769
2780
 
2781
+ # ---------------------
2782
+ # 3. Tables and columns
2770
2783
  # %%% Retrieve internal ActiveRecord table names like this:
2771
2784
  # ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
2772
2785
  # For if it's not SQLite -- so this is the Postgres and MySQL version
@@ -2896,20 +2909,34 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2896
2909
  # end
2897
2910
  # end
2898
2911
  # schema = ::Brick.default_schema # Reset back for this next round of fun
2912
+
2913
+ # ---------------------------------------------
2914
+ # 4. Foreign key info
2915
+ # (done in two parts which get JOINed together in Ruby code)
2899
2916
  kcus = nil
2917
+ entry_type = nil
2900
2918
  case ActiveRecord::Base.connection.adapter_name
2901
2919
  when 'PostgreSQL', 'Mysql2', 'Trilogy', 'SQLServer'
2902
- # All KCUs -- use this to virtually JOIN against fk_references in Ruby code
2920
+ # Part 1 -- all KCUs
2903
2921
  sql = "SELECT CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, ORDINAL_POSITION,
2904
2922
  TABLE_NAME, COLUMN_NAME
2905
2923
  FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE#{"
2906
- WHERE CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }"
2924
+ WHERE CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }#{"
2925
+ WHERE CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql
2926
+ }"
2907
2927
  kcus = ActiveRecord::Base.execute_sql(sql).each_with_object({}) do |v, s|
2908
- key = "#{v['constraint_name']}.#{v['constraint_schema']}.#{v['constraint_catalog']}.#{v['ordinal_position']}"
2909
- key << ".#{v['table_name']}.#{v['column_name']}" unless is_postgres || is_mssql
2910
- s[key] = [v['constraint_schema'], v['table_name']]
2928
+ if (entry_type ||= v.is_a?(Array) ? :array : :hash) == :hash
2929
+ key = "#{v['constraint_name']}.#{v['constraint_schema']}.#{v['constraint_catalog']}.#{v['ordinal_position']}"
2930
+ key << ".#{v['table_name']}.#{v['column_name']}" unless is_postgres || is_mssql
2931
+ s[key] = [v['constraint_schema'], v['table_name']]
2932
+ else # Array
2933
+ key = "#{v[2]}.#{v[1]}.#{v[0]}.#{v[3]}"
2934
+ key << ".#{v[4]}.#{v[5]}" unless is_postgres || is_mssql
2935
+ s[key] = [v[1], v[4]]
2936
+ end
2911
2937
  end
2912
2938
 
2939
+ # Part 2 -- fk_references
2913
2940
  sql = "SELECT kcu.CONSTRAINT_SCHEMA, kcu.TABLE_NAME, kcu.COLUMN_NAME,
2914
2941
  #{# These will get filled in with real values (effectively doing the JOIN in Ruby)
2915
2942
  is_postgres || is_mssql ? 'NULL as primary_schema, NULL as primary_table' :
@@ -2921,7 +2948,8 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2921
2948
  ON kcu.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
2922
2949
  AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
2923
2950
  AND kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME#{"
2924
- WHERE kcu.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }"
2951
+ WHERE kcu.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema}#{"
2952
+ WHERE kcu.CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql}"
2925
2953
  fk_references = ActiveRecord::Base.execute_sql(sql)
2926
2954
  when 'SQLite'
2927
2955
  sql = "SELECT NULL AS constraint_schema, m.name, fkl.\"from\", NULL AS primary_schema, fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
@@ -2949,11 +2977,13 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
2949
2977
  end
2950
2978
  ::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
2951
2979
  # ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
2952
- ::Brick.default_schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
2980
+ ::Brick.default_schema ||= 'public' if is_postgres
2953
2981
  fk_references&.each do |fk|
2954
2982
  fk = fk.values unless fk.is_a?(Array)
2955
- # Virtually JOIN against fk_references in order to change out the primary schema and primary table
2956
- if (kcu = kcus&.fetch("#{fk[6]}.#{fk[7]}.#{fk[8]}.#{fk[9]}", nil))
2983
+ # Virtually JOIN KCUs to fk_references in order to fill in the primary schema and primary table
2984
+ kcu_key = "#{fk[6]}.#{fk[7]}.#{fk[8]}.#{fk[9]}"
2985
+ kcu_key << ".#{fk[3]}.#{fk[4]}" unless is_postgres || is_mssql
2986
+ if (kcu = kcus&.fetch(kcu_key, nil))
2957
2987
  fk[3] = kcu[0]
2958
2988
  fk[4] = kcu[1]
2959
2989
  end
@@ -3000,18 +3030,86 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
3000
3030
  &.find { |k1, _v1| singular.start_with?(k1) && singular.length > k1.length }
3001
3031
  ).present?
3002
3032
  v[:auto_prefixed_schema] = tnp.first
3003
- v[:resource] = rel_name.last[(tnp_length = tnp.first.length)..-1]
3004
- [tnp.last, singular[tnp_length..-1]]
3033
+ # v[:resource] = rel_name.last[tnp.first.length..-1]
3034
+ [tnp.last, singular[tnp.first.length..-1]]
3005
3035
  else
3006
- v[:resource] = rel_name.last
3036
+ # v[:resource] = rel_name.last
3007
3037
  [singular]
3008
3038
  end
3009
- v[:class_name] = (schema_names + name_parts).map { |p| ::Brick.namify(p, :underscore).camelize }.join('::')
3039
+ proposed_name_parts = (schema_names + name_parts).map { |p| ::Brick.namify(p, :underscore).camelize }
3040
+ # Find out if the proposed name leads to a module or class that already exists and is not an AR class
3041
+ colliding_thing = nil
3042
+ loop do
3043
+ klass = Object
3044
+ proposed_name_parts.each do |part|
3045
+ if klass.const_defined?(part)
3046
+ klass = klass.const_get(part)
3047
+ else
3048
+ klass = nil
3049
+ break
3050
+ end
3051
+ end
3052
+ break if !klass || (klass < ActiveRecord::Base) # Break if all good -- no conflicts
3053
+
3054
+ # Find a unique name since there's already something that's non-AR with that same name
3055
+ last_idx = proposed_name_parts.length - 1
3056
+ proposed_name_parts[last_idx] = ::Brick.ensure_unique(proposed_name_parts[last_idx], 'X')
3057
+ colliding_thing ||= klass
3058
+ end
3059
+ v[:class_name] = proposed_name_parts.join('::')
3060
+ # Was: v[:resource] = v[:class_name].underscore.tr('/', '.').pluralize
3061
+ v[:resource] = proposed_name_parts.last.underscore.pluralize
3062
+ if colliding_thing
3063
+ message_start = if colliding_thing.is_a?(Module) && Object.const_defined?(:Rails) &&
3064
+ colliding_thing.constants.find { |c| colliding_thing.const_get(c) < Rails::Application }
3065
+ "The module for the Rails application itself, \"#{colliding_thing.name}\","
3066
+ else
3067
+ "Non-AR #{colliding_thing.class.name.downcase} \"#{colliding_thing.name}\""
3068
+ end
3069
+ puts "WARNING: #{message_start} already exists.\n Will set up to auto-create model #{v[:class_name]} for table #{k}."
3070
+ end
3010
3071
  # Track anything that's out-of-the-ordinary
3011
3072
  table_name_lookup[v[:class_name]] = k unless v[:class_name].underscore.pluralize == k
3012
3073
  end
3013
3074
  ::Brick.load_additional_references if ::Brick.initializer_loaded
3014
3075
 
3076
+ if is_postgres
3077
+ params = []
3078
+ ActiveRecord::Base.execute_sql("-- inherited and partitioned tables counts
3079
+ SELECT n.nspname, parent.relname,
3080
+ ((SUM(child.reltuples::float) / greatest(SUM(child.relpages), 1))) *
3081
+ (SUM(pg_relation_size(child.oid))::float / (current_setting('block_size')::float))::integer AS rowcount
3082
+ FROM pg_inherits
3083
+ INNER JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
3084
+ INNER JOIN pg_class child ON pg_inherits.inhrelid = child.oid
3085
+ INNER JOIN pg_catalog.pg_namespace n ON n.oid = parent.relnamespace#{
3086
+ if schema
3087
+ params = params << schema
3088
+ "
3089
+ WHERE n.nspname = COALESCE(?, 'public')"
3090
+ end}
3091
+ GROUP BY n.nspname, parent.relname, child.reltuples, child.relpages, child.oid
3092
+
3093
+ UNION ALL
3094
+
3095
+ -- table count
3096
+ SELECT n.nspname, pg_class.relname,
3097
+ (pg_class.reltuples::float / greatest(pg_class.relpages, 1)) *
3098
+ (pg_relation_size(pg_class.oid)::float / (current_setting('block_size')::float))::integer AS rowcount
3099
+ FROM pg_class
3100
+ INNER JOIN pg_catalog.pg_namespace n ON n.oid = pg_class.relnamespace#{
3101
+ if schema
3102
+ params = params << schema
3103
+ "
3104
+ WHERE n.nspname = COALESCE(?, 'public')"
3105
+ end}
3106
+ GROUP BY n.nspname, pg_class.relname, pg_class.reltuples, pg_class.relpages, pg_class.oid", params).each do |tblcount|
3107
+ # %%% What is the default schema here?
3108
+ prefix = "#{tblcount['nspname']}." unless tblcount['nspname'] == (schema || 'public')
3109
+ relations.fetch("#{prefix}#{tblcount['relname']}", nil)&.[]=(:rowcount, tblcount['rowcount'].to_i.round)
3110
+ end
3111
+ end
3112
+
3015
3113
  if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first)
3016
3114
  puts "Now switching back to \"#{orig_schema}\" schema."
3017
3115
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", orig_schema)
@@ -3151,7 +3249,7 @@ module Brick
3151
3249
  # For any appended references (those that come from config), arrive upon a definitely unique constraint name
3152
3250
  pri_tbl = is_class ? fk[4][:class].underscore : pri_tbl
3153
3251
  pri_tbl = "#{bt_assoc_name}_#{pri_tbl}" if pri_tbl&.singularize != bt_assoc_name
3154
- cnstr_name = ensure_unique(+"(brick) #{for_tbl}_#{pri_tbl}", bts, hms)
3252
+ cnstr_name = ensure_unique(+"(brick) #{for_tbl}_#{pri_tbl}", nil, bts, hms)
3155
3253
  missing = []
3156
3254
  missing << fk[1] unless relations.key?(fk[1])
3157
3255
  missing << primary_table unless is_class || relations.key?(primary_table)
@@ -3285,15 +3383,17 @@ module Brick
3285
3383
  end
3286
3384
  end
3287
3385
 
3288
- def ensure_unique(name, *sources)
3386
+ def ensure_unique(name, delimiter, *sources)
3289
3387
  base = name
3290
- if (added_num = name.slice!(/_(\d+)$/))
3388
+ delimiter ||= '_'
3389
+ # By default ends up building this regex: /_(\d+)$/
3390
+ if (added_num = name.slice!(Regexp.new("#{delimiter}(\d+)$")))
3291
3391
  added_num = added_num[1..-1].to_i
3292
3392
  else
3293
3393
  added_num = 1
3294
3394
  end
3295
3395
  while (
3296
- name = "#{base}_#{added_num += 1}"
3396
+ name = "#{base}#{delimiter}#{added_num += 1}"
3297
3397
  sources.each_with_object(nil) do |v, s|
3298
3398
  s || case v
3299
3399
  when Hash
@@ -3371,6 +3471,33 @@ module Brick
3371
3471
  end
3372
3472
  end
3373
3473
 
3474
+ def _brick_index(tbl_name, mode = nil, separator = nil, relation = nil)
3475
+ separator ||= '_'
3476
+ res_name = (tbl_name_parts = tbl_name.split('.'))[0..-2].first
3477
+ res_name << '.' if res_name
3478
+ (res_name ||= +'') << (relation || ::Brick.relations.fetch(tbl_name, nil))&.fetch(:resource, nil) || tbl_name_parts.last
3479
+
3480
+ res_parts = ((mode == :singular) ? res_name.singularize : res_name).split('.')
3481
+ res_parts.shift if ::Brick.apartment_multitenant && res_parts.length > 1 && res_parts.first == ::Brick.apartment_default_tenant
3482
+ if (aps = relation&.fetch(:auto_prefixed_schema, nil)) && res_parts.last.start_with?(aps)
3483
+ last_part = res_parts.last[aps.length..-1]
3484
+ aps = aps[0..-2] if aps[-1] == '_'
3485
+ res_parts[-1] = aps
3486
+ res_parts << last_part
3487
+ end
3488
+ path_prefix = []
3489
+ if ::Brick.config.path_prefix
3490
+ res_parts.unshift(::Brick.config.path_prefix)
3491
+ path_prefix << ::Brick.config.path_prefix
3492
+ end
3493
+ index = res_parts.map(&:underscore).join(separator)
3494
+ index = index.tr('_', 'x') if separator == 'x'
3495
+ # Rails applies an _index suffix to that route when the resource name isn't something plural
3496
+ index << '_index' if mode != :singular && separator == '_' &&
3497
+ index == (path_prefix + [name&.underscore&.tr('/', '_') || '_']).join(separator)
3498
+ index
3499
+ end
3500
+
3374
3501
  def find_col_renaming(api_ver_path, relation_name)
3375
3502
  ::Brick.config.api_column_renaming&.fetch(
3376
3503
  api_ver_path,
@@ -345,7 +345,8 @@ function linkSchemas() {
345
345
  end
346
346
  ::Brick.relations.each do |k, v|
347
347
  unless k.is_a?(Symbol) || existing.key?(class_name = v[:class_name]) || Brick.config.exclude_tables.include?(k) ||
348
- class_name.blank? || class_name.include?('::')
348
+ class_name.blank? || class_name.include?('::') ||
349
+ ['ActiveAdminComment', 'MotorAlert', 'MotorAlertLock', 'MotorApiConfig', 'MotorAudit', 'MotorConfig', 'MotorDashboard', 'MotorForm', 'MotorNote', 'MotorNoteTag', 'MotorNoteTagTag', 'MotorNotification', 'MotorQuery', 'MotorReminder', 'MotorResource', 'MotorTag', 'MotorTaggableTag'].include?(class_name)
349
350
  Object.const_get("#{class_name}Resource")
350
351
  end
351
352
  end
@@ -774,9 +775,18 @@ window.addEventListener(\"popstate\", linkSchemas);
774
775
  end
775
776
  # %%% If we are not auto-creating controllers (or routes) then omit by default, and if enabled anyway, such as in a development
776
777
  # environment or whatever, then get either the controllers or routes list instead
777
- prefix = "#{::Brick.config.path_prefix}/" if ::Brick.config.path_prefix
778
- table_options = ::Brick.relations.each_with_object({}) do |rel, s|
779
- next if rel.first.is_a?(Symbol) || ::Brick.config.exclude_tables.include?(rel.first)
778
+ table_rels = if ::Brick.config.omit_empty_tables_in_dropdown
779
+ ::Brick.relations.reject { |k, v| k.is_a?(Symbol) || v[:rowcount] == 0 }
780
+ else
781
+ ::Brick.relations
782
+ end
783
+ table_options = table_rels.sort do |a, b|
784
+ a[0] = '' if a[0].is_a?(Symbol)
785
+ b[0] = '' if b[0].is_a?(Symbol)
786
+ a.first <=> b.first
787
+ end.each_with_object(+'') do |rel, s|
788
+ next if rel.first.blank? || rel.last[:cols].empty? ||
789
+ ::Brick.config.exclude_tables.include?(rel.first)
780
790
 
781
791
  tbl_parts = rel.first.split('.')
782
792
  if (aps = rel.last.fetch(:auto_prefixed_schema, nil))
@@ -784,16 +794,16 @@ window.addEventListener(\"popstate\", linkSchemas);
784
794
  aps = aps[0..-2] if aps[-1] == '_'
785
795
  tbl_parts[-2] = aps
786
796
  end
787
- if tbl_parts.first == apartment_default_schema
788
- tbl_parts.shift
789
- end
797
+ tbl_parts.shift if tbl_parts.first == apartment_default_schema
790
798
  # %%% When table_name_prefixes are use then during rendering empty non-TNP
791
799
  # entries get added at some point when an attempt is made to find the table.
792
800
  # Will have to hunt that down at some point.
793
- s[tbl_parts.join('.')] = nil unless rel.last[:cols].empty?
794
- end.keys.sort.each_with_object(+'') do |v, s|
795
- s << "<option value=\"#{prefix}#{v.underscore.gsub('.', '/')}\">#{v}</option>"
801
+ if (rowcount = rel.last.fetch(:rowcount, nil))
802
+ rowcount = rowcount > 0 ? " (#{rowcount})" : nil
803
+ end
804
+ s << "<option value=\"#{::Brick._brick_index(rel.first, nil, '/')}\">#{rel.first}#{rowcount}</option>"
796
805
  end.html_safe
806
+ prefix = "#{::Brick.config.path_prefix}/" if ::Brick.config.path_prefix
797
807
  table_options << "<option value=\"#{prefix}brick_status\">(Status)</option>".html_safe if ::Brick.config.add_status
798
808
  table_options << "<option value=\"#{prefix}brick_orphans\">(Orphans)</option>".html_safe if is_orphans
799
809
  table_options << "<option value=\"#{prefix}brick_crosstab\">(Crosstab)</option>".html_safe if is_crosstab
@@ -1476,7 +1486,7 @@ end
1476
1486
  %>
1477
1487
  <tr>
1478
1488
  <td><%= begin
1479
- kls = Object.const_get(::Brick.relations.fetch(r[0], nil)&.fetch(:class_name, nil))
1489
+ kls = Object.const_get((rel = ::Brick.relations.fetch(r[0], nil))&.fetch(:class_name, nil))
1480
1490
  rescue
1481
1491
  end
1482
1492
  if kls.is_a?(Class) && (path_helper = respond_to?(bi_path = \"#\{kls._brick_index}_path\".to_sym) ? bi_path : nil)
@@ -1489,7 +1499,10 @@ end
1489
1499
  else
1490
1500
  ' class=\"dimmed\"'
1491
1501
  end&.html_safe %>><%= # Table
1492
- r[1] %></td>
1502
+ if (rowcount = rel&.fetch(:rowcount, nil))
1503
+ rowcount = (rowcount > 0 ? \" (#\{rowcount})\" : nil)
1504
+ end
1505
+ \"#\{r[1]}#\{rowcount}\" %></td>
1493
1506
  <td<%= lines = r[2]&.map { |line| \"#\{line.first}:#\{line.last}\" }
1494
1507
  ' class=\"dimmed\"'.html_safe unless r[2] %>><%= # Migration
1495
1508
  lines&.join('<br>')&.html_safe %></td>
@@ -1571,7 +1584,13 @@ end %>#{"
1571
1584
  #{schema_options}" if schema_options}
1572
1585
  <select id=\"tbl\">#{table_options}</select>
1573
1586
  <table id=\"resourceName\"><td><h1><%= page_title %></h1></td>
1574
- <% if Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) %>
1587
+ <% rel = Brick.relations[#{model_name}.table_name]
1588
+ if (in_app = rel.fetch(:existing, nil)&.fetch(:show, nil))
1589
+ in_app = send(\"#\{in_app}_path\", #{pk.is_a?(String) ? "obj.#{pk}" : '[' + pk.map { |pk_part| "obj.#{pk_part}" }.join(', ') + ']' }) if in_app.is_a?(Symbol) %>
1590
+ <td><%= link_to(::Brick::Rails::IN_APP.html_safe, in_app) %></td>
1591
+ <% end
1592
+
1593
+ if Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) %>
1575
1594
  <td><%= link_to_brick(
1576
1595
  ::Brick::Rails::AVO_SVG.html_safe,
1577
1596
  { show_proc: Proc.new do |obj, relation|
@@ -1596,7 +1615,7 @@ end %>#{"
1596
1615
  end %>
1597
1616
  </table>
1598
1617
  <%
1599
- if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %>
1618
+ if (description = rel&.fetch(:description, nil)) %>
1600
1619
  <span class=\"__brick\"><%= description %></span><br><%
1601
1620
  end
1602
1621
  %><%= link_to \"(See all #\{model_name.pluralize})\", see_all_path, { class: '__brick' } %>
@@ -1927,6 +1946,7 @@ document.querySelectorAll(\"input, select\").forEach(function (inp) {
1927
1946
  end
1928
1947
 
1929
1948
  if ::Brick.enable_routes?
1949
+ require 'brick/route_mapper'
1930
1950
  ActionDispatch::Routing::RouteSet.class_exec do
1931
1951
  # In order to defer auto-creation of any routes that already exist, calculate Brick routes only after having loaded all others
1932
1952
  prepend ::Brick::RouteSet
@@ -148,7 +148,8 @@ module Brick::Rails::FormBuilder
148
148
  '(hidden)'
149
149
  else
150
150
  if val.is_a?(String)
151
- val = val.dup.force_encoding('UTF-8').strip
151
+ return ::Brick::Rails.display_binary(val) unless (val_utf8 = val.dup.force_encoding('UTF-8')).valid_encoding?
152
+ val = val_utf8.strip
152
153
  return CGI.escapeHTML(val) if is_xml
153
154
 
154
155
  if val.length > max_len
@@ -2,7 +2,7 @@ module Brick::Rails::FormTags
2
2
  # Our super speedy grid
3
3
  def brick_grid(relation = nil, sequence = nil, inclusions = nil, exclusions = nil,
4
4
  cols = {}, bt_descrip: nil, poly_cols: nil, bts: {}, hms_keys: [], hms_cols: {},
5
- show_header: nil, show_row_count: nil, show_erd_button: nil, show_new_button: nil, show_avo_button: nil, show_aa_button: nil)
5
+ show_header: nil, show_row_count: nil, show_erd_button: nil, show_in_app_button: nil, show_new_button: nil, show_avo_button: nil, show_aa_button: nil)
6
6
  # When a relation is not provided, first see if one exists which matches the controller name
7
7
  unless (relation ||= instance_variable_get("@#{controller_name}".to_sym))
8
8
  # Failing that, dig through the instance variables with hopes to find something that is an ActiveRecord::Relation
@@ -33,6 +33,7 @@ module Brick::Rails::FormTags
33
33
  out = +"<div id=\"headerTopContainer\"><table id=\"headerTop\"></table>
34
34
  "
35
35
  klass = relation.klass
36
+ rel = ::Brick.relations&.fetch(relation.table_name, nil)
36
37
  unless show_header == false
37
38
  out << " <div id=\"headerTopAddNew\">
38
39
  <div id=\"headerButtonBox\">
@@ -43,6 +44,11 @@ module Brick::Rails::FormTags
43
44
  end
44
45
  unless show_erd_button == false
45
46
  out << " <div id=\"imgErd\" title=\"Show ERD\"></div>
47
+ "
48
+ end
49
+ if rel && show_in_app_button != false && (in_app = rel.fetch(:existing, nil)&.fetch(:index, nil))
50
+ in_app = send("#{in_app}_path") if in_app.is_a?(Symbol)
51
+ out << " <td title=\"Show in app\">#{link_to(::Brick::Rails::IN_APP.html_safe, in_app)}</td>
46
52
  "
47
53
  end
48
54
  if show_avo_button != false && Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) && klass.name.exclude?('::')
@@ -151,7 +157,7 @@ module Brick::Rails::FormTags
151
157
  # ActiveRecord::StatementTimeout in Warehouse::ColdRoomTemperatures_Archive#index
152
158
  # TinyTds::Error: Adaptive Server connection timed out
153
159
  # (After restarting the server it worked fine again.)
154
- rowCount = 0
160
+ row_count = 0
155
161
  relation.each do |obj|
156
162
  out << "<tr>\n"
157
163
  out << "<td class=\"col-sticky\">#{link_to('⇛', send("#{klass._brick_index(:singular)}_path".to_sym,
@@ -252,13 +258,16 @@ module Brick::Rails::FormTags
252
258
  out << '</td>'
253
259
  end
254
260
  out << '</tr>'
255
- rowCount += 1
261
+ row_count += 1
262
+ end
263
+ if rel && (total_row_count = rel.fetch(:rowcount, nil))
264
+ total_row_count = total_row_count > row_count ? " (out of #{total_row_count})" : nil
256
265
  end
257
266
  out << " </tbody>
258
267
  </table>
259
268
  <script>
260
269
  var rowCount = document.getElementById(\"rowCount\");
261
- if (rowCount) rowCount.innerHTML = \"#{pluralize(rowCount, "row")} &nbsp;\";
270
+ if (rowCount) rowCount.innerHTML = \"#{pluralize(row_count, "row")}#{total_row_count} &nbsp;\";
262
271
  </script>
263
272
  "
264
273