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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82ebef3561b9cd7b8abcbbd8a30b7687485543ddf1a0900a9935dfb99299f13f
4
- data.tar.gz: ba071d53409fd8d4833693907364c745585aeb0bb18e885fcf7cbf1bd23b9940
3
+ metadata.gz: 24731ac9bd56261179224610406247cc4438c61e411aa3fe96205b2e97fc8cb8
4
+ data.tar.gz: 785d12e36fcf788c05cff3a02ca2ec17fc418b0ea15b54cb6923c83c6f362d3b
5
5
  SHA512:
6
- metadata.gz: 69ad3ae158a8e478864db65dc808152d4b1b16bdc8779b1a651f59f199167daf00d2aa10018c5f65d5e619bbca4c09e99bc319ca736eed57023d61e6067ea365
7
- data.tar.gz: 18ca63e176e12ea50a5168e835a2eddfa66c8f522943d42e6d895d2f3e05d0cf6d396ab28d79f98898c91670b618cc27dbad5fa0bc9129a3edcfc3f74f5db029
6
+ metadata.gz: '090a3c4d1b3cb4761799e05adf5caa0c83f60cf985f97ed6856beb05620014e9e7401689719209488092c6c4a7605e53781cdb71ad9e7ec204ccaddca0d92cca'
7
+ data.tar.gz: 8d1f9512c948359e7d73cd7cc32302f226df7e3e744fab562e01e16170830be28d0e2e15bcd7b437f66ed79e366b0ff33b7f578e4c76ffde1dd62cdcec059069
@@ -26,18 +26,13 @@
26
26
 
27
27
  # colour coded origins
28
28
 
29
- # Drag something like TmfModel#name onto the rows and have it automatically add five columns -- where type=zone / where type = section / etc
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
- if (id = pk_alias.map { |pk_alias_part| obj.send(pk_alias_part) })
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
- fk_col = (associative = associatives[hm.name]).foreign_key
358
- hm.foreign_key
359
- else
360
- fk_col = hm.foreign_key
361
- hm.klass.primary_key || '*'
362
- end
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&.name || hm.klass.table_name} GROUP BY #{(1..fk_col.length).to_a.join(', ')}) AS #{tbl_alias}
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&.name || hm.klass.table_name} GROUP BY 1) AS #{tbl_alias}
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
- source = nil
661
- this_hmt_fk = if fks.length > 1
662
- singular_assoc_name = fk.first[:inverse][:assoc_name].singularize
663
- source = fk.last
664
- through = fk.first[:alternate_name].pluralize
665
- "#{singular_assoc_name}_#{hmt_fk}"
666
- else
667
- source = fk.last unless hmt_fk.singularize == fk.last
668
- through = fk.first[:assoc_name].pluralize
669
- hmt_fk
670
- end
671
- code << " has_many :#{this_hmt_fk}, through: #{(assoc_name = through.to_sym).to_sym.inspect}#{", source: :#{source}" if source}\n"
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, this_hmt_fk.to_sym, **options)
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
- ar_relation = model.all # model.primary_key ? model.order(model.primary_key) : model.all
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
- "#{obj_name}._br_#{assoc_name}_ct || 0"
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) %> &nbsp; <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 pk}
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
- bt[1].order(:#{pk}).each { |obj| option_detail << [obj.brick_descrip, obj.#{pk}] }
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 %>
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 22
8
+ TINY = 23
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
data/lib/brick.rb CHANGED
@@ -126,9 +126,7 @@ module Brick
126
126
  skip_hms[hmt.last.name] = nil
127
127
  end
128
128
  end
129
- skip_hms.each do |k, _v|
130
- puts hms.delete(k).inspect
131
- end
129
+ skip_hms.each { |k, _v| hms.delete(k) }
132
130
  [bts, hms, associatives]
133
131
  end
134
132
 
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.22
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-10 00:00:00.000000000 Z
11
+ date: 2022-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord