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 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