brick 1.0.246 → 1.0.248

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.
@@ -30,7 +30,7 @@ module Brick::Rails::FormTags
30
30
  col_keys = relation.columns.each_with_object([]) do |col, s|
31
31
  col_name = col.name
32
32
  next if inclusions&.exclude?(col_name) ||
33
- (pk.include?(col_name) && [:integer, :uuid].include?(col.type) && !bts&.key?(col_name)) ||
33
+ (pk.include?(col_name) && ([:string, :text].exclude?(col.type) || col_name == 'airtable_id') && !bts&.key?(col_name)) ||
34
34
  ::Brick.config.metadata_columns.include?(col_name) || poly_cols&.include?(col_name)
35
35
 
36
36
  s << col_name
@@ -252,25 +252,52 @@ module Brick::Rails::FormTags
252
252
 
253
253
  # -----------------------------
254
254
  # Our mega show/new/update form
255
- def brick_form_for(obj, options = {}, model = obj.class, bts = {}, pk = (obj.class.primary_key || []))
255
+ def brick_form_with(**options, &block)
256
+ obj = options.delete(:model) || instance_variable_get("@#{obj_name = controller_name.singularize}".to_sym)
257
+ unless obj
258
+ puts "WARNING: Could not find #{obj_name} object"
259
+ return
260
+ end
261
+
262
+ model = obj.class
263
+ bts = options.delete(:bts) || {}
264
+ pk = options.delete(:pk) || model.primary_key || []
256
265
  pk = [pk] unless pk.is_a?(Array)
257
266
  pk.map!(&:to_s)
267
+ brick_stuff = { fields_rendered: (fields_rendered = []), bts: bts, pk: pk }
268
+
269
+ # options[:model] = obj.becomes(model.base_class) unless options.include?(:model)
270
+ unless options.include?(:url)
271
+ options[:url] = if obj.new_record?
272
+ link_to_brick(obj.class, path_only: true) # Properly supports STI, but only works for :new
273
+ else
274
+ path_helper = obj.new_record? ? model._brick_index : model._brick_index(:singular)
275
+ send("#{path_helper}_path".to_sym, obj) if ::Brick.config.path_prefix || (path_helper != obj.class.table_name)
276
+ end
277
+ end
278
+
279
+ # We call into the older #form_for in order to have compatibility with Rails < 5.1
258
280
  form_for(obj.becomes(model.base_class), options) do |f|
259
- out = +'<table class="shadow">'
260
- has_fields = false
281
+ out = +''
282
+ out << '<table class="shadow">'
283
+ f.instance_variable_set(:@_brick, brick_stuff)
284
+ has_fields = has_updatable_fields = nil
261
285
  # If it's a new record, set any default polymorphic types
262
- bts&.each do |_k, v|
286
+ bts.each do |_k, v|
263
287
  if v[2]
264
288
  obj.send("#{model.brick_foreign_type(v.first)}=", v[1].first&.first&.name)
265
289
  end
266
290
  end if obj.new_record?
267
291
  hoa, hma, rtans = model._activestorage_actiontext_fields
268
- (model.column_names + hoa + hma + rtans.keys).each do |k|
292
+
293
+ block.call(f) if block_given?
294
+
295
+ ((model.column_names + hoa + hma + rtans.keys) - fields_rendered).each do |k|
269
296
  pk_pos = (pk.index(k)&.+ 1)
270
- next if (pk_pos && pk.length == 1 && !bts.key?(k)) ||
297
+ col = model.columns_hash[k]
298
+ next if (pk_pos && pk.length == 1 && !bts.key?(k) && [:string, :text].exclude?(col.type)) ||
271
299
  ::Brick.config.metadata_columns.include?(k)
272
300
 
273
- col = model.columns_hash[k]
274
301
  if !col
275
302
  kwargs = if hoa.include?(k) # has_one_attached
276
303
  { sql_type: 'binary', type: :file }
@@ -291,7 +318,7 @@ module Brick::Rails::FormTags
291
318
  val = obj.attributes[k]
292
319
  out << "
293
320
  <tr>
294
- <th class=\"show-field\"#{" title=\"#{col&.comment}\"".html_safe if col&.respond_to?(:comment) && !col&.comment.blank?}>"
321
+ <th class=\"show-field\"#{" title=\"#{col&.comment}\"" if col&.respond_to?(:comment) && !col&.comment.blank?}>"
295
322
  has_fields = true
296
323
  if (bt = bts[k])
297
324
  # Add a final member in this array with descriptive options to be used in <select> drop-downs
@@ -334,35 +361,38 @@ module Brick::Rails::FormTags
334
361
  else
335
362
  out << model.human_attribute_name(k, { default: k })
336
363
  end
337
- out << " (PK #{pk_pos})" if pk_pos
364
+ out << " (PK&nbsp;#{pk_pos})" if pk_pos
338
365
  out << "
339
366
  </th>
340
367
  <td>
341
368
  "
342
- if pk_pos
369
+ if pk_pos &&
370
+ # Allow new records to have a text-based primary key to be entered
371
+ (!obj.new_record? || [:string, :text].exclude?(col.type))
343
372
  out << val.to_s
344
373
  else
374
+ has_updatable_fields = true
345
375
  out << f.brick_field(k, html_options = {}, val, col, bt, bt_class, bt_name, bt_pair)
346
376
  end
347
377
  out << "
348
378
  </td>
349
379
  </tr>"
350
380
  end
351
- if has_fields
381
+ if has_updatable_fields
352
382
  out << "<tr><td colspan=\"2\">#{f.submit({ class: 'update' })}</td></tr>"
353
- else
383
+ elsif !has_fields
354
384
  out << '<tr><td colspan="2">(No displayable fields)</td></tr>'
355
385
  end
356
386
  out << '</table>'
357
387
  if model.name == 'ActiveStorage::Attachment'
358
388
  begin
359
- out << ::Brick::Rails.display_binary(obj&.blob&.download, 500_000)&.html_safe
389
+ out << ::Brick::Rails.display_binary(obj&.blob&.download, 500_000)
360
390
  rescue
361
391
  end
362
392
  end
363
- out.html_safe
364
- end
365
- end # brick_form_for
393
+ output_buffer << out.html_safe
394
+ end # form_for
395
+ end # brick_form_with
366
396
 
367
397
  # ------------------------------------------
368
398
  # Our cool N:M checkbox constellation editor
@@ -413,7 +443,7 @@ module Brick::Rails::FormTags
413
443
  end
414
444
  out << " </tbody>
415
445
  </table>
416
- <script<%= @_request.env['_brick_nonce'] %>>
446
+ <script#{@_request.env['_brick_nonce']}>
417
447
  var constellation = document.getElementById(\"#{table_name}\");
418
448
  var nextSib,
419
449
  _this;
@@ -480,7 +510,6 @@ module Brick::Rails::FormTags
480
510
  out << " </tbody>
481
511
  </table>
482
512
  "
483
-
484
513
  out.html_safe
485
514
  end # brick_bezier
486
515
 
@@ -537,7 +566,7 @@ module Brick::Rails::FormTags
537
566
  </div>
538
567
  </div>
539
568
  "
540
- out
569
+ out.html_safe
541
570
  end # brick_header
542
571
 
543
572
  # All the standard CSS with teal colouration for use with Brick
@@ -547,6 +576,8 @@ module Brick::Rails::FormTags
547
576
 
548
577
  # -----------------------------------------------------------------------------------------------
549
578
  def set_grid_javascript(klass, pk, show_new_button = nil, row_count = nil, total_row_count = nil)
579
+ camel_name = klass.name.split('::').last
580
+ camel_name[0].downcase!
550
581
  table_name = klass.table_name.split('.').last
551
582
 
552
583
  # Javascript for brick_grid and brick_constellation
@@ -556,13 +587,13 @@ module Brick::Rails::FormTags
556
587
  // Plunk the row count in now that we know it
557
588
  var rowCount = document.getElementById(\"rowCount\");
558
589
  if (rowCount) rowCount.innerHTML = \"#{pluralize(row_count, "row")}#{total_row_count} &nbsp;\";
559
- var #{table_name}HtColumns;
590
+ var #{camel_name}HtColumns;
560
591
  " unless row_count.nil?
561
592
 
562
593
  grid_scripts << "
563
594
  // Snag first TR for sticky header
564
595
  var grid = document.getElementById(\"#{table_name}\");
565
- #{table_name}HtColumns = grid && [grid.getElementsByTagName(\"TR\")[0]];
596
+ #{camel_name}HtColumns = grid && [grid.getElementsByTagName(\"TR\")[0]];
566
597
  var headerTop = document.getElementById(\"headerTop\");
567
598
  var headerCols;
568
599
  if (grid) {
@@ -615,8 +646,8 @@ function setHeaderSizes() {
615
646
 
616
647
  // Set up proper sizings of sticky column header
617
648
  var node;
618
- for (var j = 0; j < #{table_name}HtColumns.length; ++j) {
619
- var row = #{table_name}HtColumns[j];
649
+ for (var j = 0; j < #{camel_name}HtColumns.length; ++j) {
650
+ var row = #{camel_name}HtColumns[j];
620
651
  var tr = isEmpty ? document.createElement(\"TR\") : headerTop.childNodes[j];
621
652
  tr.innerHTML = row.innerHTML.trim();
622
653
  var curLeft = 0.0;
data/lib/brick/rails.rb CHANGED
@@ -6,7 +6,7 @@ require 'brick/rails/engine'
6
6
  module ::Brick::Rails
7
7
  class << self
8
8
  # Low-level way to render read-only data for a field based on its data type.
9
- # Used by both brick_grid and brick_form_for (which gets down to this low-level
9
+ # Used by both brick_grid and brick_form_with (which gets down to this low-level
10
10
  # implementation from brick_field).
11
11
  def display_value(col_type, val, lat_lng = nil)
12
12
  is_mssql_geography = nil
@@ -122,7 +122,7 @@ module ::Brick::Rails
122
122
  "<div id=\"mermaidErd\">
123
123
  <div id=\"mermaidDiagram\" class=\"mermaid\">
124
124
  erDiagram
125
- <% shown_classes = {}
125
+ <%= shown_classes = {}
126
126
 
127
127
  def erd_sidelinks(shown_classes, klass)
128
128
  links = []
@@ -137,20 +137,20 @@ erDiagram
137
137
  links.join
138
138
  end
139
139
 
140
- @_brick_bt_descrip&.each do |bt|
140
+ @_brick_bt_descrip&.each_with_object(+'') do |bt, s|
141
141
  bt_class = bt[1].first.first
142
142
  callbacks[bt_name = bt_class.name.split('::').last] = bt_class
143
143
  # Used to have: &.inverse_of before &.macro
144
144
  is_has_one = ::#{model.name}.reflect_on_association(bt.first)&.macro == :has_one ||
145
145
  ::Brick.config.has_ones&.fetch('#{model.name}', nil)&.key?(bt.first.to_s)
146
- %> <%= \"#{model_short_name} #\{is_has_one ? '||--o|' : '}o--||'} #\{bt_name} : \\\"#\{
146
+ s << \" #{model_short_name} #\{is_has_one ? '||--o|' : '}o--||'} #\{bt_name} : \\\"#\{
147
147
  bt_underscored = bt[1].first.first.name.underscore.singularize
148
148
  bt.first unless bt.first.to_s == bt_underscored.split('/').last # Was: bt_underscored.tr('/', '_')
149
- }\\\"\".html_safe %>
150
- <%= erd_sidelinks(shown_classes, bt_class).html_safe %>
151
- <% end
149
+ }\\\"\n\"
150
+ s << erd_sidelinks(shown_classes, bt_class)
151
+ end&.html_safe %><%=
152
152
  last_hm = nil
153
- @_brick_hm_counts&.each do |hm|
153
+ @_brick_hm_counts&.each_with_object(+'') do |hm, s|
154
154
  # Skip showing self-referencing HM links since they would have already been drawn while evaluating the BT side
155
155
  next if (hm_class = hm.last&.klass) == ::#{model.name}
156
156
 
@@ -159,21 +159,21 @@ erDiagram
159
159
  through_name = (through_assoc = hm.last.source_reflection).active_record.name.split('::').last
160
160
  callbacks[through_name] = through_assoc.active_record
161
161
  if last_hm == through # Same HM, so no need to build it again, and for clarity just put in a blank line
162
- %><%= \"\n\"
163
- %><% else
164
- %> <%= \"#{model_short_name} ||--o{ #\{through_name}\".html_safe %> : \"\"
165
- <%= erd_sidelinks(shown_classes, through_assoc.active_record).html_safe %>
166
- <% last_hm = through
162
+ s << \"\\n\"
163
+ else
164
+ s << \" #{model_short_name} ||--o{ #\{through_name} : \\\"\\\"\\n\"
165
+ s << erd_sidelinks(shown_classes, through_assoc.active_record)
166
+ last_hm = through
167
167
  end
168
- %> <%= \"#\{through_name} }o--|| #\{hm_name}\".html_safe %> : \"\"
169
- <%= \"#{model_short_name} }o..o{ #\{hm_name} : \\\"#\{hm.first}\\\"\".html_safe %><%
168
+ s << \" #\{through_name} }o--|| #\{hm_name} : \\\"\\\"\\n\"
169
+ s << \"#{model_short_name} }o..o{ #\{hm_name} : \\\"#\{hm.first}\\\"\\n\"
170
170
  else # has_many
171
- %> <%= \"#{model_short_name} #\{hm.last.macro == :has_and_belongs_to_many ? '}o' : '||'}--o{ #\{hm_name} : \\\"#\{
171
+ s << \" #{model_short_name} #\{hm.last.macro == :has_and_belongs_to_many ? '}o' : '||'}--o{ #\{hm_name} : \\\"#\{
172
172
  hm.first.to_s unless (last_hm = hm.first.to_s).downcase == hm_class.name.underscore.pluralize.tr('/', '_')
173
- }\\\"\".html_safe %><%
174
- end %>
175
- <%= erd_sidelinks(shown_classes, hm_class).html_safe %>
176
- <% end
173
+ }\\\"\\n\"
174
+ end
175
+ s << erd_sidelinks(shown_classes, hm_class)
176
+ end&.html_safe %><%
177
177
  def dt_lookup(dt)
178
178
  { 'integer' => 'int', 'character varying' => 'varchar', 'double precision' => 'float',
179
179
  'timestamp without time zone' => 'timestamp',
@@ -186,26 +186,26 @@ erDiagram
186
186
  pkeys = cb_relation[:pkey]&.first&.last
187
187
  fkeys = cb_relation[:fks]&.values&.each_with_object([]) { |fk, s| s << fk[:fk] if fk.fetch(:is_bt, nil) }
188
188
  cols = cb_relation[:cols]
189
- %> <%= cb_k %> {<%
190
- pkeys&.each do |pk| %>
191
- <%= \"#\{dt_lookup(cols[pk].first)} #\{pk} \\\"PK#\{' fk' if fkeys&.include?(pk)}\\\"\".html_safe %><%
192
- end %><%
193
- fkeys&.each do |fk|
189
+ %> <%= cb_k %> {<%=
190
+ pkeys&.each_with_object(+'') do |pk, s|
191
+ s << \"#\{dt_lookup(cols[pk].first)} #\{pk} \\\"PK#\{' fk' if fkeys&.include?(pk)}\\\"\"
192
+ end&.html_safe %><%=
193
+ fkeys&.each_with_object(+'') do |fk, s|
194
194
  if fk.is_a?(Array)
195
- fk.each do |fk_part| %>
196
- <%= \"#\{dt_lookup(cols[fk_part].first)} #\{fk_part} \\\"&nbsp;&nbsp;&nbsp;&nbsp;fk\\\"\".html_safe unless pkeys&.include?(fk_part) %><%
195
+ fk.each do |fk_part|
196
+ s << \"#\{dt_lookup(cols[fk_part].first)} #\{fk_part} \\\"&nbsp;&nbsp;&nbsp;&nbsp;fk\\\"\" unless pkeys&.include?(fk_part)
197
197
  end
198
198
  else # %%% Does not yet accommodate polymorphic BTs
199
- %>
200
- <%= \"#\{dt_lookup(cols[fk]&.first)} #\{fk} \\\"&nbsp;&nbsp;&nbsp;&nbsp;fk\\\"\".html_safe unless pkeys&.include?(fk) %><%
199
+ s << \"#\{dt_lookup(cols[fk]&.first)} #\{fk} \\\"&nbsp;&nbsp;&nbsp;&nbsp;fk\\\"\" unless pkeys&.include?(fk)
201
200
  end
202
- end %><%
201
+ end&.html_safe %><%=
203
202
  if (erd_sc = Brick.config.erd_show_columns) == true || erd_sc&.include?(cb_class.name)
204
- cols&.each do |col|
205
- next if pkeys.include?(col.first) || fkeys.include?(col.first) %>
206
- <%= \"#\{dt_lookup(col[1]&.first&.to_s)} #\{col.first}\".html_safe %><%
203
+ cols&.each_with_object(+'') do |col, s|
204
+ next if pkeys.include?(col.first) || fkeys.include?(col.first)
205
+
206
+ s << \"#\{dt_lookup(col[1]&.first&.to_s)} #\{col.first}\"
207
207
  end
208
- end %>
208
+ end&.html_safe %>
209
209
  }
210
210
  <% end
211
211
  # callback < %= cb_k % > erdClick
@@ -100,7 +100,7 @@ module Brick
100
100
  is_postgres = nil
101
101
  is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
102
102
  case ActiveRecord::Base.connection.adapter_name
103
- when 'PostgreSQL', 'SQLServer'
103
+ when 'PostgreSQL', 'PostGIS', 'SQLServer'
104
104
  is_postgres = !is_mssql
105
105
  db_schemas = if is_postgres
106
106
  ActiveRecord::Base.execute_sql('SELECT nspname AS table_schema, MAX(oid) AS dt FROM pg_namespace GROUP BY 1 ORDER BY 1;')
@@ -143,7 +143,7 @@ module Brick
143
143
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
144
144
  else
145
145
  puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to schema(s) called #{possible_schemas.map { |s| "\"#{s}\"" }.join(', ')}. No mentioned schema exists. ***"
146
- if ::Brick.db_schemas.key?(::Brick.apartment_default_tenant)
146
+ if Object.const_defined?('Apartment') && ::Brick.db_schemas.key?(::Brick.apartment_default_tenant)
147
147
  ::Brick.default_schema = schema = ::Brick.apartment_default_tenant
148
148
  end
149
149
  end
@@ -177,8 +177,8 @@ module Brick
177
177
  measures = []
178
178
  ::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
179
179
  case ActiveRecord::Base.connection.adapter_name
180
- when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
181
- # schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
180
+ when 'PostgreSQL', 'PostGIS', 'SQLite' # These bring back a hash for each row because the query uses column aliases
181
+ # schema ||= 'public' if ['PostgreSQL', 'PostGIS'].include?(ActiveRecord::Base.connection.adapter_name)
182
182
  retrieve_schema_and_tables(sql, is_postgres, is_mssql, schema).each do |r|
183
183
  # If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
184
184
  # is the default schema, usually 'public'.
@@ -214,6 +214,7 @@ module Brick
214
214
  cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name), r['is_nullable'] == 'NO']
215
215
  # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
216
216
  relation[:col_descrips][col_name] = r['column_description'] if r['column_description']
217
+ relation[:col_order] << col_name
217
218
  end
218
219
  else # MySQL2, OracleEnhanced, and MSSQL act a little differently, bringing back an array for each row
219
220
  schema_and_tables = case ActiveRecord::Base.connection.adapter_name
@@ -269,11 +270,12 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
269
270
  # 'data_type', 'max_length', measure, 'is_nullable'
270
271
  cols[col_name] = [r[4], r[5], measures&.include?(col_name), r[8] == 'NO']
271
272
  # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
273
+ relation[:col_order] << col_name
272
274
  end
273
275
  end
274
276
 
275
277
  # PostGIS adds three views which would confuse Rails if models were to be built for them.
276
- if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
278
+ if ['PostgreSQL', 'PostGIS'].include?(ActiveRecord::Base.connection.adapter_name)
277
279
  if relations.key?('geography_columns') && relations.key?('geometry_columns') && relations.key?('spatial_ref_sys')
278
280
  (::Brick.config.exclude_tables ||= []) << 'geography_columns'
279
281
  ::Brick.config.exclude_tables << 'geometry_columns'
@@ -282,7 +284,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
282
284
  end
283
285
 
284
286
  # # Add unique OIDs
285
- # if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
287
+ # if ['PostgreSQL', 'PostGIS'].include?(ActiveRecord::Base.connection.adapter_name)
286
288
  # ActiveRecord::Base.execute_sql(
287
289
  # "SELECT c.oid, n.nspname, c.relname
288
290
  # FROM pg_catalog.pg_namespace AS n
@@ -307,7 +309,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
307
309
  kcus = nil
308
310
  entry_type = nil
309
311
  case ActiveRecord::Base.connection.adapter_name
310
- when 'PostgreSQL', 'Mysql2', 'Trilogy', 'SQLServer'
312
+ when 'PostgreSQL', 'PostGIS', 'Mysql2', 'Trilogy', 'SQLServer'
311
313
  # Part 1 -- all KCUs
312
314
  sql = "SELECT CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, ORDINAL_POSITION,
313
315
  TABLE_NAME, COLUMN_NAME
@@ -367,7 +369,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
367
369
  fk_references = ActiveRecord::Base.execute_sql(sql)
368
370
  end
369
371
  ::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
370
- # ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
372
+ # ::Brick.default_schema ||= schema ||= 'public' if ['PostgreSQL', 'PostGIS'].include?(ActiveRecord::Base.connection.adapter_name)
371
373
  ::Brick.default_schema ||= 'public' if is_postgres
372
374
  fk_references&.each do |fk|
373
375
  fk = fk.values unless fk.is_a?(Array)
@@ -443,14 +445,18 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
443
445
  loop do
444
446
  klass = Object
445
447
  proposed_name_parts.each do |part|
446
- if klass.const_defined?(part) && klass.name != part
447
- begin
448
+ begin
449
+ if klass.const_defined?(part) && klass.name != part
448
450
  klass = klass.const_get(part)
449
- rescue NoMethodError => e
451
+ else
450
452
  klass = nil
451
453
  break
452
454
  end
453
- else
455
+ rescue NoMethodError => e
456
+ klass = nil
457
+ break
458
+ rescue NameError => e
459
+ puts "WARNING: Unable to automatically generate a class for the table named #{part}.\n In the brick.rb initializer file you can add the line \"::Brick.table_name_prefixes = { '#{part}' => '::YourModelName' }\" in order to specify a usable class name for this object."
454
460
  klass = nil
455
461
  break
456
462
  end
data/lib/brick/util.rb CHANGED
@@ -49,7 +49,7 @@ module Brick
49
49
  # then required in place of the original.
50
50
 
51
51
  Kernel.module_exec do
52
- alias_method :orig_require, :require
52
+ alias_method :_orig_require, :require
53
53
  # To be most faithful to Ruby's normal behaviour, this should look like a public singleton
54
54
  define_method(:require) do |name|
55
55
  # %%% Can get a message such as "ActionDispatch::Routing is not missing constant RouteSet! (NameError)"
@@ -62,14 +62,14 @@ module Brick
62
62
  else
63
63
  is_replaced = false
64
64
  if (replacement_path = ::Brick::Util._write_patched(folder_matcher, name, extension, ::Brick::Util._custom_require_dir, patched_filename, replacements))
65
- is_replaced = Kernel.send(:orig_require, replacement_path)
65
+ is_replaced = _orig_require(replacement_path)
66
66
  elsif replacement_path.nil?
67
67
  puts "Couldn't find #{name} to require it!"
68
68
  end
69
69
  is_replaced
70
70
  end
71
71
  else
72
- Kernel.send(:orig_require, name)
72
+ _orig_require(name)
73
73
  end
74
74
  end
75
75
  end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 246
8
+ TINY = 248
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
@@ -127,7 +127,7 @@ module Brick
127
127
  def set_db_schema(params = nil)
128
128
  # If Apartment::Tenant.current is not still the default (usually 'public') then an elevator has brought us into
129
129
  # a different tenant. If so then don't allow schema navigation.
130
- if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' && apartment_multitenant
130
+ if ['PostgreSQL', 'PostGIS'].include?(ActiveRecord::Base.connection.adapter_name) && apartment_multitenant
131
131
  current_schema = (ActiveRecord::Base.execute_sql('SELECT current_schemas(true)')
132
132
  .first['current_schemas'][1..-2]
133
133
  .split(',') - ['pg_catalog', 'pg_toast', 'heroku_ext']).first
@@ -162,7 +162,7 @@ module Brick
162
162
  (existing = @relations.find { |_k, v| v.fetch(:db_name, nil) == db_name })
163
163
  existing.last
164
164
  else
165
- Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } }.tap do |h|
165
+ Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = (k == :col_order ? [] : {}) } }.tap do |h|
166
166
  h[:db_name] = db_name if db_name
167
167
  end
168
168
  end
@@ -1547,8 +1547,8 @@ module ActiveRecord
1547
1547
 
1548
1548
  elsif private_instance_methods.include?(:select_association_list) # AR >= 6.1
1549
1549
  alias _brick_select_association_list select_association_list
1550
- def select_association_list(associations, stashed_joins = nil)
1551
- result = _brick_select_association_list(associations, stashed_joins)
1550
+ def select_association_list(associations, stashed_joins = nil, &block)
1551
+ result = _brick_select_association_list(associations, stashed_joins, &block)
1552
1552
  result.instance_variable_set(:@relation, self)
1553
1553
  result
1554
1554
  end
@@ -1686,11 +1686,11 @@ module ActiveRecord
1686
1686
  if (relation = child.instance_variable_get(:@assocs)&.instance_variable_get(:@relation))
1687
1687
  link_path = child.instance_variable_get(:@link_path)
1688
1688
  relation.brick_links(false)[link_path] = if child.table.is_a?(Arel::Nodes::TableAlias)
1689
- child.table.right
1690
- else
1691
- # Was: result.first&.left&.table_alias || child.table_name
1692
- child.table.table_alias || child.table_name
1693
- end
1689
+ child.table.right
1690
+ else
1691
+ # Was: result.first&.left&.table_alias || child.table_name
1692
+ child.table.table_alias || child.table_name
1693
+ end
1694
1694
  end
1695
1695
  result
1696
1696
  end
@@ -5,7 +5,7 @@ module Brick
5
5
  class << self
6
6
  include FancyGets
7
7
 
8
- def pick_tables(usage = :migrations)
8
+ def pick_tables(is_airtable_id, usage = :migrations)
9
9
  puts "In order to reference Airtable data you will need a Personal Access Token (PAT) which can be generated by referencing this URL:
10
10
  https://airtable.com/create/tokens
11
11
  You need only #{usage == :migrations ? 'this scope:' : "these three scopes:
@@ -19,6 +19,10 @@ Please provide your Airtable PAT:"
19
19
  require 'net/http'
20
20
  # Generate a list of bases that can be chosen
21
21
  bases = https_get('https://api.airtable.com/v0/meta/bases', pat)
22
+ if (error = bases.fetch('error', nil))
23
+ puts error.fetch('message', nil) || 'Error'
24
+ return
25
+ end
22
26
  base = gets_list(bases.fetch('bases', nil)&.map { |z| AirtableTable.new(z['id'], z['name']) })
23
27
  puts
24
28
  # Generate a list of tables that can be chosen
@@ -35,10 +39,11 @@ Please provide your Airtable PAT:"
35
39
  # Build out a '::Brick.relations' hash that represents this Airtable schema
36
40
  fks = []
37
41
  associatives = {}
38
- relations = chosen.each_with_object({}) do |table, s|
42
+ relations = chosen.each_with_object({base_name: base.name}) do |table, s|
39
43
  tbl_name = sane_table_name(table.name)
40
44
  # Build out columns and foreign keys
41
45
  cols = {}
46
+ col_order = []
42
47
  table.fields.each do |col|
43
48
  col_name = sane_name(col['name'])
44
49
  # This is like a has_many or has_many through
@@ -48,7 +53,8 @@ Please provide your Airtable PAT:"
48
53
  chosen.find { |t| t.id == col['options']['linkedTableId'] }&.name
49
54
  ))
50
55
  if col['options']['prefersSingleRecordLink'] # 1:M
51
- fks << [frn_tbl, "#{col_name}_id", tbl_name, col_name]
56
+ fks << [frn_tbl, (fk_col_name = "#{col_name}_id"), tbl_name, col_name]
57
+ col_order << fk_col_name
52
58
  else # N:M
53
59
  # Queue up to build associative table with two foreign keys
54
60
  camelized = (assoc_name = "#{tbl_name}_#{col_name}_#{frn_tbl}").camelize
@@ -64,17 +70,17 @@ Please provide your Airtable PAT:"
64
70
  else
65
71
  # puts col['type']
66
72
  dt = case col['type']
67
- when 'singleLineText', 'url', 'email', 'singleSelect'
73
+ when 'singleLineText', 'url', 'email', 'singleSelect', 'phoneNumber'
68
74
  'string'
69
75
  when 'multilineText'
70
76
  'text'
71
77
  when 'number', 'currency'
72
- 'decimal'
78
+ col['options']['precision'] == 0 ? 'integer' : 'decimal'
73
79
  when 'checkbox'
74
80
  'boolean'
75
81
  when 'date'
76
82
  'date'
77
- when 'multipleSelects'
83
+ when 'multipleSelects', 'singleCollaborator'
78
84
  # Sqlite3 can do json
79
85
  'json'
80
86
  when 'formula', 'count', 'rollup', 'multipleAttachments'
@@ -83,13 +89,21 @@ Please provide your Airtable PAT:"
83
89
  # binding.pry
84
90
  end
85
91
  cols[col_name] = [dt, nil, true, false] # true is the col[:nillable]
92
+ col_order << col_name
86
93
  end
87
94
  end
88
95
  # Put it all into a relation entry, named the same as the table
89
- pkey = table.fields.find { |f| f['id'] == table.primary_key }['name']
96
+ pkey = if is_airtable_id
97
+ cols['airtable_id'] = ['string', nil, true, false]
98
+ 'airtable_id'
99
+ else
100
+ table.fields.find { |f| f['id'] == table.primary_key }['name']
101
+ end
102
+
90
103
  s[tbl_name] = {
91
104
  pkey: { "#{tbl_name}_pkey" => [sane_name(pkey)] },
92
105
  cols: cols,
106
+ col_order: col_order,
93
107
  fks: {},
94
108
  airtable_table: table
95
109
  }
@@ -15,9 +15,10 @@ module Brick
15
15
  def airtable_migrations
16
16
  mig_path, is_insert_versions, is_delete_versions = ::Brick::MigrationsBuilder.check_folder
17
17
  return unless mig_path &&
18
- (relations = ::Brick::AirtableApiCaller.pick_tables)
18
+ (relations = ::Brick::AirtableApiCaller.pick_tables(!ENV['OMIT_AIRTABLE_ID']))
19
19
 
20
- ::Brick::MigrationsBuilder.generate_migrations(relations.keys, mig_path, is_insert_versions, is_delete_versions, relations,
20
+ chosen = relations.keys.reject { |k| k.is_a?(Symbol) }
21
+ ::Brick::MigrationsBuilder.generate_migrations(chosen, mig_path, is_insert_versions, is_delete_versions, relations,
21
22
  do_fks_last: 'Separate', do_schema_migrations: false)
22
23
  end
23
24
  end
@@ -11,7 +11,7 @@ module Brick
11
11
  desc 'Auto-generates a seeds file from existing data in an Airtable "base".'
12
12
 
13
13
  def airtable_seeds
14
- return unless (relations = ::Brick::AirtableApiCaller.pick_tables(:seeds))
14
+ return unless (relations = ::Brick::AirtableApiCaller.pick_tables(!ENV['OMIT_AIRTABLE_ID'], :seeds))
15
15
 
16
16
  ::Brick::SeedsBuilder.generate_seeds(relations)
17
17
  end
@@ -43,7 +43,7 @@ module Brick
43
43
  relation.last[:cols].each do |col, type|
44
44
  col_down = col.downcase
45
45
 
46
- if (is_possible_poly = ['character varying', 'text'].include?(type.first))
46
+ if (is_possible_poly = ['character varying', 'varchar', 'text', 'VARCHAR2'].include?(type.first))
47
47
  if col_down.end_with?('_type')
48
48
  poly_type_cut_length = -6
49
49
  col_down = col_down[0..-6]