brick 1.0.97 → 1.0.98

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: c257a0a65807fa87a13d722f3cc30c38d51bde571a070df53f4277fc6db8b7ba
4
- data.tar.gz: 74d7690a968fab671d9e9e8ac92e41885ceb44a700fd1cb293f3b79af819e463
3
+ metadata.gz: d4285ef6fb81d75bd90ceffc7d66c7fa096f04fb998da590f291f28fc9dfb45d
4
+ data.tar.gz: 2d031b156fd9c82d19af9b7818387eaecdd8cfadf939cbcf3b3b479474a77b54
5
5
  SHA512:
6
- metadata.gz: 4337faa80a6f0d9c1dadcb3271aea14ec54bb91e72cd7eead7e59c860557f828ac073cab3768feaa2db2f664d63b1604bd8f8e0d53c64621b1fcf5cc041057d9
7
- data.tar.gz: 541cade2070bd0a79b21dd7155df700b2739084ae81eaa02709f9e00164f956e62e8342330a78496c4b5fbf351cac9ea40483d3cdc32c1fb1dbe8d7e34499d2d
6
+ metadata.gz: 5967ede119f86cd92f88abb0f43bd2049eda4272c14ae7fa1c3f9ecc54c813c4c227be2d99aaa501363e42ef34a3eb5d0a0924ac4e540ab668e40a9a9d28311e
7
+ data.tar.gz: ab01005e485b9335c67ba48bb01a13db78fed3ce31293122fae8fbf1317b3bc0c3197adb087ec4b125e2ff7153e52fb55d8b7fc675b1e7759233cf8d42769ac7
@@ -540,7 +540,11 @@ module ActiveRecord
540
540
  tbl_name = "\"#{tbl_name}\"" if ::Brick.is_oracle && rel_dupe.brick_links.values.include?(tbl_name)
541
541
  field_tbl_name = nil
542
542
  v1.map { |x| [x[0..-2].map(&:to_s).join('.'), x.last] }.each_with_index do |sel_col, idx|
543
- field_tbl_name = rel_dupe.brick_links[sel_col.first].split('.').last
543
+ # %%% Strangely in Rails 7.1 on a slower system then very rarely brick_link comes back nil...
544
+ brick_link = rel_dupe.brick_links[sel_col.first]
545
+ field_tbl_name = brick_link&.split('.')&.last ||
546
+ # ... so here's a best-effort guess for what the table name might be.
547
+ rel_dupe.klass.reflect_on_association(sel_col.first).klass.table_name
544
548
  # If it's Oracle, quote any AREL aliases that had been applied
545
549
  field_tbl_name = "\"#{field_tbl_name}\"" if ::Brick.is_oracle && rel_dupe.brick_links.values.include?(field_tbl_name)
546
550
 
@@ -825,80 +829,9 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
825
829
  end
826
830
 
827
831
  if Object.const_defined?('ActionView')
832
+ require 'brick/frameworks/rails/form_tags'
828
833
  module ActionView::Helpers::FormTagHelper
829
- def link_to_brick(*args, **kwargs)
830
- return unless ::Brick.config.mode == :on
831
-
832
- text = (args.first.is_a?(String) && args.first) || args[1]
833
- klass_or_obj = ((args.first.is_a?(ActiveRecord::Relation) ||
834
- args.first.is_a?(ActiveRecord::Base) ||
835
- args.first.is_a?(Class)) &&
836
- args.first) ||
837
- @_brick_model
838
- # If not provided, do a best-effort to automatically determine the resource class or object
839
- filter_parts = []
840
- klass_or_obj ||= begin
841
- klass, sti_type = ::Brick.ctrl_to_klass(controller_path)
842
- if klass
843
- type_col = klass.inheritance_column # Usually 'type'
844
- filter_parts << "#{type_col}=#{sti_type}" if sti_type && klass.column_names.include?(type_col)
845
- path_params = request.path_parameters.dup
846
- path_params.delete(:controller)
847
- path_params.delete(:action)
848
- pk = (klass.primary_key || ActiveRecord::Base.primary_key).to_sym
849
- # Used to also have this but it's a bit too permissive to identify a primary key: (path_params.length == 1 && path_params.values.first) ||
850
- if ((id = (path_params[pk] || path_params[:id] || path_params["#{klass.name.underscore}_id".to_sym])) && (obj = klass.find_by(pk => id))) ||
851
- (['show', 'edit', 'update', 'destroy'].include?(action_name) && (obj = klass.first))
852
- obj
853
- else
854
- # %%% If there is a HMT that refers to some ___id then try to identify an appropriate filter
855
- # %%% If there is a polymorphic association that might relate to stuff in the path_params,
856
- # try to identify an appropriate ___able_id and ___able_type filter
857
- ((klass.column_names - [pk.to_s]) & path_params.keys.map(&:to_s)).each do |path_param|
858
- filter_parts << "#{path_param}=#{path_params[path_param.to_sym]}"
859
- end
860
- klass
861
- end
862
- end
863
- rescue
864
- end
865
- if klass_or_obj
866
- if klass_or_obj.is_a?(ActiveRecord::Relation)
867
- klass_or_obj.where_values_hash.each do |whr|
868
- filter_parts << "#{whr.first}=#{whr.last}" if whr.last && !whr.last.is_a?(Array)
869
- end
870
- klass_or_obj = klass_or_obj.klass
871
- type_col = klass_or_obj.inheritance_column
872
- if klass_or_obj.column_names.include?(type_col) && klass_or_obj.name != klass_or_obj.base_class.name
873
- filter_parts << "#{type_col}=#{klass_or_obj.name}"
874
- end
875
- elsif klass_or_obj.is_a?(ActiveRecord::Base) && klass_or_obj.new_record?
876
- klass_or_obj = klass_or_obj.class
877
- end
878
- filter = "?#{filter_parts.join('&')}" if filter_parts.present?
879
- if (klass_or_obj&.is_a?(Class) && klass_or_obj < ActiveRecord::Base) ||
880
- (klass_or_obj&.is_a?(ActiveRecord::Base) && klass_or_obj.new_record? && (klass_or_obj = klass_or_obj.class))
881
- lt_args = [text || "Index for #{klass_or_obj.name.pluralize}",
882
- "#{send("#{klass_or_obj._brick_index}_path")}#{filter}"]
883
- else
884
- # 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
885
- lt_args = [text || "Show this #{klass_or_obj.class.name}",
886
- "#{send("#{klass_or_obj.class._brick_index(:singular)}_path", klass_or_obj)}#{filter}"]
887
- end
888
- link_to(*lt_args, **kwargs)
889
- else
890
- # puts "Warning: link_to_brick could not find a class for \"#{controller_path}\" -- consider setting @_brick_model within that controller."
891
- # if (hits = res_names.keys & instance_variables.map { |v| v.to_s[1..-1] }).present?
892
- links = instance_variables.each_with_object([]) do |name, s|
893
- iv_name = name.to_s[1..-1]
894
- case (val = instance_variable_get(name))
895
- when ActiveRecord::Relation, ActiveRecord::Base
896
- s << link_to_brick(val, iv_name) if val
897
- end
898
- end
899
- links.join(' &nbsp; ').html_safe
900
- end
901
- end
834
+ include ::Brick::Rails::FormTags
902
835
  end
903
836
  end
904
837
 
@@ -1002,6 +935,7 @@ Module.class_exec do
1002
935
  end
1003
936
  class_name = ::Brick.namify(requested)
1004
937
  relations = ::Brick.relations
938
+ # CONTROLLER
1005
939
  result = if ::Brick.enable_controllers? &&
1006
940
  is_controller && (plural_class_name = class_name[0..-11]).length.positive?
1007
941
  # Otherwise now it's up to us to fill in the gaps
@@ -1020,6 +954,8 @@ Module.class_exec do
1020
954
  # if it's a controller and no match or a model doesn't really use the same table name, eager load all models and try to find a model class of the right name.
1021
955
  Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
1022
956
  end
957
+
958
+ # MODULE
1023
959
  elsif (::Brick.enable_models? || ::Brick.enable_controllers?) && # Schema match?
1024
960
  base_module == Object && # %%% This works for Person::Person -- but also limits us to not being able to allow more than one level of namespacing
1025
961
  (schema_name = [(singular_table_name = class_name.underscore),
@@ -1036,6 +972,50 @@ Module.class_exec do
1036
972
 
1037
973
  [built_module, "module #{schema_name}; end\n"]
1038
974
  # # %%% Perhaps an option to use the first module just as schema, and additional modules as namespace with a table name prefix applied
975
+
976
+ # AVO Resource
977
+ elsif base_module == Object && Object.const_defined?('Avo') && requested.end_with?('Resource') &&
978
+ ['MotorResource'].exclude?(requested) # Expect that anything called MotorResource could be from that administrative gem
979
+ if (model = Object.const_get(requested[0..-9]))
980
+ require 'generators/avo/resource_generator'
981
+ field_generator = Generators::Avo::ResourceGenerator.new([''])
982
+ field_generator.instance_variable_set(:@model, model)
983
+ fields = field_generator.send(:generate_fields).split("\n")
984
+ .each_with_object([]) do |f, s|
985
+ if (f = f.strip).start_with?('field ')
986
+ f = f[6..-1].split(',')
987
+ s << [f.first[1..-1].to_sym, [f[1][1..-1].split(': :').map(&:to_sym)].to_h]
988
+ end
989
+ end
990
+ built_resource = Class.new(Avo::BaseResource) do |new_resource_class|
991
+ self.model_class = model
992
+ self.title = :brick_descrip
993
+ self.includes = []
994
+ if (!model.is_view? && mod_pk = model.primary_key)
995
+ field((mod_pk.is_a?(Array) ? mod_pk.first : mod_pk).to_sym, { as: :id })
996
+ end
997
+ # Create a call such as: field :name, as: :text
998
+ fields.each do |f|
999
+ # Add proper types if this is a polymorphic belongs_to
1000
+ if f.last == { as: :belongs_to } &&
1001
+ (fk = ::Brick.relations[model.table_name][:fks].find { |k, v| v[:assoc_name] == f.first.to_s }) &&
1002
+ fk.last.fetch(:polymorphic, nil)
1003
+ poly_types = fk.last.fetch(:inverse_table, nil)&.each_with_object([]) do |poly_table, s|
1004
+ s << Object.const_get(::Brick.relations[poly_table][:class_name])
1005
+ end
1006
+ if poly_types.present?
1007
+ f.last[:polymorphic_as] = f.first
1008
+ f.last[:types] = poly_types
1009
+ end
1010
+ end
1011
+ self.send(:field, *f)
1012
+ end
1013
+ end
1014
+ Object.const_set(requested.to_sym, built_resource)
1015
+ [built_resource, nil]
1016
+ end
1017
+
1018
+ # MODEL
1039
1019
  elsif ::Brick.enable_models?
1040
1020
  # Custom inheritable Brick base model?
1041
1021
  class_name = (inheritable_name = class_name)[5..-1] if class_name.start_with?('Brick')
@@ -1397,14 +1377,19 @@ class Object
1397
1377
  end
1398
1378
 
1399
1379
  def build_controller(namespace, class_name, plural_class_name, model, relations)
1380
+ if (is_avo = (namespace.name == 'Avo' && Object.const_defined?('Avo')))
1381
+ # Basic Avo functionality is available via its own generic controller.
1382
+ # (More information on https://docs.avohq.io/2.0/controllers.html)
1383
+ controller_base = Avo::ResourcesController
1384
+ end
1400
1385
  table_name = ActiveSupport::Inflector.underscore(plural_class_name)
1401
1386
  singular_table_name = ActiveSupport::Inflector.singularize(table_name)
1402
1387
  pk = model&._brick_primary_key(relations.fetch(table_name, nil))
1403
1388
  is_postgres = ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1404
1389
  is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2'
1405
1390
 
1406
- code = +"class #{class_name} < ApplicationController\n"
1407
- built_controller = Class.new(ActionController::Base) do |new_controller_class|
1391
+ code = +"class #{class_name} < #{controller_base&.name || 'ApplicationController'}\n"
1392
+ built_controller = Class.new(controller_base || ActionController::Base) do |new_controller_class|
1408
1393
  (namespace || Object).const_set(class_name.to_sym, new_controller_class)
1409
1394
 
1410
1395
  # Brick-specific pages
@@ -1501,118 +1486,120 @@ class Object
1501
1486
  end
1502
1487
 
1503
1488
  self.protect_from_forgery unless: -> { self.request.format.js? }
1504
- self.define_method :index do
1505
- if (is_openapi || request.env['REQUEST_PATH'].start_with?(::Brick.api_root)) &&
1506
- !params&.key?('_brick_schema') &&
1507
- (referrer_params = request.env['HTTP_REFERER']&.split('?')&.last&.split('&')&.map { |x| x.split('=') }).present?
1508
- if params
1509
- referrer_params.each { |k, v| params.send(:parameters)[k] = v }
1510
- else
1511
- api_params = referrer_params&.to_h
1489
+ unless is_avo
1490
+ self.define_method :index do
1491
+ if (is_openapi || request.env['REQUEST_PATH'].start_with?(::Brick.api_root)) &&
1492
+ !params&.key?('_brick_schema') &&
1493
+ (referrer_params = request.env['HTTP_REFERER']&.split('?')&.last&.split('&')&.map { |x| x.split('=') }).present?
1494
+ if params
1495
+ referrer_params.each { |k, v| params.send(:parameters)[k] = v }
1496
+ else
1497
+ api_params = referrer_params&.to_h
1498
+ end
1512
1499
  end
1513
- end
1514
- _schema, @_is_show_schema_list = ::Brick.set_db_schema(params || api_params)
1515
-
1516
- if is_openapi
1517
- json = { 'openapi': '3.0.1', 'info': { 'title': Rswag::Ui.config.config_object[:urls].last&.fetch(:name, 'API documentation'), 'version': ::Brick.config.api_version },
1518
- 'servers': [
1519
- { 'url': '{scheme}://{defaultHost}', 'variables': {
1520
- 'scheme': { 'default': request.env['rack.url_scheme'] },
1521
- 'defaultHost': { 'default': request.env['HTTP_HOST'] }
1522
- } }
1523
- ]
1524
- }
1525
- json['paths'] = relations.inject({}) do |s, relation|
1526
- unless ::Brick.config.enable_api == false
1527
- table_description = relation.last[:description]
1528
- s["#{::Brick.config.api_root}#{relation.first.tr('.', '/')}"] = {
1529
- 'get': {
1530
- 'summary': "list #{relation.first}",
1531
- 'description': table_description,
1532
- 'parameters': relation.last[:cols].map do |k, v|
1533
- param = { 'name' => k, 'schema': { 'type': v.first } }
1534
- if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
1535
- param['description'] = col_descrip
1536
- end
1537
- param
1538
- end,
1539
- 'responses': { '200': { 'description': 'successful' } }
1540
- }
1541
- }
1542
-
1543
- s["#{::Brick.config.api_root}#{relation.first.tr('.', '/')}/{id}"] = {
1544
- 'patch': {
1545
- 'summary': "update a #{relation.first.singularize}",
1546
- 'description': table_description,
1547
- 'parameters': relation.last[:cols].reject { |k, v| Brick.config.metadata_columns.include?(k) }.map do |k, v|
1548
- param = { 'name' => k, 'schema': { 'type': v.first } }
1549
- if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
1550
- param['description'] = col_descrip
1551
- end
1552
- param
1553
- end,
1554
- 'responses': { '200': { 'description': 'successful' } }
1500
+ _schema, @_is_show_schema_list = ::Brick.set_db_schema(params || api_params)
1501
+
1502
+ if is_openapi
1503
+ json = { 'openapi': '3.0.1', 'info': { 'title': Rswag::Ui.config.config_object[:urls].last&.fetch(:name, 'API documentation'), 'version': ::Brick.config.api_version },
1504
+ 'servers': [
1505
+ { 'url': '{scheme}://{defaultHost}', 'variables': {
1506
+ 'scheme': { 'default': request.env['rack.url_scheme'] },
1507
+ 'defaultHost': { 'default': request.env['HTTP_HOST'] }
1508
+ } }
1509
+ ]
1510
+ }
1511
+ json['paths'] = relations.inject({}) do |s, relation|
1512
+ unless ::Brick.config.enable_api == false
1513
+ table_description = relation.last[:description]
1514
+ s["#{::Brick.config.api_root}#{relation.first.tr('.', '/')}"] = {
1515
+ 'get': {
1516
+ 'summary': "list #{relation.first}",
1517
+ 'description': table_description,
1518
+ 'parameters': relation.last[:cols].map do |k, v|
1519
+ param = { 'name' => k, 'schema': { 'type': v.first } }
1520
+ if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
1521
+ param['description'] = col_descrip
1522
+ end
1523
+ param
1524
+ end,
1525
+ 'responses': { '200': { 'description': 'successful' } }
1526
+ }
1555
1527
  }
1556
- } unless relation.last.fetch(:isView, nil)
1557
- s
1528
+
1529
+ s["#{::Brick.config.api_root}#{relation.first.tr('.', '/')}/{id}"] = {
1530
+ 'patch': {
1531
+ 'summary': "update a #{relation.first.singularize}",
1532
+ 'description': table_description,
1533
+ 'parameters': relation.last[:cols].reject { |k, v| Brick.config.metadata_columns.include?(k) }.map do |k, v|
1534
+ param = { 'name' => k, 'schema': { 'type': v.first } }
1535
+ if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
1536
+ param['description'] = col_descrip
1537
+ end
1538
+ param
1539
+ end,
1540
+ 'responses': { '200': { 'description': 'successful' } }
1541
+ }
1542
+ } unless relation.last.fetch(:isView, nil)
1543
+ s
1544
+ end
1558
1545
  end
1546
+ render inline: json.to_json, content_type: request.format
1547
+ return
1559
1548
  end
1560
- render inline: json.to_json, content_type: request.format
1561
- return
1562
- end
1563
1549
 
1564
- if request.format == :csv # Asking for a template?
1565
- require 'csv'
1566
- exported_csv = CSV.generate(force_quotes: false) do |csv_out|
1567
- model.df_export(model.brick_import_template).each { |row| csv_out << row }
1550
+ if request.format == :csv # Asking for a template?
1551
+ require 'csv'
1552
+ exported_csv = CSV.generate(force_quotes: false) do |csv_out|
1553
+ model.df_export(model.brick_import_template).each { |row| csv_out << row }
1554
+ end
1555
+ render inline: exported_csv, content_type: request.format
1556
+ return
1557
+ elsif request.format == :js || request.path.start_with?('/api/') # Asking for JSON?
1558
+ data = (model.is_view? || !Object.const_defined?('DutyFree')) ? model.limit(1000) : model.df_export(model.brick_import_template)
1559
+ render inline: data.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
1560
+ return
1568
1561
  end
1569
- render inline: exported_csv, content_type: request.format
1570
- return
1571
- elsif request.format == :js || request.path.start_with?('/api/') # Asking for JSON?
1572
- data = (model.is_view? || !Object.const_defined?('DutyFree')) ? model.limit(1000) : model.df_export(model.brick_import_template)
1573
- render inline: data.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
1574
- return
1575
- end
1576
1562
 
1577
- # Normal (not swagger or CSV) request
1578
-
1579
- # %%% Allow params to define which columns to use for order_by
1580
- # Overriding the default by providing a querystring param?
1581
- ordering = params['_brick_order']&.split(',')&.map(&:to_sym) || Object.send(:default_ordering, table_name, pk)
1582
- order_by, _ = model._brick_calculate_ordering(ordering, true) # Don't do the txt part
1583
-
1584
- ar_relation = ActiveRecord.version < Gem::Version.new('4') ? model.preload : model.all
1585
- @_brick_params = ar_relation.brick_select(params, (selects = []), order_by,
1586
- translations = {},
1587
- join_array = ::Brick::JoinArray.new)
1588
- # %%% Add custom HM count columns
1589
- # %%% What happens when the PK is composite?
1590
- counts = model._br_hm_counts.each_with_object([]) do |v, s|
1591
- s << if is_mysql
1592
- "`b_r_#{v.first}`.c_t_ AS \"b_r_#{v.first}_ct\""
1593
- elsif is_postgres
1594
- "\"b_r_#{v.first}\".c_t_ AS \"b_r_#{v.first}_ct\""
1595
- else
1596
- "b_r_#{v.first}.c_t_ AS \"b_r_#{v.first}_ct\""
1597
- end
1598
- end
1599
- ar_select = ar_relation.respond_to?(:_select!) ? ar_relation.dup._select!(*selects, *counts) : ar_relation.select(selects + counts)
1600
- instance_variable_set("@#{table_name.pluralize}".to_sym, ar_select)
1601
- if namespace && (idx = lookup_context.prefixes.index(table_name))
1602
- lookup_context.prefixes[idx] = "#{namespace.name.underscore}/#{lookup_context.prefixes[idx]}"
1603
- end
1604
- @_brick_excl = session[:_brick_exclude]&.split(',')&.each_with_object([]) do |excl, s|
1605
- if (excl_parts = excl.split('.')).first == table_name
1606
- s << excl_parts.last
1563
+ # Normal (not swagger or CSV) request
1564
+
1565
+ # %%% Allow params to define which columns to use for order_by
1566
+ # Overriding the default by providing a querystring param?
1567
+ ordering = params['_brick_order']&.split(',')&.map(&:to_sym) || Object.send(:default_ordering, table_name, pk)
1568
+ order_by, _ = model._brick_calculate_ordering(ordering, true) # Don't do the txt part
1569
+
1570
+ ar_relation = ActiveRecord.version < Gem::Version.new('4') ? model.preload : model.all
1571
+ @_brick_params = ar_relation.brick_select(params, (selects = []), order_by,
1572
+ translations = {},
1573
+ join_array = ::Brick::JoinArray.new)
1574
+ # %%% Add custom HM count columns
1575
+ # %%% What happens when the PK is composite?
1576
+ counts = model._br_hm_counts.each_with_object([]) do |v, s|
1577
+ s << if is_mysql
1578
+ "`b_r_#{v.first}`.c_t_ AS \"b_r_#{v.first}_ct\""
1579
+ elsif is_postgres
1580
+ "\"b_r_#{v.first}\".c_t_ AS \"b_r_#{v.first}_ct\""
1581
+ else
1582
+ "b_r_#{v.first}.c_t_ AS \"b_r_#{v.first}_ct\""
1583
+ end
1584
+ end
1585
+ ar_select = ar_relation.respond_to?(:_select!) ? ar_relation.dup._select!(*selects, *counts) : ar_relation.select(selects + counts)
1586
+ instance_variable_set("@#{table_name.pluralize}".to_sym, ar_select)
1587
+ if namespace && (idx = lookup_context.prefixes.index(table_name))
1588
+ lookup_context.prefixes[idx] = "#{namespace.name.underscore}/#{lookup_context.prefixes[idx]}"
1589
+ end
1590
+ @_brick_excl = session[:_brick_exclude]&.split(',')&.each_with_object([]) do |excl, s|
1591
+ if (excl_parts = excl.split('.')).first == table_name
1592
+ s << excl_parts.last
1593
+ end
1607
1594
  end
1608
- end
1609
- @_brick_bt_descrip = model._br_bt_descrip
1610
- @_brick_hm_counts = model._br_hm_counts
1611
- @_brick_join_array = join_array
1612
- @_brick_erd = params['_brick_erd']&.to_i
1595
+ @_brick_bt_descrip = model._br_bt_descrip
1596
+ @_brick_hm_counts = model._br_hm_counts
1597
+ @_brick_join_array = join_array
1598
+ @_brick_erd = params['_brick_erd']&.to_i
1599
+ end
1613
1600
  end
1614
1601
 
1615
- unless is_openapi
1602
+ unless is_openapi || is_avo
1616
1603
  _, order_by_txt = model._brick_calculate_ordering(default_ordering(table_name, pk)) if pk
1617
1604
  code << " def index\n"
1618
1605
  code << " @#{table_name.pluralize} = #{model.name}#{pk&.present? ? ".order(#{order_by_txt.join(', ')})" : '.all'}\n"
@@ -3,7 +3,7 @@
3
3
  module Brick
4
4
  module Rails
5
5
  # Extensions to rails controllers. Provides convenient ways to pass certain
6
- # information to the model layer, with `controller_info` and `whodunnit`.
6
+ # information to the model layer, with `controller_info`.
7
7
  # Also includes a convenient on/off switch,
8
8
  # `brick_enabled_for_controller`.
9
9
  module Controller
@@ -4,6 +4,33 @@ module Brick
4
4
  module Rails
5
5
  # See http://guides.rubyonrails.org/engines.html
6
6
  class Engine < ::Rails::Engine
7
+ JS_CHANGEOUT = "function changeout(href, param, value, trimAfter) {
8
+ var hrefParts = href.split(\"?\");
9
+ var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
10
+ if (param === undefined || param === null || param === -1) {
11
+ hrefParts = hrefParts[0].split(\"://\");
12
+ var pathParts = hrefParts[hrefParts.length - 1].split(\"/\").filter(function (pp) {return pp !== \"\";});
13
+ if (value === undefined)
14
+ // A couple possibilities if it's namespaced, starting with two parts in the path -- and then try just one
15
+ return [pathParts.slice(1, 3).join('/'), pathParts.slice(1, 2)[0]];
16
+ else {
17
+ var queryString = param ? \"?\" + params.join(\"&\") : \"\";
18
+ return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value + queryString;
19
+ }
20
+ }
21
+ if (trimAfter) {
22
+ var pathParts = hrefParts[0].split(\"/\");
23
+ while (pathParts.lastIndexOf(trimAfter) !== pathParts.length - 1) pathParts.pop();
24
+ hrefParts[0] = pathParts.join(\"/\");
25
+ }
26
+ params = params.reduce(function (s, v) { var parts = v.split(\"=\"); if (parts[1]) s[parts[0]] = parts[1]; return s; }, {});
27
+ if (value === undefined) return params[param];
28
+ params[param] = value;
29
+ var finalParams = Object.keys(params).reduce(function (s, v) { if (params[v]) s.push(v + \"=\" + params[v]); return s; }, []).join(\"&\");
30
+ return hrefParts[0] + (finalParams.length > 0 ? \"?\" + finalParams : \"\");
31
+ }
32
+ "
33
+
7
34
  # paths['app/models'] << 'lib/brick/frameworks/active_record/models'
8
35
  config.brick = ActiveSupport::OrderedOptions.new
9
36
  ActiveSupport.on_load(:before_initialize) do |app|
@@ -61,6 +88,167 @@ module Brick
61
88
  (app_config.assets.precompile ||= []) << "#{assets_path}/images/brick_erd.png"
62
89
  (app.config.assets.paths ||= []) << assets_path
63
90
  end
91
+
92
+ # Smarten up Avo so it recognises Brick's querystring option for Apartment multi-tenancy
93
+ if Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace)
94
+ module ::Avo
95
+ class ApplicationController
96
+ # Make Avo tenant-compatible when a querystring param is included such as: ?_brick_schema=globex_corp
97
+ alias _brick_avo_init init_app
98
+ def init_app
99
+ _brick_avo_init
100
+ ::Brick.set_db_schema(params)
101
+ end
102
+ end
103
+
104
+ module UrlHelpers
105
+ alias _brick_resource_path resource_path
106
+ # Accommodate STI resources
107
+ def resource_path(model:, resource:, **args)
108
+ resource ||= if (klass = model&.class)
109
+ Avo::App.resources.find { |r| r.model_class > klass }
110
+ end
111
+ _brick_resource_path(model: model, resource: resource, **args)
112
+ end
113
+ end
114
+
115
+ class Fields::BelongsToField
116
+ # When there is no Resource created for the target of a belongs_to, defer to the description that Brick would use
117
+ alias _brick_label label
118
+ def label
119
+ target_resource ? _brick_label : value.send(:brick_descrip)
120
+ end
121
+ end
122
+
123
+ class App
124
+ class << self
125
+ alias _brick_eager_load eager_load
126
+ def eager_load(entity)
127
+ _brick_eager_load(entity)
128
+ if entity == :resources
129
+ # %%% This useful logic can be DRYd up since it's very similar to what's around extensions.rb:1894
130
+ if (possible_schemas = (multitenancy = ::Brick.config.schema_behavior&.[](:multitenant)) &&
131
+ multitenancy&.[](:schema_to_analyse))
132
+ possible_schemas = [possible_schemas] unless possible_schemas.is_a?(Array)
133
+ if (possible_schema = possible_schemas.find { |ps| ::Brick.db_schemas.key?(ps) })
134
+ orig_tenant = Apartment::Tenant.current
135
+ Apartment::Tenant.switch!(possible_schema)
136
+ end
137
+ end
138
+ existing = Avo::BaseResource.descendants.each_with_object({}) do |r, s|
139
+ s[r.name[0..-9]] = nil if r.name.end_with?('Resource')
140
+ end
141
+ ::Brick.relations.each do |k, v|
142
+ unless existing.key?(class_name = v[:class_name]) || Brick.config.exclude_tables.include?(k) || class_name.blank?
143
+ Object.const_get("#{class_name}Resource")
144
+ end
145
+ end
146
+ Apartment::Tenant.switch!(orig_tenant) if orig_tenant
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ # Add our schema link Javascript code when the TurboFrameWrapper is rendered so it ends up on all index / show / etc
153
+ TurboFrameWrapperComponent.class_exec do
154
+ alias _brick_content content
155
+ def content
156
+ # Avo's logo partial fails if there is not a URL helper called exactly "root_path"
157
+ # (Finicky line over there is: avo/app/views/avo/partials/_logo.html.erb:1)
158
+ if ::Brick.instance_variable_get(:@_brick_avo_js) == view_renderer.object_id
159
+ _brick_content
160
+ else
161
+ ::Brick.instance_variable_set(:@_brick_avo_js, view_renderer.object_id)
162
+ unless ::Rails.application.routes.named_routes.names.include?(:root) || ActionView::Base.respond_to?(:root_path)
163
+ ActionView::Base.class_exec do
164
+ def root_path
165
+ Avo::App.root_path
166
+ end
167
+ end
168
+ end
169
+ "<script>
170
+ #{JS_CHANGEOUT}
171
+ window.addEventListener(\"load\", linkSchemas);
172
+ document.addEventListener(\"turbo:render\", linkSchemas);
173
+ window.addEventListener(\"popstate\", linkSchemas);
174
+ // [... document.getElementsByTagName('turbo-frame')].forEach(function (a) { a.addEventListener(\"turbo:frame-render\", linkSchemas); });
175
+ function linkSchemas() {
176
+ brickSchema = changeout(location.href, \"_brick_schema\");
177
+ if (brickSchema) {
178
+ [... document.getElementsByTagName(\"A\")].forEach(function (a) { a.href = changeout(a.href, \"_brick_schema\", brickSchema); });
179
+ [... document.getElementsByTagName(\"FORM\")].forEach(function (form) { form.action = changeout(form.action, \"_brick_schema\", brickSchema); });
180
+ }
181
+ }
182
+ </script>
183
+ #{_brick_content}".html_safe
184
+ end
185
+ end
186
+ end
187
+
188
+ # When available, add a clickable brick icon to go to the Brick version of the page
189
+ PanelComponent.class_exec do
190
+ alias _brick_init initialize
191
+ def initialize(*args)
192
+ _brick_init(*args)
193
+ @name = BrickTitle.new(@name, self)
194
+ end
195
+ end
196
+
197
+ class BrickTitle
198
+ def initialize(name, view_component)
199
+ @vc = view_component
200
+ @_name = name
201
+ end
202
+ def to_s
203
+ @_name.html_safe + @vc.instance_variable_get(:@__vc_helpers)&.link_to_brick(nil,
204
+ "<svg version=\"1.1\" style=\"display: inline; padding-left: 0.5em;\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"
205
+ viewBox=\"0 0 58 58\" height=\"1.4em\" xml:space=\"preserve\">
206
+ <g>
207
+ <polygon style=\"fill:#C2615F;\" points=\"58,15.831 19.106,35.492 0,26.644 40,6\"/>
208
+ <polygon style=\"fill:#6D4646;\" points=\"19,52 0,43.356 0,26.644 19,35\"/>
209
+ <polygon style=\"fill:#894747;\" points=\"58,31.559 19,52 19,35 58,15.831\"/>
210
+ </g>
211
+ </svg>
212
+ ".html_safe,
213
+ { title: "#{@_name} in Brick" }
214
+ )
215
+ end
216
+ end
217
+
218
+ class Fields::IndexComponent
219
+ alias _brick_resource_view_path resource_view_path
220
+ def resource_view_path
221
+ return if @resource.model&.class&.is_view?
222
+
223
+ _brick_resource_view_path
224
+ end
225
+ end
226
+ end # module Avo
227
+
228
+ # Steer any Avo-related controller/action based URL lookups to the Avo RouteSet
229
+ class ActionDispatch::Routing::RouteSet
230
+ alias _brick_url_for url_for
231
+ def url_for(options, *args)
232
+ if self != ::Avo.railtie_routes_url_helpers._routes && # This URL lookup is not on the Avo RouteSet ...
233
+ (options[:controller]&.start_with?('avo/') || # ... but it is based on an Avo controller and action?
234
+ options[:_recall]&.fetch(:controller, nil)&.start_with?('avo/')
235
+ )
236
+ options[:script_name] = ::Avo.configuration.root_path if options[:script_name].blank?
237
+ ::Avo.railtie_routes_url_helpers._routes.url_for(options, *args) # Go get the answer from the real Avo RouteSet
238
+ # Views currently do not support show / new / edit
239
+ elsif options[:controller]&.start_with?('avo/') &&
240
+ ['show', 'new', 'edit'].include?(options[:action]) &&
241
+ ((options[:id].is_a?(ActiveRecord::Base) && options[:id].class.is_view?) ||
242
+ ::Brick.relations.fetch(options[:controller][4..-1], nil)&.fetch(:isView, nil)
243
+ )
244
+ nil
245
+ else # This is either a non-Avo request or a proper Avo request, so carry on
246
+ _brick_url_for(options, *args)
247
+ end
248
+ end
249
+ end
250
+ end # Avo compatibility
251
+
64
252
  # ====================================
65
253
  # Dynamically create generic templates
66
254
  # ====================================
@@ -93,15 +281,15 @@ module Brick
93
281
  end
94
282
  end
95
283
 
96
- def path_keys(hm_assoc, fk_name, obj_name, pk)
284
+ def path_keys(hm_assoc, fk_name, pk)
285
+ pk.map!(&:to_sym)
97
286
  keys = if fk_name.is_a?(Array) && pk.is_a?(Array) # Composite keys?
98
- fk_name.zip(pk.map { |fk_part| "#{obj_name}.#{fk_part}" })
287
+ fk_name.zip(pk)
99
288
  else
100
- pk = pk.map { |pk_part| "#{obj_name}.#{pk_part}" }
101
289
  [[fk_name, pk.length == 1 ? pk.first : pk.inspect]]
102
290
  end
103
291
  keys << [hm_assoc.inverse_of.foreign_type, hm_assoc.active_record.name] if hm_assoc.options.key?(:as)
104
- keys.map { |x| "#{x.first}: #{x.last}"}.join(', ')
292
+ keys.to_h
105
293
  end
106
294
 
107
295
  alias :_brick_find_template :find_template
@@ -138,8 +326,6 @@ module Brick
138
326
  "H#{hm_assoc.macro == :has_one ? 'O' : 'M'}#{'T' if hm_assoc.options[:through]}",
139
327
  (assoc_name = hm.first)]
140
328
  hm_fk_name = if (through = hm_assoc.options[:through])
141
- next unless @_brick_model._br_hm_counts.key?(hm_assoc.name) # Skip any weird HMTs that go through a HM with a source_type
142
-
143
329
  next unless @_brick_model.instance_methods.include?(through) &&
144
330
  (associative = @_brick_model._br_associatives.fetch(hm.first, nil))
145
331
 
@@ -154,33 +340,30 @@ module Brick
154
340
  tbl_nm.slice!(0) if tbl_nm[0] == '/'
155
341
  tbl_nm = tbl_nm.tr('/', '_').pluralize
156
342
  end
157
- "'#{tbl_nm}.#{associative.foreign_key}'"
343
+ "#{tbl_nm}.#{associative.foreign_key}"
158
344
  else
159
345
  hm_assoc.foreign_key
160
346
  end
161
347
  case args.first
162
348
  when 'index'
163
- hm_entry = +"'#{hm_assoc.name}' => [#{assoc_name.inspect}"
164
- hm_entry << if hm_assoc.macro == :has_many
165
- if hm_fk_name # %%% Can remove this check when multiple foreign keys to same destination becomes bulletproof
166
- set_ct = if skip_klass_hms.key?(assoc_name.to_sym)
167
- 'nil'
168
- else
169
- # Postgres column names are limited to 63 characters
170
- "#{obj_name}.#{"b_r_#{assoc_name}_ct"[0..62]}&.to_i || 0"
171
- end
172
- ", #{set_ct}, #{path_keys(hm_assoc, hm_fk_name, obj_name, pk)}"
173
- end
174
- else # has_one
175
- # 0..62 because Postgres column names are limited to 63 characters
176
- ", nil, #{path_keys(hm_assoc, hm_fk_name, obj_name, pk)}"
177
- end
178
- hm_entry << ']'
179
- hms_columns << hm_entry
349
+ unless skip_klass_hms.key?(assoc_name.to_sym) || hm_assoc.options[:source]
350
+ hm_entry = +"'#{hm_assoc.name}' => [#{assoc_name.inspect}, "
351
+ hm_entry << if hm_assoc.macro == :has_many
352
+ # Postgres column names are limited to 63 characters
353
+ "'" + "b_r_#{assoc_name}_ct"[0..62] + "'"
354
+ else # has_one
355
+ 'nil'
356
+ end
357
+ hm_entry << ", #{path_keys(hm_assoc, hm_fk_name, pk).inspect}]"
358
+ hms_columns << hm_entry
359
+ end
180
360
  when 'show', 'new', 'update'
181
361
  hm_stuff << if hm_fk_name
182
362
  if hm_assoc.klass.column_names.include?(hm_fk_name)
183
- "<%= link_to '#{assoc_name}', #{hm_assoc.klass._brick_index}_path({ #{path_keys(hm_assoc, hm_fk_name, "@#{obj_name}", pk)} }) %>\n"
363
+ predicates = path_keys(hm_assoc, hm_fk_name, pk).map do |k, v|
364
+ v.is_a?(String) ? "#{k}: '#{v}'" : "#{k}: @#{obj_name}.#{v}"
365
+ end.join(', ')
366
+ "<%= link_to '#{assoc_name}', #{hm_assoc.klass._brick_index}_path({ #{predicates} }) %>\n"
184
367
  else
185
368
  puts "Warning: has_many :#{hm_assoc.name} in model #{hm_assoc.active_record.name} currently looks for a foreign key called \"#{hm_assoc.foreign_key}\". "\
186
369
  "Instead it should use the clause \"foreign_key: :#{hm_assoc.inverse_of&.foreign_key}\"."
@@ -437,7 +620,18 @@ end
437
620
  def slashify(*vals)
438
621
  vals.map { |val_part| val_part.is_a?(String) ? val_part.gsub('/', '^^sl^^') : val_part }
439
622
  end
440
- callbacks = {} %>"
623
+ callbacks = {} %>
624
+
625
+ <% avo_svg = \"#{
626
+ "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 84 90\" height=\"30\" fill=\"#3096F7\">
627
+ <path d=\"M83.8304 81.0201C83.8343 82.9343 83.2216 84.7996 82.0822 86.3423C80.9427 87.8851 79.3363 89.0244 77.4984 89.5931C75.6606 90.1618 73.6878 90.1302 71.8694 89.5027C70.0509 88.8753 68.4823 87.6851 67.3935 86.1065L67.0796 85.6029C66.9412 85.378 66.8146 85.1463 66.6998 84.9079L66.8821 85.3007C64.1347 81.223 60.419 77.8817 56.0639 75.5723C51.7087 73.263 46.8484 72.057 41.9129 72.0609C31.75 72.0609 22.372 77.6459 16.9336 85.336C17.1412 84.7518 17.7185 83.6137 17.9463 83.0446L19.1059 80.5265L19.1414 80.456C25.2533 68.3694 37.7252 59.9541 52.0555 59.9541C53.1949 59.9541 54.3241 60.0095 55.433 60.1102C60.748 60.6134 65.8887 62.2627 70.4974 64.9433C75.1061 67.6238 79.0719 71.2712 82.1188 75.6314C82.1188 75.6314 82.1441 75.6717 82.1593 75.6868C82.1808 75.717 82.1995 75.749 82.215 75.7825C82.2821 75.8717 82.3446 75.9641 82.4024 76.0595C82.4682 76.1653 82.534 76.4221 82.5999 76.5279C82.6657 76.6336 82.772 76.82 82.848 76.9711L83.1822 77.7063C83.6094 78.7595 83.8294 79.8844 83.8304 81.0201V81.0201Z\" fill=\"currentColor\" fill-opacity=\"0.22\"></path>
628
+ <path opacity=\"0.25\" d=\"M83.8303 81.015C83.8354 82.9297 83.2235 84.7956 82.0844 86.3393C80.9453 87.8829 79.339 89.0229 77.5008 89.5923C75.6627 90.1617 73.6895 90.1304 71.8706 89.5031C70.0516 88.8758 68.4826 87.6854 67.3935 86.1065L67.0796 85.6029C66.9412 85.3746 66.8146 85.1429 66.6998 84.9079L66.8821 85.3007C64.1353 81.222 60.4199 77.8797 56.0647 75.5695C51.7095 73.2593 46.8488 72.0524 41.9129 72.0558C31.75 72.0558 22.372 77.6408 16.9336 85.3309C17.1412 84.7467 17.7185 83.6086 17.9463 83.0395L19.1059 80.5214L19.1414 80.4509C22.1906 74.357 26.8837 69.2264 32.6961 65.6326C38.5086 62.0387 45.2114 60.1232 52.0555 60.1001C53.1949 60.1001 54.3241 60.1555 55.433 60.2562C60.7479 60.7594 65.8887 62.4087 70.4974 65.0893C75.1061 67.7698 79.0719 71.4172 82.1188 75.7775C82.1188 75.7775 82.1441 75.8177 82.1593 75.8328C82.1808 75.863 82.1995 75.895 82.215 75.9285C82.2821 76.0177 82.3446 76.1101 82.4024 76.2055L82.5999 76.5228C82.6859 76.6638 82.772 76.8149 82.848 76.966L83.1822 77.7012C83.6093 78.7544 83.8294 79.8793 83.8303 81.015Z\" fill=\"currentColor\" fill-opacity=\"0.22\"></path>
629
+ <path d=\"M42.1155 30.2056L35.3453 45.0218C35.2161 45.302 35.0189 45.5458 34.7714 45.7313C34.5239 45.9168 34.2338 46.0382 33.9274 46.0844C27.3926 47.1694 21.1567 49.5963 15.617 53.2105C15.279 53.4302 14.8783 53.5347 14.4753 53.5083C14.0723 53.4819 13.6889 53.326 13.3827 53.0641C13.0765 52.8022 12.8642 52.4485 12.7777 52.0562C12.6911 51.6638 12.7351 51.2542 12.9029 50.8889L32.2311 8.55046L33.6894 5.35254C32.8713 7.50748 32.9166 9.89263 33.816 12.0153L33.9983 12.4131L42.1155 30.2056Z\" fill=\"currentColor\" fill-opacity=\"0.22\"></path>
630
+ <path d=\"M82.812 76.8753C82.6905 76.694 82.3715 76.2207 82.2449 76.0444C82.2044 75.9739 82.2044 75.8782 82.1588 75.8127C82.1132 75.7473 82.1335 75.7724 82.1183 75.7573C79.0714 71.3971 75.1056 67.7497 70.4969 65.0692C65.8882 62.3886 60.7474 60.7393 55.4325 60.2361C54.3236 60.1354 53.1943 60.08 52.055 60.08C45.2173 60.1051 38.5214 62.022 32.7166 65.6161C26.9118 69.2102 22.2271 74.3397 19.1864 80.4308L19.151 80.5013C18.7358 81.3323 18.3458 82.1784 17.9914 83.0194L16.9786 85.2655C16.9077 85.3662 16.8419 85.472 16.771 85.5828C16.6647 85.7389 16.5584 85.9 16.4621 86.0612C15.3778 87.6439 13.8123 88.8397 11.995 89.4732C10.1776 90.1068 8.20406 90.1448 6.36344 89.5817C4.52281 89.0186 2.9119 87.884 1.76676 86.3442C0.621625 84.8044 0.00246102 82.9403 0 81.0251C0.00604053 80.0402 0.177178 79.0632 0.506372 78.1344L1.22036 76.5681C1.25084 76.5034 1.28639 76.4411 1.32669 76.3818C1.40265 76.2559 1.47861 76.135 1.56469 76.0192C1.58531 75.9789 1.60901 75.9401 1.63558 75.9034C7.06401 67.6054 14.947 61.1866 24.1977 57.5317C33.4485 53.8768 43.6114 53.166 53.2855 55.4971L48.9155 45.9286L41.9276 30.6188L33.8256 12.8263L33.6433 12.4285C32.7439 10.3058 32.6986 7.92067 33.5167 5.76573L34.0231 4.69304C34.8148 3.24136 35.9941 2.03525 37.431 1.20762C38.868 0.379997 40.5068 -0.0370045 42.1668 0.0025773C43.8268 0.0421591 45.4436 0.536787 46.839 1.43195C48.2345 2.32711 49.3543 3.58804 50.0751 5.07578L50.2523 5.47363L51.8474 8.96365L74.0974 57.708L82.812 76.8753Z\" fill=\"currentColor\" fill-opacity=\"0.22\"></path>
631
+ <path opacity=\"0.25\" d=\"M41.9129 30.649L35.3301 45.0422C35.2023 45.3204 35.0074 45.563 34.7627 45.7484C34.518 45.9337 34.2311 46.0562 33.9274 46.1048C27.3926 47.1897 21.1567 49.6166 15.617 53.2308C15.279 53.4505 14.8783 53.555 14.4753 53.5286C14.0723 53.5022 13.6889 53.3463 13.3827 53.0844C13.0765 52.8225 12.8642 52.4688 12.7777 52.0765C12.6911 51.6842 12.7351 51.2745 12.9029 50.9092L32.0285 8.99382L33.4869 5.7959C32.6687 7.95084 32.7141 10.336 33.6135 12.4586L33.7958 12.8565L41.9129 30.649Z\" fill=\"currentColor\" fill-opacity=\"0.22\"></path>
632
+ </svg>
633
+ ".gsub('"', '\"')
634
+ }\".html_safe %>"
441
635
 
442
636
  if ['index', 'show', 'new', 'update'].include?(args.first)
443
637
  poly_cols = []
@@ -520,31 +714,7 @@ document.querySelectorAll(\"input[type=submit][data-confirm]\").forEach(function
520
714
  });
521
715
  });
522
716
 
523
- function changeout(href, param, value, trimAfter) {
524
- var hrefParts = href.split(\"?\");
525
- var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
526
- if (param === undefined || param === null || param === -1) {
527
- hrefParts = hrefParts[0].split(\"://\");
528
- var pathParts = hrefParts[hrefParts.length - 1].split(\"/\").filter(function (pp) {return pp !== \"\";});
529
- if (value === undefined)
530
- // A couple possibilities if it's namespaced, starting with two parts in the path -- and then try just one
531
- return [pathParts.slice(1, 3).join('/'), pathParts.slice(1, 2)[0]];
532
- else {
533
- var queryString = param ? \"?\" + params.join(\"&\") : \"\";
534
- return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value + queryString;
535
- }
536
- }
537
- if (trimAfter) {
538
- var pathParts = hrefParts[0].split(\"/\");
539
- while (pathParts.lastIndexOf(trimAfter) !== pathParts.length - 1) pathParts.pop();
540
- hrefParts[0] = pathParts.join(\"/\");
541
- }
542
- params = params.reduce(function (s, v) { var parts = v.split(\"=\"); if (parts[1]) s[parts[0]] = parts[1]; return s; }, {});
543
- if (value === undefined) return params[param];
544
- params[param] = value;
545
- var finalParams = Object.keys(params).reduce(function (s, v) { if (params[v]) s.push(v + \"=\" + params[v]); return s; }, []).join(\"&\");
546
- return hrefParts[0] + (finalParams.length > 0 ? \"?\" + finalParams : \"\");
547
- }
717
+ #{JS_CHANGEOUT}
548
718
 
549
719
  // Snag first TR for sticky header
550
720
  var grid = document.getElementById(\"#{table_name}\");
@@ -730,11 +900,6 @@ erDiagram
730
900
  end
731
901
  inline = case args.first
732
902
  when 'index'
733
- obj_pk = if pk&.is_a?(Array) # Composite primary key?
734
- "#{pk.map { |pk_part| "#{obj_name}.#{pk_part}" }.join(', ')}" unless pk.empty?
735
- elsif pk
736
- "#{obj_name}.#{pk}"
737
- end
738
903
  if Object.const_defined?('DutyFree')
739
904
  template_link = "
740
905
  <%= link_to 'CSV', #{@_brick_model._brick_index}_path(format: :csv) %> &nbsp; <a href=\"#\" id=\"sheetsLink\">Sheets</a>
@@ -842,6 +1007,15 @@ erDiagram
842
1007
  <table id=\"resourceName\"><tr>
843
1008
  <td><h1>#{model_name}</h1></td>
844
1009
  <td id=\"imgErd\" title=\"Show ERD\"></td>
1010
+ <% if Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) %>
1011
+ <td><%= link_to_brick(
1012
+ avo_svg,
1013
+ { index_proc: Proc.new do |model|
1014
+ ::Avo.railtie_routes_url_helpers.send(\"resources_#\{model.base_class.model_name.route_key}_path\".to_sym)
1015
+ end,
1016
+ title: '#{model_name} in Avo' }
1017
+ ) %></td>
1018
+ <% end %>
845
1019
  </tr></table>#{template_link}<%
846
1020
  if description.present? %><%=
847
1021
  description %><br><%
@@ -875,149 +1049,36 @@ erDiagram
875
1049
  </script>
876
1050
  <% end %>
877
1051
  #{erd_markup}
878
- <table id=\"headerTop\"></table>
879
- <table id=\"#{table_name}\" class=\"shadow\">
880
- <thead><tr>#{"<th x-order=\"#{pk.join(',')}\"></th>" if pk.present?}<%=
881
- # Consider getting the name from the association -- hm.first.name -- if a more \"friendly\" alias should be used for a screwy table name
882
- cols = {#{hms_keys = []
883
- hms_headers.map do |hm|
884
- hms_keys << (assoc_name = (assoc = hm.first).name.to_s)
885
- "#{assoc_name.inspect} => [#{(assoc.options[:through] && !assoc.through_reflection).inspect}, #{assoc.klass.name}, #{hm[1].inspect}, #{hm[2].inspect}]"
886
- end.join(', ')}}
887
- # If the resource is missing, has the user simply created an inappropriately pluralised name for a table?
888
- @#{table_name} ||= if dym_list = instance_variables.reject do |entry|
889
- entry.to_s.start_with?('@_') ||
890
- ['@cache_hit', '@marked_for_same_origin_verification', '@view_renderer', '@view_flow', '@output_buffer', '@virtual_path'].include?(entry.to_s)
891
- end
892
- msg = \"Can't find resource \\\"#{table_name}\\\".\"
893
- # Can't be sure otherwise of what is up, so check DidYouMean and offer a suggestion.
894
- if (dym = DidYouMean::SpellChecker.new(dictionary: dym_list).correct('@#{table_name}')).present?
895
- msg << \"\nIf you meant \\\"#\{found_dym = dym.first[1..-1]}\\\" then to avoid this message add this entry into inflections.rb:\n\"
896
- msg << \" inflect.singular('#\{found_dym}', '#{obj_name}')\"
897
- puts
898
- puts \"WARNING: #\{msg}\"
899
- puts
900
- @#{table_name} = instance_variable_get(dym.first.to_sym)
901
- else
902
- raise ActiveRecord::RecordNotFound.new(msg)
1052
+
1053
+ <%= # Consider getting the name from the association -- hm.first.name -- if a more \"friendly\" alias should be used for a screwy table name
1054
+ cols = {#{hms_keys = []
1055
+ hms_headers.map do |hm|
1056
+ hms_keys << (assoc_name = (assoc = hm.first).name.to_s)
1057
+ "#{assoc_name.inspect} => [#{(assoc.options[:through] && !assoc.through_reflection).inspect}, #{assoc.klass.name}, #{hm[1].inspect}, #{hm[2].inspect}]"
1058
+ end.join(', ')}}
1059
+
1060
+ # If the resource is missing, has the user simply created an inappropriately pluralised name for a table?
1061
+ @#{table_name} ||= if dym_list = instance_variables.reject do |entry|
1062
+ entry.to_s.start_with?('@_') ||
1063
+ ['@cache_hit', '@marked_for_same_origin_verification', '@view_renderer', '@view_flow', '@output_buffer', '@virtual_path'].include?(entry.to_s)
903
1064
  end
904
- end
905
- col_keys = @#{table_name}.columns.each_with_object([]) do |col, s|
906
- col_name = col.name
907
- next if @_brick_incl&.exclude?(col_name) ||
908
- (#{(pk || []).inspect}.include?(col_name) && col.type == :integer && !bts.key?(col_name)) ||
909
- ::Brick.config.metadata_columns.include?(col_name) || poly_cols.include?(col_name)
910
-
911
- s << col_name
912
- cols[col_name] = col
913
- end
914
- unless @_brick_sequence # If no sequence is defined, start with all inclusions
915
- cust_cols = #{model_name}._br_cust_cols
916
- # HOT columns, kept as symbols
917
- hots = #{model_name}._br_bt_descrip.keys.select { |k| bts.key?(k) }
918
- @_brick_sequence = col_keys + cust_cols.keys + hots + #{(hms_keys).inspect}.reject { |assoc_name| @_brick_incl&.exclude?(assoc_name) }
919
- end
920
- @_brick_sequence.reject! { |nm| @_brick_excl.include?(nm) } if @_brick_excl # Reject exclusions
921
- @_brick_sequence.each_with_object(+'') do |col_name, s|
922
- if (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
923
- s << '<th'
924
- s << \" title=\\\"#\{col.comment}\\\"\" if col.respond_to?(:comment) && !col.comment.blank?
925
- s << if (bt = bts[col_name])
926
- # Allow sorting for any BT except polymorphics
927
- \"#\{' x-order=\"' + bt.first.to_s + '\"' unless bt[2]}>BT \" +
928
- bt[1].map { |bt_pair| bt_pair.first.bt_link(bt.first) }.join(' ')
929
- else # Normal column
930
- \"#\{' x-order=\"' + col_name + '\"' if true}>#\{col_name}\"
931
- end
932
- elsif col # HM column
933
- s << \"<th#\{' x-order=\"' + col_name + '\"' if true}>#\{col[2]} \"
934
- s << (col.first ? \"#\{col[3]}\" : \"#\{link_to(col[3], send(\"#\{col[1]._brick_index}_path\"))}\")
935
- elsif cust_cols.key?(col_name) # Custom column
936
- s << \"<th x-order=\\\"#\{col_name}\\\">#\{col_name}\"
937
- elsif col_name.is_a?(Symbol) && (hot = bts[col_name]) # has_one :through
938
- s << \"<th x-order=\\\"#\{hot.first.to_s}\\\">HOT \" +
939
- hot[1].map { |hot_pair| hot_pair.first.bt_link(col_name) }.join(' ')
940
- hot[1].first
941
- else # Bad column name!
942
- s << \"<th title=\\\"<< Unknown column >>\\\">#\{col_name}\"
943
- end
944
- s << '</th>'
945
- end.html_safe
946
- %></tr></thead>
947
- <tbody>
948
- <% # %%% Have once gotten this error with MSSQL referring to http://localhost:3000/warehouse/cold_room_temperatures__archive
949
- # ActiveRecord::StatementTimeout in Warehouse::ColdRoomTemperatures_Archive#index
950
- # TinyTds::Error: Adaptive Server connection timed out
951
- # (After restarting the server it worked fine again.)
952
- @#{table_name}.each do |#{obj_name}|
953
- hms_cols = {#{hms_columns.join(', ')}} %>
954
- <tr>#{"
955
- <td><%= link_to '⇛', #{path_obj_name}_path(slashify(#{obj_pk})), { class: 'big-arrow' } %></td>" if obj_pk}
956
- <% @_brick_sequence.each do |col_name|
957
- val = #{obj_name}.attributes[col_name] %>
958
- <td<%= ' class=\"dimmed\"'.html_safe unless cols.key?(col_name) || (cust_col = cust_cols[col_name]) ||
959
- (col_name.is_a?(Symbol) && bts.key?(col_name)) # HOT
960
- %>><%
961
- if (bt = bts[col_name])
962
- if bt[2] # Polymorphic?
963
- bt_class = #{obj_name}.send(\"#\{bt.first}_type\")
964
- base_class_underscored = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class._brick_index(:singular)
965
- poly_id = #{obj_name}.send(\"#\{bt.first}_id\")
966
- %><%= link_to(\"#\{bt_class} ##\{poly_id}\", send(\"#\{base_class_underscored}_path\".to_sym, poly_id)) if poly_id %><%
967
- else # BT or HOT
968
- bt_class = bt[1].first.first
969
- descrips = @_brick_bt_descrip[bt.first][bt_class]
970
- bt_id_col = if descrips.nil?
971
- puts \"Caught it in the act for #{obj_name} / #\{col_name}!\"
972
- # binding.pry
973
- elsif descrips.length == 1
974
- [#{obj_name}.class.reflect_on_association(bt.first)&.foreign_key]
1065
+ msg = \"Can't find resource \\\"#{table_name}\\\".\"
1066
+ # Can't be sure otherwise of what is up, so check DidYouMean and offer a suggestion.
1067
+ if (dym = DidYouMean::SpellChecker.new(dictionary: dym_list).correct('@#{table_name}')).present?
1068
+ msg << \"\nIf you meant \\\"#\{found_dym = dym.first[1..-1]}\\\" then to avoid this message add this entry into inflections.rb:\n\"
1069
+ msg << \" inflect.singular('#\{found_dym}', '#{obj_name}')\"
1070
+ puts
1071
+ puts \"WARNING: #\{msg}\"
1072
+ puts
1073
+ @#{table_name} = instance_variable_get(dym.first.to_sym)
975
1074
  else
976
- descrips.last
1075
+ raise ActiveRecord::RecordNotFound.new(msg)
977
1076
  end
978
- bt_txt = bt_class.brick_descrip(
979
- # 0..62 because Postgres column names are limited to 63 characters
980
- #{obj_name}, descrips[0..-2].map { |id| #{obj_name}.send(id.last[0..62]) }, bt_id_col
981
- )
982
- bt_txt ||= \"<span class=\\\"orphan\\\">&lt;&lt; Orphaned ID: #\{val} >></span>\".html_safe if val
983
- bt_id = bt_id_col&.map { |id_col| #{obj_name}.respond_to?(id_sym = id_col.to_sym) ? #{obj_name}.send(id_sym) : id_col } %>
984
- <%= bt_id&.first ? link_to(bt_txt, send(\"#\{bt_class.base_class._brick_index(:singular)}_path\".to_sym, bt_id)) : bt_txt %>
985
- <% end
986
- elsif (hms_col = hms_cols[col_name])
987
- if hms_col.length == 1 %>
988
- <%= hms_col.first %>
989
- <% else
990
- %><%= klass = (col = cols[col_name])[1]
991
- if col[2] == 'HO'
992
- descrips = @_brick_bt_descrip[col_name.to_sym][klass]
993
- if (ho_id = (ho_id_col = descrips.last).map { |id_col| #{obj_name}.send(id_col.to_sym) })&.first
994
- ho_txt = klass.brick_descrip(#{obj_name}, descrips[0..-2].map { |id| #{obj_name}.send(id.last[0..62]) }, ho_id_col)
995
- link_to(ho_txt, send(\"#\{klass.base_class._brick_index(:singular)}_path\".to_sym, ho_id))
996
- end
997
- elsif hms_col[1]&.positive?
998
- link_to \"#\{hms_col[1] || 'View'} #\{hms_col.first}\", send(\"#\{klass._brick_index}_path\".to_sym, hms_col[2])
999
- end %>
1000
- <% end
1001
- elsif (col = cols[col_name])
1002
- col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
1003
- %><%= display_value(col_type || col&.sql_type, val) %><%
1004
- elsif cust_col
1005
- data = cust_col.first.map { |cc_part| #{obj_name}.send(cc_part.last) }
1006
- cust_txt = #{model_name}.brick_descrip(cust_col[-2], data)
1007
- if (link_id = #{obj_name}.send(cust_col.last[1]) if cust_col.last)
1008
- %><%= link_to(cust_txt, send(\"#\{cust_col.last.first._brick_index(:singular)}_path\", link_id)) %><%
1009
- else
1010
- %><%= cust_txt %><%
1011
- end
1012
- else # Bad column name!
1013
- %>?<%
1014
- end
1015
- %></td>
1016
- <% end %>
1017
- </tr>
1018
- <% end %>
1019
- </tbody>
1020
- </table>
1077
+ end
1078
+
1079
+ # Write out the mega-grid
1080
+ brick_grid(@#{table_name}, @_brick_bt_descrip, @_brick_sequence, @_brick_incl, @_brick_excl,
1081
+ cols, poly_cols, bts, #{hms_keys.inspect}, {#{hms_columns.join(', ')}}) %>
1021
1082
 
1022
1083
  #{"<hr><%= link_to \"New #{obj_name}\", new_#{path_obj_name}_path %>" unless @_brick_model.is_view?}
1023
1084
  #{script}
@@ -1125,7 +1186,18 @@ erDiagram
1125
1186
  <p style=\"color: green\"><%= notice %></p>#{"
1126
1187
  #{schema_options}" if schema_options}
1127
1188
  <select id=\"tbl\">#{table_options}</select>
1128
- <h1><%= page_title %></h1><%
1189
+ <table><td><h1><%= page_title %></h1></td>
1190
+ <% if Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) %>
1191
+ <td><%= link_to_brick(
1192
+ avo_svg,
1193
+ { show_proc: Proc.new do |obj|
1194
+ ::Avo.railtie_routes_url_helpers.send(\"resources_#\{obj.class.base_class.model_name.singular_route_key}_path\".to_sym, obj)
1195
+ end,
1196
+ title: \"#\{page_title} in Avo\" }
1197
+ ) %></td>
1198
+ <% end %>
1199
+ </table>
1200
+ <%
1129
1201
  if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
1130
1202
  description %><br><%
1131
1203
  end
@@ -0,0 +1,219 @@
1
+ module Brick::Rails::FormTags
2
+ # Our super speedy grid
3
+ def brick_grid(relation, bt_descrip, sequence = nil, inclusions, exclusions,
4
+ cols, poly_cols, bts, hms_keys, hms_cols)
5
+ out = "<table id=\"headerTop\"></table>
6
+ <table id=\"#{relation.table_name}\" class=\"shadow\">
7
+ <thead><tr>"
8
+ pk = (klass = relation.klass).primary_key || []
9
+ pk = [pk] unless pk.is_a?(Array)
10
+ if pk.present?
11
+ out << "<th x-order=\"#{pk.join(',')}\"></th>"
12
+ end
13
+
14
+ col_keys = relation.columns.each_with_object([]) do |col, s|
15
+ col_name = col.name
16
+ next if inclusions&.exclude?(col_name) ||
17
+ (pk.include?(col_name) && [:integer, :uuid].include?(col.type) && !bts.key?(col_name)) ||
18
+ ::Brick.config.metadata_columns.include?(col_name) || poly_cols.include?(col_name)
19
+
20
+ s << col_name
21
+ cols[col_name] = col
22
+ end
23
+ unless sequence # If no sequence is defined, start with all inclusions
24
+ cust_cols = klass._br_cust_cols
25
+ # HOT columns, kept as symbols
26
+ hots = klass._br_bt_descrip.keys.select { |k| bts.key?(k) }
27
+ sequence = col_keys + cust_cols.keys + hots + hms_keys.reject { |assoc_name| inclusions&.exclude?(assoc_name) }
28
+ end
29
+ sequence.reject! { |nm| exclusions.include?(nm) } if exclusions
30
+ out << sequence.each_with_object(+'') do |col_name, s|
31
+ if (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
32
+ s << '<th'
33
+ s << " title=\"#{col.comment}\"" if col.respond_to?(:comment) && !col.comment.blank?
34
+ s << if (bt = bts[col_name])
35
+ # Allow sorting for any BT except polymorphics
36
+ "#{' x-order="' + bt.first.to_s + '"' unless bt[2]}>BT " +
37
+ bt[1].map { |bt_pair| bt_pair.first.bt_link(bt.first) }.join(' ')
38
+ else # Normal column
39
+ "#{' x-order="' + col_name + '"' if true}>#{col_name}"
40
+ end
41
+ elsif col # HM column
42
+ 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"))}")
44
+ elsif cust_cols.key?(col_name) # Custom column
45
+ s << "<th x-order=\"#{col_name}\">#{col_name}"
46
+ elsif col_name.is_a?(Symbol) && (hot = bts[col_name]) # has_one :through
47
+ s << "<th x-order=\"#{hot.first.to_s}\">HOT " +
48
+ hot[1].map { |hot_pair| hot_pair.first.bt_link(col_name) }.join(' ')
49
+ hot[1].first
50
+ else # Bad column name!
51
+ s << "<th title=\"<< Unknown column >>\">#{col_name}"
52
+ end
53
+ s << '</th>'
54
+ end
55
+ out << "</tr></thead>
56
+ <tbody>"
57
+ # %%% Have once gotten this error with MSSQL referring to http://localhost:3000/warehouse/cold_room_temperatures__archive
58
+ # ActiveRecord::StatementTimeout in Warehouse::ColdRoomTemperatures_Archive#index
59
+ # TinyTds::Error: Adaptive Server connection timed out
60
+ # (After restarting the server it worked fine again.)
61
+ relation.each do |obj|
62
+ out << "<tr>\n"
63
+ out << "<td>#{link_to('⇛', send("#{klass._brick_index(:singular)}_path".to_sym,
64
+ pk.map { |pk_part| obj.send(pk_part.to_sym) }), { class: 'big-arrow' })}</td>\n" if pk.present?
65
+ sequence.each do |col_name|
66
+ val = obj.attributes[col_name]
67
+ out << '<td'
68
+ out << ' class=\"dimmed\"' unless cols.key?(col_name) || (cust_col = cust_cols[col_name]) ||
69
+ (col_name.is_a?(Symbol) && bts.key?(col_name)) # HOT
70
+ out << '>'
71
+ if (bt = bts[col_name])
72
+ if bt[2] # Polymorphic?
73
+ bt_class = obj.send("#{bt.first}_type")
74
+ base_class_underscored = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class._brick_index(:singular)
75
+ poly_id = obj.send("#{bt.first}_id")
76
+ out << link_to("#{bt_class} ##{poly_id}", send("#{base_class_underscored}_path".to_sym, poly_id)) if poly_id
77
+ else # BT or HOT
78
+ bt_class = bt[1].first.first
79
+ descrips = bt_descrip[bt.first][bt_class]
80
+ bt_id_col = if descrips.nil?
81
+ puts "Caught it in the act for obj / #{col_name}!"
82
+ elsif descrips.length == 1
83
+ [obj.class.reflect_on_association(bt.first)&.foreign_key]
84
+ else
85
+ descrips.last
86
+ end
87
+ bt_txt = bt_class.brick_descrip(
88
+ # 0..62 because Postgres column names are limited to 63 characters
89
+ obj, descrips[0..-2].map { |id| obj.send(id.last[0..62]) }, bt_id_col
90
+ )
91
+ bt_txt ||= "<span class=\"orphan\">&lt;&lt; Orphaned ID: #{val} >></span>" if val
92
+ bt_id = bt_id_col&.map { |id_col| obj.respond_to?(id_sym = id_col.to_sym) ? obj.send(id_sym) : id_col }
93
+ out << (bt_id&.first ? link_to(bt_txt, send("#{bt_class.base_class._brick_index(:singular)}_path".to_sym, bt_id)) : bt_txt || '')
94
+ end
95
+ elsif (hms_col = hms_cols[col_name])
96
+ if hms_col.length == 1
97
+ out << hms_col.first
98
+ else
99
+ klass = (col = cols[col_name])[1]
100
+ if col[2] == 'HO'
101
+ descrips = bt_descrip[col_name.to_sym][klass]
102
+ 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
+ end
106
+ else
107
+ if (ct = obj.send(hms_col[1].to_sym)&.to_i)&.positive?
108
+ out << "#{link_to("#{ct || 'View'} #{hms_col.first}",
109
+ send("#{klass._brick_index}_path".to_sym,
110
+ hms_col[2].each_with_object({}) { |v, s| s[v.first] = v.last.is_a?(String) ? v.last : obj.send(v.last) })
111
+ )}\n"
112
+ end
113
+ end
114
+ end
115
+ elsif (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
116
+ binding.pry if col.is_a?(Array)
117
+ col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
118
+ out << display_value(col_type || col&.sql_type, val).to_s
119
+ elsif cust_col
120
+ data = cust_col.first.map { |cc_part| obj.send(cc_part.last) }
121
+ cust_txt = klass.brick_descrip(cust_col[-2], data)
122
+ if (link_id = obj.send(cust_col.last[1]) if cust_col.last)
123
+ out << link_to(cust_txt, send("#{cust_col.last.first._brick_index(:singular)}_path", link_id))
124
+ else
125
+ out << cust_txt
126
+ end
127
+ else # Bad column name!
128
+ out << '?'
129
+ end
130
+ out << '</td>'
131
+ end
132
+ out << '</tr>'
133
+ end
134
+ out << " </tbody>
135
+ </table>
136
+ "
137
+ out.html_safe
138
+ end # brick_grid
139
+
140
+ def link_to_brick(*args, **kwargs)
141
+ return unless ::Brick.config.mode == :on
142
+
143
+ text = ((args.first.is_a?(String) || args.first.is_a?(Proc)) && args.shift) || args[1]
144
+ text = text.call if text.is_a?(Proc)
145
+ klass_or_obj = ((args.first.is_a?(ActiveRecord::Relation) ||
146
+ args.first.is_a?(ActiveRecord::Base) ||
147
+ args.first.is_a?(Class)) &&
148
+ args.first) ||
149
+ @_brick_model
150
+ # If not provided, do a best-effort to automatically determine the resource class or object
151
+ filter_parts = []
152
+ klass_or_obj ||= begin
153
+ klass, sti_type = ::Brick.ctrl_to_klass(controller_path)
154
+ if klass
155
+ type_col = klass.inheritance_column # Usually 'type'
156
+ filter_parts << "#{type_col}=#{sti_type}" if sti_type && klass.column_names.include?(type_col)
157
+ path_params = request.path_parameters.dup
158
+ path_params.delete(:controller)
159
+ path_params.delete(:action)
160
+ pk = (klass.primary_key || ActiveRecord::Base.primary_key).to_sym
161
+ # Used to also have this but it's a bit too permissive to identify a primary key: (path_params.length == 1 && path_params.values.first) ||
162
+ if ((id = (path_params[pk] || path_params[:id] || path_params["#{klass.name.underscore}_id".to_sym])) && (obj = klass.find_by(pk => id))) ||
163
+ (['show', 'edit', 'update', 'destroy'].include?(action_name) && (obj = klass.first))
164
+ obj
165
+ else
166
+ # %%% If there is a HMT that refers to some ___id then try to identify an appropriate filter
167
+ # %%% If there is a polymorphic association that might relate to stuff in the path_params,
168
+ # try to identify an appropriate ___able_id and ___able_type filter
169
+ ((klass.column_names - [pk.to_s]) & path_params.keys.map(&:to_s)).each do |path_param|
170
+ filter_parts << "#{path_param}=#{path_params[path_param.to_sym]}"
171
+ end
172
+ klass
173
+ end
174
+ end
175
+ rescue
176
+ end
177
+ if klass_or_obj
178
+ if klass_or_obj.is_a?(ActiveRecord::Relation)
179
+ klass_or_obj.where_values_hash.each do |whr|
180
+ filter_parts << "#{whr.first}=#{whr.last}" if whr.last && !whr.last.is_a?(Array)
181
+ end
182
+ klass_or_obj = klass_or_obj.klass
183
+ type_col = klass_or_obj.inheritance_column
184
+ if klass_or_obj.column_names.include?(type_col) && klass_or_obj.name != klass_or_obj.base_class.name
185
+ filter_parts << "#{type_col}=#{klass_or_obj.name}"
186
+ end
187
+ end
188
+ filter = "?#{filter_parts.join('&')}" if filter_parts.present?
189
+ if (klass_or_obj&.is_a?(Class) && klass_or_obj < ActiveRecord::Base) ||
190
+ (klass_or_obj&.is_a?(ActiveRecord::Base) && klass_or_obj.new_record? && (klass_or_obj = klass_or_obj.class))
191
+ path = (proc = kwargs[:index_proc]) ? proc.call(klass_or_obj) : "#{send("#{klass_or_obj.base_class._brick_index}_path")}#{filter}"
192
+ lt_args = [text || "Index for #{klass_or_obj.name.pluralize}", path]
193
+ else
194
+ # 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
195
+ 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}"
196
+ lt_args = [text || "Show this #{klass_or_obj.class.name}", path]
197
+ end
198
+ link_to(*lt_args, **kwargs)
199
+ else
200
+ # puts "Warning: link_to_brick could not find a class for \"#{controller_path}\" -- consider setting @_brick_model within that controller."
201
+ # if (hits = res_names.keys & instance_variables.map { |v| v.to_s[1..-1] }).present?
202
+ links = instance_variables.each_with_object(Hash.new { |h, k| h[k] = [] }) do |name, s|
203
+ iv_name = name.to_s[1..-1]
204
+ case (val = instance_variable_get(name))
205
+ when ActiveRecord::Relation
206
+ s[val.klass] << iv_name
207
+ when ActiveRecord::Base
208
+ s[val] << iv_name
209
+ end
210
+ end
211
+ if links.length == 1 # If there's only one match then use any text that was supplied
212
+ link_to_brick(text || links.first.last.join('/'), links.first.first, **kwargs)
213
+ else
214
+ links.map { |k, v| link_to_brick(v.join('/'), v, **kwargs) }.join(' &nbsp; ').html_safe
215
+ end
216
+ end
217
+ end # link_to_brick
218
+
219
+ end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 97
8
+ TINY = 98
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
@@ -138,15 +138,16 @@ module Brick
138
138
  def set_db_schema(params = nil)
139
139
  # If Apartment::Tenant.current is not still the default (usually 'public') then an elevator has brought us into
140
140
  # a different tenant. If so then don't allow schema navigation.
141
- chosen = if (is_show_schema_list = (apartment_multitenant && Apartment::Tenant.current == ::Brick.default_schema)) &&
141
+ chosen = if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' &&
142
+ (current_schema = (ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2]
143
+ .split(',') - ['pg_catalog', 'pg_toast', 'heroku_ext']).first) &&
144
+ (is_show_schema_list = (apartment_multitenant && current_schema == ::Brick.default_schema)) &&
142
145
  (schema = (params ? params['_brick_schema'] : ::Brick.default_schema)) &&
143
146
  ::Brick.db_schemas&.key?(schema)
144
147
  Apartment::Tenant.switch!(schema)
145
148
  schema
146
- elsif ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
147
- # Just return the current schema
148
- current_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
149
- (current_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first
149
+ else
150
+ current_schema # Just return the current schema
150
151
  end
151
152
  [chosen == ::Brick.default_schema ? nil : chosen, is_show_schema_list]
152
153
  end
@@ -1262,8 +1263,8 @@ module ActiveRecord
1262
1263
  # entry in your .select().
1263
1264
  # More information: https://discuss.rubyonrails.org/t/includes-and-select-for-joined-data/81640
1264
1265
  def apply_column_aliases(relation)
1265
- used_cols = {}
1266
1266
  if (sel_vals = relation.select_values.map(&:to_s)).first == '_brick_eager_load'
1267
+ used_cols = {}
1267
1268
  # Find and expand out all column names being used in select(...)
1268
1269
  new_select_values = sel_vals.each_with_object([]) do |col, s|
1269
1270
  next if col == '_brick_eager_load'
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.97
4
+ version: 1.0.98
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-11-25 00:00:00.000000000 Z
11
+ date: 2022-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -216,6 +216,7 @@ files:
216
216
  - lib/brick/frameworks/rails/controller.rb
217
217
  - lib/brick/frameworks/rails/crosstab.brk
218
218
  - lib/brick/frameworks/rails/engine.rb
219
+ - lib/brick/frameworks/rails/form_tags.rb
219
220
  - lib/brick/frameworks/rspec.rb
220
221
  - lib/brick/join_array.rb
221
222
  - lib/brick/serializers/json.rb