brick 1.0.211 → 1.0.212

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: 7950119f788fe1cac3fde68806f2b4a1145da0cf17ff0d47363b102be9c69933
4
- data.tar.gz: 33bb7d89a0744890d4450afde332dbd0f0635a8d3acd0beb4107cf4e44ff9718
3
+ metadata.gz: 3e954e525ea7a17eea2b33a125283f8af47a7bad1e457780815a8f53e06ed8fc
4
+ data.tar.gz: 9ded8bfa75d407294eb3e34895d1fc36ffa17767b9cbdeee0c961d6d9e821653
5
5
  SHA512:
6
- metadata.gz: 959c6ada7e03de34f566ad6059a4431ea66d2be9f12764de3edb3760074a70f3e91354e9052bc3c1a74eed4d5d7a274a78cf490195815b736b02f282ffe03d47
7
- data.tar.gz: a87abaf8c878fecda93fc6751bae171e5194b6b63d933f83ba331191aee701859d3022754f68af3d730c25add0cc0a8174246fd18913040bed75ec700ba2b543
6
+ metadata.gz: 6c2d33f2a6247cc001071929731d2b2543d2b91ba0e8c22afeda7d9fa40e37ae9c3957d4046f8157a34b1549dca8e542de97a58f22b25a312532eed775d3989c
7
+ data.tar.gz: 4e63b7986eb7b683421ef92d00fe61a3970ed0d24fb57403a89d05c114760ad00310e6c635d66bc0388e5ca26b6c6fd2061574c83e1f70b60f90baa22babcb92
data/lib/brick/config.rb CHANGED
@@ -238,7 +238,24 @@ module Brick
238
238
  end
239
239
 
240
240
  def treat_as_associative=(tables)
241
- @mutex.synchronize { @treat_as_associative = tables }
241
+ @mutex.synchronize do
242
+ @treat_as_associative = if tables.is_a?(Hash)
243
+ tables.each_with_object({}) do |v, s|
244
+ # If it's :constellation, or anything else in a hash, we'll take its value
245
+ # (and hopefully in this case that would be either a string or nil)
246
+ dsl = ((v.last.is_a?(Symbol) && v.last) || v.last&.values&.last)
247
+ unless (dsl ||= '').is_a?(String) || dsl.is_a?(Symbol)
248
+ puts "Was really expecting #{v.first} / #{v.last.first&.first} / #{dsl} to be a string, " +
249
+ "so will disregard #{dsl} and just turn on simple constellation view for #{v.first}."
250
+ end
251
+ s[v.first] = v.last.is_a?(Hash) ? dsl : v.last
252
+ end
253
+ elsif tables.is_a?(String) # comma-separated list?
254
+ tables.split(',').each_with_object({}) { |v, s| s[v.trim] = nil }
255
+ else # Expecting an Array, and have no special presentation
256
+ tables&.each_with_object({}) { |v, s| s[v] = nil }
257
+ end
258
+ end
242
259
  end
243
260
 
244
261
  # Polymorphic associations
@@ -286,6 +303,14 @@ module Brick
286
303
  @mutex.synchronize { @model_descrips = descrips }
287
304
  end
288
305
 
306
+ def erd_show_columns
307
+ @mutex.synchronize { @erd_show_columns ||= [] }
308
+ end
309
+
310
+ def erd_show_columns=(descrips)
311
+ @mutex.synchronize { @erd_show_columns = descrips }
312
+ end
313
+
289
314
  def sti_namespace_prefixes
290
315
  @mutex.synchronize { @sti_namespace_prefixes ||= {} }
291
316
  end
@@ -107,7 +107,7 @@ module ActiveRecord
107
107
  # has_one_attached, has_many_attached, and has_rich_text
108
108
  def _activestorage_actiontext_fields
109
109
  fields = [[], [], {}]
110
- if !(self <= ActiveStorage::Blob) && respond_to?(:generated_association_methods) # ActiveStorage
110
+ if Object.const_defined?('ActiveStorage') && respond_to?(:generated_association_methods) && !(self <= ::ActiveStorage::Blob) # ActiveStorage
111
111
  generated_association_methods.instance_methods.each do |method_sym|
112
112
  method_str = method_sym.to_s
113
113
  fields[0] << method_str[0..-13].to_sym if method_str.end_with?('_attachment=') # has_one_attached
@@ -124,7 +124,7 @@ module ActiveRecord
124
124
  end
125
125
 
126
126
  def _active_storage_name(col_name)
127
- if Object.const_defined?('ActiveStorage') && (self <= ActiveStorage::Attachment || self <= ActiveStorage::Blob)
127
+ if Object.const_defined?('ActiveStorage') && (self <= ::ActiveStorage::Attachment || self <= ::ActiveStorage::Blob)
128
128
  if (col_str = col_name.to_s).end_with?('_attachments')
129
129
  col_str[0..-13]
130
130
  elsif col_str.end_with?('_blobs')
@@ -880,7 +880,7 @@ module ActiveRecord
880
880
  through_sources.push(src_ref) unless src_ref.belongs_to?
881
881
  from_clause = +"#{_br_quoted_name(through_sources.first.table_name)} br_t0"
882
882
  # ActiveStorage will not get the correct count unless we do some extra filtering later
883
- tbl_nm = 'br_t0' if Object.const_defined?('ActiveStorage') && through_sources.first.klass <= ActiveStorage::Attachment
883
+ tbl_nm = 'br_t0' if Object.const_defined?('ActiveStorage') && through_sources.first.klass <= ::ActiveStorage::Attachment
884
884
  fk_col = through_sources.shift.foreign_key
885
885
 
886
886
  idx = 0
@@ -1069,8 +1069,8 @@ JOIN (SELECT #{hm_selects.map { |s| _br_quoted_name("#{'br_t0.' if from_clause}#
1069
1069
  end
1070
1070
 
1071
1071
  # ActiveStorage compatibility
1072
- selects << 'service_name' if klass.name == 'ActiveStorage::Blob' && ActiveStorage::Blob.columns_hash.key?('service_name')
1073
- selects << 'blob_id' if klass.name == 'ActiveStorage::Attachment' && ActiveStorage::Attachment.columns_hash.key?('blob_id')
1072
+ selects << 'service_name' if klass.name == 'ActiveStorage::Blob' && ::ActiveStorage::Blob.columns_hash.key?('service_name')
1073
+ selects << 'blob_id' if klass.name == 'ActiveStorage::Attachment' && ::ActiveStorage::Attachment.columns_hash.key?('blob_id')
1074
1074
  # Pay gem compatibility
1075
1075
  selects << 'processor' if klass.name == 'Pay::Customer' && Pay::Customer.columns_hash.key?('processor')
1076
1076
  selects << 'customer_id' if klass.name == 'Pay::Subscription' && Pay::Subscription.columns_hash.key?('customer_id')
@@ -2078,6 +2078,23 @@ class Object
2078
2078
  # add_csp_hash
2079
2079
  # end
2080
2080
  # end
2081
+
2082
+ # Associate and unassociate in an N:M relation
2083
+ self.define_method :associate do
2084
+ if (base_class = (model = params['modelName']&.constantize).base_class)
2085
+ args = params['args']
2086
+ record = base_class.create(args[0] => args[1], args[2] => args[3])
2087
+ add_csp_hash
2088
+ render json: { data: record.id }
2089
+ end
2090
+ end
2091
+ self.define_method :unassociate do
2092
+ if (base_class = (model = params['modelName']&.constantize).base_class)
2093
+ base_class.find_by(base_class._pk_as_array&.first => params['id']).delete
2094
+ add_csp_hash
2095
+ end
2096
+ end
2097
+
2081
2098
  self.define_method :orphans do
2082
2099
  instance_variable_set(:@orphans, ::Brick.find_orphans(::Brick.set_db_schema(params).first))
2083
2100
  add_csp_hash
@@ -2440,7 +2457,7 @@ class Object
2440
2457
  if (new_obj = model.new(new_params)).respond_to?(:serializable_hash)
2441
2458
  # Convert any Filename objects with nil into an empty string so that #encode can be called on them
2442
2459
  new_obj.serializable_hash.each do |k, v|
2443
- new_obj.send("#{k}=", ActiveStorage::Filename.new('')) if v.is_a?(ActiveStorage::Filename) && !v.instance_variable_get(:@filename)
2460
+ new_obj.send("#{k}=", ::ActiveStorage::Filename.new('')) if v.is_a?(::ActiveStorage::Filename) && !v.instance_variable_get(:@filename)
2444
2461
  end if Object.const_defined?('ActiveStorage')
2445
2462
  end
2446
2463
  instance_variable_set("@#{singular_table_name}".to_sym, new_obj)
@@ -3087,7 +3104,7 @@ module Brick
3087
3104
  else
3088
3105
  res_name = (tbl_name_parts = tbl_name.split('.'))[0..-2].first
3089
3106
  res_name << '.' if res_name
3090
- (res_name ||= +'') << relation&.fetch(:resource, nil) || tbl_name_parts.last
3107
+ (res_name ||= +'') << (relation&.fetch(:resource, nil) || tbl_name_parts.last)
3091
3108
  end
3092
3109
 
3093
3110
  res_parts = ((mode == :singular) ? res_name.singularize : res_name).split('.')
@@ -198,7 +198,7 @@ function linkSchemas() {
198
198
 
199
199
  # Treat ActiveStorage::Blob metadata as JSON
200
200
  if ::Brick.config.table_name_prefixes.fetch('active_storage_', nil) == 'ActiveStorage' &&
201
- ActiveStorage.const_defined?('Blob')
201
+ ::ActiveStorage.const_defined?('Blob')
202
202
  unless (md = (::Brick.config.model_descrips ||= {})).key?('ActiveStorage::Blob')
203
203
  md['ActiveStorage::Blob'] = '[filename]'
204
204
  end
@@ -643,7 +643,7 @@ window.addEventListener(\"popstate\", linkSchemas);
643
643
  end
644
644
  # ActiveStorage has_one_attached and has_many_attached needs additional filtering on the name
645
645
  if (as_name = hm_assoc.klass&._active_storage_name(hm_assoc.name)) # ActiveStorage HMT
646
- prefix = 'attachments.' if hm_assoc.through_reflection&.klass&.<= ActiveStorage::Attachment
646
+ prefix = 'attachments.' if hm_assoc.through_reflection&.klass&.<= ::ActiveStorage::Attachment
647
647
  keys << ["#{prefix}name", as_name]
648
648
  end
649
649
  keys.to_h
@@ -1315,9 +1315,26 @@ end
1315
1315
  # or
1316
1316
  # Rails.application.reloader.to_prepare do ... end
1317
1317
  self.class.class_exec { include ::Brick::Rails::FormTags } unless respond_to?(:brick_grid)
1318
- # Write out the mega-grid
1318
+
1319
+ #{# 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)
1321
+ options = {}
1322
+ options[:prefix] = prefix unless prefix.blank?
1323
+ if taa.is_a?(String) # Write out a constellation
1324
+ representation = :constellation
1325
+ "
1326
+ brick_constellation(@#{res_name}, #{options.inspect}, bt_descrip: @_brick_bt_descrip, bts: bts)"
1327
+ elsif taa.is_a?(Symbol) # Write out a bezier representation
1328
+ "
1329
+ brick_bezier(@#{res_name}, #{options.inspect}, bt_descrip: @_brick_bt_descrip, bts: bts)"
1330
+ else # Write out the mega-grid
1331
+ representation = :grid
1332
+ "
1319
1333
  brick_grid(@#{res_name}, @_brick_sequence, @_brick_incl, @_brick_excl,
1320
- cols, bt_descrip: @_brick_bt_descrip, poly_cols: poly_cols, bts: bts, hms_keys: #{hms_keys.inspect}, hms_cols: {#{hms_columns.join(', ')}}) %>
1334
+ cols, bt_descrip: @_brick_bt_descrip,
1335
+ poly_cols: poly_cols, bts: bts, hms_keys: #{hms_keys.inspect}, hms_cols: {#{hms_columns.join(', ')}})"
1336
+ end}
1337
+ %>
1321
1338
 
1322
1339
  #{"<hr><%= link_to(\"New #{new_path_name = "new_#{path_obj_name}_path"
1323
1340
  obj_name}\", #{new_path_name}, { class: '__brick' }) if respond_to?(:#{new_path_name}) %>" unless @_brick_model.is_view?}
@@ -1727,10 +1744,11 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1727
1744
  }
1728
1745
  <%= \" showErd();\n\" if (@_brick_erd || 0) > 0
1729
1746
  %></script>
1730
-
1731
- <% end
1732
-
1733
- %><script>
1747
+ <% end %>
1748
+ "
1749
+ end
1750
+ if representation == :grid
1751
+ "<script>
1734
1752
  <% # Make column headers sort when clicked
1735
1753
  # %%% Create a smart javascript routine which can do this client-side %>
1736
1754
  [... document.getElementsByTagName(\"TH\")].forEach(function (th) {
@@ -84,7 +84,7 @@ module Brick::Rails::FormBuilder
84
84
  opts = enum_type.send(:mapping)&.each_with_object([]) { |v, s| s << [v.first, v.first] } || []
85
85
  out << self.select(method.to_sym, [["(No #{method} chosen)", '^^^brick_NULL^^^']] + opts, { value: val || '^^^brick_NULL^^^' }, options)
86
86
  else
87
- digit_pattern = col_type == :integer ? '\d*' : '\d*(?:\.\d*|)'
87
+ digit_pattern = col_type == :integer ? '(?:-|)\d*' : '(?:-|)\d*(?:\.\d*|)'
88
88
  # Used to do this for float / decimal: self.number_field method.to_sym
89
89
  out << self.text_field(method.to_sym, { pattern: digit_pattern, class: 'check-validity' })
90
90
  end
@@ -3,27 +3,9 @@ module Brick::Rails::FormTags
3
3
  def brick_grid(relation = nil, sequence = nil, inclusions = nil, exclusions = nil,
4
4
  cols = {}, bt_descrip: nil, poly_cols: nil, bts: {}, hms_keys: [], hms_cols: {},
5
5
  show_header: nil, show_row_count: nil, show_erd_button: nil, show_in_app_button: nil, show_new_button: nil, show_avo_button: nil, show_aa_button: nil)
6
- # When a relation is not provided, first see if one exists which matches the controller name
7
- unless (relation ||= instance_variable_get("@#{controller_name}".to_sym))
8
- # Failing that, dig through the instance variables with hopes to find something that is an ActiveRecord::Relation
9
- case (collections = _brick_resource_from_iv).length
10
- when 0
11
- puts '#brick_grid: Not having been provided with a collection to work from, searched through all instance variables to find an ActiveRecord::Relation. None could be found.'
12
- return
13
- when 1 # If there's only one type match then simply get the first one, hoping that this is what they intended
14
- relation = instance_variable_get(iv = (chosen = collections.first).last.first)
15
- puts "#brick_grid: Not having been provided with a collection to work from, first tried @#{controller_name}.
16
- Failing that, have searched through instance variables and found #{iv} of type #{chosen.first.name}.
17
- Running with it!"
18
- else
19
- myriad = collections.each_with_object([]) { |c, s| c.last.each { |iv| s << "#{iv} (#{c.first.name})" } }
20
- puts "#brick_grid: Not having been provided with a collection to work from, first tried @#{controller_name}, and then searched through all instance variables.
21
- Found ActiveRecord::Relation objects of multiple types:
22
- #{myriad.inspect}
23
- Not knowing which of these to render, have erred on the side of caution and simply provided this warning message."
24
- return
25
- end
26
- end
6
+ # When a relation is not provided, first see if one exists which matches the controller name or
7
+ # something has turned up in the instance variables.
8
+ relation ||= (instance_variable_get("@#{controller_name}".to_sym) || _brick_resource_from_iv)
27
9
 
28
10
  nfc = Brick.config.sidescroll.fetch(relation.table_name, nil)&.fetch(:num_frozen_columns, nil) ||
29
11
  Brick.config.sidescroll.fetch(:num_frozen_columns, nil) ||
@@ -458,8 +440,8 @@ function onImagesLoaded(event) {
458
440
  elsif hma.include?(k) # has_many_attached
459
441
  { sql_type: 'binary', type: :files }
460
442
  elsif rtans&.key?(k) # has_rich_text
461
- k = rtans[k]
462
- { sql_type: 'varchar', type: :text }
443
+ k = rtans[k]
444
+ { sql_type: 'varchar', type: :text }
463
445
  end
464
446
  col = (ActiveRecord::ConnectionAdapters::Column.new(
465
447
  '', nil, ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(**kwargs)
@@ -541,6 +523,159 @@ function onImagesLoaded(event) {
541
523
  end
542
524
  end # brick_form_for
543
525
 
526
+ # ------------------------------------------
527
+ # Our cool N:M checkbox constellation editor
528
+ def brick_constellation(relation = nil, options = {}, x_axis: nil, y_axis: nil, bt_descrip: nil, bts: {})
529
+ x_axis, x_list, y_axis, y_list, existing = _n_m_prep(relation, x_axis, y_axis)
530
+
531
+ # HTML for constellation
532
+ prefix = options[:prefix]
533
+ out = +"<form action=\"#{"#{prefix}/" if prefix}brick_constellation\">
534
+ <table id=\"#{table_name = relation.table_name.split('.').last}\" class=\"shadow\">
535
+ <thead><tr><td></td>
536
+ "
537
+ # Header row with X axis values
538
+ x_list.each do |x_item|
539
+ out << " <th>#{x_item.first}</th>
540
+ "
541
+ end
542
+ out << " </tr></thead>
543
+ <tbody>
544
+ "
545
+ obj_path = "#{relation.klass._brick_index(:singular)}_path".to_sym
546
+ link_arrow = link_to('⇛', send(obj_path, '____'), { class: 'big-arrow' })
547
+ pk_as_array = relation.klass._pk_as_array
548
+ y_list.each do |y_item|
549
+ out << " <tr><th>#{y_item.first}</th>
550
+ "
551
+ x_list.each do |x_item|
552
+ 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
554
+ out << " <td><input type=\"checkbox\" name=\"#{table_name}\" #{"x-id=\"#{item_id}\" " if checked
555
+ }\" value=\"#{x_item.last}_#{y_item.last}\"#{' checked' if checked}>
556
+ #{link_arrow.gsub('____', item_id) if checked}</td>
557
+ "
558
+ end
559
+ out << " </tr>
560
+ "
561
+ end
562
+ out << " </tbody>
563
+ </table>
564
+ <script>
565
+ var constellation = document.getElementById(\"#{table_name}\");
566
+ var nextSib,
567
+ _this;
568
+ [... constellation.getElementsByTagName(\"INPUT\")].forEach(function (x) {
569
+ x.addEventListener(\"change\", function (y) {
570
+ _this = this;
571
+ if (this.checked) {
572
+ var ids = this.value.split(\"_\");
573
+ doFetch(\"POST\", {modelName: \"#{relation.klass.name}\",
574
+ args: [#{x_axis[1].inspect}, ids[0], #{y_axis[1].inspect}, ids[1]],
575
+ _brick_action: \"/#{prefix}brick_associate\"},
576
+ function (p) { // If it returns successfully, create an <a> element
577
+ p.text().then(function (response) {
578
+ var recordId = JSON.parse(response).data;
579
+ if (recordId) {
580
+ console.log(_this.getAttribute(\"x-id\"));
581
+ var tmp = document.createElement(\"DIV\");
582
+ tmp.innerHTML = \"#{link_arrow.gsub('"', '\"')}\".replace(\"____\", recordId);
583
+ _this.parentElement.append(tmp.firstChild);
584
+ }
585
+ });
586
+ }
587
+ );
588
+ } else if (nextSib = this.nextElementSibling) {
589
+ doFetch(\"DELETE\", {modelName: \"#{relation.klass.name}\",
590
+ id: this.getAttribute(\"x-id\"),
591
+ _brick_action: \"/#{prefix}brick_associate\"},
592
+ function (p) { // If it returns successfully, remove the an <a> element
593
+ _this.parentElement.removeChild(nextSib);
594
+ }
595
+ );
596
+ }
597
+ });
598
+ });
599
+ </script>
600
+ </form>
601
+ "
602
+ out.html_safe
603
+ end # brick_constellation
604
+
605
+ # ---------------------------------
606
+ # Our cool N:M bezier visualisation
607
+ # (...... work in progress .......)
608
+ def brick_bezier(relation = nil, options = {}, x_axis: nil, y_axis: nil, bt_descrip: nil, bts: {})
609
+ x_axis, x_list, y_axis, y_list, existing = _n_m_prep(relation, x_axis, y_axis)
610
+ # HTML for constellation
611
+ # X axis (List on left side)
612
+ out = +"<table id=\"#{x_axis.first}\" class=\"shadow\">
613
+ <tbody>
614
+ "
615
+ x_list.each_with_index { |x_item, idx| out << " <tr>#{"<th rowspan=\"#{x_list.length}\">#{x_axis.first}</th>" if idx.zero?}<td>#{x_item.first}</td></tr>" }
616
+ out << " </tbody>
617
+ </table>
618
+ "
619
+
620
+ # Y axis (List on right side)
621
+ out << "<table id=\"#{y_axis.first}\" class=\"shadow\">
622
+ <tbody>
623
+ "
624
+ y_list.each_with_index { |y_item, idx| out << " <tr><td>#{y_item.first}</td>#{"<th rowspan=\"#{y_list.length}\">#{y_axis.first}</th>" if idx.zero?}</tr>" }
625
+ out << " </tbody>
626
+ </table>
627
+ "
628
+
629
+ out.html_safe
630
+ end # brick_bezier
631
+
632
+ def _n_m_prep(relation, x_axis, y_axis)
633
+ relation ||= (instance_variable_get("@#{controller_name}".to_sym) || _brick_resource_from_iv)
634
+ # Just find the first two BT things at this point
635
+
636
+ 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
+ fk_assocs = klass.reflect_on_all_associations.each_with_object([]) do |assoc, s|
640
+ s << [assoc.name.to_s, assoc.foreign_key, assoc.klass] if assoc.belongs_to?
641
+ end
642
+
643
+ if (x_axis = fk_assocs.find { |assoc| assoc.include?(x_axis) })
644
+ fk_assocs -= x_axis
645
+ end
646
+ if (y_axis = fk_assocs.find { |assoc| assoc.include?(y_axis) })
647
+ fk_assocs -= y_axis
648
+ end
649
+ y_axis = fk_assocs.shift unless y_axis
650
+ x_axis = fk_assocs.shift unless x_axis
651
+ puts "FK Leftovers: #{fk_assocs.join(', ')}" unless fk_assocs.empty?
652
+
653
+ existing = relation.each_with_object([]) do |row, s|
654
+ row_id = row.send(klass.primary_key)
655
+ if (x_id = row.send(x_axis[1])) && (y_id = row.send(y_axis[1]))
656
+ s << [row_id, x_id, y_id]
657
+ end
658
+ end
659
+ x_list = _expand_collection(x_axis.last.all)
660
+ y_list = _expand_collection(y_axis.last.all)
661
+ [x_axis, x_list, y_axis, y_list, existing]
662
+ end
663
+
664
+ def _expand_collection(relation)
665
+ collection, descrip_cols = relation.brick_list
666
+ details = []
667
+ obj_pk = relation.klass.primary_key
668
+ collection&.brick_(:each) do |obj|
669
+ details << [
670
+ obj.brick_descrip(
671
+ descrip_cols&.first&.map { |col2| obj.send(col2.last) },
672
+ obj_pk
673
+ ), obj.send(obj_pk)
674
+ ]
675
+ end
676
+ details
677
+ end
678
+
544
679
  # --------------------------------
545
680
  def link_to_brick(*args, **kwargs)
546
681
  return unless ::Brick.config.mode == :on
@@ -627,7 +762,7 @@ function onImagesLoaded(event) {
627
762
  else
628
763
  # puts "Warning: link_to_brick could not find a class for \"#{controller_path}\" -- consider setting @_brick_model within that controller."
629
764
  # if (hits = res_names.keys & instance_variables.map { |v| v.to_s[1..-1] }).present?
630
- if (links = _brick_resource_from_iv(true)).length == 1 # If there's only one match then use any text that was supplied
765
+ if (links = _brick_relation_from_iv(true)).length == 1 # If there's only one match then use any text that was supplied
631
766
  link_to_brick(text || links.first.last.join('/'), links.first.first, **kwargs)
632
767
  else
633
768
  links.each_with_object([]) do |v, s|
@@ -677,7 +812,7 @@ btnAddCol.addEventListener(\"click\", function () {
677
812
  private
678
813
 
679
814
  # Dig through all instance variables with hopes to find any that appear related to ActiveRecord
680
- def _brick_resource_from_iv(trim_ampersand = false)
815
+ def _brick_relation_from_iv(trim_ampersand = false)
681
816
  instance_variables.each_with_object(Hash.new { |h, k| h[k] = [] }) do |name, s|
682
817
  iv_name = trim_ampersand ? name.to_s[1..-1] : name
683
818
  case (val = instance_variable_get(name))
@@ -688,4 +823,26 @@ private
688
823
  end
689
824
  end
690
825
  end
826
+
827
+ def _brick_resource_from_iv
828
+ # Failing that, dig through the instance variables with hopes to find something that is an ActiveRecord::Relation
829
+ case (collections = _brick_relation_from_iv).length
830
+ when 0
831
+ puts '#brick_grid: Not having been provided with a collection to work from, searched through all instance variables to find an ActiveRecord::Relation. None could be found.'
832
+ return
833
+ when 1 # If there's only one type match then simply get the first one, hoping that this is what they intended
834
+ relation = instance_variable_get(iv = (chosen = collections.first).last.first)
835
+ puts "#brick_grid: Not having been provided with a collection to work from, first tried @#{controller_name}.
836
+ Failing that, have searched through instance variables and found #{iv} of type #{chosen.first.name}.
837
+ Running with it!"
838
+ relation
839
+ else
840
+ myriad = collections.each_with_object([]) { |c, s| c.last.each { |iv| s << "#{iv} (#{c.first.name})" } }
841
+ puts "#brick_grid: Not having been provided with a collection to work from, first tried @#{controller_name}, and then searched through all instance variables.
842
+ Found ActiveRecord::Relation objects of multiple types:
843
+ #{myriad.inspect}
844
+ Not knowing which of these to render, have erred on the side of caution and simply provided this warning message."
845
+ return
846
+ end
847
+ end
691
848
  end
@@ -173,7 +173,12 @@ erDiagram
173
173
  <%= erd_sidelinks(shown_classes, hm_class).html_safe %>
174
174
  <% end
175
175
  def dt_lookup(dt)
176
- { 'integer' => 'int', }[dt] || dt&.tr(' ', '_') || 'int'
176
+ puts dt.inspect
177
+ { 'integer' => 'int', 'character varying' => 'varchar', 'double precision' => 'float',
178
+ 'timestamp without time zone' => 'timestamp',
179
+ 'timestamp with time zone' => 'timestamp',
180
+ 'time without time zone' => 'time',
181
+ 'time with time zone' => 'time' }[dt] || dt&.tr(' ', '_') || 'int'
177
182
  end
178
183
  callbacks.merge({#{model_short_name.inspect} => #{model.name}}).each do |cb_k, cb_class|
179
184
  cb_relation = ::Brick.relations[cb_class.table_name]
@@ -193,6 +198,12 @@ erDiagram
193
198
  %>
194
199
  <%= \"#\{dt_lookup(cols[fk]&.first)} #\{fk} \\\"&nbsp;&nbsp;&nbsp;&nbsp;fk\\\"\".html_safe unless pkeys&.include?(fk) %><%
195
200
  end
201
+ end %><%
202
+ if (erd_sc = Brick.config.erd_show_columns) == true || erd_sc&.include?(cb_class.name)
203
+ cols&.each do |col|
204
+ next if pkeys.include?(col.first) || fkeys.include?(col.first) %>
205
+ <%= \"#\{dt_lookup(col[1]&.first&.to_s)} #\{col.first}\".html_safe %><%
206
+ end
196
207
  end %>
197
208
  }
198
209
  <% end
@@ -287,6 +287,15 @@ module Brick
287
287
  # post("/#{controller_prefix}brick_schema", to: 'brick_gem#schema_create', as: schema_as.to_s)
288
288
  # end
289
289
 
290
+ if (associate_as = "#{controller_prefix.tr('/', '_')}brick_associate".to_sym)
291
+ (
292
+ !(associate_route = instance_variable_get(:@set).named_routes.find { |route| route.first == associate_as }&.last) ||
293
+ !associate_route.ast.to_s.include?("/#{controller_prefix}brick_associate/")
294
+ )
295
+ post("/#{controller_prefix}brick_associate", to: 'brick_gem#associate', as: associate_as.to_s)
296
+ delete("/#{controller_prefix}brick_associate", to: 'brick_gem#unassociate')
297
+ end
298
+
290
299
  if ::Brick.config.add_orphans && (orphans_as = "#{controller_prefix.tr('/', '_')}brick_orphans".to_sym)
291
300
  (
292
301
  !(orphans_route = instance_variable_get(:@set).named_routes.find { |route| route.first == orphans_as }&.last) ||
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 211
8
+ TINY = 212
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
@@ -310,6 +310,14 @@ if ActiveRecord::Base.respond_to?(:brick_select) && !::Brick.initializer_loaded
310
310
  # # have a table to still be treated as associative, causing HMTs to be auto-generated.)
311
311
  # Brick.treat_as_associative = ['flights']
312
312
 
313
+ # # Further, if you want to present a given associative table in various ways then you can choose a 2D
314
+ # # constellation map of checkboxes, or bezier curves showing the association between a list at the left and at
315
+ # # the right. Indicating just :bezier is the same as :bezier_full, which shows the full list of all possible
316
+ # # things that can be associated. :bezier_union shows just the ones that are currently wired up, and
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!] : []]'] }
320
+
313
321
  # # We normally don't show the timestamp columns \"created_at\", \"updated_at\", and \"deleted_at\", and also do
314
322
  # # not consider them when finding associative tables to support an N:M association. (That is, ones that can be a
315
323
  # # part of a has_many :through association.) If you want to use different exclusion columns than our defaults
@@ -334,6 +342,14 @@ if ActiveRecord::Base.respond_to?(:brick_select) && !::Brick.initializer_loaded
334
342
  # # user, then you can use model_descrips like this, putting expressions with property references in square brackets:
335
343
  # Brick.model_descrips = { 'User' => '[profile.firstname] [profile.lastname]' }
336
344
 
345
+ # # ERD SETTINGS
346
+
347
+ # # By default the Entity Relationship Diagram fragment which is available to be shown on the Grid page includes
348
+ # # primary and foreign keys. In order for it to show all columns in all cases, set this value to +true+:
349
+ # Brick.config.erd_show_columns = true
350
+ # # or to show all columns for specific tables, supply an array of model names:
351
+ # Brick.config.erd_show_columns = ['User', 'OrderDetail']
352
+
337
353
  # # SINGLE TABLE INHERITANCE
338
354
 
339
355
  # # Specify STI subclasses either directly by name or as a general module prefix that should always relate to a specific
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.211
4
+ version: 1.0.212
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-03-24 00:00:00.000000000 Z
11
+ date: 2024-03-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -285,7 +285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
285
285
  - !ruby/object:Gem::Version
286
286
  version: 1.3.6
287
287
  requirements: []
288
- rubygems_version: 3.1.6
288
+ rubygems_version: 3.2.33
289
289
  signing_key:
290
290
  specification_version: 4
291
291
  summary: Create a Rails app from data alone