brick 1.0.211 → 1.0.212

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