brick 1.0.103 → 1.0.105
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/brick/config.rb +4 -4
- data/lib/brick/extensions.rb +36 -12
- data/lib/brick/frameworks/rails/engine.rb +32 -10
- data/lib/brick/frameworks/rails/form_tags.rb +87 -76
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +69 -14
- data/lib/generators/brick/install_generator.rb +9 -6
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48fc69654d7cbdf5c7feb9ba7803b7a953c6c0ccd3c176614fe9a61994bb39e1
|
4
|
+
data.tar.gz: f3a4837d334cf97cb491cacd658cb6e6d8e3f3a5c640f80c151a083bf6811b7a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e51134710bae78f033b9dcf7827cd7c087cfd642ef8bc6244e2429f22158a80e9f804022bc703f55fa55f85cbc009ff25440289205ade1b415f5ba0af3d44c77
|
7
|
+
data.tar.gz: d6069676ad70e8401f7597b92b641f0f95e3c28fffa54747f630932500c778d7b4ac967c5bbd70059647d11d398598362d9c0bb5c82ef0500b1025371104a578
|
data/lib/brick/config.rb
CHANGED
@@ -94,13 +94,13 @@ module Brick
|
|
94
94
|
@mutex.synchronize { @enable_api = enable }
|
95
95
|
end
|
96
96
|
|
97
|
-
def
|
97
|
+
def api_roots
|
98
98
|
ver = api_version
|
99
|
-
@mutex.synchronize { @
|
99
|
+
@mutex.synchronize { @api_roots || ["/api/#{ver}/"] }
|
100
100
|
end
|
101
101
|
|
102
|
-
def
|
103
|
-
@mutex.synchronize { @
|
102
|
+
def api_roots=(path)
|
103
|
+
@mutex.synchronize { @api_roots = path }
|
104
104
|
end
|
105
105
|
|
106
106
|
def api_version
|
data/lib/brick/extensions.rb
CHANGED
@@ -257,11 +257,11 @@ module ActiveRecord
|
|
257
257
|
assoc_html_name ? "#{assoc_name}-#{link}".html_safe : link
|
258
258
|
end
|
259
259
|
|
260
|
-
def self._brick_index(mode = nil)
|
260
|
+
def self._brick_index(mode = nil, separator = '_')
|
261
261
|
tbl_parts = ((mode == :singular) ? table_name.singularize : table_name).split('.')
|
262
262
|
tbl_parts.shift if ::Brick.apartment_multitenant && tbl_parts.length > 1 && tbl_parts.first == ::Brick.apartment_default_tenant
|
263
263
|
tbl_parts.unshift(::Brick.config.path_prefix) if ::Brick.config.path_prefix
|
264
|
-
index = tbl_parts.map(&:underscore).join(
|
264
|
+
index = tbl_parts.map(&:underscore).join(separator)
|
265
265
|
# Rails applies an _index suffix to that route when the resource name is singular
|
266
266
|
index << '_index' if mode != :singular && index == index.singularize
|
267
267
|
index
|
@@ -396,6 +396,8 @@ module ActiveRecord
|
|
396
396
|
end
|
397
397
|
|
398
398
|
class Relation
|
399
|
+
attr_accessor :_brick_page_num
|
400
|
+
|
399
401
|
# Links from ActiveRecord association pathing names over to real table correlation names
|
400
402
|
# that get chosen when the AREL AST tree is walked.
|
401
403
|
def brick_links
|
@@ -785,8 +787,21 @@ JOIN (SELECT #{hm_selects.map { |s| "#{'br_t0.' if from_clause}#{s}" }.join(', '
|
|
785
787
|
end
|
786
788
|
self.order_values |= final_order_by # Same as: order!(*final_order_by)
|
787
789
|
end
|
788
|
-
|
789
|
-
|
790
|
+
if (page = params['_brick_page']&.to_i)
|
791
|
+
page = 1 if page < 1
|
792
|
+
limit = params['_brick_page_size'] || 1000
|
793
|
+
offset = (page - 1) * limit.to_i
|
794
|
+
else
|
795
|
+
offset = params['_brick_offset']
|
796
|
+
limit = params['_brick_limit']
|
797
|
+
end
|
798
|
+
if offset.is_a?(Numeric) || offset&.present?
|
799
|
+
offset = offset.to_i
|
800
|
+
self.offset_value = offset unless offset == 0
|
801
|
+
@_brick_page_num = (offset / limit.to_i) + 1 if limit&.!= 0 && (offset % limit.to_i) == 0
|
802
|
+
end
|
803
|
+
# By default just 1000 rows (Like doing: limit!(1000) but this way is compatible with AR <= 4.2)
|
804
|
+
self.limit_value = limit&.to_i || 1000 unless limit.is_a?(String) && limit.empty?
|
790
805
|
wheres unless wheres.empty? # Return the specific parameters that we did use
|
791
806
|
end
|
792
807
|
|
@@ -1511,7 +1526,11 @@ class Object
|
|
1511
1526
|
self.protect_from_forgery unless: -> { self.request.format.js? }
|
1512
1527
|
unless is_avo
|
1513
1528
|
self.define_method :index do
|
1514
|
-
|
1529
|
+
current_api_root = ::Brick.config.api_roots.find do |ar|
|
1530
|
+
request.path.start_with?(ar) || # Exact match?
|
1531
|
+
request.path.split('/')[-2] == ar.split('/').last # Version at least matches?
|
1532
|
+
end
|
1533
|
+
if (current_api_root || is_openapi) &&
|
1515
1534
|
!params&.key?('_brick_schema') &&
|
1516
1535
|
(referrer_params = request.env['HTTP_REFERER']&.split('?')&.last&.split('&')&.map { |x| x.split('=') }).present?
|
1517
1536
|
if params
|
@@ -1523,6 +1542,7 @@ class Object
|
|
1523
1542
|
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params || api_params)
|
1524
1543
|
|
1525
1544
|
if is_openapi
|
1545
|
+
current_api_ver = current_api_root.split('/').last&.[](1..-1).to_i
|
1526
1546
|
json = { 'openapi': '3.0.1', 'info': { 'title': Rswag::Ui.config.config_object[:urls].last&.fetch(:name, 'API documentation'), 'version': ::Brick.config.api_version },
|
1527
1547
|
'servers': [
|
1528
1548
|
{ 'url': '{scheme}://{defaultHost}', 'variables': {
|
@@ -1531,15 +1551,19 @@ class Object
|
|
1531
1551
|
} }
|
1532
1552
|
]
|
1533
1553
|
}
|
1534
|
-
json['paths'] = relations.
|
1554
|
+
json['paths'] = relations.each_with_object({}) do |relation, s|
|
1535
1555
|
unless ::Brick.config.enable_api == false
|
1556
|
+
next if (api_vers = relation.last.fetch(:api, nil)) &&
|
1557
|
+
!(api_ver_path = api_vers[current_api_ver])
|
1558
|
+
|
1559
|
+
relation_name = api_ver_path || relation.first.tr('.', '/')
|
1536
1560
|
table_description = relation.last[:description]
|
1537
|
-
s["#{
|
1561
|
+
s["#{current_api_root}#{relation_name}"] = {
|
1538
1562
|
'get': {
|
1539
1563
|
'summary': "list #{relation.first}",
|
1540
1564
|
'description': table_description,
|
1541
1565
|
'parameters': relation.last[:cols].map do |k, v|
|
1542
|
-
param = { 'name' => k, 'schema': { 'type': v.first } }
|
1566
|
+
param = { in: 'query', 'name' => k, 'schema': { 'type': v.first } }
|
1543
1567
|
if (col_descrip = relation.last.fetch(:col_descrips, nil)&.fetch(k, nil))
|
1544
1568
|
param['description'] = col_descrip
|
1545
1569
|
end
|
@@ -1549,7 +1573,7 @@ class Object
|
|
1549
1573
|
}
|
1550
1574
|
}
|
1551
1575
|
|
1552
|
-
s["#{
|
1576
|
+
s["#{current_api_root}#{relation_name}/{id}"] = {
|
1553
1577
|
'patch': {
|
1554
1578
|
'summary': "update a #{relation.first.singularize}",
|
1555
1579
|
'description': table_description,
|
@@ -1563,7 +1587,6 @@ class Object
|
|
1563
1587
|
'responses': { '200': { 'description': 'successful' } }
|
1564
1588
|
}
|
1565
1589
|
} unless relation.last.fetch(:isView, nil)
|
1566
|
-
s
|
1567
1590
|
end
|
1568
1591
|
end
|
1569
1592
|
render inline: json.to_json, content_type: request.format
|
@@ -1577,9 +1600,10 @@ class Object
|
|
1577
1600
|
end
|
1578
1601
|
render inline: exported_csv, content_type: request.format
|
1579
1602
|
return
|
1580
|
-
elsif request.format == :js ||
|
1603
|
+
elsif request.format == :js || current_api_root # Asking for JSON?
|
1604
|
+
# %%% Add: where, order, page, page_size, offset, limit
|
1581
1605
|
data = (model.is_view? || !Object.const_defined?('DutyFree')) ? model.limit(1000) : model.df_export(model.brick_import_template)
|
1582
|
-
render inline: data.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
|
1606
|
+
render inline: { data: data }.to_json, content_type: request.format == '*/*' ? 'application/json' : request.format
|
1583
1607
|
return
|
1584
1608
|
end
|
1585
1609
|
|
@@ -228,8 +228,8 @@ window.addEventListener(\"popstate\", linkSchemas);
|
|
228
228
|
# When available, add a clickable brick icon to go to the Brick version of the page
|
229
229
|
PanelComponent.class_exec do
|
230
230
|
alias _brick_init initialize
|
231
|
-
def initialize(*args)
|
232
|
-
_brick_init(*args)
|
231
|
+
def initialize(*args, **kwargs)
|
232
|
+
_brick_init(*args, **kwargs)
|
233
233
|
@name = BrickTitle.new(@name, self)
|
234
234
|
end
|
235
235
|
end
|
@@ -263,6 +263,16 @@ window.addEventListener(\"popstate\", linkSchemas);
|
|
263
263
|
_brick_resource_view_path
|
264
264
|
end
|
265
265
|
end
|
266
|
+
|
267
|
+
module Concerns::HasFields
|
268
|
+
class_methods do
|
269
|
+
alias _brick_field field
|
270
|
+
def field(name, *args, **kwargs, &block)
|
271
|
+
kwargs.merge!(args.pop) if args.last.is_a?(Hash)
|
272
|
+
_brick_field(name, **kwargs, &block)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
266
276
|
end # module Avo
|
267
277
|
|
268
278
|
# Steer any Avo-related controller/action based URL lookups to the Avo RouteSet
|
@@ -454,6 +464,12 @@ window.addEventListener(\"popstate\", linkSchemas);
|
|
454
464
|
table_options << "<option value=\"#{prefix}brick_orphans\">(Orphans)</option>".html_safe if is_orphans
|
455
465
|
table_options << "<option value=\"#{prefix}brick_orphans\">(Crosstab)</option>".html_safe if is_crosstab
|
456
466
|
css = +"<style>
|
467
|
+
#titleSticky {
|
468
|
+
position: sticky;
|
469
|
+
display: inline-block;
|
470
|
+
left: 0;
|
471
|
+
}
|
472
|
+
|
457
473
|
h1, h3 {
|
458
474
|
margin-bottom: 0;
|
459
475
|
}
|
@@ -465,7 +481,6 @@ h1, h3 {
|
|
465
481
|
cursor: pointer;
|
466
482
|
}
|
467
483
|
#mermaidErd {
|
468
|
-
position: relative;
|
469
484
|
display: none;
|
470
485
|
}
|
471
486
|
#mermaidErd .exclude {
|
@@ -866,6 +881,7 @@ if (grid) {
|
|
866
881
|
// });
|
867
882
|
}
|
868
883
|
function setHeaderSizes() {
|
884
|
+
document.getElementById(\"titleBox\").style.width = grid.clientWidth;
|
869
885
|
// console.log(\"start\");
|
870
886
|
// See if the headerTop is already populated
|
871
887
|
// %%% Grab the TRs from headerTop, clear it out, do this stuff, add them back
|
@@ -1112,13 +1128,16 @@ erDiagram
|
|
1112
1128
|
%></title>
|
1113
1129
|
</head>
|
1114
1130
|
<body>
|
1131
|
+
<div id=\"titleBox\"><div id=\"titleSticky\">
|
1115
1132
|
<p style=\"color: green\"><%= notice %></p>#{"
|
1116
1133
|
#{schema_options}" if schema_options}
|
1117
1134
|
<select id=\"tbl\">#{table_options}</select>
|
1118
1135
|
<table id=\"resourceName\"><tr>
|
1119
|
-
<td><h1><%=
|
1136
|
+
<td><h1><%= td_count = 2
|
1137
|
+
model.name %></h1></td>
|
1120
1138
|
<td id=\"imgErd\" title=\"Show ERD\"></td>
|
1121
|
-
<% if Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace)
|
1139
|
+
<% if Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace)
|
1140
|
+
td_count += 1 %>
|
1122
1141
|
<td><%= link_to_brick(
|
1123
1142
|
avo_svg,
|
1124
1143
|
{ index_proc: Proc.new do |avo_model|
|
@@ -1127,7 +1146,9 @@ erDiagram
|
|
1127
1146
|
title: \"#\{model.name} in Avo\" }
|
1128
1147
|
) %></td>
|
1129
1148
|
<% end %>
|
1130
|
-
</tr
|
1149
|
+
</tr><%= if (page_num = @#{table_name}._brick_page_num)
|
1150
|
+
\"<tr><td colspan=\\\"#\{td_count}\\\">Page #\{page_num}</td></tr>\".html_safe
|
1151
|
+
end %></table>#{template_link}<%
|
1131
1152
|
if description.present? %><%=
|
1132
1153
|
description %><br><%
|
1133
1154
|
end
|
@@ -1162,6 +1183,7 @@ erDiagram
|
|
1162
1183
|
});
|
1163
1184
|
</script>
|
1164
1185
|
<% end %>
|
1186
|
+
</div></div>
|
1165
1187
|
#{erd_markup}
|
1166
1188
|
|
1167
1189
|
<%= # Consider getting the name from the association -- hm.first.name -- if a more \"friendly\" alias should be used for a screwy table name
|
@@ -1327,7 +1349,7 @@ end
|
|
1327
1349
|
options[:url] = send(\"#\{#{model_name}._brick_index(:singular)}_path\".to_sym, obj) if ::Brick.config.path_prefix
|
1328
1350
|
%>
|
1329
1351
|
<br><br>
|
1330
|
-
|
1352
|
+
<%= form_for(obj.becomes(#{model_name}), options) do |f| %>
|
1331
1353
|
<table class=\"shadow\">
|
1332
1354
|
<% has_fields = false
|
1333
1355
|
@#{obj_name}.attributes.each do |k, val|
|
@@ -1432,7 +1454,7 @@ end
|
|
1432
1454
|
is_revert = false %>
|
1433
1455
|
<% else %>
|
1434
1456
|
<%= is_revert = false
|
1435
|
-
display_value(col_type, val) %>
|
1457
|
+
display_value(col_type, val).html_safe %>
|
1436
1458
|
<% end
|
1437
1459
|
end
|
1438
1460
|
if is_revert
|
@@ -1449,7 +1471,7 @@ end
|
|
1449
1471
|
<tr><td colspan=\"2\">(No displayable fields)</td></tr>
|
1450
1472
|
<% end %>
|
1451
1473
|
</table>
|
1452
|
-
<%
|
1474
|
+
<% end %>
|
1453
1475
|
|
1454
1476
|
#{unless args.first == 'new'
|
1455
1477
|
# Was: confirm_are_you_sure = ActionView.version < ::Gem::Version.new('7.0') ? "data: { confirm: 'Delete #\{model_name} -- Are you sure?' }" : "form: { data: { turbo_confirm: 'Delete #\{model_name} -- Are you sure?' } }"
|
@@ -1538,7 +1560,7 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
|
|
1538
1560
|
if (imgErd) imgErd.addEventListener(\"click\", showErd);
|
1539
1561
|
function showErd() {
|
1540
1562
|
imgErd.style.display = \"none\";
|
1541
|
-
mermaidErd.style.display = \"
|
1563
|
+
mermaidErd.style.display = \"block\";
|
1542
1564
|
if (mermaidCode) return; // Cut it short if we've already rendered the diagram
|
1543
1565
|
|
1544
1566
|
mermaidCode = document.createElement(\"SCRIPT\");
|
@@ -56,85 +56,85 @@ module Brick::Rails::FormTags
|
|
56
56
|
end
|
57
57
|
out << "</tr></thead>
|
58
58
|
<tbody>"
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
else
|
102
|
-
hm_klass = (col = cols[col_name])[1]
|
103
|
-
if col[2] == 'HO'
|
104
|
-
descrips = bt_descrip[col_name.to_sym][hm_klass]
|
105
|
-
if (ho_id = (ho_id_col = descrips.last).map { |id_col| obj.send(id_col.to_sym) })&.first
|
106
|
-
ho_txt = hm_klass.brick_descrip(obj, descrips[0..-2].map { |id| obj.send(id.last[0..62]) }, ho_id_col)
|
107
|
-
out << link_to(ho_txt, send("#{hm_klass.base_class._brick_index(:singular)}_path".to_sym, ho_id))
|
108
|
-
end
|
59
|
+
# %%% Have once gotten this error with MSSQL referring to http://localhost:3000/warehouse/cold_room_temperatures__archive
|
60
|
+
# ActiveRecord::StatementTimeout in Warehouse::ColdRoomTemperatures_Archive#index
|
61
|
+
# TinyTds::Error: Adaptive Server connection timed out
|
62
|
+
# (After restarting the server it worked fine again.)
|
63
|
+
relation.each do |obj|
|
64
|
+
out << "<tr>\n"
|
65
|
+
out << "<td>#{link_to('⇛', send("#{klass._brick_index(:singular)}_path".to_sym,
|
66
|
+
pk.map { |pk_part| obj.send(pk_part.to_sym) }), { class: 'big-arrow' })}</td>\n" if pk.present?
|
67
|
+
sequence.each do |col_name|
|
68
|
+
val = obj.attributes[col_name]
|
69
|
+
out << '<td'
|
70
|
+
out << ' class=\"dimmed\"' unless cols.key?(col_name) || (cust_col = cust_cols[col_name]) ||
|
71
|
+
(col_name.is_a?(Symbol) && bts.key?(col_name)) # HOT
|
72
|
+
out << '>'
|
73
|
+
if (bt = bts[col_name])
|
74
|
+
if bt[2] # Polymorphic?
|
75
|
+
bt_class = obj.send("#{bt.first}_type")
|
76
|
+
base_class_underscored = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class._brick_index(:singular)
|
77
|
+
poly_id = obj.send("#{bt.first}_id")
|
78
|
+
out << link_to("#{bt_class} ##{poly_id}", send("#{base_class_underscored}_path".to_sym, poly_id)) if poly_id
|
79
|
+
else # BT or HOT
|
80
|
+
bt_class = bt[1].first.first
|
81
|
+
descrips = bt_descrip[bt.first][bt_class]
|
82
|
+
bt_id_col = if descrips.nil?
|
83
|
+
puts "Caught it in the act for obj / #{col_name}!"
|
84
|
+
elsif descrips.length == 1
|
85
|
+
[obj.class.reflect_on_association(bt.first)&.foreign_key]
|
86
|
+
else
|
87
|
+
descrips.last
|
88
|
+
end
|
89
|
+
bt_txt = bt_class.brick_descrip(
|
90
|
+
# 0..62 because Postgres column names are limited to 63 characters
|
91
|
+
obj, descrips[0..-2].map { |id| obj.send(id.last[0..62]) }, bt_id_col
|
92
|
+
)
|
93
|
+
bt_txt = display_binary(bt_txt).html_safe if bt_txt&.encoding&.name == 'ASCII-8BIT'
|
94
|
+
bt_txt ||= "<span class=\"orphan\"><< Orphaned ID: #{val} >></span>" if val
|
95
|
+
bt_id = bt_id_col&.map { |id_col| obj.respond_to?(id_sym = id_col.to_sym) ? obj.send(id_sym) : id_col }
|
96
|
+
out << (bt_id&.first ? link_to(bt_txt, send("#{bt_class.base_class._brick_index(:singular)}_path".to_sym, bt_id)) : bt_txt || '')
|
97
|
+
end
|
98
|
+
elsif (hms_col = hms_cols[col_name])
|
99
|
+
if hms_col.length == 1
|
100
|
+
out << hms_col.first
|
109
101
|
else
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
102
|
+
hm_klass = (col = cols[col_name])[1]
|
103
|
+
if col[2] == 'HO'
|
104
|
+
descrips = bt_descrip[col_name.to_sym][hm_klass]
|
105
|
+
if (ho_id = (ho_id_col = descrips.last).map { |id_col| obj.send(id_col.to_sym) })&.first
|
106
|
+
ho_txt = hm_klass.brick_descrip(obj, descrips[0..-2].map { |id| obj.send(id.last[0..62]) }, ho_id_col)
|
107
|
+
out << link_to(ho_txt, send("#{hm_klass.base_class._brick_index(:singular)}_path".to_sym, ho_id))
|
108
|
+
end
|
109
|
+
else
|
110
|
+
if (ct = obj.send(hms_col[1].to_sym)&.to_i)&.positive?
|
111
|
+
out << "#{link_to("#{ct || 'View'} #{hms_col.first}",
|
112
|
+
send("#{hm_klass._brick_index}_path".to_sym,
|
113
|
+
hms_col[2].each_with_object({}) { |v, s| s[v.first] = v.last.is_a?(String) ? v.last : obj.send(v.last) })
|
114
|
+
)}\n"
|
115
|
+
end
|
115
116
|
end
|
116
117
|
end
|
118
|
+
elsif (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
|
119
|
+
binding.pry if col.is_a?(Array)
|
120
|
+
col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
|
121
|
+
out << display_value(col_type || col&.sql_type, val).to_s
|
122
|
+
elsif cust_col
|
123
|
+
data = cust_col.first.map { |cc_part| obj.send(cc_part.last) }
|
124
|
+
cust_txt = klass.brick_descrip(cust_col[-2], data)
|
125
|
+
if (link_id = obj.send(cust_col.last[1]) if cust_col.last)
|
126
|
+
out << link_to(cust_txt, send("#{cust_col.last.first._brick_index(:singular)}_path", link_id))
|
127
|
+
else
|
128
|
+
out << (cust_txt || '')
|
129
|
+
end
|
130
|
+
else # Bad column name!
|
131
|
+
out << '?'
|
117
132
|
end
|
118
|
-
|
119
|
-
binding.pry if col.is_a?(Array)
|
120
|
-
col_type = col&.sql_type == 'geography' ? col.sql_type : col&.type
|
121
|
-
out << display_value(col_type || col&.sql_type, val).to_s
|
122
|
-
elsif cust_col
|
123
|
-
data = cust_col.first.map { |cc_part| obj.send(cc_part.last) }
|
124
|
-
cust_txt = klass.brick_descrip(cust_col[-2], data)
|
125
|
-
if (link_id = obj.send(cust_col.last[1]) if cust_col.last)
|
126
|
-
out << link_to(cust_txt, send("#{cust_col.last.first._brick_index(:singular)}_path", link_id))
|
127
|
-
else
|
128
|
-
out << (cust_txt || '')
|
129
|
-
end
|
130
|
-
else # Bad column name!
|
131
|
-
out << '?'
|
133
|
+
out << '</td>'
|
132
134
|
end
|
133
|
-
out << '</
|
135
|
+
out << '</tr>'
|
134
136
|
end
|
135
|
-
|
136
|
-
end
|
137
|
-
out << " </tbody>
|
137
|
+
out << " </tbody>
|
138
138
|
</table>
|
139
139
|
"
|
140
140
|
out.html_safe
|
@@ -143,6 +143,16 @@ module Brick::Rails::FormTags
|
|
143
143
|
def link_to_brick(*args, **kwargs)
|
144
144
|
return unless ::Brick.config.mode == :on
|
145
145
|
|
146
|
+
kwargs.merge!(args.pop) if args.last.is_a?(Hash)
|
147
|
+
# Avoid infinite recursion
|
148
|
+
if (visited = kwargs.fetch(:visited, nil))
|
149
|
+
return if visited.key?(object_id)
|
150
|
+
|
151
|
+
kwargs[:visited][object_id] = nil
|
152
|
+
else
|
153
|
+
kwargs[:visited] = {}
|
154
|
+
end
|
155
|
+
|
146
156
|
text = ((args.first.is_a?(String) || args.first.is_a?(Proc)) && args.shift) || args[1]
|
147
157
|
text = text.call if text.is_a?(Proc)
|
148
158
|
klass_or_obj = ((args.first.is_a?(ActiveRecord::Relation) ||
|
@@ -192,13 +202,14 @@ module Brick::Rails::FormTags
|
|
192
202
|
app_routes = Rails.application.routes # In case we're operating in another engine, reference the application since Brick routes are placed there.
|
193
203
|
if (klass_or_obj&.is_a?(Class) && klass_or_obj < ActiveRecord::Base) ||
|
194
204
|
(klass_or_obj&.is_a?(ActiveRecord::Base) && klass_or_obj.new_record? && (klass_or_obj = klass_or_obj.class))
|
195
|
-
path = (proc = kwargs[:index_proc]) ? proc.call(klass_or_obj) : "#{app_routes.path_for(controller: klass_or_obj.base_class._brick_index, action: :index)}#{filter}"
|
205
|
+
path = (proc = kwargs[:index_proc]) ? proc.call(klass_or_obj) : "#{app_routes.path_for(controller: klass_or_obj.base_class._brick_index(nil, '/'), action: :index)}#{filter}"
|
196
206
|
lt_args = [text || "Index for #{klass_or_obj.name.pluralize}", path]
|
197
207
|
else
|
198
208
|
# 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
|
199
|
-
path = (proc = kwargs[:show_proc]) ? proc.call(klass_or_obj) : "#{app_routes.path_for(controller: klass_or_obj.class.base_class._brick_index, action: :show, id: klass_or_obj)}#{filter}"
|
209
|
+
path = (proc = kwargs[:show_proc]) ? proc.call(klass_or_obj) : "#{app_routes.path_for(controller: klass_or_obj.class.base_class._brick_index(nil, '/'), action: :show, id: klass_or_obj)}#{filter}"
|
200
210
|
lt_args = [text || "Show this #{klass_or_obj.class.name}", path]
|
201
211
|
end
|
212
|
+
kwargs.delete(:visited)
|
202
213
|
link_to(*lt_args, **kwargs)
|
203
214
|
else
|
204
215
|
# puts "Warning: link_to_brick could not find a class for \"#{controller_path}\" -- consider setting @_brick_model within that controller."
|
@@ -215,7 +226,7 @@ module Brick::Rails::FormTags
|
|
215
226
|
if links.length == 1 # If there's only one match then use any text that was supplied
|
216
227
|
link_to_brick(text || links.first.last.join('/'), links.first.first, **kwargs)
|
217
228
|
else
|
218
|
-
links.
|
229
|
+
links.each_with_object([]) { |v, s| s << link if link = link_to_brick(v.join('/'), v, **kwargs) }.join(' ').html_safe
|
219
230
|
end
|
220
231
|
end
|
221
232
|
end # link_to_brick
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -334,12 +334,17 @@ module Brick
|
|
334
334
|
|
335
335
|
# @api public
|
336
336
|
def api_root=(path)
|
337
|
-
Brick.config.
|
337
|
+
Brick.config.api_roots = [path]
|
338
338
|
end
|
339
339
|
|
340
340
|
# @api public
|
341
|
-
def
|
342
|
-
Brick.config.
|
341
|
+
def api_roots=(paths)
|
342
|
+
Brick.config.api_roots = paths
|
343
|
+
end
|
344
|
+
|
345
|
+
# @api public
|
346
|
+
def api_roots
|
347
|
+
Brick.config.api_roots
|
343
348
|
end
|
344
349
|
|
345
350
|
# @api public
|
@@ -657,19 +662,65 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
657
662
|
# Turn something like {"::Spouse"=>"Person", "::Friend"=>"Person"} into {"Person"=>["Spouse", "Friend"]}
|
658
663
|
s[v.last] << v.first[2..-1] unless v.first.end_with?('::')
|
659
664
|
end
|
665
|
+
versioned_views = {} # Track which views have already been done for each api_root
|
660
666
|
::Brick.relations.each do |k, v|
|
661
667
|
next if !(controller_name = v.fetch(:resource, nil)&.pluralize) || existing_controllers.key?(controller_name)
|
662
668
|
|
669
|
+
schema_name = v.fetch(:schema, nil)
|
663
670
|
options = {}
|
664
671
|
options[:only] = [:index, :show] if v.key?(:isView)
|
665
|
-
# First do the API routes
|
672
|
+
# First do the API routes if necessary
|
666
673
|
full_resource = nil
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
#
|
672
|
-
|
674
|
+
::Brick.api_roots&.each do |api_root|
|
675
|
+
api_done_views = (versioned_views[api_root] ||= {})
|
676
|
+
found = nil
|
677
|
+
view_relation = nil
|
678
|
+
# If it's a view then see if there's a versioned one available by searching for resource names
|
679
|
+
# versioned with the closest number (equal to or less than) compared with our API version number.
|
680
|
+
if v.key?(:isView) && (ver = k.match(/^v([\d_]*)/).captures.first)[-1] == '_'
|
681
|
+
next if api_done_views.key?(unversioned = k[ver.length + 1..-1])
|
682
|
+
|
683
|
+
# # if ().length.positive? # Does it have a version number?
|
684
|
+
# try_num = (ver_num = (ver = ver[1..-1].gsub('_', '.')).to_d)
|
685
|
+
|
686
|
+
# Expect that the last item in the path generally holds versioning information
|
687
|
+
api_ver = api_root.split('/')[-1]&.gsub('_', '.')
|
688
|
+
vn_idx = api_ver.rindex(/[^\d._]/) # Position of the first numeric digit at the end of the version number
|
689
|
+
# Was: .to_d
|
690
|
+
api_ver_num = api_ver[vn_idx + 1..-1].gsub('_', '.').to_i # Attempt to turn something like "v3" into the decimal value 3
|
691
|
+
# puts [api_ver, vn_idx, api_ver_num, unversioned].inspect
|
692
|
+
|
693
|
+
next if ver.to_i > api_ver_num # Don't surface any newer views in an older API
|
694
|
+
|
695
|
+
api_ver_num -= 1 until api_ver_num.zero? ||
|
696
|
+
(view_relation = ::Brick.relations.fetch(
|
697
|
+
found = "v#{api_ver_num}_#{k[ver.length + 1..-1]}", nil
|
698
|
+
))
|
699
|
+
api_done_views[unversioned] = nil # Mark that for this API version this view is done
|
700
|
+
|
701
|
+
# puts "Found #{found}" if view_relation
|
702
|
+
# If we haven't found "v3_view_name" or "v2_view_name" or so forth, at the last
|
703
|
+
# fall back to simply looking for "v_view_name", and then finally "view_name".
|
704
|
+
unversioned = "v_#{unversioned}"
|
705
|
+
view_relation ||= ::Brick.relations.fetch(found = unversioned,
|
706
|
+
::Brick.relations.fetch(found = unversioned, nil)
|
707
|
+
)
|
708
|
+
if found && view_relation && k != (found = unversioned)
|
709
|
+
view_relation[:api][api_ver_num] = found
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
# view_ver_num = if (first_part = k.split('_').first) =~ /^v[\d_]+/
|
714
|
+
# first_part[1..-1].gsub('_', '.').to_i
|
715
|
+
# end
|
716
|
+
controller_name = view_relation.fetch(:resource, nil)&.pluralize if view_relation
|
717
|
+
if schema_name
|
718
|
+
full_resource = "#{schema_name}/#{found || v[:resource]}"
|
719
|
+
send(:get, "#{api_root}#{full_resource}", { to: "#{controller_prefix}#{schema_name}/#{controller_name}#index" })
|
720
|
+
else
|
721
|
+
# Normally goes to something like: /api/v1/employees
|
722
|
+
send(:get, "#{api_root}#{found || v[:resource]}", { to: "#{controller_prefix}#{controller_name}#index" })
|
723
|
+
end
|
673
724
|
end
|
674
725
|
|
675
726
|
# Track routes being built
|
@@ -716,15 +767,19 @@ In config/initializers/brick.rb appropriate entries would look something like:
|
|
716
767
|
unless ::Brick.routes_done
|
717
768
|
if Object.const_defined?('Rswag::Ui')
|
718
769
|
rswag_path = ::Rails.application.routes.routes.find { |r| r.app.app == Rswag::Ui::Engine }&.instance_variable_get(:@path_formatter)&.instance_variable_get(:@parts)&.join
|
719
|
-
|
770
|
+
first_endpoint_parts = nil
|
771
|
+
(doc_endpoints = Rswag::Ui.config.config_object[:urls]&.uniq!)&.each do |doc_endpoint|
|
720
772
|
puts "Mounting OpenApi 3.0 documentation endpoint for \"#{doc_endpoint[:name]}\" on #{doc_endpoint[:url]}"
|
721
773
|
send(:get, doc_endpoint[:url], { to: 'brick_openapi#index' })
|
722
774
|
endpoint_parts = doc_endpoint[:url]&.split('/')
|
723
|
-
|
724
|
-
|
775
|
+
first_endpoint_parts ||= endpoint_parts
|
776
|
+
end
|
777
|
+
if doc_endpoints.present?
|
778
|
+
if rswag_path && first_endpoint_parts
|
779
|
+
puts "API documentation now available when navigating to: /#{first_endpoint_parts&.find(&:present?)}/index.html"
|
725
780
|
else
|
726
781
|
puts "In order to make documentation available you can put this into your routes.rb:"
|
727
|
-
puts " mount Rswag::Ui::Engine => '/#{
|
782
|
+
puts " mount Rswag::Ui::Engine => '/#{first_endpoint_parts&.find(&:present?) || 'api-docs'}'"
|
728
783
|
end
|
729
784
|
else
|
730
785
|
sample_path = rswag_path || '/api-docs'
|
@@ -159,8 +159,9 @@ if ActiveRecord::Base.respond_to?(:brick_select)
|
|
159
159
|
# Brick.enable_views = true # Setting this to \"false\" will disable views in development
|
160
160
|
|
161
161
|
# # If The Brick sees that RSwag gem is present, it allows for API resources to be automatically served out.
|
162
|
-
# # You can configure
|
163
|
-
#
|
162
|
+
# # You can configure one or more root path(s) for these resources, and when there are multiple then an attempt
|
163
|
+
# # is made to return data from that version of the view or table name, or the most recent prior to that version:
|
164
|
+
# ::Brick.api_roots = ['/api/v1/']
|
164
165
|
# # You may also want to add an OpenAPI 3.0 documentation endpoint using Rswag::Ui:
|
165
166
|
# Rswag::Ui.configure do |config|
|
166
167
|
# config.swagger_endpoint '/api-docs/v1/swagger.json', 'API V1 Docs'
|
@@ -288,16 +289,18 @@ if ActiveRecord::Base.respond_to?(:brick_select)
|
|
288
289
|
|
289
290
|
# # POLYMORPHIC ASSOCIATIONS
|
290
291
|
|
291
|
-
# #
|
292
|
-
|
292
|
+
# # Polymorphic associations are set up by providing a model name and polymorphic association name#{poly}
|
293
|
+
|
294
|
+
# # For multi-tenant databases that use a separate schema for each tenant, a single representative database schema
|
295
|
+
# # can be analysed to determine the range of polymorphic classes that can be used for each association. Hopefully
|
296
|
+
# # the schema chosen is one loaded with existing data that is representative of all possible polymorphic
|
297
|
+
# # associations.
|
293
298
|
# Brick.schema_behavior = :namespaced
|
294
299
|
#{Brick.config.schema_behavior.present? ? " Brick.schema_behavior = { multitenant: { schema_to_analyse: #{
|
295
300
|
Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil).inspect}" :
|
296
301
|
" # Brick.schema_behavior = { multitenant: { schema_to_analyse: 'engineering'"
|
297
302
|
} } }
|
298
303
|
|
299
|
-
# # Polymorphic associations are set up by providing a model name and polymorphic association name#{poly}
|
300
|
-
|
301
304
|
# # DEFAULT ROOT ROUTE
|
302
305
|
|
303
306
|
# # If a default route is not supplied, Brick attempts to find the most \"central\" table and wires up the default
|
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.
|
4
|
+
version: 1.0.105
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|