brick 1.0.22 → 1.0.23
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 +78 -39
- data/lib/brick/frameworks/rails/engine.rb +107 -5
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +1 -3
- 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: 24731ac9bd56261179224610406247cc4438c61e411aa3fe96205b2e97fc8cb8
|
4
|
+
data.tar.gz: 785d12e36fcf788c05cff3a02ca2ec17fc418b0ea15b54cb6923c83c6f362d3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '090a3c4d1b3cb4761799e05adf5caa0c83f60cf985f97ed6856beb05620014e9e7401689719209488092c6c4a7605e53781cdb71ad9e7ec204ccaddca0d92cca'
|
7
|
+
data.tar.gz: 8d1f9512c948359e7d73cd7cc32302f226df7e3e744fab562e01e16170830be28d0e2e15bcd7b437f66ed79e366b0ff33b7f578e4c76ffde1dd62cdcec059069
|
data/lib/brick/extensions.rb
CHANGED
@@ -26,18 +26,13 @@
|
|
26
26
|
|
27
27
|
# colour coded origins
|
28
28
|
|
29
|
-
# Drag something like
|
29
|
+
# Drag something like HierModel#name onto the rows and have it automatically add five columns -- where type=zone / where type = section / etc
|
30
30
|
|
31
31
|
# Support for Postgres / MySQL enums (add enum to model, use model enums to make a drop-down in the UI)
|
32
32
|
|
33
33
|
# Currently quadrupling up routes
|
34
34
|
|
35
35
|
|
36
|
-
# From the North app:
|
37
|
-
# undefined method `built_in_role_path' when referencing show on a subclassed STI:
|
38
|
-
# http://localhost:3000/roles/3?_brick_schema=cust1
|
39
|
-
|
40
|
-
|
41
36
|
# ==========================================================
|
42
37
|
# Dynamically create model or controller classes when needed
|
43
38
|
# ==========================================================
|
@@ -166,11 +161,15 @@ module ActiveRecord
|
|
166
161
|
if is_brackets_have_content
|
167
162
|
output
|
168
163
|
elsif pk_alias
|
169
|
-
|
164
|
+
id = []
|
165
|
+
pk_alias.each do |pk_alias_part|
|
166
|
+
if (pk_part = obj.send(pk_alias_part))
|
167
|
+
id << pk_part
|
168
|
+
end
|
169
|
+
end
|
170
|
+
if id.present?
|
170
171
|
"#{klass.name} ##{id.join(', ')}"
|
171
172
|
end
|
172
|
-
# elsif klass.primary_key
|
173
|
-
# "#{klass.name} ##{obj.send(klass.primary_key)}"
|
174
173
|
else
|
175
174
|
obj.to_s
|
176
175
|
end
|
@@ -184,6 +183,14 @@ module ActiveRecord
|
|
184
183
|
model_underscore == assoc_name ? link : "#{assoc_name}-#{link}".html_safe
|
185
184
|
end
|
186
185
|
|
186
|
+
def self.brick_import_template
|
187
|
+
template = constants.include?(:IMPORT_TEMPLATE) ? self::IMPORT_TEMPLATE : suggest_template(false, false, 0)
|
188
|
+
# Add the primary key to the template as being unique (unless it's already there)
|
189
|
+
template[:uniques] = [pk = primary_key.to_sym]
|
190
|
+
template[:all].unshift(pk) unless template[:all].include?(pk)
|
191
|
+
template
|
192
|
+
end
|
193
|
+
|
187
194
|
private
|
188
195
|
|
189
196
|
def self._brick_get_fks
|
@@ -354,23 +361,23 @@ module ActiveRecord
|
|
354
361
|
hm_counts.each do |k, hm|
|
355
362
|
associative = nil
|
356
363
|
count_column = if hm.options[:through]
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
364
|
+
fk_col = (associative = associatives[hm.name]).foreign_key
|
365
|
+
hm.foreign_key
|
366
|
+
else
|
367
|
+
fk_col = hm.foreign_key
|
368
|
+
hm.klass.primary_key || '*'
|
369
|
+
end
|
363
370
|
tbl_alias = "_br_#{hm.name}"
|
364
371
|
pri_tbl = hm.active_record
|
365
372
|
if fk_col.is_a?(Array) # Composite key?
|
366
373
|
on_clause = []
|
367
374
|
fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{tbl_alias}.#{fk_col_part} = #{pri_tbl.table_name}.#{pri_tbl.primary_key[idx]}" }
|
368
375
|
joins!("LEFT OUTER
|
369
|
-
JOIN (SELECT #{fk_col.join(', ')}, COUNT(#{count_column}) AS _ct_ FROM #{associative&.
|
376
|
+
JOIN (SELECT #{fk_col.join(', ')}, COUNT(#{count_column}) AS _ct_ FROM #{associative&.table_name || hm.klass.table_name} GROUP BY #{(1..fk_col.length).to_a.join(', ')}) AS #{tbl_alias}
|
370
377
|
ON #{on_clause.join(' AND ')}")
|
371
378
|
else
|
372
379
|
joins!("LEFT OUTER
|
373
|
-
JOIN (SELECT #{fk_col}, COUNT(#{count_column}) AS _ct_ FROM #{associative&.
|
380
|
+
JOIN (SELECT #{fk_col}, COUNT(#{count_column}) AS _ct_ FROM #{associative&.table_name || hm.klass.table_name} GROUP BY 1) AS #{tbl_alias}
|
374
381
|
ON #{tbl_alias}.#{fk_col} = #{pri_tbl.table_name}.#{pri_tbl.primary_key}")
|
375
382
|
end
|
376
383
|
end
|
@@ -657,31 +664,34 @@ class Object
|
|
657
664
|
end
|
658
665
|
hmts.each do |hmt_fk, fks|
|
659
666
|
fks.each do |fk|
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
667
|
+
through = fk.first[:assoc_name]
|
668
|
+
hmt_name = if fks.length > 1
|
669
|
+
if fks[0].first[:inverse][:assoc_name] == fks[1].first[:inverse][:assoc_name] # Same BT names pointing back to us? (Most common scenario)
|
670
|
+
"#{hmt_fk}_through_#{fk.first[:assoc_name]}"
|
671
|
+
else # Use BT names to provide uniqueness
|
672
|
+
through = fk.first[:alternate_name].pluralize
|
673
|
+
singular_assoc_name = fk.first[:inverse][:assoc_name].singularize
|
674
|
+
"#{singular_assoc_name}_#{hmt_fk}"
|
675
|
+
end
|
676
|
+
else
|
677
|
+
hmt_fk
|
678
|
+
end
|
679
|
+
source = fk.last unless hmt_name.singularize == fk.last
|
680
|
+
code << " has_many :#{hmt_name}, through: #{(assoc_name = through.to_sym).to_sym.inspect}#{", source: :#{source}" if source}\n"
|
672
681
|
options = { through: assoc_name }
|
673
682
|
options[:source] = source.to_sym if source
|
674
|
-
self.send(:has_many,
|
675
|
-
end
|
676
|
-
end
|
677
|
-
# Not NULLables
|
678
|
-
relation[:cols].each do |col, datatype|
|
679
|
-
if (datatype[3] && ar_pks.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
|
680
|
-
::Brick.config.not_nullables.include?("#{matching}.#{col}")
|
681
|
-
code << " validates :#{col}, presence: true\n"
|
682
|
-
self.send(:validates, col.to_sym, { presence: true })
|
683
|
+
self.send(:has_many, hmt_name.to_sym, **options)
|
683
684
|
end
|
684
685
|
end
|
686
|
+
# # Not NULLables
|
687
|
+
# # %%% For the minute we've had to pull this out because it's been troublesome implementing the NotNull validator
|
688
|
+
# relation[:cols].each do |col, datatype|
|
689
|
+
# if (datatype[3] && ar_pks.exclude?(col) && ::Brick.config.metadata_columns.exclude?(col)) ||
|
690
|
+
# ::Brick.config.not_nullables.include?("#{matching}.#{col}")
|
691
|
+
# code << " validates :#{col}, not_null: true\n"
|
692
|
+
# self.send(:validates, col.to_sym, { not_null: true })
|
693
|
+
# end
|
694
|
+
# end
|
685
695
|
end
|
686
696
|
code << "end # model #{model_name}\n\n"
|
687
697
|
end # class definition
|
@@ -700,9 +710,22 @@ class Object
|
|
700
710
|
code << " @#{table_name} = #{model.name}#{model.primary_key ? ".order(#{model.primary_key.inspect})" : '.all'}\n"
|
701
711
|
code << " @#{table_name}.brick_select(params)\n"
|
702
712
|
code << " end\n"
|
713
|
+
self.protect_from_forgery unless: -> { self.request.format.js? }
|
703
714
|
self.define_method :index do
|
704
715
|
::Brick.set_db_schema(params)
|
705
|
-
|
716
|
+
if request.format == :csv # Asking for a template?
|
717
|
+
require 'csv'
|
718
|
+
exported_csv = CSV.generate(force_quotes: false) do |csv_out|
|
719
|
+
model.df_export(model.brick_import_template).each { |row| csv_out << row }
|
720
|
+
end
|
721
|
+
render inline: exported_csv, content_type: request.format
|
722
|
+
return
|
723
|
+
elsif request.format == :js # Asking for JSON?
|
724
|
+
render inline: model.df_export(model.brick_import_template).to_json, content_type: request.format
|
725
|
+
return
|
726
|
+
end
|
727
|
+
|
728
|
+
ar_relation = model.primary_key ? model.order("#{model.table_name}.#{model.primary_key}") : model.all
|
706
729
|
@_brick_params = ar_relation.brick_select(params, (selects = []), (bt_descrip = {}), (hm_counts = {}), (join_array = ::Brick::JoinArray.new))
|
707
730
|
# %%% Add custom HM count columns
|
708
731
|
# %%% What happens when the PK is composite?
|
@@ -739,6 +762,22 @@ class Object
|
|
739
762
|
code << " end\n"
|
740
763
|
self.define_method :update do
|
741
764
|
::Brick.set_db_schema(params)
|
765
|
+
|
766
|
+
if request.format == :csv # Importing CSV?
|
767
|
+
require 'csv'
|
768
|
+
# See if internally it's likely a TSV file (tab-separated)
|
769
|
+
tab_counts = []
|
770
|
+
5.times { tab_counts << request.body.readline.count("\t") unless request.body.eof? }
|
771
|
+
request.body.rewind
|
772
|
+
separator = "\t" if tab_counts.length > 0 && tab_counts.uniq.length == 1 && tab_counts.first > 0
|
773
|
+
result = model.df_import(CSV.parse(request.body, { col_sep: separator || :auto }), model.brick_import_template)
|
774
|
+
# render inline: exported_csv, content_type: request.format
|
775
|
+
return
|
776
|
+
# elsif request.format == :js # Asking for JSON?
|
777
|
+
# render inline: model.df_export(true).to_json, content_type: request.format
|
778
|
+
# return
|
779
|
+
end
|
780
|
+
|
742
781
|
instance_variable_set("@#{singular_table_name}".to_sym, (obj = model.find(params[:id].split(','))))
|
743
782
|
obj = obj.first if obj.is_a?(Array)
|
744
783
|
obj.send(:update, send(params_name = params_name.to_sym))
|
@@ -80,6 +80,7 @@ module Brick
|
|
80
80
|
pk = @_brick_model.primary_key
|
81
81
|
obj_name = model_name.underscore
|
82
82
|
table_name = model_name.pluralize.underscore
|
83
|
+
template_link = nil
|
83
84
|
bts, hms, associatives = ::Brick.get_bts_and_hms(@_brick_model) # This gets BT and HM and also has_many :through (HMT)
|
84
85
|
hms_columns = [] # Used for 'index'
|
85
86
|
skip_klass_hms = ::Brick.config.skip_index_hms[model_name] || {}
|
@@ -96,7 +97,9 @@ module Brick
|
|
96
97
|
set_ct = if skip_klass_hms.key?(assoc_name.to_sym)
|
97
98
|
'nil'
|
98
99
|
else
|
99
|
-
|
100
|
+
# Postgres column names are limited to 63 characters
|
101
|
+
attrib_name = "_br_#{assoc_name}_ct"[0..62]
|
102
|
+
"#{obj_name}.#{attrib_name} || 0"
|
100
103
|
end
|
101
104
|
"<%= ct = #{set_ct}
|
102
105
|
link_to \"#\{ct || 'View'\} #{assoc_name}\", #{hm_assoc.klass.name.underscore.pluralize}_path({ #{path_keys(hm_fk_name, obj_name, pk)} }) unless ct&.zero? %>\n"
|
@@ -115,6 +118,13 @@ module Brick
|
|
115
118
|
table_options = (::Brick.relations.keys - ::Brick.config.exclude_tables)
|
116
119
|
.each_with_object(+'') { |v, s| s << "<option value=\"#{v.underscore.pluralize}\">#{v}</option>" }.html_safe
|
117
120
|
css = +"<style>
|
121
|
+
#dropper {
|
122
|
+
background-color: #eee;
|
123
|
+
}
|
124
|
+
#btnImport {
|
125
|
+
display: none;
|
126
|
+
}
|
127
|
+
|
118
128
|
table {
|
119
129
|
border-collapse: collapse;
|
120
130
|
margin: 25px 0;
|
@@ -260,11 +270,101 @@ function changeout(href, param, value) {
|
|
260
270
|
elsif pk
|
261
271
|
"#{obj_name}.#{pk}"
|
262
272
|
end
|
273
|
+
if Object.const_defined?('DutyFree')
|
274
|
+
template_link = "
|
275
|
+
<%= link_to 'CSV', #{table_name}_path(format: :csv) %> <a href=\"#\" id=\"sheetsLink\">Sheets</a>
|
276
|
+
<div id=\"dropper\" contenteditable=\"true\"></div>
|
277
|
+
<input type=\"button\" id=\"btnImport\" value=\"Import\">"
|
278
|
+
end
|
263
279
|
"#{css}
|
264
280
|
<p style=\"color: green\"><%= notice %></p>#{"
|
265
281
|
<select id=\"schema\">#{schema_options}</select>" if ::Brick.db_schemas.length > 1}
|
266
282
|
<select id=\"tbl\">#{table_options}</select>
|
267
|
-
<h1>#{model_name.pluralize}</h1
|
283
|
+
<h1>#{model_name.pluralize}</h1>#{template_link}
|
284
|
+
<script>
|
285
|
+
var dropperDiv = document.getElementById(\"dropper\");
|
286
|
+
var btnImport = document.getElementById(\"btnImport\");
|
287
|
+
var droppedTSV;
|
288
|
+
if (dropperDiv) { // Other interesting events: blur keyup input
|
289
|
+
dropperDiv.addEventListener(\"paste\", function (evt) {
|
290
|
+
droppedTSV = evt.clipboardData.getData('text/plain');
|
291
|
+
var html = evt.clipboardData.getData('text/html');
|
292
|
+
var tbl = html.substring(html.indexOf(\"<tbody>\") + 7, html.lastIndexOf(\"</tbody>\"));
|
293
|
+
console.log(tbl);
|
294
|
+
btnImport.style.display = droppedTSV.length > 0 ? \"block\" : \"none\";
|
295
|
+
});
|
296
|
+
btnImport.addEventListener(\"click\", function () {
|
297
|
+
fetch(changeout(<%= #{obj_name}_path(-1, format: :csv).inspect.html_safe %>, \"_brick_schema\", brickSchema), {
|
298
|
+
method: 'PATCH',
|
299
|
+
headers: { 'Content-Type': 'text/tab-separated-values' },
|
300
|
+
body: droppedTSV
|
301
|
+
}).then(function (tsvResponse) {
|
302
|
+
btnImport.style.display = \"none\";
|
303
|
+
console.log(\"toaster\", tsvResponse);
|
304
|
+
});
|
305
|
+
});
|
306
|
+
}
|
307
|
+
var sheetUrl;
|
308
|
+
var spreadsheetId;
|
309
|
+
var sheetsLink = document.getElementById(\"sheetsLink\");
|
310
|
+
function gapiLoaded() {
|
311
|
+
// Have a click on the sheets link to bring up the sign-in window. (Must happen from some kind of user click.)
|
312
|
+
sheetsLink.addEventListener(\"click\", async function (evt) {
|
313
|
+
evt.preventDefault();
|
314
|
+
await gapi.load(\"client\", function () {
|
315
|
+
gapi.client.init({ // Load the discovery doc to initialize the API
|
316
|
+
clientId: \"487319557829-fgj4u660igrpptdji7ev0r5hb6kh05dh.apps.googleusercontent.com\",
|
317
|
+
scope: \"https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive.file\",
|
318
|
+
discoveryDocs: [\"https://sheets.googleapis.com/$discovery/rest?version=v4\"]
|
319
|
+
}).then(function () {
|
320
|
+
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSignInStatus);
|
321
|
+
updateSignInStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
|
322
|
+
});
|
323
|
+
});
|
324
|
+
});
|
325
|
+
}
|
326
|
+
|
327
|
+
async function updateSignInStatus(isSignedIn) {
|
328
|
+
if (isSignedIn) {
|
329
|
+
console.log(\"turds!\");
|
330
|
+
await gapi.client.sheets.spreadsheets.create({
|
331
|
+
properties: {
|
332
|
+
title: #{table_name.inspect},
|
333
|
+
},
|
334
|
+
sheets: [
|
335
|
+
// sheet1, sheet2, sheet3
|
336
|
+
]
|
337
|
+
}).then(function (response) {
|
338
|
+
sheetUrl = response.result.spreadsheetUrl;
|
339
|
+
spreadsheetId = response.result.spreadsheetId;
|
340
|
+
sheetsLink.setAttribute(\"href\", sheetUrl); // response.result.spreadsheetUrl
|
341
|
+
console.log(\"x1\", sheetUrl);
|
342
|
+
|
343
|
+
// Get JSON data
|
344
|
+
fetch(changeout(<%= #{table_name}_path(format: :js).inspect.html_safe %>, \"_brick_schema\", brickSchema)).then(function (response) {
|
345
|
+
response.json().then(function (data) {
|
346
|
+
gapi.client.sheets.spreadsheets.values.append({
|
347
|
+
spreadsheetId: spreadsheetId,
|
348
|
+
range: \"Sheet1\",
|
349
|
+
valueInputOption: \"RAW\",
|
350
|
+
insertDataOption: \"INSERT_ROWS\"
|
351
|
+
}, {
|
352
|
+
range: \"Sheet1\",
|
353
|
+
majorDimension: \"ROWS\",
|
354
|
+
values: data,
|
355
|
+
}).then(function (response2) {
|
356
|
+
// console.log(\"beefcake\", response2);
|
357
|
+
});
|
358
|
+
});
|
359
|
+
});
|
360
|
+
});
|
361
|
+
window.open(sheetUrl, '_blank');
|
362
|
+
}
|
363
|
+
}
|
364
|
+
</script>
|
365
|
+
<script async defer src=\"https://apis.google.com/js/api.js\" onload=\"gapiLoaded()\"></script>
|
366
|
+
|
367
|
+
|
268
368
|
<% if @_brick_params&.present? %><h3>where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %></h3><% end %>
|
269
369
|
<table id=\"#{table_name}\">
|
270
370
|
<thead><tr>#{'<th></th>' if pk}
|
@@ -285,12 +385,12 @@ function changeout(href, param, value) {
|
|
285
385
|
<tbody>
|
286
386
|
<% @#{table_name}.each do |#{obj_name}| %>
|
287
387
|
<tr>#{"
|
288
|
-
<td><%= link_to '⇛', #{obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if
|
388
|
+
<td><%= link_to '⇛', #{obj_name}_path(#{obj_pk}), { class: 'big-arrow' } %></td>" if obj_pk}
|
289
389
|
<% #{obj_name}.attributes.each do |k, val| %>
|
290
390
|
<% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) || k.start_with?('_brfk_') || (k.start_with?('_br_') && k.end_with?('_ct')) %>
|
291
391
|
<td>
|
292
392
|
<% if (bt = bts[k]) %>
|
293
|
-
<%# binding.pry # Postgres column names are limited to 63 characters
|
393
|
+
<%# binding.pry # Postgres column names are limited to 63 characters %>
|
294
394
|
<% bt_txt = bt[1].brick_descrip(#{obj_name}, @_brick_bt_descrip[bt.first][1].map { |z| #{obj_name}.send(z.last[0..62]) }, @_brick_bt_descrip[bt.first][2]) %>
|
295
395
|
<% bt_id_col = @_brick_bt_descrip[bt.first][2]; bt_id = #{obj_name}.send(*bt_id_col) if bt_id_col&.present? %>
|
296
396
|
<%= bt_id ? link_to(bt_txt, send(\"#\{bt_obj_path_base = bt[1].name.underscore\}_path\".to_sym, bt_id)) : bt_txt %>
|
@@ -324,6 +424,7 @@ function changeout(href, param, value) {
|
|
324
424
|
<table>
|
325
425
|
<% @#{obj_name}.first.attributes.each do |k, val| %>
|
326
426
|
<tr>
|
427
|
+
<%# %%% Accommodate composite keys %>
|
327
428
|
<% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
|
328
429
|
<th class=\"show-field\">
|
329
430
|
<% if (bt = bts[k])
|
@@ -332,7 +433,8 @@ function changeout(href, param, value) {
|
|
332
433
|
# %%% Only do this if the user has permissions to edit this bt field
|
333
434
|
if bt.length < 4
|
334
435
|
bt << (option_detail = [[\"(No #\{bt_name\} chosen)\", '^^^brick_NULL^^^']])
|
335
|
-
|
436
|
+
# %%% Accommodate composite keys for obj.pk at the end here
|
437
|
+
bt[1].order(bt[1].primary_key).each { |obj| option_detail << [obj.brick_descrip, obj.send(bt[1].primary_key)] }
|
336
438
|
end %>
|
337
439
|
BT <%= bt[1].bt_link(bt.first) %>
|
338
440
|
<% else %>
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
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.23
|
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-05-
|
11
|
+
date: 2022-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|