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.
@@ -119,7 +119,8 @@ module Brick
119
119
  stuck = {}
120
120
  indexes = {} # Track index names to make sure things are unique
121
121
  built_schemas = {} # Track all built schemas so we can place an appropriate drop_schema command only in the first
122
- # migration in which that schema is referenced, thereby allowing rollbacks to function properly.
122
+ # migration in which that schema is referenced, thereby allowing rollbacks to function properly.
123
+ is_airtable = nil
123
124
  versions_to_create = [] # Resulting versions to be used when updating the schema_migrations table
124
125
  # Start by making migrations for fringe tables (those with no foreign keys).
125
126
  # Continue layer by layer, creating migrations for tables that reference ones already done, until
@@ -148,8 +149,9 @@ module Brick
148
149
  mig = gen_migration_columns(relations, tbl, (tbl_parts = tbl.split('.')), (add_fks = []), built_schemas, mig_path, current_mig_time,
149
150
  key_type, is_4x_rails, ar_version, do_fks_last, versions_to_create)
150
151
  after_fks.concat(add_fks) if do_fks_last
151
- current_mig_time[0] += 1.minute
152
- versions_to_create << migration_file_write(mig_path, "create_#{::Brick._brick_index(tbl, nil, separator, relations[tbl])}", current_mig_time, ar_version, mig)
152
+ increment_time(mig_path, current_mig_time)
153
+ is_airtable ||= (relation = relations[tbl])&.fetch(:airtable_table, nil)
154
+ versions_to_create << migration_file_write(mig_path, "create_#{::Brick._brick_index(tbl, nil, separator, relation, true)}", current_mig_time, ar_version, mig)
153
155
  end
154
156
  done.concat(fringe)
155
157
  chosen -= done
@@ -161,15 +163,15 @@ module Brick
161
163
  mig = gen_migration_columns(relations, tbl, (tbl_parts = tbl.split('.')), (add_fks = []), built_schemas, mig_path, current_mig_time,
162
164
  key_type, is_4x_rails, ar_version, do_fks_last, versions_to_create)
163
165
  after_fks.concat(add_fks)
164
- current_mig_time[0] += 1.minute
166
+ increment_time(mig_path, current_mig_time)
165
167
  versions_to_create << migration_file_write(mig_path, "create_#{
166
- ::Brick._brick_index(tbl, :migration, separator, relations[tbl])
168
+ ::Brick._brick_index(tbl, :migration, separator, relations[tbl], true)
167
169
  }", current_mig_time, ar_version, mig)
168
170
  end
169
171
  done.concat(chosen)
170
172
  chosen.clear
171
173
 
172
- case do_fks_last
174
+ case after_fks.present? && do_fks_last
173
175
  when 'Separate' # Add a final migration to create all the foreign keys
174
176
  mig = +" def change\n"
175
177
  after_fks.each do |add_fk|
@@ -201,9 +203,10 @@ module Brick
201
203
  'rb'
202
204
  end
203
205
  mig << +" end\n"
204
- current_mig_time[0] += 1.minute
205
- versions_to_create << migration_file_write(mig_path, "create_brick_fks.#{fks_extension}", current_mig_time, ar_version, mig)
206
- puts "Have written out a final migration called 'create_brick_fks.#{fks_extension}' which creates #{after_fks.length} foreign keys."
206
+ increment_time(mig_path, current_mig_time)
207
+ base_name = "_#{relations[:base_name]&.tr(' ', '')&.underscore}" if relations[:base_name]
208
+ versions_to_create << migration_file_write(mig_path, "create_brick_fks#{base_name}.#{fks_extension}", current_mig_time, ar_version, mig)
209
+ puts "Have written out a final migration called 'create_brick_fks#{base_name}.#{fks_extension}' which creates #{after_fks.length} foreign keys."
207
210
  if fks_extension == 'rbx'
208
211
  puts " This file extension (.rbx) will cause it not to run yet when you do a 'rails db:migrate'.
209
212
  The idea here is to do all data loading first, and then rename that migration file back
@@ -247,7 +250,7 @@ module Brick
247
250
  ". Here's the top 5 blockers" if stuck_sorted.length > 5
248
251
  }:"
249
252
  pp stuck_sorted[0..4]
250
- elsif do_schema_migrations # Successful, and now we can update the schema_migrations table accordingly
253
+ elsif do_schema_migrations && !is_airtable # Successful, and now we can update the schema_migrations table accordingly
251
254
  smtn = ::Brick._schema_migrations_table_name
252
255
  if ActiveRecord::Migration.table_exists?(smtn)
253
256
  # Remove to_delete - to_create
@@ -298,7 +301,7 @@ module Brick
298
301
  end
299
302
  unless schema.blank? || built_schemas.key?(schema)
300
303
  mig = +" def change\n create_schema(:#{schema}) unless schema_exists?(:#{schema})\n end\n"
301
- current_mig_time[0] += 1.minute
304
+ increment_time(mig_path, current_mig_time)
302
305
  versions_to_create << migration_file_write(mig_path, "create_db_schema_#{schema.underscore}", current_mig_time, ar_version, mig)
303
306
  built_schemas[schema] = nil
304
307
  end
@@ -352,7 +355,8 @@ module Brick
352
355
  mig = +" def change\n return unless reverting? || !table_exists?(#{tbl_code})\n\n"
353
356
  mig << " create_table #{tbl_code}#{id_option} do |t|\n"
354
357
  possible_ts = [] # Track possible generic timestamps
355
- relation[:cols].each do |col, col_type|
358
+ (relation.key?(:col_order) && relation[:col_order].present? && relation[:col_order].map { |col_name| [col_name, relation[:cols][col_name]] } ||
359
+ relation[:cols]).each do |col, col_type|
356
360
  sql_type = SQL_TYPES[col_type.first] || SQL_TYPES[col_type[0..1]] ||
357
361
  SQL_TYPES.find { |r| r.first.is_a?(Regexp) && col_type.first =~ r.first }&.last ||
358
362
  col_type.first
@@ -449,7 +453,16 @@ module Brick
449
453
  " t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}#{suffix}\n"
450
454
  end
451
455
 
456
+ def increment_time(mig_path, current_mig_time)
457
+ loop do
458
+ current_mig_time[0] += 1.minute
459
+ break if Dir.glob("#{mig_path}/#{current_mig_time.first.strftime('%Y%m%d%H%M00')}_*.*").empty?
460
+
461
+ end
462
+ end
463
+
452
464
  def migration_file_write(mig_path, name, current_mig_time, ar_version, mig)
465
+ name.gsub!(/[^a-zA-Z0-9_\.\p{L}\p{N}]/, 'x') # Strip out any weird unicode characters
453
466
  File.open("#{mig_path}/#{version = current_mig_time.first.strftime('%Y%m%d%H%M00')}_#{name}#{'.rb' unless name.index('.')}", "w") do |f|
454
467
  f.write "class #{name.split('.').first.camelize} < ActiveRecord::Migration#{ar_version}\n"
455
468
  f.write mig
@@ -13,9 +13,10 @@ module Brick
13
13
  end
14
14
 
15
15
  def generate_seeds(relations = nil)
16
+ action = 'Overwrite'
16
17
  if File.exist?((seed_file_path = "#{::Rails.root}/db/seed") + 's.rb')
17
- puts "WARNING: seeds file #{seed_file_path}s.rb appears to already be present.\nOverwrite?"
18
- return unless gets_list(list: ['No', 'Yes']) == 'Yes'
18
+ puts "WARNING: seeds file #{seed_file_path}s.rb appears to already be present.\nAppend or Overwrite?"
19
+ return if (action = gets_list(list: ['Cancel', 'Append', 'Overwrite'])) == 'Cancel'
19
20
 
20
21
  puts "\n"
21
22
  end
@@ -24,7 +25,7 @@ module Brick
24
25
  is_airtable = true # So far the only thing that feeds us relations is Airtable
25
26
  require 'generators/brick/airtable_api_caller'
26
27
  # include ::Brick::MigrationsBuilder
27
- chosen = relations.map { |k, v| SeedModel.new(k, nil, false, v[:airtable_table]) }
28
+ chosen = relations.each_with_object([]) { |v, s| s << SeedModel.new(v.first, nil, false, v.last[:airtable_table]) unless v.first.is_a?(Symbol) }
28
29
  else
29
30
  ::Brick.mode = :on
30
31
  ActiveRecord::Base.establish_connection
@@ -57,11 +58,14 @@ module Brick
57
58
  end
58
59
  end
59
60
  batch_num = 1
60
- seeds_file = File.open("#{seed_file_path}s.rb", 'w')
61
+ seeds_file = File.open("#{seed_file_path}s.rb", action == 'Overwrite' ? 'w' : 'a+')
62
+ seeds_file.write("\n") if action == 'Append'
61
63
  seeds_len = lines_len = 0
62
64
  seeds_file.write('# Seeds file for ')
63
65
  seed_rows = nil
64
- if (arbc = ActiveRecord::Base.connection).respond_to?(:current_database) # SQLite3 can't do this!
66
+ if is_airtable
67
+ seeds_file.write("Airtable Base \"#{relations[:base_name]}\":\n")
68
+ elsif (arbc = ActiveRecord::Base.connection).respond_to?(:current_database) # SQLite3 can't do this!
65
69
  seeds_file.write("#{arbc.current_database}:\n")
66
70
  elsif (filename = arbc.instance_variable_get(:@connection_parameters)&.fetch(:database, nil))
67
71
  seeds_file.write("#{filename}:\n")
@@ -146,19 +150,38 @@ module Brick
146
150
 
147
151
  has_rows = false
148
152
  is_empty = true
149
- klass_name = is_airtable ? ::Brick::AirtableApiCaller.sane_table_name(relation[:airtable_table]&.name)&.singularize&.camelize : klass.name
150
- # Pull the records
151
- collection = if is_airtable
152
- if (airtable_table = relation[:airtable_table])
153
- ::Brick::AirtableApiCaller.https_get("https://api.airtable.com/v0/#{airtable_table.base_id}/#{airtable_table.id}").fetch('records', nil)
154
- end
155
- else
156
- # Must convert to a symbol when doing ORDER BY due to this ActiveRecord nuance:
157
- # https://github.com/rails/rails/issues/2601
158
- klass.order(*pkey_cols.map(&:to_sym)) unless relation[:pkey].empty?
159
- end
153
+ collection = nil
154
+ klass_name = if is_airtable
155
+ airtable_table = relation[:airtable_table]
156
+ ::Brick.config.table_name_prefixes&.fetch(tbl, nil) ||
157
+ tbl.singularize.camelize
158
+ else
159
+ # Must convert to a symbol when doing ORDER BY due to this ActiveRecord nuance:
160
+ # https://github.com/rails/rails/issues/2601
161
+ collection = klass.order(*pkey_cols.map(&:to_sym)).enum_for unless relation[:pkey].empty?
162
+ klass.name
163
+ end
160
164
  seed_rows = +''
161
- collection&.each do |obj|
165
+ airtable_idx = offset = nil
166
+ while collection || airtable_table do
167
+ obj = if is_airtable
168
+ if collection.nil? || (airtable_idx += 1) >= collection.length
169
+ break if collection && !offset
170
+
171
+ offset_param = "?offset=#{offset}" if offset
172
+ result = ::Brick::AirtableApiCaller.https_get("https://api.airtable.com/v0/#{airtable_table.base_id}/#{airtable_table.id}#{offset_param}")
173
+ offset = result.fetch('offset', nil)
174
+ collection = result.fetch('records', nil)
175
+ airtable_idx = 0
176
+ end
177
+ collection[airtable_idx]
178
+ else
179
+ begin
180
+ collection.next
181
+ rescue StopIteration
182
+ break
183
+ end
184
+ end
162
185
  if is_airtable
163
186
  fields = obj['fields'].each_with_object({}) do |field, s|
164
187
  if relation[:cols].keys.include?(col_name = ::Brick::AirtableApiCaller.sane_name(field.first))
@@ -178,7 +201,7 @@ module Brick
178
201
  end
179
202
  end
180
203
  end
181
- objects = relation[:airtable_table].objects
204
+ objects = airtable_table.objects
182
205
  obj = objects[airtable_id = obj['id']] = AirtableObject.new(seed_model, obj['fields'], obj['createdTime'])
183
206
  end
184
207
  unless has_rows
@@ -204,7 +227,12 @@ module Brick
204
227
  # Used to be: obj.send(col)
205
228
  # (and with that it was possible to raise ActiveRecord::Encryption::Errors::Configuration...)
206
229
  # %%% should test further and see if that is possible with this code!)
207
- if (val = obj.attributes_before_type_cast[col])
230
+ val = if is_airtable && col == 'airtable_id'
231
+ pk_val
232
+ else
233
+ obj.attributes_before_type_cast[col]
234
+ end
235
+ if val
208
236
  if (val.is_a?(Time) || val.is_a?(Date))
209
237
  val = val.to_s
210
238
  elsif val.is_a?(Numeric)
@@ -283,28 +311,42 @@ end\n"
283
311
  end
284
312
  seed_rows = +''
285
313
  end
286
- unless has_rows || klass_name.nil?
314
+ unless has_rows || (is_airtable && !airtable_table)
287
315
  seeds_file.write(" # (Skipping #{klass_name} as it has no rows)\n")
288
316
  end
289
317
  end
290
318
  done.concat(fringe)
291
319
  chosen -= done
292
320
  end
293
- airtable_assocs_done = {}
294
- airtable_assoc_recids.each do |table_name, assoc_pairs| # N:M links
295
- # Make note of any other tables which have exactly duplicated data
296
- dupes = airtable_assocs_done.select do |dupe_table, dupe_pairs|
297
- dupe_table != table_name && assoc_pairs.length > 0 && assoc_pairs.length == dupe_pairs.length &&
298
- dupe_pairs.all? { |pair_name, _v3| assoc_pairs.keys.include?(pair_name) }
321
+
322
+ if airtable_assoc_recids.present?
323
+ seed_rows << "\n# Seeding #{airtable_assoc_recids.count} sets of Airtable associations:\n"
324
+ airtable_assocs_done = {}
325
+ dupe_list = []
326
+ airtable_assoc_recids.each do |table_name, assoc_pairs| # N:M links
327
+ # Make note of any other tables which have exactly duplicated data
328
+ dupes = airtable_assocs_done.select do |dupe_table, dupe_pairs|
329
+ dupe_table != table_name && assoc_pairs.length > 0 && assoc_pairs.length == dupe_pairs.length &&
330
+ dupe_pairs.all? { |pair_name, _v3| assoc_pairs.keys.include?(pair_name) }
331
+ end
332
+ seed_rows << " if ActiveRecord::Migration.table_exists?('#{table_name}')\n"
333
+ unless dupes.empty?
334
+ seed_rows << " # Duplicate data found in: #{dupes.keys.join(', ')}\n"
335
+ dupes.keys.each { |k| dupe_list << "#{table_name} | #{k}" }
336
+ end
337
+ seed_rows << " puts 'Airtable associations for #{table_name.camelize.singularize}'\n"
338
+ assoc_pairs.each do |_k2, pair_values|
339
+ seed_rows << "#{table_name.singularize.camelize}.create(#{pair_values})\n"
340
+ end
341
+ seed_rows << " end\n"
342
+ airtable_assocs_done[table_name] = assoc_pairs
299
343
  end
300
- seed_rows << " if ActiveRecord::Migration.table_exists?('#{table_name}')\n"
301
- seed_rows << " # Duplicate data found in: #{dupes.keys.join(', ')}\n" unless dupes.empty?
302
- seed_rows << " puts 'Seeding Airtable associations for #{table_name.camelize.singularize}'\n"
303
- assoc_pairs.each do |_k2, pair_values|
304
- seed_rows << "#{table_name.singularize.camelize}.create(#{pair_values})\n"
344
+
345
+ if dupe_list.present?
346
+ puts "\nAssociative tables with duplicate data:"
347
+ dupe_list.each { |entry| puts " #{entry}" }
348
+ puts '(You can safely remove one of the two tables in each of the pairs listed above.)'
305
349
  end
306
- seed_rows << " end\n"
307
- airtable_assocs_done[table_name] = assoc_pairs
308
350
  end
309
351
  seeds_file.write(seed_rows) unless seed_rows.blank?
310
352
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brick
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.245
4
+ version: 1.0.247
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-06-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activerecord
@@ -246,7 +245,6 @@ homepage: https://github.com/lorint/brick
246
245
  licenses:
247
246
  - MIT
248
247
  metadata: {}
249
- post_install_message:
250
248
  rdoc_options: []
251
249
  require_paths:
252
250
  - lib
@@ -261,8 +259,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
261
259
  - !ruby/object:Gem::Version
262
260
  version: 1.3.6
263
261
  requirements: []
264
- rubygems_version: 3.4.19
265
- signing_key:
262
+ rubygems_version: 4.0.18
266
263
  specification_version: 4
267
264
  summary: Create a Rails app from data alone
268
265
  test_files: []