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.
- checksums.yaml +4 -4
- data/lib/brick/compatibility.rb +1 -1
- data/lib/brick/extensions.rb +69 -43
- data/lib/brick/rails/engine.rb +70 -72
- data/lib/brick/rails/form_builder.rb +18 -16
- data/lib/brick/rails/form_tags.rb +55 -24
- data/lib/brick/rails.rb +34 -34
- data/lib/brick/reflect_tables.rb +18 -12
- data/lib/brick/util.rb +3 -3
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +9 -9
- data/lib/generators/brick/airtable_api_caller.rb +21 -7
- data/lib/generators/brick/airtable_migrations_generator.rb +3 -2
- data/lib/generators/brick/airtable_seeds_generator.rb +1 -1
- data/lib/generators/brick/install_generator.rb +1 -1
- data/lib/generators/brick/migrations_builder.rb +39 -15
- data/lib/generators/brick/seeds_builder.rb +77 -35
- metadata +1 -1
|
@@ -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) && [:
|
|
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
|
|
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 = +'
|
|
260
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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}\""
|
|
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
|
|
364
|
+
out << " (PK #{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
|
|
381
|
+
if has_updatable_fields
|
|
352
382
|
out << "<tr><td colspan=\"2\">#{f.submit({ class: 'update' })}</td></tr>"
|
|
353
|
-
|
|
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)
|
|
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 #
|
|
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
|
|
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} \";
|
|
559
|
-
var #{
|
|
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
|
-
#{
|
|
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 < #{
|
|
619
|
-
var row = #{
|
|
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
|
|
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
|
-
|
|
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&.
|
|
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
|
-
|
|
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
|
-
}\\\"\"
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
}\\\"\n\"
|
|
150
|
+
s << erd_sidelinks(shown_classes, bt_class)
|
|
151
|
+
end&.html_safe %><%=
|
|
152
152
|
last_hm = nil
|
|
153
|
-
@_brick_hm_counts&.
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
-
}\\\"\"
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
|
|
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&.
|
|
191
|
-
|
|
192
|
-
end
|
|
193
|
-
fkeys&.
|
|
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
|
-
|
|
195
|
+
fk.each do |fk_part|
|
|
196
|
+
s << \"#\{dt_lookup(cols[fk_part].first)} #\{fk_part} \\\" 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} \\\" fk\\\"\".html_safe unless pkeys&.include?(fk) %><%
|
|
199
|
+
s << \"#\{dt_lookup(cols[fk]&.first)} #\{fk} \\\" 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&.
|
|
205
|
-
next if pkeys.include?(col.first) || fkeys.include?(col.first)
|
|
206
|
-
|
|
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
|
data/lib/brick/reflect_tables.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
447
|
-
|
|
448
|
+
begin
|
|
449
|
+
if klass.const_defined?(part) && klass.name != part
|
|
448
450
|
klass = klass.const_get(part)
|
|
449
|
-
|
|
451
|
+
else
|
|
450
452
|
klass = nil
|
|
451
453
|
break
|
|
452
454
|
end
|
|
453
|
-
|
|
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 :
|
|
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 =
|
|
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
|
-
|
|
72
|
+
_orig_require(name)
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
end
|
data/lib/brick/version_number.rb
CHANGED
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
|
|
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
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
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 =
|
|
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
|
-
|
|
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]
|