brick 1.0.213 → 1.0.215

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: 02a0436d0c71a40e1aa3858e4fdf20822f32b02864e621964bce288b8b21ad60
4
- data.tar.gz: 8257218e3c1e9c26842753590e627802842c67dec93891aec7139fd2f31d67c0
3
+ metadata.gz: 53a4013353cb1918fd78cd37988cb934a83be74305a3351ede0082e95ce971db
4
+ data.tar.gz: 8cee272dceee0c0938675e999f36ad3df9d833d0b11a62c07a595cd1664dd21b
5
5
  SHA512:
6
- metadata.gz: 222b736da74b699a1f352ffff9f087f39048fceaf995dc28733823b4d38271c0bc04ecba506fb6fd027a7f39883009baede3d9c70f81a46dc023f692d6c831e4
7
- data.tar.gz: bdee88e65dad07aa66b6ce9458a44ba8d1ad069b937d5bceae65f020162107336b6d7619aee03ed4c56c25a3e38b4c17ce0352677883426802dfe28ac3c8567a
6
+ metadata.gz: 99ad38c47011412ed7d145f4315c8e971ceab2d301d2a84be35471313fcc73686dec7c105aede9ef33b7202c16e50878139e753dd0cc3b0359cf84db5d8ad033
7
+ data.tar.gz: f3b9d54daeb4d49d3abddf50a4d267a315ea280a4b3f59d812107d25b46d5c13be0fe1f83d518cd6f91e2a248e87bc65dd35d18e3430fc5a44f2fd1bf2e32f4e
data/lib/brick/config.rb CHANGED
@@ -429,19 +429,6 @@ module Brick
429
429
  end
430
430
  end
431
431
 
432
- def acts_as_list_cols
433
- @mutex.synchronize { @acts_as_list || {} }
434
- end
435
-
436
- # Get something like:
437
- # { 'on_call_list' => { _brick_default: [:last_name, :first_name] } }
438
- # { 'on_call_list' => { _brick_default: :sequence } }
439
- def acts_as_list_cols=(position_cols)
440
- @mutex.synchronize do
441
- @acts_as_list ||= position_cols
442
- end
443
- end
444
-
445
432
  def metadata_columns
446
433
  @mutex.synchronize { @metadata_columns ||= ['created_at', 'updated_at', 'deleted_at'] }
447
434
  end
@@ -71,15 +71,24 @@ module ActiveRecord
71
71
  end
72
72
 
73
73
  def real_model(params)
74
- if params && (sub_model = params.fetch(type_col = inheritance_column, nil))
75
- sub_model = sub_model.first if sub_model.is_a?(Array) # Support the params style that gets returned from #_brick_querying
74
+ if params && ((sub_name = params.fetch(inheritance_column, nil)).present? ||
75
+ (sub_name = params[name.underscore]&.fetch(inheritance_column, nil)))
76
+ sub_name = sub_name.first if sub_name.is_a?(Array) # Support the params style that gets returned from #_brick_querying
76
77
  # Make sure the chosen model is really the same or a subclass of this model
77
- (possible_model = sub_model.constantize) <= self ? possible_model : self
78
+ return self if sub_name.blank?
79
+
80
+ (possible_model = sub_name.constantize) <= self ? possible_model : self
78
81
  else
79
82
  self
80
83
  end
81
84
  end
82
85
 
86
+ # Accommodate STI
87
+ def real_singular(params)
88
+ real_model = real_model(params)
89
+ [real_model, real_model.name.underscore.split('/').last]
90
+ end
91
+
83
92
  def json_column?(col)
84
93
  col.type == :json || ::Brick.config.json_columns[table_name]&.include?(col.name) ||
85
94
  (
@@ -196,33 +205,6 @@ module ActiveRecord
196
205
  def _brick_monetized_attributes
197
206
  @_brick_monetized_attributes ||= respond_to?(:monetized_attributes) ? monetized_attributes.values : {}
198
207
  end
199
-
200
- # def acts_as_list(aal_cols = nil)
201
- # if aal_cols
202
- # aal_cols = [aal_cols] unless aal_cols.is_a?(Array)
203
- # @acts_as_list_cols = aal_cols.each_with_object([]) do |aal_col, s|
204
- # if column_names.include?(aal_col = aal_col.to_s) && !s.include?(aal_col)
205
- # s << aal_col
206
- # end
207
- # end
208
- # else
209
- # if [:integer, :bigint].include?(columns_hash['position']&.type)
210
- # @acts_as_list_cols = ['position']
211
- # else
212
- # return
213
- # end
214
- # end
215
- # # Override save in order to update neighbours when necessary
216
- # alias _brick_save save
217
- # def save
218
- # # @acts_as_list_cols
219
- # # -1
220
- # @acts_as_list_cols.each do |aal_col|
221
- # binding.pry if (aal_change = changes[aal_col])
222
- # end
223
- # _brick_save
224
- # end
225
- # end
226
208
  end
227
209
 
228
210
  def self.brick_parse_dsl(join_array = nil, prefix = [], translations = {}, is_polymorphic = false, dsl = nil, emit_dsl = false)
@@ -1765,7 +1747,9 @@ class Object
1765
1747
  end
1766
1748
  # Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
1767
1749
  code << " self.table_name = '#{self.table_name = matching}'\n" if (inheritable_name || model_name).underscore.pluralize != matching
1768
- if (inh_col = ::Brick.config.sti_type_column.find { |_k, v| v.include?(matching) }&.first)
1750
+
1751
+ if (inh_col = relation.fetch(:sti_col, nil) ||
1752
+ ::Brick.config.sti_type_column.find { |_k, v| v.include?(matching) }&.first)
1769
1753
  new_model_class.inheritance_column = inh_col
1770
1754
  code << " self.inheritance_column = '#{inh_col}'\n"
1771
1755
  end
@@ -1816,11 +1800,6 @@ class Object
1816
1800
  end
1817
1801
  end
1818
1802
 
1819
- if (sti_col = relation.fetch(:sti_col, nil))
1820
- new_model_class.send(:'inheritance_column=', sti_col)
1821
- code << " self.inheritance_column = #{sti_col.inspect}\n"
1822
- end
1823
-
1824
1803
  unless is_sti
1825
1804
  fks = relation[:fks] || {}
1826
1805
  # Do the bulk of the has_many / belongs_to processing, and store details about HMT so they can be done at the very last
@@ -1902,12 +1881,6 @@ class Object
1902
1881
  end
1903
1882
  end
1904
1883
 
1905
- # Apply any acts_as_list things
1906
- if (aal_col = ::Brick.config.acts_as_list_cols.fetch(matching, nil))
1907
- new_model_class.send(:acts_as_list, aal_col.to_sym)
1908
- code << " acts_as_list :#{aal_col}\n"
1909
- end
1910
-
1911
1884
  # Auto-support Ransack if it's present
1912
1885
  if self.respond_to?(:ransackable_attributes)
1913
1886
  def self.ransackable_attributes(auth_object = nil)
@@ -2480,13 +2453,15 @@ class Object
2480
2453
  code << " @#{plural_table_name}._brick_querying(params, brick_col_names: true)\n"
2481
2454
  code << " end\n"
2482
2455
 
2483
- is_pk_string = nil
2456
+ # ----------------------------------------------------------------------------------
2457
+
2484
2458
  if pk.present?
2485
2459
  code << " def show\n"
2486
2460
  code << " #{find_by_name = "find_#{singular_table_name}"}\n"
2487
2461
  code << " end\n"
2488
2462
  self.define_method :show do
2489
2463
  _schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
2464
+ _, singular_table_name = model.real_singular(params)
2490
2465
  instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
2491
2466
  add_csp_hash("'unsafe-inline'")
2492
2467
  end
@@ -2505,7 +2480,8 @@ class Object
2505
2480
  send(params_name_sym)
2506
2481
  rescue
2507
2482
  end
2508
- new_params ||= model.attribute_names.each_with_object({}) do |a, s|
2483
+ real_model, singular_table_name = model.real_singular(params)
2484
+ new_params ||= real_model.attribute_names.each_with_object({}) do |a, s|
2509
2485
  if (val = params["__#{a}"])
2510
2486
  # val = case new_obj.class.column_for_attribute(a).type
2511
2487
  # when :datetime, :date, :time, :timestamp
@@ -2516,7 +2492,7 @@ class Object
2516
2492
  s[a] = val
2517
2493
  end
2518
2494
  end
2519
- if (new_obj = model.new(new_params)).respond_to?(:serializable_hash)
2495
+ if (new_obj = real_model.new(new_params)).respond_to?(:serializable_hash)
2520
2496
  # Convert any Filename objects with nil into an empty string so that #encode can be called on them
2521
2497
  new_obj.serializable_hash.each do |k, v|
2522
2498
  new_obj.send("#{k}=", ::ActiveStorage::Filename.new('')) if v.is_a?(::ActiveStorage::Filename) && !v.instance_variable_get(:@filename)
@@ -2542,12 +2518,20 @@ class Object
2542
2518
  end
2543
2519
  render json: { result: ::Brick.unexclude_column(table_name, col) }
2544
2520
  else
2545
- created_obj = model.send(:create, send(params_name_sym))
2546
- @_lookup_context.instance_variable_set(:@_brick_model, model)
2521
+ real_model = model.real_model(params)
2522
+ singular_table_name = real_model.name.underscore.split('/').last
2523
+ created_obj = model.send(:new, send(params_name_sym))
2524
+ if created_obj.respond_to?(inh_col = model.inheritance_column) && created_obj.send(inh_col) == ''
2525
+ created_obj.send("#{inh_col}=", model.name)
2526
+ end
2527
+ created_obj.save
2528
+ @_lookup_context.instance_variable_set(:@_brick_model, real_model)
2547
2529
  if created_obj.errors.empty?
2530
+ instance_variable_set("@#{singular_table_name}".to_sym, created_obj)
2548
2531
  index
2549
2532
  render :index
2550
2533
  else # Surface errors to the user in a flash message
2534
+ instance_variable_set("@#{singular_table_name}".to_sym, created_obj)
2551
2535
  flash.now.alert = (created_obj.errors.errors.map { |err| "<b>#{err.attribute}</b> #{err.message}" }.join(', '))
2552
2536
  new
2553
2537
  render :new
@@ -2566,6 +2550,7 @@ class Object
2566
2550
  code << " end\n"
2567
2551
  self.define_method :edit do
2568
2552
  _schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
2553
+ _, singular_table_name = model.real_singular(params)
2569
2554
  instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
2570
2555
  add_csp_hash
2571
2556
  end
@@ -2593,6 +2578,7 @@ class Object
2593
2578
  # return
2594
2579
  end
2595
2580
 
2581
+ _, singular_table_name = model.real_singular(params)
2596
2582
  instance_variable_set("@#{singular_table_name}".to_sym, (obj = find_obj))
2597
2583
  upd_params = send(params_name_sym)
2598
2584
  json_overrides = ::Brick.config.json_columns&.fetch(table_name, nil)
@@ -2679,11 +2665,11 @@ class Object
2679
2665
  if is_need_params
2680
2666
  code << " def #{params_name}\n"
2681
2667
  permits_txt = model._brick_find_permits(model, permits = model._brick_all_fields(true))
2682
- code << " params.require(:#{require_name = model.name.underscore.tr('/', '_')
2668
+ code << " params.require(:#{model.base_class.name.underscore.tr('/', '_')
2683
2669
  }).permit(#{permits_txt.map(&:inspect).join(', ')})\n"
2684
2670
  code << " end\n"
2685
2671
  self.define_method(params_name) do
2686
- params.require(require_name.to_sym).permit(permits)
2672
+ params.require(model.base_class.name.underscore.tr('/', '_').to_sym).permit(permits)
2687
2673
  end
2688
2674
  private params_name
2689
2675
  # Get column names for params from relations[model.table_name][:cols].keys
@@ -2978,7 +2964,9 @@ module Brick
2978
2964
  else
2979
2965
  inverse_table = [primary_table] if polymorphic_class
2980
2966
  assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
2981
- assoc_bt[:optional] = true if is_optional
2967
+ assoc_bt[:optional] = true if (is_optional ||
2968
+ (is_optional.nil? && !relations[fk[1]][:cols][fk[2]][3])
2969
+ ) && ActiveRecord.version >= ::Gem::Version.new('5.0')
2982
2970
  assoc_bt[:polymorphic] = [polymorphic_class] if polymorphic_class
2983
2971
  end
2984
2972
  if is_class
@@ -292,9 +292,10 @@ function linkSchemas() {
292
292
  if is_2x # Avo 2.x?
293
293
  "::#{class_name}Resource".constantize
294
294
  else # Avo 3.x
295
- unless ::Avo::BaseResource.constants.include?(class_name.to_sym) ||
296
- ::Avo::Resources.constants.include?(class_name.to_sym)
297
- ::Brick.avo_3x_resource(Object.const_get(class_name), class_name)
295
+ if ::Avo::BaseResource.constants.exclude?(class_name.to_sym) &&
296
+ ::Avo::Resources.constants.exclude?(class_name.to_sym) &&
297
+ (klass = Object.const_get(class_name)).is_a?(Class)
298
+ ::Brick.avo_3x_resource(klass, class_name)
298
299
  end
299
300
  end
300
301
  end
@@ -809,6 +810,7 @@ h1, h3 {
809
810
  background-repeat: no-repeat;
810
811
  background-size: 100% 100%;
811
812
  width: 28px;
813
+ height: 32px;
812
814
  cursor: pointer;
813
815
  }
814
816
  #mermaidErd {
@@ -942,10 +944,10 @@ table.shadow > tbody > tr {
942
944
  table tbody tr:nth-of-type(even) {
943
945
  background-color: #f3f3f3;
944
946
  }
945
- table tbody tr:nth-of-type(even) .col-sticky {
947
+ table tbody tr:nth-of-type(even) .alternating-gray {
946
948
  background-color: #fff;
947
949
  }
948
- table tbody tr:nth-of-type(odd) .col-sticky {
950
+ table tbody tr:nth-of-type(odd) .alternating-gray {
949
951
  background-color: #f3f3f3;
950
952
  }
951
953
 
@@ -991,6 +993,11 @@ a.big-arrow {
991
993
  background-color: red;
992
994
  color: white;
993
995
  }
996
+ .brick-note {
997
+ font-size: 0.7em;
998
+ color: #A0FFA0;
999
+ max-width: 0;
1000
+ }
994
1001
 
995
1002
  #revertTemplate {
996
1003
  display: none;
@@ -1060,7 +1067,7 @@ document.querySelectorAll(\"input[type=submit][data-confirm]\").forEach(function
1060
1067
  "\nbrickTestSchema = \"#{::Brick.test_schema}\";" if ::Brick.test_schema
1061
1068
  }
1062
1069
  function doFetch(method, payload, success) {
1063
- payload.authenticity_token = <%= session[:_csrf_token].inspect.html_safe %>;
1070
+ payload.authenticity_token = <%= (session[:_csrf_token] || form_authenticity_token).inspect.html_safe %>;
1064
1071
  var action = payload._brick_action || location.href;
1065
1072
  delete payload._brick_action;
1066
1073
  if (!success) {
@@ -1317,10 +1324,11 @@ end
1317
1324
  self.class.class_exec { include ::Brick::Rails::FormTags } unless respond_to?(:brick_grid)
1318
1325
 
1319
1326
  #{# Determine if we should render an N:M representation or the standard "mega_grid"
1320
- taa = ::Brick.config.treat_as_associative&.fetch(res_name, nil)
1327
+ taa = ::Brick.config.treat_as_associative&.fetch(table_name, nil)
1321
1328
  options = {}
1322
1329
  options[:prefix] = prefix unless prefix.blank?
1323
- if taa.is_a?(String) # Write out a constellation
1330
+ if taa.is_a?(String) || # Write out a constellation
1331
+ (taa.is_a?(Array) && (options[:axes] = taa[0..-2]) && (options[:dsl] = taa.last))
1324
1332
  representation = :constellation
1325
1333
  "
1326
1334
  brick_constellation(@#{res_name}, #{options.inspect}, bt_descrip: @_brick_bt_descrip, bts: bts)"
@@ -1336,8 +1344,7 @@ end
1336
1344
  end}
1337
1345
  %>
1338
1346
 
1339
- #{"<hr><%= link_to(\"New #{new_path_name = "new_#{path_obj_name}_path"
1340
- obj_name}\", #{new_path_name}, { class: '__brick' }) if respond_to?(:#{new_path_name}) %>" unless @_brick_model.is_view?}
1347
+ #{"<hr><%= link_to_brick(model, new: true, class: '__brick') %>" unless @_brick_model.is_view?}
1341
1348
  #{script}
1342
1349
  </body>
1343
1350
  </html>
@@ -1437,7 +1444,7 @@ end
1437
1444
  <head>
1438
1445
  #{css}
1439
1446
  <title><%=
1440
- base_model = (model = (obj = @#{obj_name})&.class).base_class
1447
+ base_model = (model = (obj = @#{obj_name}).class).base_class
1441
1448
  see_all_path = send(\"#\{base_model._brick_index}_path\")
1442
1449
  #{(inh_col = @_brick_model.inheritance_column).present? &&
1443
1450
  " if obj.respond_to?(:#{inh_col}) && (model_name = @#{obj_name}.#{inh_col}) &&
@@ -1445,6 +1452,7 @@ end
1445
1452
  see_all_path << \"?#{inh_col}=#\{model_name}\"
1446
1453
  end
1447
1454
  model_name = base_model.name if model_name.is_a?(Numeric)"}
1455
+ model_name = nil if model_name == ''
1448
1456
  page_title = (\"#\{model_name ||= model.name}: #\{obj&.brick_descrip || controller_name}\")
1449
1457
  %></title>
1450
1458
  </head>
@@ -1510,8 +1518,12 @@ end
1510
1518
  # path_options = [obj.#{pk}]
1511
1519
  # path_options << { '_brick_schema': } if
1512
1520
  options = {}
1513
- path_helper = obj.new_record? ? #{model_name}._brick_index : #{model_name}._brick_index(:singular)
1514
- options[:url] = send(\"#\{path_helper}_path\".to_sym, obj) if ::Brick.config.path_prefix || (path_helper != obj.class.table_name)
1521
+ options[:url] = if obj.new_record?
1522
+ link_to_brick(obj.class, path_only: true) # Properly supports STI, but only works for :new
1523
+ else
1524
+ path_helper = obj.new_record? ? #{model_name}._brick_index : #{model_name}._brick_index(:singular)
1525
+ options[:url] = send(\"#\{path_helper}_path\".to_sym, obj) if ::Brick.config.path_prefix || (path_helper != obj.class.table_name)
1526
+ end
1515
1527
  %>
1516
1528
  <br><br>
1517
1529
 
@@ -1748,7 +1760,7 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1748
1760
  "
1749
1761
  end
1750
1762
  if representation == :grid
1751
- "<script>
1763
+ inline << "<script>
1752
1764
  <% # Make column headers sort when clicked
1753
1765
  # %%% Create a smart javascript routine which can do this client-side %>
1754
1766
  [... document.getElementsByTagName(\"TH\")].forEach(function (th) {
@@ -6,70 +6,20 @@ module Brick::Rails::FormTags
6
6
  # When a relation is not provided, first see if one exists which matches the controller name or
7
7
  # something has turned up in the instance variables.
8
8
  relation ||= (instance_variable_get("@#{controller_name}".to_sym) || _brick_resource_from_iv)
9
+ klass = relation.klass
9
10
 
10
11
  nfc = Brick.config.sidescroll.fetch(relation.table_name, nil)&.fetch(:num_frozen_columns, nil) ||
11
12
  Brick.config.sidescroll.fetch(:num_frozen_columns, nil) ||
12
13
  0
13
14
 
14
- # HTML for brick_grid
15
- out = +"<div id=\"headerTopContainer\"><table id=\"headerTop\"></table>
16
- "
17
- klass = relation.klass
15
+ out = +''
18
16
  rel = ::Brick.relations&.fetch(relation.table_name, nil)
19
- unless show_header == false
20
- out << " <div id=\"headerTopAddNew\">
21
- <div id=\"headerButtonBox\">
22
- "
23
- unless show_row_count == false
24
- out << " <div id=\"rowCount\"></div>
25
- "
26
- end
27
- unless show_erd_button == false
28
- out << " <div id=\"imgErd\" title=\"Show ERD\"></div>
29
- "
30
- end
31
- if rel && show_in_app_button != false && (in_app = rel.fetch(:existing, nil)&.fetch(:index, nil))
32
- begin
33
- in_app = send("#{in_app}_path") if in_app.is_a?(Symbol)
34
- out << " <td title=\"Show in app\">#{link_to(::Brick::Rails::IN_APP.html_safe, in_app)}</td>
35
- "
36
- rescue ActionController::UrlGenerationError # Avoid snags like "No route matches {:action=>"index", :controller=>"categories/products"}, missing required keys: [:category_id]"
37
- end
38
- end
39
- if show_avo_button != false && Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) && klass.name.exclude?('::')
40
- out << "
41
- <td>#{link_to_brick(
42
- ::Brick::Rails::AVO_SVG.html_safe,
43
- { index_proc: Proc.new do |_avo_model, relation|
44
- path_helper = "resources_#{relation.fetch(:auto_prefixed_schema, nil)}#{klass.model_name.route_key}_path".to_sym
45
- ::Avo.railtie_routes_url_helpers.send(path_helper) if ::Avo.railtie_routes_url_helpers.respond_to?(path_helper)
46
- end,
47
- title: "#{klass.name} in Avo" }
48
- )}</td>
49
- "
50
- end
51
-
52
- if show_aa_button != false && Object.const_defined?('ActiveAdmin')
53
- ActiveAdmin.application.namespaces.names.each do |ns|
54
- out << "
55
- <td>#{link_to_brick(
56
- ::Brick::Rails::AA_PNG.html_safe,
57
- { index_proc: Proc.new do |aa_model, relation|
58
- path_helper = "#{ns}_#{relation.fetch(:auto_prefixed_schema, nil)}#{aa_model.model_name.route_key}_path".to_sym
59
- send(path_helper) if respond_to?(path_helper)
60
- end,
61
- title: "#{klass.name} in ActiveAdmin" }
62
- )}</td>
63
- "
64
- end
65
- end
66
- out << " </div>
67
- </div>
68
- "
17
+ if show_header != false
18
+ out << brick_header(rel, klass, show_row_count, show_erd_button, show_in_app_button, show_avo_button, show_aa_button)
69
19
  end
70
20
 
71
- out << "</div>
72
- <table id=\"#{table_name = relation.table_name.split('.').last}\" class=\"shadow\"#{ " x-num-frozen=\"#{nfc}\"" if nfc.positive? }>
21
+ # HTML for brick_grid
22
+ out << "<table id=\"#{table_name = relation.table_name.split('.').last}\" class=\"shadow\"#{ " x-num-frozen=\"#{nfc}\"" if nfc.positive? }>
73
23
  <thead><tr>"
74
24
  pk = klass.primary_key || []
75
25
  pk = [pk] unless pk.is_a?(Array)
@@ -145,13 +95,13 @@ module Brick::Rails::FormTags
145
95
  row_count = 0
146
96
  relation.each do |obj|
147
97
  out << "<tr>\n"
148
- out << "<td class=\"col-sticky\">#{link_to('⇛', send("#{klass._brick_index(:singular)}_path".to_sym,
149
- pk.map { |pk_part| obj.send(pk_part.to_sym) }), { class: 'big-arrow' })}</td>\n" if pk.present?
98
+ out << "<td class=\"col-sticky alternating-gray\">#{link_to('⇛', send("#{klass._brick_index(:singular)}_path".to_sym,
99
+ pk.map { |pk_part| obj.send(pk_part.to_sym) }), { class: 'big-arrow' })}</td>\n" if pk.present?
150
100
  sequence.each_with_index do |col_name, idx|
151
101
  val = obj.attributes[col_name]
152
102
  bt = bts[col_name] || composite_bt_names[col_name]
153
103
  out << '<td'
154
- (classes ||= []) << 'col-sticky' if idx < nfc
104
+ (classes ||= []) << 'col-sticky alternating-gray' if idx < nfc
155
105
  (classes ||= []) << 'dimmed' unless cols.key?(col_name) || (cust_col = cust_cols[col_name]) ||
156
106
  (col_name.is_a?(Symbol) && bts.key?(col_name)) # HOT
157
107
  (classes ||= []) << 'right' if val.is_a?(Numeric) && !bt
@@ -255,161 +205,14 @@ module Brick::Rails::FormTags
255
205
  out << '</tr>'
256
206
  row_count += 1
257
207
  end
258
- if rel && (total_row_count = rel.fetch(:rowcount, nil))
259
- total_row_count = total_row_count > row_count ? " (out of #{total_row_count})" : nil
260
- end
261
208
  out << " </tbody>
262
209
  </table>
263
- <script>
264
- var rowCount = document.getElementById(\"rowCount\");
265
- if (rowCount) rowCount.innerHTML = \"#{pluralize(row_count, "row")}#{total_row_count} &nbsp;\";
266
- </script>
267
- "
268
-
269
- # Javascript for brick_grid
270
- (@_brick_javascripts ||= {})[:grid_scripts] = "
271
- var #{table_name}HtColumns;
272
-
273
- // Snag first TR for sticky header
274
- var grid = document.getElementById(\"#{table_name}\");
275
- #{table_name}HtColumns = grid && [grid.getElementsByTagName(\"TR\")[0]];
276
- var headerTop = document.getElementById(\"headerTop\");
277
- var headerCols;
278
- if (grid) {
279
- // COLUMN HEADER AND TABLE CELL HIGHLIGHTING
280
- var gridHighHeader = null,
281
- gridHighCell = null;
282
- grid.addEventListener(\"mouseenter\", gridMove);
283
- grid.addEventListener(\"mousemove\", gridMove);
284
- grid.addEventListener(\"mouseleave\", function (evt) {
285
- if (gridHighCell) gridHighCell.classList.remove(\"highlight\");
286
- gridHighCell = null;
287
- if (gridHighHeader) gridHighHeader.classList.remove(\"highlight\");
288
- gridHighHeader = null;
289
- });
290
- function gridMove(evt) {
291
- var lastHighCell = gridHighCell;
292
- gridHighCell = document.elementFromPoint(evt.x, evt.y);
293
- while (gridHighCell && gridHighCell.tagName !== \"TD\" && gridHighCell.tagName !== \"TH\")
294
- gridHighCell = gridHighCell.parentElement;
295
- if (gridHighCell) {
296
- if (lastHighCell !== gridHighCell) {
297
- gridHighCell.classList.add(\"highlight\");
298
- if (lastHighCell) lastHighCell.classList.remove(\"highlight\");
299
- }
300
- var lastHighHeader = gridHighHeader;
301
- if ((gridHighHeader = headerCols[gridHighCell.cellIndex]) && lastHighHeader !== gridHighHeader) {
302
- if (gridHighHeader) gridHighHeader.classList.add(\"highlight\");
303
- if (lastHighHeader) lastHighHeader.classList.remove(\"highlight\");
304
- }
305
- }
306
- }
307
- // // LESS TOUCHY NAVIGATION BACK OR FORWARD IN HISTORY WHEN USING MOUSE WHEEL
308
- // grid.addEventListener(\"wheel\", function (evt) {
309
- // grid.scrollLeft += evt.deltaX;
310
- // document.body.scrollTop += (evt.deltaY * 0.6);
311
- // evt.preventDefault();
312
- // return false;
313
- // });
314
- }
315
- function setHeaderSizes() {
316
- if (grid.clientWidth > window.outerWidth)
317
- document.getElementById(\"titleBox\").style.width = grid.clientWidth;
318
- // console.log(\"start\");
319
- // See if the headerTop is already populated
320
- // %%% Grab the TRs from headerTop, clear it out, do this stuff, add them back
321
- headerTop.innerHTML = \"\"; // %%% Would love to not have to clear it out like this every time! (Currently doing this to support resize events.)
322
- var isEmpty = headerTop.childElementCount === 0;
323
- var numFixed = parseInt(grid.getAttribute(\"x-num-frozen\")) || 0;
324
- var fixedColLefts = [0];
325
-
326
- // Set up proper sizings of sticky column header
327
- var node;
328
- for (var j = 0; j < #{table_name}HtColumns.length; ++j) {
329
- var row = #{table_name}HtColumns[j];
330
- var tr = isEmpty ? document.createElement(\"TR\") : headerTop.childNodes[j];
331
- tr.innerHTML = row.innerHTML.trim();
332
- var curLeft = 0.0;
333
- // Match up widths from the original column headers
334
- for (var i = 0; i < row.childNodes.length; ++i) {
335
- node = row.childNodes[i];
336
- if (node.nodeType === 1) {
337
- var th = tr.childNodes[i];
338
- th.style.minWidth = th.style.maxWidth = getComputedStyle(node).width;
339
- // Add \"left: __px\" style to the fixed-width column THs
340
- if (i <= numFixed) {
341
- th.style.position = \"sticky\";
342
- th.style.backgroundColor = \"#008061\";
343
- th.style.zIndex = \"1\";
344
- th.style.left = curLeft + \"px\";
345
- fixedColLefts.push(curLeft += node.clientWidth);
346
- }
347
- if (#{pk&.present? ? 'i > 0' : 'true'}) {
348
- // Add <span> at the end
349
- var span = document.createElement(\"SPAN\");
350
- span.className = \"exclude\";
351
- span.innerHTML = \"X\";
352
- span.addEventListener(\"click\", function (e) {
353
- e.stopPropagation();
354
- doFetch(\"POST\", {_brick_exclude: this.parentElement.getAttribute(\"x-order\")});
355
- });
356
- th.appendChild(span);
357
- }
358
- }
359
- }
360
- headerCols = tr.childNodes;
361
- if (isEmpty) headerTop.appendChild(tr);
362
- }
363
- // Add \"left: __px\" style to all fixed-width column TDs
364
- [...grid.children[1].children].forEach(function (row) {
365
- for (var j = 1; j <= numFixed; ++j) {
366
- row.children[j].style.left = fixedColLefts[j] + 'px';
367
- }
368
- });
369
- grid.style.marginTop = \"-\" + getComputedStyle(headerTop).height;
370
- // console.log(\"end\");
371
- }
372
-
373
- if (headerTop) {
374
- onImagesLoaded(function() {
375
- setHeaderSizes();
376
- });
377
- window.addEventListener(\"resize\", function(event) {
378
- setHeaderSizes();
379
- }, true);#{
380
- if !klass.is_view? && respond_to?(new_path_name = "new_#{klass._brick_index(:singular)}_path")
381
- "
382
- var headerButtonBox = document.getElementById(\"headerButtonBox\");
383
- if (headerButtonBox) {
384
- var addNew = document.createElement(\"A\");
385
- addNew.id = \"addNew\";
386
- addNew.href = \"#{send(new_path_name)}\";
387
- addNew.title = \"New #{table_name.singularize}\";
388
- addNew.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"#fff\" d=\"M24 10h-10v-10h-4v10h-10v4h10v10h4v-10h10z\"/></svg>';
389
- headerButtonBox.append(addNew);
390
- }
391
- "
392
- end}
393
- }
394
-
395
- function onImagesLoaded(event) {
396
- var images = document.getElementsByTagName(\"IMG\");
397
- var numLoaded = images.length;
398
- for (var i = 0; i < images.length; ++i) {
399
- if (images[i].complete)
400
- --numLoaded;
401
- else {
402
- images[i].addEventListener(\"load\", function() {
403
- if (--numLoaded <= 0)
404
- event();
405
- });
406
- }
407
- }
408
- if (numLoaded <= 0)
409
- event();
410
- }
411
210
  "
211
+ if rel && (total_row_count = rel.fetch(:rowcount, nil))
212
+ total_row_count = total_row_count > row_count ? " (out of #{total_row_count})" : nil
213
+ end
412
214
 
215
+ set_grid_javascript(klass, pk, show_new_button, row_count, total_row_count)
413
216
  out.html_safe
414
217
  end # brick_grid
415
218
 
@@ -418,7 +221,7 @@ function onImagesLoaded(event) {
418
221
  def brick_form_for(obj, options = {}, model = obj.class, bts = {}, pk = (obj.class.primary_key || []))
419
222
  pk = [pk] unless pk.is_a?(Array)
420
223
  pk.map!(&:to_s)
421
- form_for(obj.becomes(model), options) do |f|
224
+ form_for(obj.becomes(model.base_class), options) do |f|
422
225
  out = +'<table class="shadow">'
423
226
  has_fields = false
424
227
  # If it's a new record, set any default polymorphic types
@@ -525,32 +328,43 @@ function onImagesLoaded(event) {
525
328
 
526
329
  # ------------------------------------------
527
330
  # Our cool N:M checkbox constellation editor
528
- def brick_constellation(relation = nil, options = {}, x_axis: nil, y_axis: nil, bt_descrip: nil, bts: {})
331
+ def brick_constellation(relation = nil, options = {}, x_axis: nil, y_axis: nil, bt_descrip: nil, bts: {},
332
+ show_header: nil, show_erd_button: nil, show_in_app_button: nil, show_new_button: nil, show_avo_button: nil, show_aa_button: nil)
333
+ relation ||= (instance_variable_get("@#{controller_name}".to_sym) || _brick_resource_from_iv)
334
+ klass = relation.klass
335
+ if (axes = options[:axes])
336
+ x_axis, y_axis = axes
337
+ end
529
338
  x_axis, x_list, y_axis, y_list, existing = _n_m_prep(relation, x_axis, y_axis)
530
339
 
340
+ out = +''
341
+ rel = ::Brick.relations&.fetch(relation.table_name, nil)
342
+ if show_header != false
343
+ out << brick_header(rel, klass, false, show_erd_button, show_in_app_button, show_avo_button, show_aa_button)
344
+ end
345
+
531
346
  # HTML for constellation
532
347
  prefix = options[:prefix]
533
- out = +"<form action=\"#{"#{prefix}/" if prefix}brick_constellation\">
348
+ out << "<form action=\"#{"#{prefix}/" if prefix}brick_constellation\">
534
349
  <table id=\"#{table_name = relation.table_name.split('.').last}\" class=\"shadow\">
535
- <thead><tr><td></td>
536
- "
350
+ <thead><tr><td class=\"brick-note\">Checkbox changes are saved immediately</td>"
537
351
  # Header row with X axis values
352
+ # (In order for grid highlighting to function, these TH elements must have no whitespace between them.
353
+ # In this way the Javascript headerCols array will be set properly.
538
354
  x_list.each do |x_item|
539
- out << " <th>#{x_item.first}</th>
540
- "
355
+ out << "<th>#{x_item.first}</th>"
541
356
  end
542
- out << " </tr></thead>
357
+ out << "</tr></thead>
543
358
  <tbody>
544
359
  "
545
- obj_path = "#{relation.klass._brick_index(:singular)}_path".to_sym
360
+ obj_path = "#{klass._brick_index(:singular)}_path".to_sym
546
361
  link_arrow = link_to('⇛', send(obj_path, '____'), { class: 'big-arrow' })
547
- pk_as_array = relation.klass._pk_as_array
548
362
  y_list.each do |y_item|
549
- out << " <tr><th>#{y_item.first}</th>
363
+ out << " <tr><th class=\"col-sticky\">#{y_item.first}</th>
550
364
  "
551
365
  x_list.each do |x_item|
552
366
  checked = existing.find { |e| e[1] == x_item.last && e[2] == y_item.last }
553
- item_id = pk_as_array.map { |pk_part| checked.first }.join('%2F') if checked
367
+ item_id = checked.first.join('%2F') if checked
554
368
  out << " <td><input type=\"checkbox\" name=\"#{table_name}\" #{"x-id=\"#{item_id}\" " if checked
555
369
  }\" value=\"#{x_item.last}_#{y_item.last}\"#{' checked' if checked}>
556
370
  #{link_arrow.gsub('____', item_id) if checked}</td>
@@ -570,14 +384,14 @@ function onImagesLoaded(event) {
570
384
  _this = this;
571
385
  if (this.checked) {
572
386
  var ids = this.value.split(\"_\");
573
- doFetch(\"POST\", {modelName: \"#{relation.klass.name}\",
387
+ doFetch(\"POST\", {modelName: \"#{klass.name}\",
574
388
  args: [#{x_axis[1].inspect}, ids[0], #{y_axis[1].inspect}, ids[1]],
575
389
  _brick_action: \"/#{prefix}brick_associate\"},
576
390
  function (p) { // If it returns successfully, create an <a> element
577
391
  p.text().then(function (response) {
578
392
  var recordId = JSON.parse(response).data;
579
393
  if (recordId) {
580
- console.log(_this.getAttribute(\"x-id\"));
394
+ // console.log(_this.getAttribute(\"x-id\"));
581
395
  var tmp = document.createElement(\"DIV\");
582
396
  tmp.innerHTML = \"#{link_arrow.gsub('"', '\"')}\".replace(\"____\", recordId);
583
397
  _this.parentElement.append(tmp.firstChild);
@@ -586,7 +400,7 @@ function onImagesLoaded(event) {
586
400
  }
587
401
  );
588
402
  } else if (nextSib = this.nextElementSibling) {
589
- doFetch(\"DELETE\", {modelName: \"#{relation.klass.name}\",
403
+ doFetch(\"DELETE\", {modelName: \"#{klass.name}\",
590
404
  id: this.getAttribute(\"x-id\"),
591
405
  _brick_action: \"/#{prefix}brick_associate\"},
592
406
  function (p) { // If it returns successfully, remove the an <a> element
@@ -599,6 +413,7 @@ function onImagesLoaded(event) {
599
413
  </script>
600
414
  </form>
601
415
  "
416
+ set_grid_javascript(klass, klass._pk_as_array, false)
602
417
  out.html_safe
603
418
  end # brick_constellation
604
419
 
@@ -606,7 +421,9 @@ function onImagesLoaded(event) {
606
421
  # Our cool N:M bezier visualisation
607
422
  # (...... work in progress .......)
608
423
  def brick_bezier(relation = nil, options = {}, x_axis: nil, y_axis: nil, bt_descrip: nil, bts: {})
424
+ relation ||= (instance_variable_get("@#{controller_name}".to_sym) || _brick_resource_from_iv)
609
425
  x_axis, x_list, y_axis, y_list, existing = _n_m_prep(relation, x_axis, y_axis)
426
+ rel = ::Brick.relations&.fetch(relation.table_name, nil)
610
427
  # HTML for constellation
611
428
  # X axis (List on left side)
612
429
  out = +"<table id=\"#{x_axis.first}\" class=\"shadow\">
@@ -629,13 +446,222 @@ function onImagesLoaded(event) {
629
446
  out.html_safe
630
447
  end # brick_bezier
631
448
 
449
+ # ---------------------------------------------------------------------------------------------------------
450
+ def brick_header(rel, klass, show_row_count, show_erd_button, show_in_app_button, show_avo_button, show_aa_button)
451
+ out = +"<div id=\"headerTopContainer\"><table id=\"headerTop\"></table>
452
+ <div id=\"headerTopAddNew\">
453
+ <div id=\"headerButtonBox\">
454
+ "
455
+ unless show_row_count == false
456
+ out << " <div id=\"rowCount\"></div>
457
+ "
458
+ end
459
+ unless show_erd_button == false
460
+ out << " <div id=\"imgErd\" title=\"Show ERD\"></div>
461
+ "
462
+ end
463
+ if rel && show_in_app_button != false && (in_app = rel.fetch(:existing, nil)&.fetch(:index, nil))
464
+ begin
465
+ in_app = send("#{in_app}_path") if in_app.is_a?(Symbol)
466
+ out << " <td title=\"Show in app\">#{link_to(::Brick::Rails::IN_APP.html_safe, in_app)}</td>
467
+ "
468
+ rescue ActionController::UrlGenerationError # Avoid snags like "No route matches {:action=>"index", :controller=>"categories/products"}, missing required keys: [:category_id]"
469
+ end
470
+ end
471
+ if show_avo_button != false && Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) && klass.name.exclude?('::')
472
+ out << "
473
+ <td>#{link_to_brick(
474
+ ::Brick::Rails::AVO_SVG.html_safe,
475
+ { index_proc: Proc.new do |_avo_model, relation|
476
+ path_helper = "resources_#{relation.fetch(:auto_prefixed_schema, nil)}#{klass.model_name.route_key}_path".to_sym
477
+ ::Avo.railtie_routes_url_helpers.send(path_helper) if ::Avo.railtie_routes_url_helpers.respond_to?(path_helper)
478
+ end,
479
+ title: "#{klass.name} in Avo" }
480
+ )}</td>
481
+ "
482
+ end
483
+
484
+ if show_aa_button != false && Object.const_defined?('ActiveAdmin')
485
+ ActiveAdmin.application.namespaces.names.each do |ns|
486
+ out << "
487
+ <td>#{link_to_brick(
488
+ ::Brick::Rails::AA_PNG.html_safe,
489
+ { index_proc: Proc.new do |aa_model, relation|
490
+ path_helper = "#{ns}_#{relation.fetch(:auto_prefixed_schema, nil)}#{aa_model.model_name.route_key}_path".to_sym
491
+ send(path_helper) if respond_to?(path_helper)
492
+ end,
493
+ title: "#{rel[:class_name]} in ActiveAdmin" }
494
+ )}</td>
495
+ "
496
+ end
497
+ end
498
+ out << " </div>
499
+ </div>
500
+ </div>
501
+ "
502
+ out
503
+ end # brick_header
504
+
505
+ # -----------------------------------------------------------------------------------------------
506
+ def set_grid_javascript(klass, pk, show_new_button = nil, row_count = nil, total_row_count = nil)
507
+ table_name = klass.table_name.split('.').last
508
+
509
+ # Javascript for brick_grid and brick_constellation
510
+ grid_scripts = (@_brick_javascripts ||= {})[:grid_scripts] = +''
511
+
512
+ grid_scripts << "
513
+ // Plunk the row count in now that we know it
514
+ var rowCount = document.getElementById(\"rowCount\");
515
+ if (rowCount) rowCount.innerHTML = \"#{pluralize(row_count, "row")}#{total_row_count} &nbsp;\";
516
+ var #{table_name}HtColumns;
517
+ " unless row_count.nil?
518
+
519
+ grid_scripts << "
520
+ // Snag first TR for sticky header
521
+ var grid = document.getElementById(\"#{table_name}\");
522
+ #{table_name}HtColumns = grid && [grid.getElementsByTagName(\"TR\")[0]];
523
+ var headerTop = document.getElementById(\"headerTop\");
524
+ var headerCols;
525
+ if (grid) {
526
+ // COLUMN HEADER AND TABLE CELL HIGHLIGHTING
527
+ var gridHighHeader = null,
528
+ gridHighCell = null;
529
+ grid.addEventListener(\"mouseenter\", gridMove);
530
+ grid.addEventListener(\"mousemove\", gridMove);
531
+ grid.addEventListener(\"mouseleave\", function (evt) {
532
+ if (gridHighCell) gridHighCell.classList.remove(\"highlight\");
533
+ gridHighCell = null;
534
+ if (gridHighHeader) gridHighHeader.classList.remove(\"highlight\");
535
+ gridHighHeader = null;
536
+ });
537
+ function gridMove(evt) {
538
+ var lastHighCell = gridHighCell;
539
+ gridHighCell = document.elementFromPoint(evt.x, evt.y);
540
+ while (gridHighCell && gridHighCell.tagName !== \"TD\" && gridHighCell.tagName !== \"TH\")
541
+ gridHighCell = gridHighCell.parentElement;
542
+ if (gridHighCell) {
543
+ if (lastHighCell !== gridHighCell) {
544
+ gridHighCell.classList.add(\"highlight\");
545
+ if (lastHighCell) lastHighCell.classList.remove(\"highlight\");
546
+ }
547
+ var lastHighHeader = gridHighHeader;
548
+ if ((gridHighHeader = headerCols[gridHighCell.cellIndex]) && lastHighHeader !== gridHighHeader) {
549
+ if (gridHighHeader) gridHighHeader.classList.add(\"highlight\");
550
+ if (lastHighHeader) lastHighHeader.classList.remove(\"highlight\");
551
+ }
552
+ }
553
+ }
554
+ // // Less touchy navigation back or forward in history when using mouse wheel
555
+ // grid.addEventListener(\"wheel\", function (evt) {
556
+ // grid.scrollLeft += evt.deltaX;
557
+ // document.body.scrollTop += (evt.deltaY * 0.6);
558
+ // evt.preventDefault();
559
+ // return false;
560
+ // });
561
+ }
562
+ function setHeaderSizes() {
563
+ if (grid.clientWidth > window.outerWidth)
564
+ document.getElementById(\"titleBox\").style.width = grid.clientWidth;
565
+ // console.log(\"start\");
566
+ // See if the headerTop is already populated
567
+ // %%% Grab the TRs from headerTop, clear it out, do this stuff, add them back
568
+ headerTop.innerHTML = \"\"; // %%% Would love to not have to clear it out like this every time! (Currently doing this to support resize events.)
569
+ var isEmpty = headerTop.childElementCount === 0;
570
+ var numFixed = parseInt(grid.getAttribute(\"x-num-frozen\")) || 0;
571
+ var fixedColLefts = [0];
572
+
573
+ // Set up proper sizings of sticky column header
574
+ var node;
575
+ for (var j = 0; j < #{table_name}HtColumns.length; ++j) {
576
+ var row = #{table_name}HtColumns[j];
577
+ var tr = isEmpty ? document.createElement(\"TR\") : headerTop.childNodes[j];
578
+ tr.innerHTML = row.innerHTML.trim();
579
+ var curLeft = 0.0;
580
+ // Match up widths from the original column headers
581
+ for (var i = 0; i < row.childNodes.length; ++i) {
582
+ node = row.childNodes[i];
583
+ if (node.nodeType === 1) {
584
+ var th = tr.childNodes[i];
585
+ th.style.minWidth = th.style.maxWidth = getComputedStyle(node).width;
586
+ // Add \"left: __px\" style to the fixed-width column THs
587
+ if (i <= numFixed) {
588
+ th.style.position = \"sticky\";
589
+ th.style.backgroundColor = \"#008061\";
590
+ th.style.zIndex = \"1\";
591
+ th.style.left = curLeft + \"px\";
592
+ fixedColLefts.push(curLeft += node.clientWidth);
593
+ }
594
+ if (#{pk&.present? ? 'i > 0' : 'true'}) {
595
+ // Add <span> at the end
596
+ var span = document.createElement(\"SPAN\");
597
+ span.className = \"exclude\";
598
+ span.innerHTML = \"X\";
599
+ span.addEventListener(\"click\", function (e) {
600
+ e.stopPropagation();
601
+ doFetch(\"POST\", {_brick_exclude: this.parentElement.getAttribute(\"x-order\")});
602
+ });
603
+ th.appendChild(span);
604
+ }
605
+ }
606
+ }
607
+ headerCols = tr.childNodes;
608
+ if (isEmpty) headerTop.appendChild(tr);
609
+ }
610
+ // Add \"left: __px\" style to all fixed-width column TDs
611
+ [...grid.children[1].children].forEach(function (row) {
612
+ for (var j = 1; j <= numFixed; ++j) {
613
+ row.children[j].style.left = fixedColLefts[j] + 'px';
614
+ }
615
+ });
616
+ grid.style.marginTop = \"-\" + getComputedStyle(headerTop).height;
617
+ // console.log(\"end\");
618
+ }
619
+
620
+ if (headerTop) {
621
+ onImagesLoaded(function() {
622
+ setHeaderSizes();
623
+ });
624
+ window.addEventListener(\"resize\", function(event) {
625
+ setHeaderSizes();
626
+ }, true);#{
627
+ "
628
+ var headerButtonBox = document.getElementById(\"headerButtonBox\");
629
+ if (headerButtonBox) {
630
+ var addNew = document.createElement(\"A\");
631
+ addNew.id = \"addNew\";
632
+ addNew.href = \"#{link_to_brick(klass, new: true, path_only: true)}\";
633
+ addNew.title = \"New #{table_name.singularize}\";
634
+ addNew.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"#fff\" d=\"M24 10h-10v-10h-4v10h-10v4h10v10h4v-10h10z\"/></svg>';
635
+ headerButtonBox.append(addNew);
636
+ }
637
+ " unless klass.is_view? || show_new_button == false
638
+ }
639
+ }
640
+
641
+ function onImagesLoaded(event) {
642
+ var images = document.getElementsByTagName(\"IMG\");
643
+ var numLoaded = images.length;
644
+ for (var i = 0; i < images.length; ++i) {
645
+ if (images[i].complete)
646
+ --numLoaded;
647
+ else {
648
+ images[i].addEventListener(\"load\", function() {
649
+ if (--numLoaded <= 0)
650
+ event();
651
+ });
652
+ }
653
+ }
654
+ if (numLoaded <= 0)
655
+ event();
656
+ }
657
+ "
658
+ end
659
+
660
+ # -------------------------------------
632
661
  def _n_m_prep(relation, x_axis, y_axis)
633
- relation ||= (instance_variable_get("@#{controller_name}".to_sym) || _brick_resource_from_iv)
634
662
  # Just find the first two BT things at this point
635
663
 
636
664
  klass = relation.klass
637
- rel = ::Brick.relations&.fetch(relation.table_name, nil)
638
- # fk_assocs = rel[:fks].map { |k, fk| [fk[:assoc_name], fk[:fk]] }
639
665
  fk_assocs = klass.reflect_on_all_associations.each_with_object([]) do |assoc, s|
640
666
  s << [assoc.name.to_s, assoc.foreign_key, assoc.klass] if assoc.belongs_to?
641
667
  end
@@ -650,8 +676,9 @@ function onImagesLoaded(event) {
650
676
  x_axis = fk_assocs.shift unless x_axis
651
677
  puts "FK Leftovers: #{fk_assocs.join(', ')}" unless fk_assocs.empty?
652
678
 
679
+ pk_as_array = klass._pk_as_array
653
680
  existing = relation.each_with_object([]) do |row, s|
654
- row_id = row.send(klass.primary_key)
681
+ row_id = pk_as_array.map { |pk_part| row.send(pk_part) }
655
682
  if (x_id = row.send(x_axis[1])) && (y_id = row.send(y_axis[1]))
656
683
  s << [row_id, x_id, y_id]
657
684
  end
@@ -690,8 +717,10 @@ function onImagesLoaded(event) {
690
717
  kwargs[:visited] = {}
691
718
  end
692
719
 
693
- text = ((args.first.is_a?(String) || args.first.is_a?(Proc)) && args.shift) || args[1]
694
- text = text.call if text.is_a?(Proc)
720
+ unless (is_path_only = kwargs.delete(:path_only))
721
+ text = ((args.first.is_a?(String) || args.first.is_a?(Proc)) && args.shift) || args[1]
722
+ text = text.call if text.is_a?(Proc)
723
+ end
695
724
  klass_or_obj = ((args.first.is_a?(ActiveRecord::Relation) ||
696
725
  args.first.is_a?(ActiveRecord::Base) ||
697
726
  args.first.is_a?(Class)) &&
@@ -739,6 +768,8 @@ function onImagesLoaded(event) {
739
768
  filter_parts << "#{whr.first}=#{whr.last}" if whr.last && !whr.last.is_a?(Array)
740
769
  end
741
770
  klass_or_obj = klass_or_obj.klass
771
+ end
772
+ if klass_or_obj.is_a?(Class) && klass_or_obj <= ActiveRecord::Base
742
773
  type_col = klass_or_obj.inheritance_column
743
774
  if klass_or_obj.column_names.include?(type_col) && klass_or_obj.name != klass_or_obj.base_class.name
744
775
  filter_parts << "#{type_col}=#{klass_or_obj.name}"
@@ -750,11 +781,22 @@ function onImagesLoaded(event) {
750
781
  relation = ::Brick.relations.fetch(rel_name || klass.table_name, nil)
751
782
  if (klass_or_obj&.is_a?(Class) && klass_or_obj < ActiveRecord::Base) ||
752
783
  (klass_or_obj&.is_a?(ActiveRecord::Base) && klass_or_obj.new_record? && (klass_or_obj = klass_or_obj.class))
753
- path = (proc = kwargs[:index_proc]) ? proc.call(klass_or_obj, relation) : "#{app_routes.path_for(controller: klass_or_obj.base_class._brick_index(nil, '/', relation, true), action: :index)}#{filter}"
754
- lt_args = [text || "Index for #{klass_or_obj.name.pluralize}", path]
784
+ if kwargs.delete(:new)
785
+ path = (proc = kwargs[:new_proc]) ? proc.call(klass_or_obj, relation) : "#{app_routes.path_for(controller: klass_or_obj.base_class._brick_index(:singular, '/', relation, true), action: :new)}#{filter}"
786
+ return path if is_path_only
787
+
788
+ lt_args = [text || "New #{klass_or_obj.name}", path]
789
+ else
790
+ path = (proc = kwargs[:index_proc]) ? proc.call(klass_or_obj, relation) : "#{app_routes.path_for(controller: klass_or_obj.base_class._brick_index(nil, '/', relation, true), action: :index)}#{filter}"
791
+ return path if is_path_only
792
+
793
+ lt_args = [text || "Index for #{klass_or_obj.name.pluralize}", path]
794
+ end
755
795
  else
756
796
  # 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
757
797
  path = (proc = kwargs[:show_proc]) ? proc.call(klass_or_obj, relation) : "#{app_routes.path_for(controller: klass_or_obj.class.base_class._brick_index(nil, '/', relation, true), action: :show, id: klass_or_obj)}#{filter}"
798
+ return path if is_path_only
799
+
758
800
  lt_args = [text || "Show this #{klass_or_obj.class.name}", path]
759
801
  end
760
802
  kwargs.delete(:visited)
@@ -341,7 +341,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
341
341
  fk[4].downcase! if fk[4] =~ /^[A-Z0-9_]+$/
342
342
  fk[2] = connection.send(:oracle_downcase, fk[2])
343
343
  end
344
- ::Brick._add_bt_and_hm(fk, relations)
344
+ ::Brick._add_bt_and_hm(fk, relations, nil, nil)
345
345
  end
346
346
  kcus = nil # Allow this large item to be garbage collected
347
347
  end
@@ -62,7 +62,14 @@ module Brick
62
62
  end
63
63
  else
64
64
  # puts "#{' ' * ind}resources :#{res_name} #{options.inspect unless options.blank?}"
65
- send(:resources, res_name.to_sym, **options)
65
+ if options.key(:only) || !Object.const_defined?('Rails') ||
66
+ !::Rails.application.config.respond_to?(:api_only) || !::Rails.application.config.api_only
67
+ resources_options = options
68
+ else # If it's a Rails API app and no :only options are defined, normally routes for :new and :edit
69
+ # are not supplied. But by default we may want for them to be shown.
70
+ resources_options = options.merge(only: [:index, :show, :create, :update, :destroy, :new, :edit])
71
+ end
72
+ send(:resources, res_name.to_sym, **resources_options)
66
73
  end
67
74
  end
68
75
 
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 213
8
+ TINY = 215
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
@@ -315,8 +315,8 @@ if ActiveRecord::Base.respond_to?(:brick_select) && !::Brick.initializer_loaded
315
315
  # # the right. Indicating just :bezier is the same as :bezier_full, which shows the full list of all possible
316
316
  # # things that can be associated. :bezier_union shows just the ones that are currently wired up, and
317
317
  # # :bezier_excluded, :bezier_excluded_left, or :bezier_excluded_right shows the ones not yet wired up.
318
- # Brick.treat_as_associative = { 'flights' => [:bezier, 'departure.code', 'arrival.code'],
319
- # 'crew' => [:constellation, 'flight', 'personnel', '[used ? [used it!] : []]'] }
318
+ # Brick.treat_as_associative = { 'flights' => { bezier: ['departure.code', 'arrival.code'] },
319
+ # 'crew' => { constellation: ['flight', 'personnel', '[used ? [used it!] : []]'] } }
320
320
 
321
321
  # # We normally don't show the timestamp columns \"created_at\", \"updated_at\", and \"deleted_at\", and also do
322
322
  # # not consider them when finding associative tables to support an N:M association. (That is, ones that can be a
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.213
4
+ version: 1.0.215
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-01 00:00:00.000000000 Z
11
+ date: 2024-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -178,20 +178,6 @@ dependencies:
178
178
  - - ">="
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0'
181
- - !ruby/object:Gem::Dependency
182
- name: mysql2
183
- requirement: !ruby/object:Gem::Requirement
184
- requirements:
185
- - - "~>"
186
- - !ruby/object:Gem::Version
187
- version: '0.5'
188
- type: :development
189
- prerelease: false
190
- version_requirements: !ruby/object:Gem::Requirement
191
- requirements:
192
- - - "~>"
193
- - !ruby/object:Gem::Version
194
- version: '0.5'
195
181
  - !ruby/object:Gem::Dependency
196
182
  name: pg
197
183
  requirement: !ruby/object:Gem::Requirement