brick 1.0.190 → 1.0.192

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: 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