brick 1.0.245 → 1.0.247
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/extensions.rb +128 -97
- data/lib/brick/rails/engine.rb +43 -38
- data/lib/brick/rails/form_builder.rb +18 -16
- data/lib/brick/rails/form_tags.rb +78 -36
- data/lib/brick/rails.rb +2 -2
- data/lib/brick/reflect_tables.rb +12 -7
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +1 -1
- 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 +25 -12
- data/lib/generators/brick/seeds_builder.rb +75 -33
- metadata +3 -6
|
@@ -9,18 +9,19 @@ module Brick::Rails::FormBuilder
|
|
|
9
9
|
def brick_field(method, html_options = {}, val = nil, col = nil,
|
|
10
10
|
bt = nil, bt_class = nil, bt_name = nil, bt_pair = nil)
|
|
11
11
|
model = self.object.class
|
|
12
|
-
col ||= model.columns_hash[method]
|
|
12
|
+
col ||= model.columns_hash[method.to_s]
|
|
13
13
|
out = +'<table><tr><td>'
|
|
14
14
|
html_options[:class] = 'dimmed' unless val
|
|
15
15
|
is_revert = true
|
|
16
16
|
template = instance_variable_get(:@template)
|
|
17
|
+
method_sym = method.to_sym
|
|
17
18
|
if bt
|
|
18
19
|
bt_class ||= bt[1].first.first
|
|
19
20
|
bt_name ||= bt[1].map { |x| x.first.name }.join('/')
|
|
20
21
|
bt_pair ||= bt[1].first
|
|
21
22
|
|
|
22
23
|
html_options[:prompt] = "Select #{bt_name}"
|
|
23
|
-
out << self.select(
|
|
24
|
+
out << self.select(method_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options)
|
|
24
25
|
bt_obj = nil
|
|
25
26
|
begin
|
|
26
27
|
bt_obj = bt_class&.find_by(bt_pair[1] => val)
|
|
@@ -38,8 +39,8 @@ module Brick::Rails::FormBuilder
|
|
|
38
39
|
"<span class=\"orphan\">Orphaned ID: #{val}</span>".html_safe
|
|
39
40
|
end
|
|
40
41
|
out << bt_link if bt_link
|
|
41
|
-
elsif model._brick_monetized_attributes&.include?(method)
|
|
42
|
-
out << self.text_field(
|
|
42
|
+
elsif model._brick_monetized_attributes&.include?(method.to_s)
|
|
43
|
+
out << self.text_field(method_sym, html_options.merge({ value: Money.new(val.to_i).format }))
|
|
43
44
|
else
|
|
44
45
|
col_type = if model.json_column?(col) || val.is_a?(Array)
|
|
45
46
|
:json
|
|
@@ -61,40 +62,40 @@ module Brick::Rails::FormBuilder
|
|
|
61
62
|
out << "<img src=\"#{url}\" title=\"#{val}\">"
|
|
62
63
|
elsif model.respond_to?(:enumerized_attributes) && (opts = (attr = model.enumerized_attributes[method])&.options).present?
|
|
63
64
|
enum_html_options = attr.kind_of?(Enumerize::Multiple) ? html_options.merge({ multiple: true, size: opts.length + 1 }) : html_options
|
|
64
|
-
out << self.select(
|
|
65
|
+
out << self.select(method_sym, [["(No #{method} chosen)", '^^^brick_NULL^^^']] + opts, { value: val || '^^^brick_NULL^^^' }, enum_html_options)
|
|
65
66
|
else
|
|
66
67
|
spit_out_text_field = true
|
|
67
68
|
end
|
|
68
69
|
elsif col_type == :enum
|
|
69
70
|
if col.respond_to?(:limit) && col.limit.present?
|
|
70
|
-
out << self.select(
|
|
71
|
+
out << self.select(method_sym, [["(No #{method} chosen)", '^^^brick_NULL^^^']] + col.limit, { value: val.to_s || '^^^brick_NULL^^^' }, html_options)
|
|
71
72
|
else
|
|
72
73
|
spit_out_text_field = true
|
|
73
74
|
end
|
|
74
75
|
else
|
|
75
76
|
template.instance_variable_set(:@_text_fields_present, true)
|
|
76
|
-
out << self.hidden_field(
|
|
77
|
+
out << self.hidden_field(method_sym, html_options)
|
|
77
78
|
out << "<trix-editor input=\"#{self.field_id(method)}\"></trix-editor>"
|
|
78
79
|
end
|
|
79
80
|
if spit_out_text_field
|
|
80
81
|
# %%% Need to update the max-width with javascript when page width is adjusted?
|
|
81
82
|
html_options.merge!(style: 'min-width: 154px;field-sizing: content;') # max-width: auto;
|
|
82
|
-
out << self.text_field(
|
|
83
|
+
out << self.text_field(method_sym, html_options)
|
|
83
84
|
end
|
|
84
85
|
when :boolean
|
|
85
|
-
out << self.check_box(
|
|
86
|
+
out << self.check_box(method_sym)
|
|
86
87
|
when :integer, :decimal, :float
|
|
87
|
-
if model.respond_to?(:attribute_types) && (enum_type = model.attribute_types[method]).is_a?(ActiveRecord::Enum::EnumType)
|
|
88
|
+
if model.respond_to?(:attribute_types) && (enum_type = model.attribute_types[method.to_s]).is_a?(ActiveRecord::Enum::EnumType)
|
|
88
89
|
opts = enum_type.send(:mapping)&.each_with_object([]) { |v, s| s << [v.first, v.first] } || []
|
|
89
|
-
out << self.select(
|
|
90
|
+
out << self.select(method_sym, [["(No #{method} chosen)", '^^^brick_NULL^^^']] + opts, { value: val || '^^^brick_NULL^^^' }, options)
|
|
90
91
|
else
|
|
91
92
|
digit_pattern = col_type == :integer ? '(?:-|)\d*' : '(?:-|)\d*(?:\.\d*|)'
|
|
92
|
-
# Used to do this for float / decimal: self.number_field
|
|
93
|
-
out << self.text_field(
|
|
93
|
+
# Used to do this for float / decimal: self.number_field method_sym
|
|
94
|
+
out << self.text_field(method_sym, { pattern: digit_pattern, class: 'check-validity' })
|
|
94
95
|
end
|
|
95
96
|
when *DT_PICKERS.keys
|
|
96
97
|
template.instance_variable_set(:@_date_fields_present, true)
|
|
97
|
-
out << self.text_field(
|
|
98
|
+
out << self.text_field(method_sym, { class: DT_PICKERS[col_type] })
|
|
98
99
|
when :uuid
|
|
99
100
|
is_revert = false
|
|
100
101
|
# Postgres naturally uses the +uuid_generate_v4()+ function from the uuid-ossp extension
|
|
@@ -146,7 +147,7 @@ module Brick::Rails::FormBuilder
|
|
|
146
147
|
end
|
|
147
148
|
out << self.hidden_field("_brick_attached_#{method}", value: existing.join(',')) unless existing.blank?
|
|
148
149
|
# Render a "Choose File(s)" input element
|
|
149
|
-
args = [
|
|
150
|
+
args = [method_sym]
|
|
150
151
|
args << { multiple: true } if col&.type == :files
|
|
151
152
|
out << self.file_field(*args)
|
|
152
153
|
when :primary_key
|
|
@@ -163,7 +164,7 @@ module Brick::Rails::FormBuilder
|
|
|
163
164
|
end
|
|
164
165
|
# Because there are so danged many quotes in JSON, escape them specially by converting to backticks.
|
|
165
166
|
# (and previous to this, escape backticks with our own goofy code of ^^br_btick__ )
|
|
166
|
-
out << (json_field = self.hidden_field(
|
|
167
|
+
out << (json_field = self.hidden_field(method_sym, { class: 'jsonpicker', value: val_str&.gsub('`', '^^br_btick__')&.tr('\"', '`')&.html_safe }))
|
|
167
168
|
out << "<div id=\"_br_json_#{self.field_id(method)}\"></div>"
|
|
168
169
|
else
|
|
169
170
|
is_revert = false
|
|
@@ -177,6 +178,7 @@ module Brick::Rails::FormBuilder
|
|
|
177
178
|
end
|
|
178
179
|
out << "</td></tr></table>
|
|
179
180
|
"
|
|
181
|
+
@_brick&.fetch(:fields_rendered, nil)&.<< method.to_s # Track that we've rendered this one
|
|
180
182
|
out.html_safe
|
|
181
183
|
end # brick_field
|
|
182
184
|
end
|
|
@@ -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
|
|
@@ -112,7 +112,7 @@ module Brick::Rails::FormTags
|
|
|
112
112
|
next unless klass.table_exists?
|
|
113
113
|
|
|
114
114
|
rid = pk.map { |pk_part| obj.send(pk_part.to_sym) }
|
|
115
|
-
out << "<tr x-id=\"#{rid.join('/')}\">\n"
|
|
115
|
+
out << "<tr x-id=\"#{slashed_rid = rid.join('/')}\">\n"
|
|
116
116
|
out << "<td class=\"col-sticky alternating-gray\">#{link_to('⇛', send("#{klass._brick_index(:singular)}_path".to_sym, rid),
|
|
117
117
|
{ class: 'big-arrow' })}</td>\n" if pk.present?
|
|
118
118
|
ac = obj.instance_variable_get(:@association_cache) || obj.instance_variable_set(:@association_cache, {})
|
|
@@ -134,8 +134,14 @@ module Brick::Rails::FormTags
|
|
|
134
134
|
if bt[2] && obj.respond_to?(poly_id_col = "#{bt.first}_id") # Polymorphic?
|
|
135
135
|
if (poly_id = obj.send(poly_id_col))
|
|
136
136
|
bt_class = obj.send(klass.brick_foreign_type(bt.first))
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
root_class_name = (::Brick.existing_stis[bt_class] || bt_class)
|
|
138
|
+
begin
|
|
139
|
+
base_class_underscored = root_class_name.constantize.base_class._brick_index(:singular)
|
|
140
|
+
out << link_to("#{bt_class} ##{poly_id}", send("#{base_class_underscored}_path".to_sym, poly_id))
|
|
141
|
+
rescue NameError => ee
|
|
142
|
+
puts "WARNING: While searching for a polymorphically associated #{bt.first} for the #{klass.name} with ID #{slashed_rid}, was unable to locate a model called \"#{root_class_name}\". This model might have been deleted, thereby orphaning rows in the #{klass.table_name} table."
|
|
143
|
+
out << "<span class=\"orphan\"><< Orphaned \"#{root_class_name}\" with ID: #{slashed_rid} >></span>"
|
|
144
|
+
end
|
|
139
145
|
end
|
|
140
146
|
else # BT or HOT
|
|
141
147
|
bt_class = bt[1].first.first
|
|
@@ -151,11 +157,17 @@ module Brick::Rails::FormTags
|
|
|
151
157
|
end
|
|
152
158
|
br_descrip_args = [obj]
|
|
153
159
|
br_descrip_args += [bt_class._brick_deserialized_value(obj, descrips[0..-2]), bt_id_col] if descrips
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
out << if bt_id_col.blank? && bt[1].first[1] # Is this a BT which references a table with no primary key?
|
|
161
|
+
# Although we have no way to directly reference this record via a show action, we can at least create a link like this:
|
|
162
|
+
# http://localhost:3000/brick/local_authority_postcode?__value=PA2Y+5LQ
|
|
163
|
+
link_to(val, send("#{bt_class.base_class._brick_index(:singular)}_path".to_sym, '__the_id__')).gsub('__the_id__', "?__#{bt[1].first[1]}=#{CGI.escape(val)}")
|
|
164
|
+
else # Normal BT or HOT
|
|
165
|
+
bt_txt = bt_class.brick_descrip(*br_descrip_args)
|
|
166
|
+
bt_txt = ::Brick::Rails.display_binary(bt_txt).html_safe if bt_txt&.encoding&.name == 'ASCII-8BIT'
|
|
167
|
+
bt_txt ||= "<span class=\"orphan\"><< Orphaned ID: #{val} >></span>" if val
|
|
168
|
+
bt_id = bt_class._brick_deserialized_id(obj, bt_id_col)
|
|
169
|
+
(bt_id&.first ? link_to(bt_txt, send("#{bt_class.base_class._brick_index(:singular)}_path".to_sym, bt_id)) : bt_txt || '')
|
|
170
|
+
end
|
|
159
171
|
end
|
|
160
172
|
elsif (hms_col = hms_cols[col_name])
|
|
161
173
|
if hms_col.length == 1
|
|
@@ -165,15 +177,14 @@ module Brick::Rails::FormTags
|
|
|
165
177
|
if col[2] == 'HO'
|
|
166
178
|
descrips = bt_descrip[col_name.to_sym][hm_klass]
|
|
167
179
|
if (descrip_parts = hm_klass._brick_deserialized_value(obj, descrips[0..-2]))&.first
|
|
180
|
+
ho_id = hm_klass._brick_deserialized_id(obj, ho_id_col = descrips.last)
|
|
168
181
|
ho_txt = if hm_klass.name == 'ActiveStorage::Attachment'
|
|
169
182
|
begin
|
|
170
183
|
::Brick::Rails.display_binary(obj.send(col[3])&.blob&.download)&.html_safe
|
|
171
184
|
rescue
|
|
185
|
+
# Attachment is missing for some reason ... default to showing just a description.
|
|
172
186
|
end
|
|
173
|
-
|
|
174
|
-
hm_klass.brick_descrip(obj, descrip_parts, ho_id_col = descrips.last)
|
|
175
|
-
end
|
|
176
|
-
ho_id = hm_klass._brick_deserialized_id(obj, ho_id_col)
|
|
187
|
+
end || hm_klass.brick_descrip(obj, descrip_parts, ho_id_col)
|
|
177
188
|
out << link_to(ho_txt, send("#{hm_klass.base_class._brick_index(:singular)}_path".to_sym, ho_id))
|
|
178
189
|
end
|
|
179
190
|
elsif obj.respond_to?(ct_col = hms_col[1].to_sym) && (ct = obj.send(ct_col)&.to_i)&.positive?
|
|
@@ -241,25 +252,52 @@ module Brick::Rails::FormTags
|
|
|
241
252
|
|
|
242
253
|
# -----------------------------
|
|
243
254
|
# Our mega show/new/update form
|
|
244
|
-
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 || []
|
|
245
265
|
pk = [pk] unless pk.is_a?(Array)
|
|
246
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
|
|
247
280
|
form_for(obj.becomes(model.base_class), options) do |f|
|
|
248
|
-
out = +'
|
|
249
|
-
|
|
281
|
+
out = +''
|
|
282
|
+
out << '<table class="shadow">'
|
|
283
|
+
f.instance_variable_set(:@_brick, brick_stuff)
|
|
284
|
+
has_fields = has_updatable_fields = nil
|
|
250
285
|
# If it's a new record, set any default polymorphic types
|
|
251
|
-
bts
|
|
286
|
+
bts.each do |_k, v|
|
|
252
287
|
if v[2]
|
|
253
288
|
obj.send("#{model.brick_foreign_type(v.first)}=", v[1].first&.first&.name)
|
|
254
289
|
end
|
|
255
290
|
end if obj.new_record?
|
|
256
291
|
hoa, hma, rtans = model._activestorage_actiontext_fields
|
|
257
|
-
|
|
292
|
+
|
|
293
|
+
block.call(f) if block_given?
|
|
294
|
+
|
|
295
|
+
((model.column_names + hoa + hma + rtans.keys) - fields_rendered).each do |k|
|
|
258
296
|
pk_pos = (pk.index(k)&.+ 1)
|
|
259
|
-
|
|
297
|
+
col = model.columns_hash[k]
|
|
298
|
+
next if (pk_pos && pk.length == 1 && !bts.key?(k) && [:string, :text].exclude?(col.type)) ||
|
|
260
299
|
::Brick.config.metadata_columns.include?(k)
|
|
261
300
|
|
|
262
|
-
col = model.columns_hash[k]
|
|
263
301
|
if !col
|
|
264
302
|
kwargs = if hoa.include?(k) # has_one_attached
|
|
265
303
|
{ sql_type: 'binary', type: :file }
|
|
@@ -280,7 +318,7 @@ module Brick::Rails::FormTags
|
|
|
280
318
|
val = obj.attributes[k]
|
|
281
319
|
out << "
|
|
282
320
|
<tr>
|
|
283
|
-
<th class=\"show-field\"#{" title=\"#{col&.comment}\""
|
|
321
|
+
<th class=\"show-field\"#{" title=\"#{col&.comment}\"" if col&.respond_to?(:comment) && !col&.comment.blank?}>"
|
|
284
322
|
has_fields = true
|
|
285
323
|
if (bt = bts[k])
|
|
286
324
|
# Add a final member in this array with descriptive options to be used in <select> drop-downs
|
|
@@ -323,35 +361,38 @@ module Brick::Rails::FormTags
|
|
|
323
361
|
else
|
|
324
362
|
out << model.human_attribute_name(k, { default: k })
|
|
325
363
|
end
|
|
326
|
-
out << " (PK
|
|
364
|
+
out << " (PK #{pk_pos})" if pk_pos
|
|
327
365
|
out << "
|
|
328
366
|
</th>
|
|
329
367
|
<td>
|
|
330
368
|
"
|
|
331
|
-
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))
|
|
332
372
|
out << val.to_s
|
|
333
373
|
else
|
|
374
|
+
has_updatable_fields = true
|
|
334
375
|
out << f.brick_field(k, html_options = {}, val, col, bt, bt_class, bt_name, bt_pair)
|
|
335
376
|
end
|
|
336
377
|
out << "
|
|
337
378
|
</td>
|
|
338
379
|
</tr>"
|
|
339
380
|
end
|
|
340
|
-
if
|
|
381
|
+
if has_updatable_fields
|
|
341
382
|
out << "<tr><td colspan=\"2\">#{f.submit({ class: 'update' })}</td></tr>"
|
|
342
|
-
|
|
383
|
+
elsif !has_fields
|
|
343
384
|
out << '<tr><td colspan="2">(No displayable fields)</td></tr>'
|
|
344
385
|
end
|
|
345
386
|
out << '</table>'
|
|
346
387
|
if model.name == 'ActiveStorage::Attachment'
|
|
347
388
|
begin
|
|
348
|
-
out << ::Brick::Rails.display_binary(obj&.blob&.download, 500_000)
|
|
389
|
+
out << ::Brick::Rails.display_binary(obj&.blob&.download, 500_000)
|
|
349
390
|
rescue
|
|
350
391
|
end
|
|
351
392
|
end
|
|
352
|
-
out.html_safe
|
|
353
|
-
end
|
|
354
|
-
end #
|
|
393
|
+
output_buffer << out.html_safe
|
|
394
|
+
end # form_for
|
|
395
|
+
end # brick_form_with
|
|
355
396
|
|
|
356
397
|
# ------------------------------------------
|
|
357
398
|
# Our cool N:M checkbox constellation editor
|
|
@@ -402,7 +443,7 @@ module Brick::Rails::FormTags
|
|
|
402
443
|
end
|
|
403
444
|
out << " </tbody>
|
|
404
445
|
</table>
|
|
405
|
-
<script
|
|
446
|
+
<script#{@_request.env['_brick_nonce']}>
|
|
406
447
|
var constellation = document.getElementById(\"#{table_name}\");
|
|
407
448
|
var nextSib,
|
|
408
449
|
_this;
|
|
@@ -469,7 +510,6 @@ module Brick::Rails::FormTags
|
|
|
469
510
|
out << " </tbody>
|
|
470
511
|
</table>
|
|
471
512
|
"
|
|
472
|
-
|
|
473
513
|
out.html_safe
|
|
474
514
|
end # brick_bezier
|
|
475
515
|
|
|
@@ -526,7 +566,7 @@ module Brick::Rails::FormTags
|
|
|
526
566
|
</div>
|
|
527
567
|
</div>
|
|
528
568
|
"
|
|
529
|
-
out
|
|
569
|
+
out.html_safe
|
|
530
570
|
end # brick_header
|
|
531
571
|
|
|
532
572
|
# All the standard CSS with teal colouration for use with Brick
|
|
@@ -536,6 +576,8 @@ module Brick::Rails::FormTags
|
|
|
536
576
|
|
|
537
577
|
# -----------------------------------------------------------------------------------------------
|
|
538
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!
|
|
539
581
|
table_name = klass.table_name.split('.').last
|
|
540
582
|
|
|
541
583
|
# Javascript for brick_grid and brick_constellation
|
|
@@ -545,13 +587,13 @@ module Brick::Rails::FormTags
|
|
|
545
587
|
// Plunk the row count in now that we know it
|
|
546
588
|
var rowCount = document.getElementById(\"rowCount\");
|
|
547
589
|
if (rowCount) rowCount.innerHTML = \"#{pluralize(row_count, "row")}#{total_row_count} \";
|
|
548
|
-
var #{
|
|
590
|
+
var #{camel_name}HtColumns;
|
|
549
591
|
" unless row_count.nil?
|
|
550
592
|
|
|
551
593
|
grid_scripts << "
|
|
552
594
|
// Snag first TR for sticky header
|
|
553
595
|
var grid = document.getElementById(\"#{table_name}\");
|
|
554
|
-
#{
|
|
596
|
+
#{camel_name}HtColumns = grid && [grid.getElementsByTagName(\"TR\")[0]];
|
|
555
597
|
var headerTop = document.getElementById(\"headerTop\");
|
|
556
598
|
var headerCols;
|
|
557
599
|
if (grid) {
|
|
@@ -604,8 +646,8 @@ function setHeaderSizes() {
|
|
|
604
646
|
|
|
605
647
|
// Set up proper sizings of sticky column header
|
|
606
648
|
var node;
|
|
607
|
-
for (var j = 0; j < #{
|
|
608
|
-
var row = #{
|
|
649
|
+
for (var j = 0; j < #{camel_name}HtColumns.length; ++j) {
|
|
650
|
+
var row = #{camel_name}HtColumns[j];
|
|
609
651
|
var tr = isEmpty ? document.createElement(\"TR\") : headerTop.childNodes[j];
|
|
610
652
|
tr.innerHTML = row.innerHTML.trim();
|
|
611
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
|
|
@@ -179,7 +179,7 @@ erDiagram
|
|
|
179
179
|
'timestamp without time zone' => 'timestamp',
|
|
180
180
|
'timestamp with time zone' => 'timestamp',
|
|
181
181
|
'time without time zone' => 'time',
|
|
182
|
-
'time with time zone' => 'time' }[dt] || dt&.tr(' (', '_')
|
|
182
|
+
'time with time zone' => 'time' }[dt] || dt&.tr(' (', '_')&.tr(')', '') || 'int'
|
|
183
183
|
end
|
|
184
184
|
callbacks.merge({#{model_short_name.inspect} => ::#{model.name}}).each do |cb_k, cb_class|
|
|
185
185
|
cb_relation = ::Brick.relations[cb_class.table_name]
|
data/lib/brick/reflect_tables.rb
CHANGED
|
@@ -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
|
|
@@ -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,6 +270,7 @@ 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
|
|
|
@@ -425,8 +427,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
|
425
427
|
end
|
|
426
428
|
)&.present?
|
|
427
429
|
if tnp.last&.start_with?('::') # TNP that points to an exact class?
|
|
428
|
-
|
|
429
|
-
[singular]
|
|
430
|
+
tnp.last[2..-1].split('::')
|
|
430
431
|
elsif tnp.last
|
|
431
432
|
v[:auto_prefixed_schema], v[:auto_prefixed_class] = tnp
|
|
432
433
|
# v[:resource] = rel_name.last[tnp.first.length..-1]
|
|
@@ -444,14 +445,18 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
|
444
445
|
loop do
|
|
445
446
|
klass = Object
|
|
446
447
|
proposed_name_parts.each do |part|
|
|
447
|
-
|
|
448
|
-
|
|
448
|
+
begin
|
|
449
|
+
if klass.const_defined?(part) && klass.name != part
|
|
449
450
|
klass = klass.const_get(part)
|
|
450
|
-
|
|
451
|
+
else
|
|
451
452
|
klass = nil
|
|
452
453
|
break
|
|
453
454
|
end
|
|
454
|
-
|
|
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."
|
|
455
460
|
klass = nil
|
|
456
461
|
break
|
|
457
462
|
end
|
data/lib/brick/version_number.rb
CHANGED
data/lib/brick.rb
CHANGED
|
@@ -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
|
|
@@ -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]
|