brick 1.0.213 → 1.0.215

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