brick 1.0.51 → 1.0.54
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 +4 -4
- data/lib/brick/extensions.rb +77 -28
- data/lib/brick/frameworks/rails/engine.rb +156 -71
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +9 -0
- data/lib/generators/brick/install_generator.rb +7 -7
- data/lib/generators/brick/migrations_generator.rb +160 -0
- data/lib/generators/brick/model_generator.rb +0 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '09f9f2f9f7e21a96d29cdad815c069e52280ea068051f27a542541bcefd33e87'
|
4
|
+
data.tar.gz: 54e032633f930a43d1bc929d05d241367d9ebbdb8010f588b922d1111077524e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56cad221c2b20ac62839c1b7904c8acb9bc392b0779946bbe325c48c8ee5cf00b33413e070674343809ee851804e5f9bbec2d6469a148688c4aecbbcaee3e09d
|
7
|
+
data.tar.gz: 9757effb2a1d058ae4ccd259b59e0218e002928d90dadfd6ff3d0acd05fb12e27cfcfef30afa3964637e0e4f1e4246b410deba45fb01e452e20056df94820377
|
data/lib/brick/extensions.rb
CHANGED
@@ -1116,6 +1116,11 @@ class Object
|
|
1116
1116
|
if namespace && (idx = lookup_context.prefixes.index(table_name))
|
1117
1117
|
lookup_context.prefixes[idx] = "#{namespace.name.underscore}/#{lookup_context.prefixes[idx]}"
|
1118
1118
|
end
|
1119
|
+
@_brick_excl = session[:_brick_exclude]&.split(',')&.each_with_object([]) do |excl, s|
|
1120
|
+
if (excl_parts = excl.split('.')).first == table_name
|
1121
|
+
s << excl_parts.last
|
1122
|
+
end
|
1123
|
+
end
|
1119
1124
|
@_brick_bt_descrip = model._br_bt_descrip
|
1120
1125
|
@_brick_hm_counts = model._br_hm_counts
|
1121
1126
|
@_brick_join_array = join_array
|
@@ -1124,9 +1129,7 @@ class Object
|
|
1124
1129
|
is_pk_string = nil
|
1125
1130
|
if (pk_col = model&.primary_key)
|
1126
1131
|
code << " def show\n"
|
1127
|
-
code <<
|
1128
|
-
id = id.first if id.is_a?(Array) && id.length == 1
|
1129
|
-
@#{singular_table_name} = #{model.name}.find(id)\n")
|
1132
|
+
code << " #{find_by_name = "find_#{singular_table_name}"}\n"
|
1130
1133
|
code << " end\n"
|
1131
1134
|
self.define_method :show do
|
1132
1135
|
::Brick.set_db_schema(params)
|
@@ -1137,35 +1140,59 @@ class Object
|
|
1137
1140
|
params[:id]&.split(/[\/,_]/)
|
1138
1141
|
end
|
1139
1142
|
id = id.first if id.is_a?(Array) && id.length == 1
|
1140
|
-
instance_variable_set("@#{singular_table_name}".to_sym,
|
1143
|
+
instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
|
1141
1144
|
end
|
1142
1145
|
end
|
1143
1146
|
|
1144
1147
|
# By default, views get marked as read-only
|
1145
|
-
unless
|
1146
|
-
code << "
|
1148
|
+
# unless model.readonly # (relation = relations[model.table_name]).key?(:isView)
|
1149
|
+
code << " def new\n"
|
1150
|
+
code << " @#{singular_table_name} = #{model.name}.new\n"
|
1151
|
+
code << " end\n"
|
1152
|
+
self.define_method :new do
|
1153
|
+
::Brick.set_db_schema(params)
|
1154
|
+
instance_variable_set("@#{singular_table_name}".to_sym, model.new)
|
1155
|
+
end
|
1156
|
+
|
1157
|
+
params_name_sym = (params_name = "#{singular_table_name}_params").to_sym
|
1158
|
+
|
1159
|
+
code << " def create\n"
|
1160
|
+
code << " @#{singular_table_name} = #{model.name}.create(#{params_name})\n"
|
1161
|
+
code << " end\n"
|
1162
|
+
self.define_method :create do
|
1163
|
+
::Brick.set_db_schema(params)
|
1164
|
+
if (is_json = request.content_type == 'application/json') && (col = params['_brick_exclude'])
|
1165
|
+
session[:_brick_exclude] = ((session[:_brick_exclude]&.split(',') || []) + ["#{table_name}.#{col}"]).join(',')
|
1166
|
+
render json: { result: ::Brick.exclude_column(table_name, col) }
|
1167
|
+
elsif is_json && (col = params['_brick_unexclude'])
|
1168
|
+
if (excls = ((session[:_brick_exclude]&.split(',') || []) - ["#{table_name}.#{col}"]).join(',')).empty?
|
1169
|
+
session.delete(:_brick_exclude)
|
1170
|
+
else
|
1171
|
+
session[:_brick_exclude] = excls
|
1172
|
+
end
|
1173
|
+
render json: { result: ::Brick.unexclude_column(table_name, col) }
|
1174
|
+
else
|
1175
|
+
instance_variable_set("@#{singular_table_name}".to_sym,
|
1176
|
+
model.send(:create, send(params_name_sym)))
|
1177
|
+
end
|
1178
|
+
end
|
1147
1179
|
|
1148
|
-
if
|
1180
|
+
if pk_col
|
1149
1181
|
# if (schema = ::Brick.config.schema_behavior[:multitenant]&.fetch(:schema_to_analyse, nil)) && ::Brick.db_schemas&.include?(schema)
|
1150
1182
|
# ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?;", schema)
|
1151
1183
|
# end
|
1152
1184
|
|
1153
1185
|
is_need_params = true
|
1154
|
-
# code << " # (Define :destroy)\n"
|
1155
1186
|
code << " def edit\n"
|
1156
|
-
code <<
|
1187
|
+
code << " #{find_by_name}\n"
|
1157
1188
|
code << " end\n"
|
1158
1189
|
self.define_method :edit do
|
1159
1190
|
::Brick.set_db_schema(params)
|
1160
|
-
|
1161
|
-
id = id.first if id.is_a?(Array) && id.length == 1
|
1162
|
-
instance_variable_set("@#{singular_table_name}".to_sym, model.find(id))
|
1191
|
+
instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
|
1163
1192
|
end
|
1164
1193
|
|
1165
1194
|
code << " def update\n"
|
1166
|
-
code <<
|
1167
|
-
params_name = "#{singular_table_name}_params"
|
1168
|
-
code << " @#{singular_table_name}.update(#{params_name})\n"
|
1195
|
+
code << " #{find_by_name}.update(#{params_name})\n"
|
1169
1196
|
code << " end\n"
|
1170
1197
|
self.define_method :update do
|
1171
1198
|
::Brick.set_db_schema(params)
|
@@ -1184,31 +1211,50 @@ class Object
|
|
1184
1211
|
# return
|
1185
1212
|
end
|
1186
1213
|
|
1214
|
+
instance_variable_set("@#{singular_table_name}".to_sym, (obj = find_obj))
|
1215
|
+
obj.send(:update, send(params_name_sym))
|
1216
|
+
end
|
1217
|
+
|
1218
|
+
code << " def destroy\n"
|
1219
|
+
code << " #{find_by_name}.destroy\n"
|
1220
|
+
code << " end\n"
|
1221
|
+
self.define_method :destroy do
|
1222
|
+
::Brick.set_db_schema(params)
|
1223
|
+
instance_variable_set("@#{singular_table_name}".to_sym, find_obj.send(:destroy))
|
1224
|
+
end
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
code << "private\n" if pk_col || is_need_params
|
1228
|
+
|
1229
|
+
if pk_col
|
1230
|
+
code << " def find_#{singular_table_name}
|
1231
|
+
id = params[:id]&.split(/[\\/,_]/)
|
1232
|
+
@#{singular_table_name} = #{model.name}.find(id.is_a?(Array) && id.length == 1 ? id.first : id)
|
1233
|
+
end\n"
|
1234
|
+
self.define_method :find_obj do
|
1187
1235
|
id = is_pk_string ? params[:id] : params[:id]&.split(/[\/,_]/)
|
1188
|
-
|
1189
|
-
instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(id)))
|
1190
|
-
obj = obj.first if obj.is_a?(Array)
|
1191
|
-
obj.send(:update, send(params_name = params_name.to_sym))
|
1236
|
+
model.find(id.is_a?(Array) && id.length == 1 ? id.first : id)
|
1192
1237
|
end
|
1193
1238
|
end
|
1194
1239
|
|
1195
1240
|
if is_need_params
|
1196
|
-
code << "private\n"
|
1197
1241
|
code << " def #{params_name}\n"
|
1198
|
-
permits = model.columns_hash.keys.map
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1242
|
+
permits = model.columns_hash.keys.map(&:to_sym)
|
1243
|
+
permits_txt = permits.map(&:inspect) +
|
1244
|
+
model.reflect_on_all_associations.select { |assoc| assoc.macro == :has_many && assoc.options[:through] }.map do |assoc|
|
1245
|
+
permits << { "#{assoc.name.to_s.singularize}_ids".to_sym => [] }
|
1246
|
+
"#{assoc.name.to_s.singularize}_ids: []"
|
1247
|
+
end
|
1202
1248
|
code << " params.require(:#{require_name = model.name.underscore.tr('/', '_')
|
1203
|
-
}).permit(#{
|
1249
|
+
}).permit(#{permits_txt.join(', ')})\n"
|
1204
1250
|
code << " end\n"
|
1205
1251
|
self.define_method(params_name) do
|
1206
|
-
params.require(require_name.to_sym).permit(
|
1252
|
+
params.require(require_name.to_sym).permit(permits)
|
1207
1253
|
end
|
1208
1254
|
private params_name
|
1209
1255
|
# Get column names for params from relations[model.table_name][:cols].keys
|
1210
1256
|
end
|
1211
|
-
end
|
1257
|
+
# end
|
1212
1258
|
code << "end # #{namespace_name}#{class_name}\n\n"
|
1213
1259
|
end # class definition
|
1214
1260
|
[built_controller, code]
|
@@ -1243,7 +1289,10 @@ module ActiveRecord::ConnectionHandling
|
|
1243
1289
|
alias _brick_establish_connection establish_connection
|
1244
1290
|
def establish_connection(*args)
|
1245
1291
|
conn = _brick_establish_connection(*args)
|
1246
|
-
|
1292
|
+
begin
|
1293
|
+
_brick_reflect_tables
|
1294
|
+
rescue ActiveRecord::NoDatabaseError
|
1295
|
+
end
|
1247
1296
|
conn
|
1248
1297
|
end
|
1249
1298
|
|
@@ -132,27 +132,24 @@ module Brick
|
|
132
132
|
end
|
133
133
|
case args.first
|
134
134
|
when 'index'
|
135
|
-
|
135
|
+
hm_entry = +"'#{hm_assoc.name}' => [#{assoc_name.inspect}"
|
136
|
+
hm_entry << if hm_assoc.macro == :has_many
|
137
|
+
if hm_fk_name # %%% Can remove this check when multiple foreign keys to same destination becomes bulletproof
|
136
138
|
set_ct = if skip_klass_hms.key?(assoc_name.to_sym)
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
if hm_fk_name
|
144
|
-
"<%= ct = #{set_ct}
|
145
|
-
link_to \"#\{ct || 'View'\} #{assoc_name}\", #{hm_assoc.klass.name.underscore.tr('/', '_').pluralize}_path({ #{path_keys(hm_assoc, hm_fk_name, obj_name, pk)} }) unless ct&.zero? %>\n"
|
146
|
-
else # %%% Would be able to remove this when multiple foreign keys to same destination becomes bulletproof
|
147
|
-
"#{assoc_name}\n"
|
148
|
-
end
|
149
|
-
else # has_one
|
150
|
-
# 0..62 because Postgres column names are limited to 63 characters
|
151
|
-
"<%= descrips = @_brick_bt_descrip[#{hm.first.inspect}][ho_class = #{hm[1].klass.name}]
|
152
|
-
ho_txt = ho_class.brick_descrip(#{obj_name}, descrips[0..-2].map { |id| #{obj_name}.send(id.last[0..62]) }, (ho_id_col = descrips.last))
|
153
|
-
ho_id = ho_id_col.map { |id_col| #{obj_name}.send(id_col.to_sym) }
|
154
|
-
ho_id&.first ? link_to(ho_txt, send(\"#\{ho_class.base_class.name.underscore.tr('/', '_')\}_path\".to_sym, ho_id)) : ho_txt %>\n"
|
139
|
+
'nil'
|
140
|
+
else
|
141
|
+
# Postgres column names are limited to 63 characters
|
142
|
+
"#{obj_name}.#{"_br_#{assoc_name}_ct"[0..62]} || 0"
|
143
|
+
end
|
144
|
+
", #{set_ct}, #{path_keys(hm_assoc, hm_fk_name, obj_name, pk)}"
|
155
145
|
end
|
146
|
+
else # has_one
|
147
|
+
# 0..62 because Postgres column names are limited to 63 characters
|
148
|
+
", nil, #{path_keys(hm_assoc, hm_fk_name, obj_name, pk)}"
|
149
|
+
end
|
150
|
+
hm_entry << ']'
|
151
|
+
puts hm_entry
|
152
|
+
hms_columns << hm_entry
|
156
153
|
when 'show', 'update'
|
157
154
|
hm_stuff << if hm_fk_name
|
158
155
|
"<%= link_to '#{assoc_name}', #{hm_assoc.klass.name.underscore.tr('/', '_').pluralize}_path({ #{path_keys(hm_assoc, hm_fk_name, "@#{obj_name}", pk)} }) %>\n"
|
@@ -178,6 +175,9 @@ module Brick
|
|
178
175
|
end.html_safe
|
179
176
|
table_options << '<option value="brick_orphans">(Orphans)</option>'.html_safe if is_orphans
|
180
177
|
css = +"<style>
|
178
|
+
h1, h3 {
|
179
|
+
margin-bottom: 0;
|
180
|
+
}
|
181
181
|
#dropper {
|
182
182
|
background-color: #eee;
|
183
183
|
}
|
@@ -199,19 +199,41 @@ table {
|
|
199
199
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
|
200
200
|
}
|
201
201
|
|
202
|
-
|
202
|
+
tr th {
|
203
203
|
background-color: #009879;
|
204
204
|
color: #fff;
|
205
205
|
text-align: left;
|
206
206
|
}
|
207
|
-
#headerTop
|
207
|
+
#headerTop tr th {
|
208
|
+
position: relative;
|
209
|
+
}
|
210
|
+
#headerTop tr th .exclude {
|
211
|
+
position: absolute;
|
212
|
+
display: none;
|
213
|
+
top: 0;
|
214
|
+
right: 0;
|
215
|
+
}
|
216
|
+
#headerTop tr th:hover {
|
208
217
|
background-color: #18B090;
|
209
218
|
}
|
210
|
-
|
219
|
+
#exclusions {
|
220
|
+
font-size: 0.7em;
|
221
|
+
}
|
222
|
+
#exclusions div {
|
223
|
+
border: 1px solid blue;
|
224
|
+
display: inline-block;
|
225
|
+
cursor: copy;
|
226
|
+
}
|
227
|
+
#headerTop tr th:hover .exclude {
|
228
|
+
display: inline;
|
229
|
+
cursor: pointer;
|
230
|
+
color: red;
|
231
|
+
}
|
232
|
+
tr th a {
|
211
233
|
color: #80FFB8;
|
212
234
|
}
|
213
235
|
|
214
|
-
|
236
|
+
tr th, tr td {
|
215
237
|
padding: 0.2em 0.5em;
|
216
238
|
}
|
217
239
|
|
@@ -257,6 +279,7 @@ a.big-arrow {
|
|
257
279
|
}
|
258
280
|
.dimmed {
|
259
281
|
background-color: #C0C0C0;
|
282
|
+
text-align: center;
|
260
283
|
}
|
261
284
|
.orphan {
|
262
285
|
color: red;
|
@@ -285,8 +308,8 @@ input+svg.revert {
|
|
285
308
|
color: #FFF;
|
286
309
|
}
|
287
310
|
</style>
|
288
|
-
<% is_includes_dates = nil
|
289
311
|
|
312
|
+
<% is_includes_dates = nil
|
290
313
|
def is_bcrypt?(val)
|
291
314
|
val.is_a?(String) && val.length == 60 && val.start_with?('$2a$')
|
292
315
|
end
|
@@ -418,8 +441,19 @@ function setHeaderSizes() {
|
|
418
441
|
for (var i = 0; i < row.childNodes.length; ++i) {
|
419
442
|
node = row.childNodes[i];
|
420
443
|
if (node.nodeType === 1) {
|
421
|
-
var
|
422
|
-
style.minWidth = style.maxWidth = getComputedStyle(node).width;
|
444
|
+
var th = tr.childNodes[i];
|
445
|
+
th.style.minWidth = th.style.maxWidth = getComputedStyle(node).width;
|
446
|
+
if (#{pk&.present? ? 'i > 0' : 'true'}) {
|
447
|
+
// Add <span> at the end
|
448
|
+
var span = document.createElement(\"SPAN\");
|
449
|
+
span.className = \"exclude\";
|
450
|
+
span.innerHTML = \"X\";
|
451
|
+
span.addEventListener(\"click\", function (e) {
|
452
|
+
e.stopPropagation();
|
453
|
+
doFetch(\"POST\", {_brick_exclude: this.parentElement.getAttribute(\"x-order\")});
|
454
|
+
});
|
455
|
+
th.appendChild(span);
|
456
|
+
}
|
423
457
|
}
|
424
458
|
}
|
425
459
|
if (isEmpty) headerTop.appendChild(tr);
|
@@ -427,6 +461,18 @@ function setHeaderSizes() {
|
|
427
461
|
grid.style.marginTop = \"-\" + getComputedStyle(headerTop).height;
|
428
462
|
// console.log(\"end\");
|
429
463
|
}
|
464
|
+
function doFetch(method, payload, success) {
|
465
|
+
payload.authenticity_token = <%= session[:_csrf_token].inspect.html_safe %>;
|
466
|
+
if (!success) {
|
467
|
+
success = function (p) {p.text().then(function (response) {
|
468
|
+
var result = JSON.parse(response).result;
|
469
|
+
if (result) location.href = location.href;
|
470
|
+
});};
|
471
|
+
}
|
472
|
+
var options = {method: method, headers: {\"Content-Type\": \"application/json\"}};
|
473
|
+
if (payload) options.body = JSON.stringify(payload);
|
474
|
+
return fetch(location.href, options).then(success);
|
475
|
+
}
|
430
476
|
if (headerTop) {
|
431
477
|
setHeaderSizes();
|
432
478
|
window.addEventListener('resize', function(event) {
|
@@ -461,8 +507,8 @@ if (headerTop) {
|
|
461
507
|
});
|
462
508
|
btnImport.addEventListener(\"click\", function () {
|
463
509
|
fetch(changeout(<%= #{path_obj_name}_path(-1, format: :csv).inspect.html_safe %>, \"_brick_schema\", brickSchema), {
|
464
|
-
method:
|
465
|
-
headers: {
|
510
|
+
method: \"PATCH\",
|
511
|
+
headers: { \"Content-Type\": \"text/tab-separated-values\" },
|
466
512
|
body: droppedTSV
|
467
513
|
}).then(function (tsvResponse) {
|
468
514
|
btnImport.style.display = \"none\";
|
@@ -540,6 +586,7 @@ if (headerTop) {
|
|
540
586
|
if (description = (relation = Brick.relations[#{model_name}.table_name])&.fetch(:description, nil)) %><%=
|
541
587
|
description %><br><%
|
542
588
|
end
|
589
|
+
# FILTER PARAMETERS
|
543
590
|
if @_brick_params&.present? %>
|
544
591
|
<% if @_brick_params.length == 1 # %%% Does not yet work with composite keys
|
545
592
|
k, id = @_brick_params.first
|
@@ -551,55 +598,78 @@ if (headerTop) {
|
|
551
598
|
end
|
552
599
|
end %>
|
553
600
|
(<%= link_to 'See all #{model_plural.split('::').last}', #{path_obj_name.pluralize}_path %>)
|
601
|
+
<% end
|
602
|
+
# COLUMN EXCLUSIONS
|
603
|
+
if @_brick_excl&.present? %>
|
604
|
+
<div id=\"exclusions\">Excluded columns:
|
605
|
+
<% @_brick_excl.each do |excl| %>
|
606
|
+
<div class=\"colExclusion\"><%= excl %></div>
|
607
|
+
<% end %>
|
608
|
+
</div>
|
609
|
+
<script>
|
610
|
+
[... document.getElementsByClassName(\"colExclusion\")].forEach(function (excl) {
|
611
|
+
excl.addEventListener(\"click\", function () {
|
612
|
+
doFetch(\"POST\", {_brick_unexclude: this.innerHTML});
|
613
|
+
});
|
614
|
+
});
|
615
|
+
</script>
|
554
616
|
<% end %>
|
555
|
-
<br>
|
556
617
|
<table id=\"headerTop\">
|
557
618
|
<table id=\"#{table_name}\">
|
558
|
-
<thead><tr>#{"<th x-order=\"#{pk.join(',')}\"></th>" if pk.present?}
|
559
|
-
|
560
|
-
|
561
|
-
|
619
|
+
<thead><tr>#{"<th x-order=\"#{pk.join(',')}\"></th>" if pk.present?}<%=
|
620
|
+
# Consider getting the name from the association -- hm.first.name -- if a more \"friendly\" alias should be used for a screwy table name
|
621
|
+
cols = {#{hms_keys = []
|
622
|
+
hms_headers.map do |hm|
|
623
|
+
hms_keys << (assoc_name = (assoc = hm.first).name.to_s)
|
624
|
+
"#{assoc_name.inspect} => [#{(assoc.options[:through] && !assoc.through_reflection).inspect}, #{assoc.klass.name}, #{hm[1].inspect}, #{hm[2].inspect}]"
|
625
|
+
end.join(', ')}}
|
626
|
+
col_keys = @#{table_name}.columns.each_with_object([]) do |col, s|
|
627
|
+
col_name = col.name
|
628
|
+
next if @_brick_incl&.exclude?(col_name) ||
|
629
|
+
(#{(pk || []).inspect}.include?(col_name) && col.type == :integer && !bts.key?(col_name)) ||
|
562
630
|
::Brick.config.metadata_columns.include?(col_name) || poly_cols.include?(col_name)
|
563
631
|
|
564
|
-
|
565
|
-
|
566
|
-
if (bt = bts[col_name]) %>
|
567
|
-
<%= \" x-order=\\\"#\{bt.first}\\\"\".html_safe unless bt[2] # Allow sorting any BT except polymorphics
|
568
|
-
%>>BT <%
|
569
|
-
bt[1].each do |bt_pair| %><%=
|
570
|
-
bt_pair.first.bt_link(bt.first) %> <%
|
571
|
-
end %><%
|
572
|
-
else %><%= \" x-order=\\\"#\{col_name}\\\"\".html_safe if true # Currently we always allow click to sort
|
573
|
-
%>><%= col_name %><%
|
574
|
-
end
|
575
|
-
%></th><%
|
632
|
+
s << col_name
|
633
|
+
cols[col_name] = col
|
576
634
|
end
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
635
|
+
unless @_brick_sequence # If no sequence is defined, start with all inclusions
|
636
|
+
@_brick_sequence = col_keys + #{(hms_keys).inspect}.reject { |assoc_name| @_brick_incl&.exclude?(assoc_name) }
|
637
|
+
end
|
638
|
+
@_brick_sequence.reject! { |nm| @_brick_excl.include?(nm) } if @_brick_excl # Reject exclusions
|
639
|
+
@_brick_sequence.each_with_object(+'') do |col_name, s|
|
640
|
+
if (col = cols[col_name]).is_a?(ActiveRecord::ConnectionAdapters::Column)
|
641
|
+
s << '<th'
|
642
|
+
s << \" title=\\\"#\{col.comment}\\\"\" if col.respond_to?(:comment) && !col.comment.blank?
|
643
|
+
s << if (bt = bts[col_name])
|
644
|
+
# Allow sorting for any BT except polymorphics
|
645
|
+
\"#\{' x-order=\"' + bt.first.to_s + '\"' unless bt[2]}>BT \" +
|
646
|
+
bt[1].map { |bt_pair| bt_pair.first.bt_link(bt.first) }.join(' ')
|
647
|
+
else # Normal column
|
648
|
+
\"#\{' x-order=\"' + col_name + '\"' if true}>#\{col_name}\"
|
649
|
+
end
|
650
|
+
elsif col # HM column
|
651
|
+
s << \"<th#\{' x-order=\"' + col_name + '\"' if true}>#\{col[2]} \"
|
652
|
+
s << (col.first ? \"#\{col[3]}\" : \"#\{link_to(col[3], send(\"#\{col[1].name.underscore.tr('/', '_').pluralize}_path\"))}\")
|
653
|
+
else # Bad column name!
|
654
|
+
s << \"<th title=\\\"<< Unknown column >>\\\">#\{col_name}\"
|
655
|
+
end
|
656
|
+
s << '</th>'
|
657
|
+
end.html_safe
|
658
|
+
%></tr></thead>
|
589
659
|
<tbody>
|
590
|
-
<% @#{table_name}.each do |#{obj_name}|
|
660
|
+
<% @#{table_name}.each do |#{obj_name}|
|
661
|
+
hms_cols = {#{hms_columns.join(', ')}} %>
|
591
662
|
<tr>#{"
|
592
663
|
<td><%= link_to '⇛', #{path_obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if obj_pk}
|
593
|
-
<%
|
664
|
+
<% @_brick_sequence.each do |col_name|
|
594
665
|
val = #{obj_name}.attributes[col_name] %>
|
595
|
-
<td
|
596
|
-
|
597
|
-
|
666
|
+
<td<%= ' class=\"dimmed\"'.html_safe unless cols.key?(col_name)%>><%
|
667
|
+
if (bt = bts[col_name])
|
668
|
+
if bt[2] # Polymorphic?
|
598
669
|
bt_class = #{obj_name}.send(\"#\{bt.first\}_type\")
|
599
670
|
base_class = (::Brick.existing_stis[bt_class] || bt_class).constantize.base_class.name.underscore
|
600
671
|
poly_id = #{obj_name}.send(\"#\{bt.first\}_id\")
|
601
|
-
%><%= link_to(\"#\{bt_class\} ##\{poly_id\}\",
|
602
|
-
send(\"#\{base_class\}_path\".to_sym, poly_id)) if poly_id %><%
|
672
|
+
%><%= link_to(\"#\{bt_class\} ##\{poly_id\}\", send(\"#\{base_class\}_path\".to_sym, poly_id)) if poly_id %><%
|
603
673
|
else
|
604
674
|
bt_txt = (bt_class = bt[1].first.first).brick_descrip(
|
605
675
|
# 0..62 because Postgres column names are limited to 63 characters
|
@@ -608,14 +678,29 @@ if (headerTop) {
|
|
608
678
|
bt_txt ||= \"<span class=\\\"orphan\\\"><< Orphaned ID: #\{val} >></span>\".html_safe if val
|
609
679
|
bt_id = bt_id_col.map { |id_col| #{obj_name}.send(id_col.to_sym) } %>
|
610
680
|
<%= bt_id&.first ? link_to(bt_txt, send(\"#\{bt_class.base_class.name.underscore.tr('/', '_')\}_path\".to_sym, bt_id)) : bt_txt %>
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
681
|
+
<% end
|
682
|
+
elsif (hms_col = hms_cols[col_name])
|
683
|
+
if hms_col.length == 1 %>
|
684
|
+
<%= hms_col.first %>
|
685
|
+
<% else
|
686
|
+
klass = (col = cols[col_name])[1]
|
687
|
+
txt = if col[2] == 'HO'
|
688
|
+
descrips = @_brick_bt_descrip[col_name.to_sym][klass]
|
689
|
+
ho_txt = klass.brick_descrip(#{obj_name}, descrips[0..-2].map { |id| #{obj_name}.send(id.last[0..62]) }, (ho_id_col = descrips.last))
|
690
|
+
ho_id = ho_id_col.map { |id_col| #{obj_name}.send(id_col.to_sym) }
|
691
|
+
ho_id&.first ? link_to(ho_txt, send(\"#\{klass.base_class.name.underscore.tr('/', '_')\}_path\".to_sym, ho_id)) : ho_txt
|
692
|
+
else
|
693
|
+
\"#\{hms_col[1] || 'View'\} #\{hms_col.first}\"
|
694
|
+
end %>
|
695
|
+
<%= link_to txt, send(\"#\{klass.name.underscore.tr('/', '_').pluralize}_path\".to_sym, hms_col[2]) unless hms_col[1]&.zero? %>
|
696
|
+
<% end
|
697
|
+
elsif cols.key?(col_name)
|
698
|
+
%><%= hide_bcrypt(val) %><%
|
699
|
+
else # Bad column name!
|
700
|
+
%>?<%
|
701
|
+
end
|
702
|
+
%></td>
|
617
703
|
<% end %>
|
618
|
-
#{hms_columns.each_with_object(+'') { |hm_col, s| s << "<td>#{hm_col}</td>" }}
|
619
704
|
</tr>
|
620
705
|
<% end %>
|
621
706
|
</tbody>
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
@@ -190,6 +190,15 @@ module Brick
|
|
190
190
|
[bts, hms]
|
191
191
|
end
|
192
192
|
|
193
|
+
def exclude_column(table, col)
|
194
|
+
puts "Excluding #{table}.#{col}"
|
195
|
+
true
|
196
|
+
end
|
197
|
+
def unexclude_column(table, col)
|
198
|
+
puts "Unexcluding #{table}.#{col}"
|
199
|
+
true
|
200
|
+
end
|
201
|
+
|
193
202
|
# Switches Brick auto-models on or off, for all threads
|
194
203
|
# @api public
|
195
204
|
def enable_models=(value)
|
@@ -7,13 +7,13 @@ module Brick
|
|
7
7
|
class InstallGenerator < ::Rails::Generators::Base
|
8
8
|
# include ::Rails::Generators::Migration
|
9
9
|
|
10
|
-
source_root File.expand_path('templates', __dir__)
|
11
|
-
class_option(
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
)
|
10
|
+
# source_root File.expand_path('templates', __dir__)
|
11
|
+
# class_option(
|
12
|
+
# :with_changes,
|
13
|
+
# type: :boolean,
|
14
|
+
# default: false,
|
15
|
+
# desc: 'Store changeset (diff) with each version'
|
16
|
+
# )
|
17
17
|
|
18
18
|
desc 'Generates an initializer file for configuring Brick'
|
19
19
|
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
require 'rails/generators/active_record'
|
5
|
+
require 'fancy_gets'
|
6
|
+
|
7
|
+
module Brick
|
8
|
+
# Auto-generates migrations
|
9
|
+
class MigrationsGenerator < ::Rails::Generators::Base
|
10
|
+
include FancyGets
|
11
|
+
# include ::Rails::Generators::Migration
|
12
|
+
|
13
|
+
# SQL types that are the same as their migration data type name: text, integer, bigint, smallint, date, boolean, decimal, float
|
14
|
+
SQL_TYPES = { 'character varying' =>'string',
|
15
|
+
'timestamp without time zone' =>'timestamp',
|
16
|
+
'timestamp with time zone' =>'timestamp',
|
17
|
+
'double precision' =>'float' } # might work with 'double'
|
18
|
+
# (Still need to find what "inet" and "json" data types map to.)
|
19
|
+
|
20
|
+
# # source_root File.expand_path('templates', __dir__)
|
21
|
+
# class_option(
|
22
|
+
# :with_changes,
|
23
|
+
# type: :boolean,
|
24
|
+
# default: false,
|
25
|
+
# desc: 'Add IMPORT_TEMPLATE to model'
|
26
|
+
# )
|
27
|
+
|
28
|
+
desc 'Auto-generates migrations for an existing database.'
|
29
|
+
|
30
|
+
def brick_migrations
|
31
|
+
# If Apartment is active, see if a default schema to analyse is indicated
|
32
|
+
|
33
|
+
# # Load all models
|
34
|
+
# Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
|
35
|
+
|
36
|
+
if (tables = ::Brick.relations.reject { |k, v| v.key?(:isView) && v[:isView] == true }.map(&:first).sort).empty?
|
37
|
+
puts "No tables found in database #{ActiveRecord::Base.connection.current_database}."
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
ar_version = unless ActiveRecord.version < ::Gem::Version.new('5.0')
|
42
|
+
arv = ActiveRecord.version.segments[0..1]
|
43
|
+
arv.pop if arv&.last == 0
|
44
|
+
"[#{arv.join('.')}]"
|
45
|
+
end
|
46
|
+
default_mig_path = (mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{File.expand_path(__dir__)}/db/migrate")
|
47
|
+
if Dir.exist?(mig_path)
|
48
|
+
if Dir["#{mig_path}/**/*.rb"].present?
|
49
|
+
puts "WARNING: migrations folder #{mig_path} appears to already have ruby files present."
|
50
|
+
mig_path2 = "#{File.expand_path(__dir__)}/tmp/brick_migrations"
|
51
|
+
if Dir.exist?(mig_path2)
|
52
|
+
if Dir["#{mig_path2}/**/*.rb"].present?
|
53
|
+
puts "As well, temporary folder #{mig_path2} also has ruby files present."
|
54
|
+
puts "Choose a destination -- all existing .rb files will be removed:"
|
55
|
+
mig_path2 = gets_list(list: ['Cancel operation!', "Add all migration files to #{mig_path} anyway", mig_path, mig_path2])
|
56
|
+
return if mig_path2.start_with?('Cancel')
|
57
|
+
|
58
|
+
if mig_path2.start_with?('Add all migration files to ')
|
59
|
+
mig_path2 = mig_path
|
60
|
+
else
|
61
|
+
Dir["#{mig_path2}/**/*.rb"].each { |rb| File.delete(rb) }
|
62
|
+
end
|
63
|
+
else
|
64
|
+
puts "Using temporary folder #{mig_path2} for created migration files.\n\n"
|
65
|
+
end
|
66
|
+
else
|
67
|
+
puts "Creating the temporary folder #{mig_path2} for created migration files.\n\n"
|
68
|
+
Dir.mkdir(mig_path2)
|
69
|
+
end
|
70
|
+
mig_path = mig_path2
|
71
|
+
else
|
72
|
+
puts "Using standard migration folder #{mig_path} for created migration files.\n\n"
|
73
|
+
end
|
74
|
+
else
|
75
|
+
puts "Creating standard ActiveRecord migration folder #{mig_path} to hold new migration files.\n\n"
|
76
|
+
Dir.mkdir(mig_path)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Generate a list of tables that can be chosen
|
80
|
+
chosen = gets_list(list: tables, chosen: tables.dup)
|
81
|
+
# Work from now back the same number of minutes as expected migrations to create
|
82
|
+
current_mig_time = Time.now - chosen.length.minutes
|
83
|
+
done = []
|
84
|
+
fks = {}
|
85
|
+
stuck = {}
|
86
|
+
# Start by making migrations for fringe tables (those with no foreign keys).
|
87
|
+
# Continue layer by layer, creating migrations for tables that reference ones already done, until
|
88
|
+
# no more migrations can be created. (At that point hopefully all tables are accounted for.)
|
89
|
+
while (fringe = chosen.reject do |tbl|
|
90
|
+
snags = ::Brick.relations[tbl][:fks].select do |_k, v|
|
91
|
+
v[:is_bt] && !v[:polymorphic] &&
|
92
|
+
tbl != v[:inverse_table] && # Ignore self-referencing associations (stuff like "parent_id")
|
93
|
+
!done.include?(v[:inverse_table])
|
94
|
+
end
|
95
|
+
stuck[tbl] = snags if snags.present?
|
96
|
+
end).present?
|
97
|
+
fringe.each do |tbl|
|
98
|
+
tbl = tbl[7..-1] if tbl.start_with?('public.') # %%% Provide better multitenancy support later
|
99
|
+
pkey_cols = ::Brick.relations[chosen.first][:pkey].values.flatten
|
100
|
+
# %%% For the moment we're skipping polymorphics
|
101
|
+
fkey_cols = ::Brick.relations[tbl][:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
|
102
|
+
mig = +"class Create#{table_name = tbl.camelize} < ActiveRecord::Migration#{ar_version}\n"
|
103
|
+
# %%% Support missing foreign key (by adding: ,id: false)
|
104
|
+
# also bigint / uuid / other non-standard data type for primary key
|
105
|
+
mig << " def change\n create_table :#{tbl} do |t|\n"
|
106
|
+
possible_ts = [] # Track possible generic timestamps
|
107
|
+
(relation = ::Brick.relations[tbl])[:cols].each do |col, col_type|
|
108
|
+
next if pkey_cols.include?(col)
|
109
|
+
|
110
|
+
# See if there are generic timestamps
|
111
|
+
if (sql_type = SQL_TYPES[col_type.first]) == 'timestamp' && ['created_at','updated_at'].include?(col)
|
112
|
+
possible_ts << col
|
113
|
+
next
|
114
|
+
end
|
115
|
+
|
116
|
+
sql_type ||= col_type.first
|
117
|
+
# Determine if this column is used as part of a foreign key
|
118
|
+
if fk = fkey_cols.find { |assoc| col == assoc[:fk] }
|
119
|
+
mig << " t.references :#{fk[:assoc_name]}#{", type: #{sql_type}" unless sql_type == 'integer'}\n"
|
120
|
+
else
|
121
|
+
mig << emit_column(sql_type, col)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
if possible_ts.length == 2 # Both created_at and updated_at
|
125
|
+
mig << "\n t.timestamps\n"
|
126
|
+
else # Just one or the other
|
127
|
+
possible_ts.each { |ts| emit_column('timestamp', ts) }
|
128
|
+
end
|
129
|
+
mig << " end\n end\nend\n"
|
130
|
+
current_mig_time += 1.minute
|
131
|
+
File.open("#{mig_path}/#{current_mig_time.strftime('%Y%m%d%H%M00')}_create_#{tbl}.rb", "w") { |f| f.write mig }
|
132
|
+
end
|
133
|
+
done.concat(fringe)
|
134
|
+
chosen -= done
|
135
|
+
end
|
136
|
+
stuck_counts = Hash.new { |h, k| h[k] = 0 }
|
137
|
+
chosen.each do |leftover|
|
138
|
+
puts "Can't do #{leftover} because:\n #{stuck[leftover].map do |snag|
|
139
|
+
stuck_counts[snag.last[:inverse_table]] += 1
|
140
|
+
snag.last[:assoc_name]
|
141
|
+
end.join(', ')}"
|
142
|
+
end
|
143
|
+
pretty_mig_path = mig_path[cur_path.length] if mig_path.start_with?(cur_path = File.expand_path(__dir__))
|
144
|
+
puts "*** Created #{done.length} migration files under #{pretty_mig_path || mig_path} ***"
|
145
|
+
if (stuck_sorted = stuck_counts.to_a.sort { |a, b| b.last <=> a.last }).length.positive?
|
146
|
+
puts "-----------------------------------------"
|
147
|
+
puts "Unable to create migrations for #{stuck_sorted.length} tables#{
|
148
|
+
". Here's the top 5 blockers" if stuck_sorted.length > 5
|
149
|
+
}:"
|
150
|
+
pp stuck_sorted[0..4]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def emit_column(type, name)
|
157
|
+
" t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}\n"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -59,7 +59,6 @@ module Brick
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
models.each do |m| # Find longest name in the list for future use to show lists on the right side of the screen
|
62
|
-
# Strangely this can't be inlined since it assigns to "len"
|
63
62
|
if longest_length < (len = m.name.length)
|
64
63
|
longest_length = len
|
65
64
|
end
|
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.54
|
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-
|
11
|
+
date: 2022-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -238,6 +238,7 @@ files:
|
|
238
238
|
- lib/brick/version_number.rb
|
239
239
|
- lib/generators/brick/USAGE
|
240
240
|
- lib/generators/brick/install_generator.rb
|
241
|
+
- lib/generators/brick/migrations_generator.rb
|
241
242
|
- lib/generators/brick/model_generator.rb
|
242
243
|
- lib/generators/brick/templates/add_object_changes_to_versions.rb.erb
|
243
244
|
- lib/generators/brick/templates/create_versions.rb.erb
|