brick 1.0.61 → 1.0.64

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: 67de1c0cde3f28379388dfe0c2aa488197c5cfaad1a108408b506ba66ab64b38
4
- data.tar.gz: 7d3e40960043e5e09e483dee791dd5f7c1d0f54f692a39ec2ef85b1b5cba1073
3
+ metadata.gz: c48ee6a2f143563e2e443f6edf0dc0e04e800e8fae6951e05b0362956129e950
4
+ data.tar.gz: f632f6e795dcf9b3aeb97e36a37c8074302c5967206b5f415a9a4b862bb4fda7
5
5
  SHA512:
6
- metadata.gz: 90ae5f021e562d3188c4bde03e9b71a744417c5223f5736cff97cce90646b8b06860b245dcb718414fdce00df2c772bcbe1ad0d102310d7f3ff030641591387d
7
- data.tar.gz: 704f2dde1c0043a141d96ff272346ac583e7f784df494bb4ce39f4bf5e2cd06ffadfa37783a1bfb0671c5a5fd120819c9f286e5c9bd0608764aa10338840d881
6
+ metadata.gz: 0cd5e05899a2f780a0bbf378f8f49e49f604b90cf122f2cd7efe34839c434af814c9a228f36bd18b112cd8e40b6736e88742ea0f2a1a0b55ac3f8f573e614918
7
+ data.tar.gz: a34a9f47746159f6b4b39f8a3cd994055e022be9eefe8737b73c63e5d475b283ae3b99a6a11e3faf140ca6efbf67d7f400e03147a757c237c8875f11ea2c0ea4
@@ -1132,6 +1132,7 @@ class Object
1132
1132
  @_brick_bt_descrip = model._br_bt_descrip
1133
1133
  @_brick_hm_counts = model._br_hm_counts
1134
1134
  @_brick_join_array = join_array
1135
+ @_brick_erd = params['_brick_erd']&.to_i
1135
1136
  end
1136
1137
 
1137
1138
  _, order_by_txt = model._brick_calculate_ordering(default_ordering(table_name, pk))
@@ -1542,8 +1543,10 @@ module ActiveRecord::ConnectionHandling
1542
1543
  ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
1543
1544
  AND kcu.TABLE_NAME = tc.TABLE_NAME
1544
1545
  AND kcu.CONSTRAINT_NAME = tc.constraint_name
1545
- WHERE t.table_schema #{
1546
- is_postgres ? "NOT IN ('information_schema', 'pg_catalog')" : "= '#{ActiveRecord::Base.connection.current_database.tr("'", "''")}'"}#{"
1546
+ WHERE t.table_schema #{is_postgres ?
1547
+ "NOT IN ('information_schema', 'pg_catalog')"
1548
+ :
1549
+ "= '#{ActiveRecord::Base.connection.current_database.tr("'", "''")}'"}#{"
1547
1550
  AND t.table_schema = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }
1548
1551
  -- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
1549
1552
  AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
@@ -47,7 +47,10 @@ module Brick
47
47
  end
48
48
 
49
49
  # After we're initialized and before running the rest of stuff, put our configuration in place
50
- ActiveSupport.on_load(:after_initialize) do
50
+ ActiveSupport.on_load(:after_initialize) do |app|
51
+ assets_path = File.expand_path("#{__dir__}/../../../../vendor/assets")
52
+ (app.config.assets.precompile ||= []) << "#{assets_path}/images/brick_erd.png"
53
+ (app.config.assets.paths ||= []) << assets_path
51
54
  # ====================================
52
55
  # Dynamically create generic templates
53
56
  # ====================================
@@ -179,6 +182,28 @@ module Brick
179
182
  h1, h3 {
180
183
  margin-bottom: 0;
181
184
  }
185
+ #imgErd {
186
+ background-image:url(assets/brick_erd.png);
187
+ background-size: 100% 100%;
188
+ width: 2.2em;
189
+ height: 2.2em;
190
+ cursor: pointer;
191
+ }
192
+ #mermaidErd {
193
+ position: relative;
194
+ display: none;
195
+ }
196
+ #mermaidErd .exclude {
197
+ position: absolute;
198
+ color: red;
199
+ top: 0;
200
+ right: 0;
201
+ cursor: pointer;
202
+ }
203
+ .relatedModel {
204
+ cursor: pointer;
205
+ }
206
+
182
207
  #dropper {
183
208
  background-color: #eee;
184
209
  }
@@ -196,6 +221,8 @@ table {
196
221
  border-collapse: collapse;
197
222
  font-size: 0.9em;
198
223
  font-family: sans-serif;
224
+ }
225
+ table.shadow {
199
226
  min-width: 400px;
200
227
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
201
228
  }
@@ -213,6 +240,7 @@ tr th {
213
240
  display: none;
214
241
  top: 0;
215
242
  right: 0;
243
+ cursor: pointer;
216
244
  }
217
245
  #headerTop tr th:hover {
218
246
  background-color: #18B090;
@@ -245,7 +273,7 @@ tr th, tr td {
245
273
  color: #80B8D2;
246
274
  }
247
275
 
248
- table tbody tr {
276
+ table.shadow tbody tr {
249
277
  border-bottom: thin solid #dddddd;
250
278
  }
251
279
 
@@ -253,7 +281,7 @@ table tbody tr:nth-of-type(even) {
253
281
  background-color: #f3f3f3;
254
282
  }
255
283
 
256
- table tbody tr:last-of-type {
284
+ table.shadow tbody tr:last-of-type {
257
285
  border-bottom: 2px solid #009879;
258
286
  }
259
287
 
@@ -327,6 +355,28 @@ def hide_bcrypt(val, max_len = 200)
327
355
  end
328
356
  val
329
357
  end
358
+ end
359
+ def display_value(col_type, val)
360
+ case col_type
361
+ when 'geometry'
362
+ if Object.const_defined?('RGeo')
363
+ @is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2' if @is_mysql.nil?
364
+ if @is_mysql
365
+ # MySQL's \"Internal Geometry Format\" is like WKB, but with an initial 4 bytes that indicates the SRID.
366
+ srid = val[..3].unpack('I')
367
+ val = val[4..]
368
+ end
369
+ RGeo::WKRep::WKBParser.new.parse(val)
370
+ else
371
+ '(Add RGeo gem to parse geometry detail)'
372
+ end
373
+ else
374
+ if col_type
375
+ hide_bcrypt(val)
376
+ else
377
+ '?'
378
+ end
379
+ end
330
380
  end %>"
331
381
 
332
382
  if ['index', 'show', 'update'].include?(args.first)
@@ -401,25 +451,28 @@ window.addEventListener(\"pageshow\", function() {
401
451
 
402
452
  function changeout(href, param, value, trimAfter) {
403
453
  var hrefParts = href.split(\"?\");
404
- if (param === undefined || param === null) {
454
+ var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
455
+ if (param === undefined || param === null || param === -1) {
405
456
  hrefParts = hrefParts[0].split(\"://\");
406
457
  var pathParts = hrefParts[hrefParts.length - 1].split(\"/\");
407
458
  if (value === undefined)
408
459
  // A couple possibilities if it's namespaced, starting with two parts in the path -- and then try just one
409
460
  return [pathParts.slice(1, 3).join('/'), pathParts.slice(1, 2)[0]];
410
- else
411
- return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value;
461
+ else {
462
+ var queryString = param ? \"?\" + params.join(\"&\") : \"\";
463
+ return hrefParts[0] + \"://\" + pathParts[0] + \"/\" + value + queryString;
464
+ }
412
465
  }
413
466
  if (trimAfter) {
414
467
  var pathParts = hrefParts[0].split(\"/\");
415
468
  while (pathParts.lastIndexOf(trimAfter) !== pathParts.length - 1) pathParts.pop();
416
469
  hrefParts[0] = pathParts.join(\"/\");
417
470
  }
418
- var params = hrefParts.length > 1 ? hrefParts[1].split(\"&\") : [];
419
- params = params.reduce(function (s, v) { var parts = v.split(\"=\"); s[parts[0]] = parts[1]; return s; }, {});
471
+ params = params.reduce(function (s, v) { var parts = v.split(\"=\"); if (parts[1]) s[parts[0]] = parts[1]; return s; }, {});
420
472
  if (value === undefined) return params[param];
421
473
  params[param] = value;
422
- return hrefParts[0] + \"?\" + Object.keys(params).reduce(function (s, v) { s.push(v + \"=\" + params[v]); return s; }, []).join(\"&\");
474
+ var finalParams = Object.keys(params).reduce(function (s, v) { if (params[v]) s.push(v + \"=\" + params[v]); return s; }, []).join(\"&\");
475
+ return hrefParts[0] + (finalParams.length > 0 ? \"?\" + finalParams : \"\");
423
476
  }
424
477
 
425
478
  // Snag first TR for sticky header
@@ -583,7 +636,10 @@ if (headerTop) {
583
636
  <p style=\"color: green\"><%= notice %></p>#{"
584
637
  <select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
585
638
  <select id=\"tbl\">#{table_options}</select>
586
- <h1>#{model_plural = model_name.pluralize}</h1>#{template_link}<%
639
+ <table id=\"resourceName\"><tr>
640
+ <td><h1>#{model_plural = model_name.pluralize}</h1></td>
641
+ <td id=\"imgErd\" title=\"Show ERD\"></td>
642
+ </tr></table>#{template_link}<%
587
643
  if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
588
644
  description %><br><%
589
645
  end
@@ -614,10 +670,69 @@ if (headerTop) {
614
670
  });
615
671
  });
616
672
  </script>
673
+ <% end
674
+ if true # @_brick_erd
675
+ %><div id=\"mermaidErd\" class=\"mermaid\">
676
+ erDiagram
677
+ <% model_short_name = #{@_brick_model.name.split('::').last.inspect}
678
+ callbacks = {}
679
+ @_brick_bt_descrip.each do |bt|
680
+ bt_class = bt[1].first.first
681
+ callbacks[bt_name = bt_class.name.split('::').last] = bt_class
682
+ is_has_one = #{@_brick_model.name}.reflect_on_association(bt.first).inverse_of&.macro == :has_one ||
683
+ ::Brick.config.has_ones&.fetch('#{@_brick_model.name}', nil)&.key?(bt.first.to_s)
684
+ %> <%= \"#\{model_short_name} #\{is_has_one ? '||' : '}o'}--|| #\{bt_name} : \\\"#\{
685
+ bt.first unless bt.first.to_s == bt[1].first.first.name.underscore.singularize.tr('/', '_')
686
+ }\\\"\".html_safe %>
687
+ <% end
688
+ last_through = nil
689
+ @_brick_hm_counts.each do |hm|
690
+ # Skip showing self-referencing HM links since they would have already been drawn while evaluating the BT side
691
+ next if (hm_class = hm.last.klass) == #{@_brick_model.name}
692
+
693
+ callbacks[hm_name = hm_class.name.split('::').last] = hm_class
694
+ if (through = hm.last.options[:through]&.to_s) # has_many :through (HMT)
695
+ callbacks[through.singularize.camelize] = (through_assoc = hm.last.source_reflection).active_record
696
+ if last_through == through # Same HM, so no need to build it again, and for clarity just put in a blank line
697
+ %><%= \"\n\"
698
+ %><% else
699
+ %> <%= \"#\{model_short_name} ||--o{ #\{through_assoc.active_record.name}\".html_safe %> : \"\"
700
+ <% last_through = through
701
+ end
702
+ %> <%= \"#\{through_assoc.active_record.name} }o--|| #\{hm_name}\".html_safe %> : \"\"
703
+ <%= \"#\{model_short_name} }o..o{ #\{hm_name} : \\\"#\{hm.first}\\\"\".html_safe %><%
704
+ else # has_many
705
+ %> <%= \"#\{model_short_name} ||--o{ #\{hm_name} : \\\"#\{
706
+ hm_name unless hm.first.to_s == hm_class.name.underscore.pluralize.tr('/', '_')
707
+ }\\\"\".html_safe %><%
708
+ end %>
709
+ <% end
710
+ callbacks.merge({model_short_name => #{@_brick_model.name}}).each do |cb_k, cb_class|
711
+ cb_relation = ::Brick.relations[cb_class.table_name]
712
+ pkeys = cb_relation[:pkey]&.first&.last
713
+ fkeys = cb_relation[:fks]&.values&.each_with_object([]) { |fk, s| s << fk[:fk] if fk.fetch(:is_bt, nil) }
714
+ %> <%= cb_k %> {<%
715
+ pkeys&.each do |pk| %>
716
+ <%= \"int #\{pk} \\\"PK#\{' fk' if fkeys&.include?(pk)}\\\"\".html_safe %><%
717
+ end %><%
718
+ fkeys&.each do |fk|
719
+ if fk.is_a?(Array)
720
+ fk.each do |fk_part| %>
721
+ <%= \"int #\{fk_part} \\\"&nbsp;&nbsp;&nbsp;&nbsp;fk\\\"\".html_safe unless pkeys&.include?(fk_part) %><%
722
+ end
723
+ else %>
724
+ <%= \"int #\{fk} \\\"&nbsp;&nbsp;&nbsp;&nbsp;fk\\\"\".html_safe unless pkeys&.include?(fk) %><%
725
+ end
726
+ end %>
727
+ }
728
+ <% end
729
+ # callback < %= cb_k % > erdClick
730
+ %>
731
+ </div>
617
732
  <% end
618
733
 
619
- %><table id=\"headerTop\">
620
- <table id=\"#{table_name}\">
734
+ %><table id=\"headerTop\"></table>
735
+ <table id=\"#{table_name}\" class=\"shadow\">
621
736
  <thead><tr>#{"<th x-order=\"#{pk.join(',')}\"></th>" if pk.present?}<%=
622
737
  # Consider getting the name from the association -- hm.first.name -- if a more \"friendly\" alias should be used for a screwy table name
623
738
  cols = {#{hms_keys = []
@@ -683,7 +798,7 @@ if (headerTop) {
683
798
  <% end
684
799
  elsif (hms_col = hms_cols[col_name])
685
800
  if hms_col.length == 1 %>
686
- <%= hms_col.first %>
801
+ <%= hms_col.first %>
687
802
  <% else
688
803
  klass = (col = cols[col_name])[1]
689
804
  txt = if col[2] == 'HO'
@@ -696,8 +811,8 @@ if (headerTop) {
696
811
  end %>
697
812
  <%= link_to txt, send(\"#\{klass.name.underscore.tr('/', '_').pluralize}_path\".to_sym, hms_col[2]) unless hms_col[1]&.zero? %>
698
813
  <% end
699
- elsif cols.key?(col_name)
700
- %><%= hide_bcrypt(val) %><%
814
+ elsif (col = cols[col_name])
815
+ %><%= display_value(col&.type || col&.sql_type, val) %><%
701
816
  else # Bad column name!
702
817
  %>?<%
703
818
  end
@@ -720,7 +835,7 @@ if (headerTop) {
720
835
  <select id=\"schema\">#{schema_options}</select>" if ::Brick.config.schema_behavior[:multitenant] && ::Brick.db_schemas.length > 1}
721
836
  <select id=\"tbl\">#{table_options}</select>
722
837
  <h1>Status</h1>
723
- <table id=\"status\"><thead><tr>
838
+ <table id=\"status\" class=\"shadow\"><thead><tr>
724
839
  <th>Resource</th>
725
840
  <th>Table</th>
726
841
  <th>Migration</th>
@@ -754,7 +869,7 @@ if (headerTop) {
754
869
  %></td>
755
870
  <tr>
756
871
  <% end %>
757
- </tbody><table>
872
+ </tbody></table>
758
873
  #{script}"
759
874
 
760
875
  when 'orphans'
@@ -796,7 +911,7 @@ end
796
911
  # path_options << { '_brick_schema': } if
797
912
  # url = send(:#{model_name.underscore}_path, obj.#{pk})
798
913
  form_for(obj.becomes(#{model_name})) do |f| %>
799
- <table>
914
+ <table class=\"shadow\">
800
915
  <% has_fields = false
801
916
  @#{obj_name}.attributes.each do |k, val|
802
917
  col = #{model_name}.columns_hash[k] %>
@@ -853,7 +968,7 @@ end
853
968
  <% else
854
969
  html_options = {}
855
970
  html_options[:class] = 'dimmed' unless val
856
- case (col_type = #{model_name}.column_for_attribute(k).type)
971
+ case (col_type = col.type || col.sql_type)
857
972
  when :string, :text %>
858
973
  <% if is_bcrypt?(val) # || .readonly? %>
859
974
  <%= hide_bcrypt(val, 1000) %>
@@ -883,6 +998,8 @@ end
883
998
  # If it's not yet enabled then: create extension ltree;
884
999
  val %>
885
1000
  <% when :binary, :primary_key %>
1001
+ <% else %>
1002
+ <%= display_value(col_type, val) %>
886
1003
  <% end %>
887
1004
  <% end %>
888
1005
  </td>
@@ -903,7 +1020,7 @@ end
903
1020
  if (pk = hm.first.klass.primary_key)
904
1021
  hm_singular_name = (hm_name = hm.first.name.to_s).singularize.underscore
905
1022
  obj_pk = (pk.is_a?(Array) ? pk : [pk]).each_with_object([]) { |pk_part, s| s << "#{hm_singular_name}.#{pk_part}" }.join(', ')
906
- s << "<table id=\"#{hm_name}\">
1023
+ s << "<table id=\"#{hm_name}\" class=\"shadow\">
907
1024
  <tr><th>#{hm[3]}</th></tr>
908
1025
  <% collection = @#{obj_name}.#{hm_name}
909
1026
  collection = collection.is_a?(ActiveRecord::Associations::CollectionProxy) ? collection.order(#{pk.inspect}) : [collection].compact
@@ -938,7 +1055,62 @@ flatpickr(\".datetimepicker\", {enableTime: true});
938
1055
  flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
939
1056
  </script>
940
1057
  <% end %>
1058
+
1059
+ <% if true # @_brick_erd
1060
+ %>
941
1061
  <script>
1062
+ var imgErd = document.getElementById(\"imgErd\");
1063
+ var mermaidErd = document.getElementById(\"mermaidErd\");
1064
+ var mermaidCode;
1065
+ var cbs = {<%= callbacks.map { |k, v| \"#\{k}: \\\"#\{v.name.underscore.pluralize}\\\"\" }.join(', ').html_safe %>};
1066
+ imgErd.addEventListener(\"click\", showErd);
1067
+ function showErd() {
1068
+ imgErd.style.display = \"none\";
1069
+ mermaidErd.style.display = \"inline-block\";
1070
+ if (mermaidCode) return; // Cut it short if we've already rendered the diagram
1071
+
1072
+ mermaidCode = document.createElement(\"SCRIPT\");
1073
+ mermaidCode.setAttribute(\"src\", \"https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js\");
1074
+ mermaidCode.addEventListener(\"load\", function () {
1075
+ mermaid.initialize({
1076
+ startOnLoad: true,
1077
+ securityLevel: \"loose\",
1078
+ mermaid: {callback: function(objId) {
1079
+ var svg = document.getElementById(objId);
1080
+ svg.removeAttribute(\"width\");
1081
+ var cb;
1082
+ for(cb in cbs) {
1083
+ var gErd = svg.getElementById(cb);
1084
+ gErd.setAttribute(\"class\", \"relatedModel\");
1085
+ gErd.addEventListener(\"click\",
1086
+ function (evt) {
1087
+ location.href = changeout(changeout(location.href, -1, cbs[this.id]), \"_brick_erd\", \"1\");
1088
+ }
1089
+ );
1090
+ }
1091
+ }}
1092
+ });
1093
+ mermaid.contentLoaded();
1094
+ // Add <span> at the end
1095
+ var span = document.createElement(\"SPAN\");
1096
+ span.className = \"exclude\";
1097
+ span.innerHTML = \"X\";
1098
+ span.addEventListener(\"click\", function (e) {
1099
+ e.stopPropagation();
1100
+ imgErd.style.display = \"table-cell\";
1101
+ mermaidErd.style.display = \"none\";
1102
+ window.history.pushState({}, '', changeout(location.href, '_brick_erd', null));
1103
+ });
1104
+ mermaidErd.appendChild(span);
1105
+ });
1106
+ document.body.appendChild(mermaidCode);
1107
+ }
1108
+ <%= \" showErd();\n\" if (@_brick_erd || 0) > 0
1109
+ %></script>
1110
+
1111
+ <% end
1112
+
1113
+ %><script>
942
1114
  <% # Make column headers sort when clicked
943
1115
  # %%% Create a smart javascript routine which can do this client-side %>
944
1116
  [... document.getElementsByTagName(\"TH\")].forEach(function (th) {
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 61
8
+ TINY = 64
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
@@ -21,7 +21,7 @@ module Brick
21
21
  'timestamp with time zone' => 'timestamp',
22
22
  'time without time zone' => 'time',
23
23
  'time with time zone' => 'time',
24
- 'double precision' => 'float', # might work with 'double'
24
+ 'double precision' => 'float',
25
25
  'smallint' => 'integer' } # %%% Need to put in "limit: 2"
26
26
  # (Still need to find what "inet" and "json" data types map to.)
27
27
 
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.61
4
+ version: 1.0.64
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-23 00:00:00.000000000 Z
11
+ date: 2022-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord