brick 1.0.99 → 1.0.101

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: 431e2aee4c3c199095d8475eeedcc46bd95e86a47c51623e3cea7fbbde59edcd
4
- data.tar.gz: 32144a9a9ab6ec60ef297f27ae73e35cc891e5f9462b9beae4e7a0534ab77135
3
+ metadata.gz: eba38d653370b0cc3e98f2adc82d8c475854440745bd35caab9a9a4253fc152c
4
+ data.tar.gz: 18ca517b90266cf5db711040a7f40c8e848600135ef0a39ad91da9cd2a0b1c89
5
5
  SHA512:
6
- metadata.gz: f471bc993d97e92deae3c03e6cac9db0e350e72bd070afe5bcde1d0ca18d650b546db0d018a101009e429e9870a7e2c5e566f980c9e5b1319bc18873271c5550
7
- data.tar.gz: 95f128f0f2a29cf0d1af1b5001eae27885b7e11ce23194e4a4da0141a0bfc059edd1917036f4addf1ee2aa4b69f763962e821ca31f9473048a6934dbecd8518e
6
+ metadata.gz: 859f48999fb0e8d1010a701e29c9f0275f716b20f54b4348576f4dbff7f68da1eb83709a51a1a7acd7debaf4dc1f82e863048ef16cda6b5f59862246e0c50787
7
+ data.tar.gz: d93c3db79715f62705c0b8f2f5bd2640a2756e1fbcb6aa6a7cf017a18bc31fa5e98496ea41539611c149f84870f24596c105d146484519b7f040d2f36b25ce45
@@ -404,21 +404,9 @@ module ActiveRecord
404
404
  def brick_select(params, selects = [], order_by = nil, translations = {}, join_array = ::Brick::JoinArray.new)
405
405
  is_add_bts = is_add_hms = true
406
406
 
407
- if selects.empty?
408
- # Build out cust_cols, bt_descrip and hm_counts now so that they are available on the
409
- # model early in case the user wants to do an ORDER BY based on any of that.
410
- model._brick_calculate_bts_hms(translations, join_array) if is_add_bts || is_add_hms
411
- else
412
- is_api = true
413
- # If there are any provided selects, treat them as API columns and build them as cust_cols since they
414
- # can be built using DSL.
415
- # false = not polymorphic, and true = yes -- please emit_dsl
416
- selects.each do |api_col|
417
- pieces, my_dsl = brick_parse_dsl(join_array, [], translations, false, "[#{api_col}]", true)
418
- _br_cust_cols[api_col.tr('.', '_')] = [pieces, my_dsl]
419
- end
420
- selects.clear # Now these have become custom columns
421
- end
407
+ # Build out cust_cols, bt_descrip and hm_counts now so that they are available on the
408
+ # model early in case the user wants to do an ORDER BY based on any of that.
409
+ model._brick_calculate_bts_hms(translations, join_array) if is_add_bts || is_add_hms
422
410
 
423
411
  is_postgres = ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
424
412
  is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2'
@@ -426,7 +414,7 @@ module ActiveRecord
426
414
  is_distinct = nil
427
415
  wheres = {}
428
416
  params.each do |k, v|
429
- next if ['_brick_schema', '_brick_order', '_brick_api', 'controller', 'action'].include?(k)
417
+ next if ['_brick_schema', '_brick_order', 'controller', 'action'].include?(k)
430
418
 
431
419
  if (where_col = (ks = k.split('.')).last)[-1] == '!'
432
420
  where_col = where_col[0..-2]
@@ -454,14 +442,17 @@ module ActiveRecord
454
442
  # ActiveRecord::StatementInvalid (TinyTds::Error: DBPROCESS is dead or not enabled)
455
443
  # Relevant info here: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/402
456
444
  columns.each do |col|
457
- next if is_api && id_parts.exclude?(col.name) # Only keep the ID columns if this is an API request
458
-
459
445
  col_alias = " AS #{col.name}_" if (col_name = col.name) == 'class'
460
446
  selects << if is_mysql
461
447
  "`#{tbl_no_schema}`.`#{col_name}`#{col_alias}"
462
448
  elsif is_postgres || is_mssql
463
- # Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
464
- cast_as_text = '::text' if is_distinct && Brick.relations[klass.table_name]&.[](:cols)&.[](col_name)&.first&.start_with?('xml')
449
+ if is_distinct # Postgres can not use DISTINCT with any columns that are XML or JSON
450
+ cast_as_text = if Brick.relations[klass.table_name]&.[](:cols)&.[](col_name)&.first == 'json'
451
+ '::jsonb' # Convert JSON to JSONB
452
+ elsif Brick.relations[klass.table_name]&.[](:cols)&.[](col_name)&.first&.start_with?('xml')
453
+ '::text' # Convert XML to text
454
+ end
455
+ end
465
456
  "\"#{tbl_no_schema}\".\"#{col_name}\"#{cast_as_text}#{col_alias}"
466
457
  elsif col.type # Could be Sqlite or Oracle
467
458
  if col_alias || !(/^[a-z0-9_]+$/ =~ col_name)
@@ -505,6 +496,7 @@ module ActiveRecord
505
496
  # binding.pry
506
497
  next
507
498
  end
499
+
508
500
  key_klass = nil
509
501
  key_tbl_name = nil
510
502
  dest_pk = nil
@@ -644,19 +636,26 @@ module ActiveRecord
644
636
  through_sources.map do |a|
645
637
  from_clause << "\n LEFT OUTER JOIN #{a.table_name} br_t#{idx += 1} "
646
638
  from_clause << if (src_ref = a.source_reflection).macro == :belongs_to
647
- (nm = hmt_assoc.source_reflection.inverse_of&.name)
648
- # binding.pry unless nm
639
+ nm = hmt_assoc.source_reflection.inverse_of&.name
649
640
  link_back << nm
650
641
  "ON br_t#{idx}.id = br_t#{idx - 1}.#{a.foreign_key}"
651
642
  elsif src_ref.options[:as]
652
643
  "ON br_t#{idx}.#{src_ref.type} = '#{src_ref.active_record.name}'" + # "polymorphable_type"
653
644
  " AND br_t#{idx}.#{src_ref.foreign_key} = br_t#{idx - 1}.id"
654
645
  elsif src_ref.options[:source_type]
655
- print "Skipping #{hm.name} --HMT-> #{hm.source_reflection.name} as it uses source_type which is not yet supported"
656
- nix << k
657
- bail_out = true
658
- break
659
- else # Standard has_many
646
+ if a == hm.source_reflection
647
+ print "Skipping #{hm.name} --HMT-> #{hm.source_reflection.name} as it uses source_type in a way which is not yet supported"
648
+ nix << k
649
+ bail_out = true
650
+ break
651
+ # "ON br_t#{idx}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
652
+ # "br_t#{idx}.#{a.foreign_key} = br_t#{idx - 1}.id"
653
+ else # Works for HMT through a polymorphic HO
654
+ link_back << hmt_assoc.source_reflection.inverse_of&.name # Some polymorphic "_able" thing
655
+ "ON br_t#{idx - 1}.#{a.foreign_type} = '#{src_ref.options[:source_type]}' AND " \
656
+ "br_t#{idx - 1}.#{a.foreign_key} = br_t#{idx}.id"
657
+ end
658
+ else # Standard has_many or has_one
660
659
  # binding.pry unless (
661
660
  nm = hmt_assoc.source_reflection.inverse_of&.name
662
661
  # )
@@ -719,12 +718,13 @@ module ActiveRecord
719
718
  on_clause << "#{tbl_alias}.#{poly_type} = '#{name}'"
720
719
  end
721
720
  unless from_clause
721
+ tbl_nm = hm.macro == :has_and_belongs_to_many ? hm.join_table : hm.table_name
722
722
  hm_table_name = if is_mysql
723
- "`#{hm.table_name}`"
723
+ "`#{tbl_nm}`"
724
724
  elsif is_postgres || is_mssql
725
- "\"#{(hm.table_name).gsub('.', '"."')}\""
725
+ "\"#{(tbl_nm).gsub('.', '"."')}\""
726
726
  else
727
- hm.table_name
727
+ tbl_nm
728
728
  end
729
729
  end
730
730
  group_bys = ::Brick.is_oracle || is_mssql ? hm_selects : (1..hm_selects.length).to_a
@@ -984,7 +984,7 @@ Module.class_exec do
984
984
  ::Brick.is_oracle ? class_name.upcase : class_name,
985
985
  (plural_class_name = class_name.pluralize)].find { |s| Brick.db_schemas&.include?(s) }&.camelize ||
986
986
  (::Brick.config.sti_namespace_prefixes&.key?("::#{class_name}::") && class_name) ||
987
- (::Brick.config.table_name_prefixes&.values.include?(class_name) && class_name))
987
+ (::Brick.config.table_name_prefixes.values.include?(class_name) && class_name))
988
988
  return self.const_get(schema_name) if self.const_defined?(schema_name)
989
989
 
990
990
  # Build out a module for the schema if it's namespaced
@@ -1403,8 +1403,8 @@ class Object
1403
1403
  # (More information on https://docs.avohq.io/2.0/controllers.html)
1404
1404
  controller_base = Avo::ResourcesController
1405
1405
  end
1406
- table_name = ActiveSupport::Inflector.underscore(plural_class_name)
1407
- singular_table_name = ActiveSupport::Inflector.singularize(table_name)
1406
+ table_name = model&.table_name || ActiveSupport::Inflector.underscore(plural_class_name)
1407
+ singular_table_name = ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.underscore(plural_class_name))
1408
1408
  pk = model&._brick_primary_key(relations.fetch(table_name, nil))
1409
1409
  is_postgres = ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1410
1410
  is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2'
@@ -1589,14 +1589,6 @@ class Object
1589
1589
  order_by, _ = model._brick_calculate_ordering(ordering, true) # Don't do the txt part
1590
1590
 
1591
1591
  ar_relation = ActiveRecord.version < Gem::Version.new('4') ? model.preload : model.all
1592
-
1593
- if (cc = params['_brick_api']&.split(','))
1594
- is_api = true
1595
- selects = cc
1596
- counts = [] # No need for any extra HM count columns
1597
- model._br_cust_cols.clear
1598
- end
1599
-
1600
1592
  @_brick_params = ar_relation.brick_select(params, (selects ||= []), order_by,
1601
1593
  translations = {},
1602
1594
  join_array = ::Brick::JoinArray.new)
@@ -1611,14 +1603,14 @@ class Object
1611
1603
  "b_r_#{v.first}.c_t_ AS \"b_r_#{v.first}_ct\""
1612
1604
  end
1613
1605
  end
1614
-
1615
1606
  ar_select = ar_relation.respond_to?(:_select!) ? ar_relation.dup._select!(*selects, *counts) : ar_relation.select(selects + counts)
1616
- instance_variable_set("@#{table_name.pluralize}".to_sym, ar_select)
1617
- if namespace && (idx = lookup_context.prefixes.index(table_name))
1607
+ instance_variable_set("@#{table_name.split('.').last}".to_sym, ar_select)
1608
+ table_name_no_schema = singular_table_name.pluralize
1609
+ if namespace && (idx = lookup_context.prefixes.index(table_name_no_schema))
1618
1610
  lookup_context.prefixes[idx] = "#{namespace.name.underscore}/#{lookup_context.prefixes[idx]}"
1619
1611
  end
1620
1612
  @_brick_excl = session[:_brick_exclude]&.split(',')&.each_with_object([]) do |excl, s|
1621
- if (excl_parts = excl.split('.')).first == table_name
1613
+ if (excl_parts = excl.split('.')).first == table_name_no_schema
1622
1614
  s << excl_parts.last
1623
1615
  end
1624
1616
  end
@@ -1749,7 +1741,12 @@ class Object
1749
1741
  val_part.gsub('^^sl^^', '/')
1750
1742
  end
1751
1743
  end
1752
- model.find(id.is_a?(Array) && id.length == 1 ? id.first : id)
1744
+ # Support friendly_id gem
1745
+ if Object.const_defined?('FriendlyId') && model.instance_variable_get(:@friendly_id_config)
1746
+ model.friendly.find(id.is_a?(Array) && id.length == 1 ? id.first : id)
1747
+ else
1748
+ model.find(id.is_a?(Array) && id.length == 1 ? id.first : id)
1749
+ end
1753
1750
  end
1754
1751
  end
1755
1752
 
@@ -54,7 +54,7 @@ module Brick
54
54
  end
55
55
 
56
56
  # When table names have specific prefixes, automatically place them in their own module with a table_name_prefix.
57
- ::Brick.table_name_prefixes = app.config.brick.fetch(:table_name_prefixes, [])
57
+ ::Brick.table_name_prefixes = app.config.brick.fetch(:table_name_prefixes, {})
58
58
 
59
59
  # Columns to treat as being metadata for purposes of identifying associative tables for has_many :through
60
60
  ::Brick.metadata_columns = app.config.brick.fetch(:metadata_columns, ['created_at', 'updated_at', 'deleted_at'])
@@ -197,7 +197,7 @@ function linkSchemas() {
197
197
  class BrickTitle
198
198
  def initialize(name, view_component)
199
199
  @vc = view_component
200
- @_name = name
200
+ @_name = name || ''
201
201
  end
202
202
  def to_s
203
203
  @_name.html_safe + @vc.instance_variable_get(:@__vc_helpers)&.link_to_brick(nil,
@@ -313,10 +313,10 @@ function linkSchemas() {
313
313
  end
314
314
 
315
315
  if @_brick_model
316
- pk = @_brick_model._brick_primary_key(::Brick.relations.fetch(@_brick_model&.table_name, nil))
316
+ pk = @_brick_model._brick_primary_key(::Brick.relations.fetch((table_name = @_brick_model.table_name.split('.').last), nil))
317
317
  obj_name = model_name.split('::').last.underscore
318
318
  path_obj_name = @_brick_model._brick_index(:singular)
319
- table_name = obj_name.pluralize
319
+ table_name ||= obj_name.pluralize
320
320
  template_link = nil
321
321
  bts, hms = ::Brick.get_bts_and_hms(@_brick_model) # This gets BT and HM and also has_many :through (HMT)
322
322
  hms_columns = [] # Used for 'index'
@@ -346,13 +346,16 @@ function linkSchemas() {
346
346
  end
347
347
  case args.first
348
348
  when 'index'
349
- unless skip_klass_hms.key?(assoc_name.to_sym) || hm_assoc.options[:source]
349
+ if !skip_klass_hms.key?(assoc_name.to_sym) && (
350
+ @_brick_model._br_hm_counts.key?(assoc_name) ||
351
+ @_brick_model._br_bt_descrip.key?(assoc_name) # Will end up here if it's a has_one
352
+ )
350
353
  hm_entry = +"'#{hm_assoc.name}' => [#{assoc_name.inspect}, "
351
- hm_entry << if hm_assoc.macro == :has_many
354
+ hm_entry << if hm_assoc.macro == :has_one
355
+ 'nil'
356
+ else # :has_many or :has_and_belongs_to_many
352
357
  # Postgres column names are limited to 63 characters
353
358
  "'" + "b_r_#{assoc_name}_ct"[0..62] + "'"
354
- else # has_one
355
- 'nil'
356
359
  end
357
360
  hm_entry << ", #{path_keys(hm_assoc, hm_fk_name, pk).inspect}]"
358
361
  hms_columns << hm_entry
@@ -424,14 +427,6 @@ h1, h3 {
424
427
  cursor: pointer;
425
428
  }
426
429
 
427
- #apiToggle {
428
- border: 2px solid purple;
429
- }
430
-
431
- #apiToggle, .apiColName {
432
- cursor: pointer;
433
- }
434
-
435
430
  #dropper {
436
431
  background-color: #eee;
437
432
  }
@@ -572,10 +567,58 @@ def hide_bcrypt(val, max_len = 200)
572
567
  '(hidden)'
573
568
  else
574
569
  if val.is_a?(String)
575
- val = \"#\{val[0...max_len]}...\" if val.length > max_len
570
+ if (val = val.dup.strip).length > max_len
571
+ if val[0] == '<' # Seems to be HTML?
572
+ cur_len = 0
573
+ cur_idx = 0
574
+ # Find which HTML tags we might be inside so we can apply ending tags to balance
575
+ element_name = nil
576
+ in_closing = nil
577
+ elements = []
578
+ val.each_char do |ch|
579
+ case ch
580
+ when '<'
581
+ element_name = +''
582
+ when '/' # First character of tag is '/'?
583
+ in_closing = true if element_name == ''
584
+ when '>'
585
+ if element_name
586
+ if in_closing
587
+ if (idx = elements.index { |tag| tag.downcase == element_name.downcase })
588
+ elements.delete_at(idx)
589
+ end
590
+ elsif (tag_name = element_name.split.first).present?
591
+ elements.unshift(tag_name)
592
+ end
593
+ element_name = nil
594
+ in_closing = nil
595
+ end
596
+ else
597
+ element_name << ch if element_name
598
+ end
599
+ cur_idx += 1
600
+ # Unless it's inside wickets then this is real text content, and see if we're at the limit
601
+ break if element_name.nil? && ((cur_len += 1) > max_len)
602
+ end
603
+ val = val[0..cur_idx]
604
+ # Somehow still in the middle of an opening tag right at the end? (Should never happen)
605
+ if !in_closing && (tag_name = element_name&.split&.first)&.present?
606
+ elements.unshift(tag_name)
607
+ val << '>'
608
+ end
609
+ elements.each do |closing_tag|
610
+ val << \"</#\{closing_tag}>\"
611
+ end
612
+ else # Not HTML, just cut it at the length
613
+ val = val[0...max_len]
614
+ end
615
+ val = \"#\{val}...\"
616
+ end
576
617
  val = val.dup.force_encoding('UTF-8') unless val.encoding.name == 'UTF-8'
618
+ val
619
+ else
620
+ val.to_s
577
621
  end
578
- val
579
622
  end
580
623
  end
581
624
  def display_value(col_type, val)
@@ -1002,8 +1045,12 @@ erDiagram
1002
1045
  +"<html>
1003
1046
  <head>
1004
1047
  #{css}
1005
- <title>#{model_name} <%
1006
- if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)).present?
1048
+ <title><% model = #{model_name}
1049
+ if sub_model = @_brick_params&.fetch(type_col = model.inheritance_column, nil)&.first
1050
+ model = Object.const_get(sub_model.to_sym)
1051
+ end
1052
+ %><%= model.name %><%
1053
+ if (description = (relation = Brick.relations[model.table_name])&.fetch(:description, nil)).present?
1007
1054
  %> - <%= description
1008
1055
  %><% end
1009
1056
  %></title>
@@ -1012,19 +1059,16 @@ erDiagram
1012
1059
  <p style=\"color: green\"><%= notice %></p>#{"
1013
1060
  #{schema_options}" if schema_options}
1014
1061
  <select id=\"tbl\">#{table_options}</select>
1015
-
1016
- <%= pick_api(#{model_name}).html_safe %>
1017
-
1018
1062
  <table id=\"resourceName\"><tr>
1019
- <td><h1>#{model_name}</h1></td>
1063
+ <td><h1><%= model.name %></h1></td>
1020
1064
  <td id=\"imgErd\" title=\"Show ERD\"></td>
1021
1065
  <% if Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) %>
1022
1066
  <td><%= link_to_brick(
1023
1067
  avo_svg,
1024
- { index_proc: Proc.new do |model|
1025
- ::Avo.railtie_routes_url_helpers.send(\"resources_#\{model.base_class.model_name.route_key}_path\".to_sym)
1068
+ { index_proc: Proc.new do |avo_model|
1069
+ ::Avo.railtie_routes_url_helpers.send(\"resources_#\{model.model_name.route_key}_path\".to_sym)
1026
1070
  end,
1027
- title: '#{model_name} in Avo' }
1071
+ title: \"#\{model.name} in Avo\" }
1028
1072
  ) %></td>
1029
1073
  <% end %>
1030
1074
  </tr></table>#{template_link}<%
@@ -1036,13 +1080,13 @@ erDiagram
1036
1080
  <% if @_brick_params.length == 1 # %%% Does not yet work with composite keys
1037
1081
  k, id = @_brick_params.first
1038
1082
  id = id.first if id.is_a?(Array) && id.length == 1
1039
- origin = (key_parts = k.split('.')).length == 1 ? #{model_name} : #{model_name}.reflect_on_association(key_parts.first).klass
1083
+ origin = (key_parts = k.split('.')).length == 1 ? model : model.reflect_on_association(key_parts.first).klass
1040
1084
  if (destination_fk = Brick.relations[origin.table_name][:fks].values.find { |fk| fk[:fk] == key_parts.last }) &&
1041
1085
  (obj = (destination = origin.reflect_on_association(destination_fk[:assoc_name])&.klass)&.find(id)) %>
1042
1086
  <h3>for <%= link_to \"#{"#\{obj.brick_descrip\} (#\{destination.name\})\""}, send(\"#\{destination._brick_index(:singular)\}_path\".to_sym, id) %></h3><%
1043
1087
  end
1044
1088
  end %>
1045
- (<%= link_to 'See all #{model_name.split('::').last.pluralize}', #{@_brick_model._brick_index}_path %>)
1089
+ (<%= link_to \"See all #\{model.base_class.name.split('::').last.pluralize}\", #{@_brick_model._brick_index}_path %>)
1046
1090
  <% end
1047
1091
  # COLUMN EXCLUSIONS
1048
1092
  if @_brick_excl&.present? %>
@@ -1069,10 +1113,10 @@ erDiagram
1069
1113
  end.join(', ')}}
1070
1114
 
1071
1115
  # If the resource is missing, has the user simply created an inappropriately pluralised name for a table?
1072
- @#{table_name} ||= if dym_list = instance_variables.reject do |entry|
1073
- entry.to_s.start_with?('@_') ||
1074
- ['@cache_hit', '@marked_for_same_origin_verification', '@view_renderer', '@view_flow', '@output_buffer', '@virtual_path'].include?(entry.to_s)
1075
- end
1116
+ @#{table_name} ||= if (dym_list = instance_variables.reject do |entry|
1117
+ entry.to_s.start_with?('@_') ||
1118
+ ['@cache_hit', '@marked_for_same_origin_verification', '@view_renderer', '@view_flow', '@output_buffer', '@virtual_path'].include?(entry.to_s)
1119
+ end).present?
1076
1120
  msg = \"Can't find resource \\\"#{table_name}\\\".\"
1077
1121
  # Can't be sure otherwise of what is up, so check DidYouMean and offer a suggestion.
1078
1122
  if (dym = DidYouMean::SpellChecker.new(dictionary: dym_list).correct('@#{table_name}')).present?
@@ -1088,13 +1132,8 @@ erDiagram
1088
1132
  end
1089
1133
 
1090
1134
  # Write out the mega-grid
1091
- if params['_brick_api'] # API response?
1092
- brick_grid(@#{table_name}, @_brick_bt_descrip, @_brick_sequence, [], @_brick_excl,
1093
- cols, poly_cols, {}, [], {})
1094
- else
1095
- brick_grid(@#{table_name}, @_brick_bt_descrip, @_brick_sequence, @_brick_incl, @_brick_excl,
1096
- cols, poly_cols, bts, #{hms_keys.inspect}, {#{hms_columns.join(', ')}})
1097
- end %>
1135
+ brick_grid(@#{table_name}, @_brick_bt_descrip, @_brick_sequence, @_brick_incl, @_brick_excl,
1136
+ cols, poly_cols, bts, #{hms_keys.inspect}, {#{hms_columns.join(', ')}}) %>
1098
1137
 
1099
1138
  #{"<hr><%= link_to \"New #{obj_name}\", new_#{path_obj_name}_path %>" unless @_brick_model.is_view?}
1100
1139
  #{script}
@@ -11,7 +11,7 @@ module Brick::Rails::FormTags
11
11
  out << "<th x-order=\"#{pk.join(',')}\"></th>"
12
12
  end
13
13
 
14
- col_keys ||= relation.columns.each_with_object([]) do |col, s|
14
+ col_keys = relation.columns.each_with_object([]) do |col, s|
15
15
  col_name = col.name
16
16
  next if inclusions&.exclude?(col_name) ||
17
17
  (pk.include?(col_name) && [:integer, :uuid].include?(col.type) && !bts.key?(col_name)) ||
@@ -39,8 +39,10 @@ module Brick::Rails::FormTags
39
39
  "#{' x-order="' + col_name + '"' if true}>#{col_name}"
40
40
  end
41
41
  elsif col # HM column
42
+ options = {}
43
+ options[col[1].inheritance_column] = col[1].name unless col[1] == col[1].base_class
42
44
  s << "<th#{' x-order="' + col_name + '"' if true}>#{col[2]} "
43
- s << (col.first ? "#{col[3]}" : "#{link_to(col[3], send("#{col[1]._brick_index}_path"))}")
45
+ s << (col.first ? "#{col[3]}" : "#{link_to(col[3], send("#{col[1]._brick_index}_path", options))}")
44
46
  elsif cust_cols.key?(col_name) # Custom column
45
47
  s << "<th x-order=\"#{col_name}\">#{col_name}"
46
48
  elsif col_name.is_a?(Symbol) && (hot = bts[col_name]) # has_one :through
@@ -96,17 +98,17 @@ module Brick::Rails::FormTags
96
98
  if hms_col.length == 1
97
99
  out << hms_col.first
98
100
  else
99
- klass = (col = cols[col_name])[1]
101
+ hm_klass = (col = cols[col_name])[1]
100
102
  if col[2] == 'HO'
101
- descrips = bt_descrip[col_name.to_sym][klass]
103
+ descrips = bt_descrip[col_name.to_sym][hm_klass]
102
104
  if (ho_id = (ho_id_col = descrips.last).map { |id_col| obj.send(id_col.to_sym) })&.first
103
- ho_txt = klass.brick_descrip(obj, descrips[0..-2].map { |id| obj.send(id.last[0..62]) }, ho_id_col)
104
- out << link_to(ho_txt, send("#{klass.base_class._brick_index(:singular)}_path".to_sym, ho_id))
105
+ ho_txt = hm_klass.brick_descrip(obj, descrips[0..-2].map { |id| obj.send(id.last[0..62]) }, ho_id_col)
106
+ out << link_to(ho_txt, send("#{hm_klass.base_class._brick_index(:singular)}_path".to_sym, ho_id))
105
107
  end
106
108
  else
107
109
  if (ct = obj.send(hms_col[1].to_sym)&.to_i)&.positive?
108
110
  out << "#{link_to("#{ct || 'View'} #{hms_col.first}",
109
- send("#{klass._brick_index}_path".to_sym,
111
+ send("#{hm_klass._brick_index}_path".to_sym,
110
112
  hms_col[2].each_with_object({}) { |v, s| s[v.first] = v.last.is_a?(String) ? v.last : obj.send(v.last) })
111
113
  )}\n"
112
114
  end
@@ -137,51 +139,6 @@ module Brick::Rails::FormTags
137
139
  out.html_safe
138
140
  end # brick_grid
139
141
 
140
- # Recursively render UL and LI to choose columns to include
141
- def pick_api(model, is_last = nil, visited = [])
142
- out = +''
143
- if visited.empty?
144
- out << '<span id="apiToggle">API buffet</span><div id="apiBuffet" style="display: none;">'
145
- out << '<textarea id="apiCode" cols="100"></textarea>'
146
- out << '<input type="button" id="apiRender" value="Render">'
147
- end
148
- out << "\n#{indent = ' ' * visited.length}<ul>"
149
- model.column_names.each { |col_name| out << "\n#{indent} <li class=\"apiColName\" x-nm=\"#{visited.map { |v| "#{v.last}." }.join}#{col_name}\">#{col_name}</li>" }
150
- unless is_last || visited.length == 2
151
- model.reflect_on_all_associations.each_with_object({}) do |v, s|
152
- next if v.macro == :has_many || v.polymorphic?
153
-
154
- out << "\n#{indent} <li><b>#{v.name}</b>#{pick_api(v.klass, visited.map(&:first).include?(v.klass),
155
- visited.dup << [v.klass, v.name]
156
- )}"
157
- out << "\n#{indent} </li>"
158
- s[v.name] = nil
159
- end
160
- end
161
- out << "\n#{indent}</ul>"
162
- if visited.empty?
163
- out << '</div><script>[... document.getElementsByClassName("apiColName")].forEach(function (li) {li.addEventListener("click", apiColClick);});'
164
- out << "
165
- var colList = [];
166
- var apiBuffet = document.getElementById(\"apiBuffet\");
167
- var apiCode = document.getElementById(\"apiCode\");
168
- function apiColClick(evt) {
169
- apiCode.innerHTML += this.getAttribute(\"x-nm\") + \",\\n\";
170
- }
171
- document.getElementById(\"apiToggle\").addEventListener(\"click\", function () {
172
- apiBuffet.style.display = (apiBuffet.style.display === \"block\" ? \"none\" : \"block\");
173
- });
174
- // Cheap and cheerful way to render a list of columns just for the demo
175
- document.getElementById(\"apiRender\").addEventListener(\"click\", function () {
176
- var changecolList = apiCode.innerHTML.substring(0, apiCode.innerHTML.length - 2);
177
- location.href = changeout(location.href, \"_brick_api\", changecolList);
178
- });
179
- </script>
180
- "
181
- end
182
- out
183
- end
184
-
185
142
  def link_to_brick(*args, **kwargs)
186
143
  return unless ::Brick.config.mode == :on
187
144
 
@@ -231,13 +188,14 @@ document.getElementById(\"apiRender\").addEventListener(\"click\", function () {
231
188
  end
232
189
  end
233
190
  filter = "?#{filter_parts.join('&')}" if filter_parts.present?
191
+ app_routes = Rails.application.routes # In case we're operating in another engine, reference the application since Brick routes are placed there.
234
192
  if (klass_or_obj&.is_a?(Class) && klass_or_obj < ActiveRecord::Base) ||
235
193
  (klass_or_obj&.is_a?(ActiveRecord::Base) && klass_or_obj.new_record? && (klass_or_obj = klass_or_obj.class))
236
- path = (proc = kwargs[:index_proc]) ? proc.call(klass_or_obj) : "#{send("#{klass_or_obj.base_class._brick_index}_path")}#{filter}"
194
+ path = (proc = kwargs[:index_proc]) ? proc.call(klass_or_obj) : "#{app_routes.path_for(controller: klass_or_obj.base_class._brick_index, action: :index)}#{filter}"
237
195
  lt_args = [text || "Index for #{klass_or_obj.name.pluralize}", path]
238
196
  else
239
197
  # If there are multiple incoming parameters then last one is probably the actual ID, and first few might be some nested tree of stuff leading up to it
240
- path = (proc = kwargs[:show_proc]) ? proc.call(klass_or_obj) : "#{send("#{klass_or_obj.class.base_class._brick_index(:singular)}_path", klass_or_obj)}#{filter}"
198
+ path = (proc = kwargs[:show_proc]) ? proc.call(klass_or_obj) : "#{app_routes.path_for(controller: klass_or_obj.class.base_class._brick_index, action: :show, id: klass_or_obj)}#{filter}"
241
199
  lt_args = [text || "Show this #{klass_or_obj.class.name}", path]
242
200
  end
243
201
  link_to(*lt_args, **kwargs)
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 99
8
+ TINY = 101
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
@@ -640,50 +640,62 @@ In config/initializers/brick.rb appropriate entries would look something like:
640
640
  table_class_length = 38 # Length of "Classes that can be built from tables:"
641
641
  view_class_length = 37 # Length of "Classes that can be built from views:"
642
642
 
643
- brick_routes_create = lambda do |schema_name, controller_name, v, options|
643
+ brick_routes_create = lambda do |schema_name, res_name, options|
644
644
  if schema_name # && !Object.const_defined('Apartment')
645
645
  send(:namespace, schema_name) do
646
- send(:resources, v[:resource].to_sym, **options)
646
+ send(:resources, res_name.to_sym, **options)
647
647
  end
648
648
  else
649
- send(:resources, v[:resource].to_sym, **options)
649
+ send(:resources, res_name.to_sym, **options)
650
650
  end
651
651
  end
652
652
 
653
653
  # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
654
654
  # If auto-controllers and auto-models are both enabled then this makes sense:
655
655
  controller_prefix = (path_prefix ? "#{path_prefix}/" : '')
656
+ sti_subclasses = ::Brick.config.sti_namespace_prefixes.each_with_object(Hash.new { |h, k| h[k] = [] }) do |v, s|
657
+ # Turn something like {"::Spouse"=>"Person", "::Friend"=>"Person"} into {"Person"=>["Spouse", "Friend"]}
658
+ s[v.last] << v.first[2..-1] unless v.first.end_with?('::')
659
+ end
656
660
  ::Brick.relations.each do |k, v|
657
- unless !(controller_name = v.fetch(:resource, nil)&.pluralize) || existing_controllers.key?(controller_name)
658
- options = {}
659
- options[:only] = [:index, :show] if v.key?(:isView)
660
- # First do the API routes
661
- full_resource = nil
662
- if (schema_name = v.fetch(:schema, nil))
663
- full_resource = "#{schema_name}/#{v[:resource]}"
664
- send(:get, "#{::Brick.api_root}#{full_resource}", { to: "#{controller_prefix}#{schema_name}/#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
661
+ next if !(controller_name = v.fetch(:resource, nil)&.pluralize) || existing_controllers.key?(controller_name)
662
+
663
+ options = {}
664
+ options[:only] = [:index, :show] if v.key?(:isView)
665
+ # First do the API routes
666
+ full_resource = nil
667
+ if (schema_name = v.fetch(:schema, nil))
668
+ full_resource = "#{schema_name}/#{v[:resource]}"
669
+ send(:get, "#{::Brick.api_root}#{full_resource}", { to: "#{controller_prefix}#{schema_name}/#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
670
+ else
671
+ # Normally goes to something like: /api/v1/employees
672
+ send(:get, "#{::Brick.api_root}#{v[:resource]}", { to: "#{controller_prefix}#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
673
+ end
674
+
675
+ # Track routes being built
676
+ if (class_name = v.fetch(:class_name, nil))
677
+ if v.key?(:isView)
678
+ view_class_length = class_name.length if class_name.length > view_class_length
679
+ views
665
680
  else
666
- # Normally goes to something like: /api/v1/employees
667
- send(:get, "#{::Brick.api_root}#{v[:resource]}", { to: "#{controller_prefix}#{controller_name}#index" }) if Object.const_defined?('Rswag::Ui')
668
- end
669
- # Now the normal routes
670
- if path_prefix
671
- # Was: send(:scope, path: path_prefix) do
672
- send(:namespace, path_prefix) do
673
- brick_routes_create.call(schema_name, controller_name, v, options)
681
+ table_class_length = class_name.length if class_name.length > table_class_length
682
+ tables
683
+ end << [class_name, full_resource || v[:resource]]
684
+ end
685
+
686
+ # Now the normal routes
687
+ if path_prefix
688
+ # Was: send(:scope, path: path_prefix) do
689
+ send(:namespace, path_prefix) do
690
+ brick_routes_create.call(schema_name, v[:resource], options)
691
+ sti_subclasses.fetch(class_name, nil)&.each do |sc| # Add any STI subclass routes for this relation
692
+ brick_routes_create.call(schema_name, sc.underscore.tr('/', '_').pluralize, options)
674
693
  end
675
- else
676
- brick_routes_create.call(schema_name, controller_name, v, options)
677
694
  end
678
-
679
- if (class_name = v.fetch(:class_name, nil))
680
- if v.key?(:isView)
681
- view_class_length = class_name.length if class_name.length > view_class_length
682
- views
683
- else
684
- table_class_length = class_name.length if class_name.length > table_class_length
685
- tables
686
- end << [class_name, full_resource || v[:resource]]
695
+ else
696
+ brick_routes_create.call(schema_name, v[:resource], options)
697
+ sti_subclasses.fetch(class_name, nil)&.each do |sc| # Add any STI subclass routes for this relation
698
+ brick_routes_create.call(schema_name, sc.underscore.tr('/', '_').pluralize, options)
687
699
  end
688
700
  end
689
701
  end
@@ -1322,24 +1334,26 @@ module ActiveRecord
1322
1334
  private
1323
1335
 
1324
1336
  # %%% Pretty much have to flat-out replace this guy (I think anyway)
1325
- # Good with Rails 5.24 and 7 on this
1326
- def build(associations, base_klass, root = nil, path = '')
1327
- root ||= associations
1328
- associations.map do |name, right|
1329
- reflection = find_reflection base_klass, name
1330
- reflection.check_validity!
1331
- reflection.check_eager_loadable!
1332
-
1333
- if reflection.polymorphic?
1334
- raise EagerLoadPolymorphicError.new(reflection)
1335
- end
1337
+ # Good with Rails 5.24 through 7 on this
1338
+ # Ransack gem includes Polyamorous which replaces #build in a different way (which we handle below)
1339
+ unless Gem::Specification.all_names.any? { |g| g.start_with?('ransack-') }
1340
+ def build(associations, base_klass, root = nil, path = '')
1341
+ root ||= associations
1342
+ associations.map do |name, right|
1343
+ reflection = find_reflection base_klass, name
1344
+ reflection.check_validity!
1345
+ reflection.check_eager_loadable!
1346
+
1347
+ if reflection.polymorphic?
1348
+ raise EagerLoadPolymorphicError.new(reflection)
1349
+ end
1336
1350
 
1337
- # %%% The path
1338
- link_path = path.blank? ? name.to_s : path + ".#{name}"
1339
- ja = JoinAssociation.new(reflection, build(right, reflection.klass, root, link_path))
1340
- ja.instance_variable_set(:@link_path, link_path) # Make note on the JoinAssociation of its AR path
1341
- ja.instance_variable_set(:@assocs, root)
1342
- ja
1351
+ link_path = path.blank? ? name.to_s : path + ".#{name}"
1352
+ ja = JoinAssociation.new(reflection, build(right, reflection.klass, root, link_path))
1353
+ ja.instance_variable_set(:@link_path, link_path) # Make note on the JoinAssociation of its AR path
1354
+ ja.instance_variable_set(:@assocs, root)
1355
+ ja
1356
+ end
1343
1357
  end
1344
1358
  end
1345
1359
 
@@ -1348,10 +1362,9 @@ module ActiveRecord
1348
1362
  alias _brick_table_aliases_for table_aliases_for
1349
1363
  def table_aliases_for(parent, node)
1350
1364
  result = _brick_table_aliases_for(parent, node)
1351
-
1352
1365
  # Capture the table alias name that was chosen
1353
- link_path = node.instance_variable_get(:@link_path)
1354
1366
  if (relation = node.instance_variable_get(:@assocs)&.instance_variable_get(:@relation))
1367
+ link_path = node.instance_variable_get(:@link_path)
1355
1368
  relation.brick_links[link_path] = result.first.table_alias || result.first.table_name
1356
1369
  end
1357
1370
  result
@@ -1360,11 +1373,14 @@ module ActiveRecord
1360
1373
  alias _brick_make_constraints make_constraints
1361
1374
  def make_constraints(parent, child, join_type)
1362
1375
  result = _brick_make_constraints(parent, child, join_type)
1363
-
1364
1376
  # Capture the table alias name that was chosen
1365
- link_path = child.instance_variable_get(:@link_path)
1366
1377
  if (relation = child.instance_variable_get(:@assocs)&.instance_variable_get(:@relation))
1367
- relation.brick_links[link_path] = result.first.left.table_alias || result.first.left.table_name
1378
+ link_path = child.instance_variable_get(:@link_path)
1379
+ relation.brick_links[link_path] = if child.table.is_a?(Arel::Nodes::TableAlias)
1380
+ child.table.right
1381
+ else
1382
+ result.first&.left&.table_alias || child.table_name
1383
+ end
1368
1384
  end
1369
1385
  result
1370
1386
  end
@@ -1374,4 +1390,47 @@ module ActiveRecord
1374
1390
  end
1375
1391
  end
1376
1392
 
1393
+ # Now the Ransack Polyamorous version of #build
1394
+ if Gem::Specification.all_names.any? { |g| g.start_with?('ransack-') }
1395
+ require "polyamorous/activerecord_#{::ActiveRecord::VERSION::STRING[0, 3]}_ruby_2/join_dependency"
1396
+ module Polyamorous::JoinDependencyExtensions
1397
+ def build(associations, base_klass, root = nil, path = '')
1398
+ root ||= associations
1399
+ puts associations.map(&:first)
1400
+
1401
+ associations.map do |name, right|
1402
+ link_path = path.blank? ? name.to_s : path + ".#{name}"
1403
+ ja = if name.is_a? ::Polyamorous::Join
1404
+ reflection = find_reflection base_klass, name.name
1405
+ reflection.check_validity!
1406
+ reflection.check_eager_loadable!
1407
+
1408
+ klass = if reflection.polymorphic?
1409
+ name.klass || base_klass
1410
+ else
1411
+ reflection.klass
1412
+ end
1413
+ ::ActiveRecord::Associations::JoinDependency::JoinAssociation.new(
1414
+ reflection, build(right, klass, root, link_path), name.klass, name.type
1415
+ )
1416
+ else
1417
+ reflection = find_reflection base_klass, name
1418
+ reflection.check_validity!
1419
+ reflection.check_eager_loadable!
1420
+
1421
+ if reflection.polymorphic?
1422
+ raise ActiveRecord::EagerLoadPolymorphicError.new(reflection)
1423
+ end
1424
+ ::ActiveRecord::Associations::JoinDependency::JoinAssociation.new(
1425
+ reflection, build(right, reflection.klass, root, link_path)
1426
+ )
1427
+ end
1428
+ ja.instance_variable_set(:@link_path, link_path) # Make note on the JoinAssociation of its AR path
1429
+ ja.instance_variable_set(:@assocs, root)
1430
+ ja
1431
+ end
1432
+ end
1433
+ end
1434
+ end
1435
+
1377
1436
  require 'brick/extensions'
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.99
4
+ version: 1.0.101
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-12-06 00:00:00.000000000 Z
11
+ date: 2022-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord