brick 1.0.22 → 1.0.23
Sign up to get free protection for your applications and to get access to all the features.
- 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
|