brick 1.0.55 → 1.0.58

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 430a2dfa5ef0caee9b99bc341ccd42a9d29e8f1d54071284aa2469e1baa0c659
4
- data.tar.gz: b36e18de169bd9032e6c383548ec30cce06eeb97a35e8c3e4f4da935aff8f603
3
+ metadata.gz: 508d41fa8b75089e0467ece56ab53fc78b651f3951eabe62d108a62fa8b0f35e
4
+ data.tar.gz: dcd419560d850fb2014aab3076a6a80f5a555320ac2c2183e3381a916d94dbc8
5
5
  SHA512:
6
- metadata.gz: c51491575b9ee56c3789619bfc74a2d9c0525750687224e94e120cbae42fad8db1abda9d82978115d9266c55fa0c6f914ce273a3613915657d8a0cf4e7fe6aeb
7
- data.tar.gz: a0646cfdc9a2c844fd1149968d0175346c48835c6245d7fe92d0126d60708cf76514704e29dc3f506d2e06e546c8c995bf7103d6ef42be481bb931b4d2bfa73f
6
+ metadata.gz: 49c90c7fbb1845e151f91b9df7f3b639d587e6239dae5e7f31306807a2b4dc7ac83436d78d46839accfea7c614fd2a94f2460443ea3daae424d280fae17cac77
7
+ data.tar.gz: 7dd02fd9ab902cfe6359b6be95101ec3a89c47d8b7bc71692ab4537f5ffeb3a24a4ee73063fc10b3d4197ce59fd5c94380f7534f66647d8c03dd512c6ef0c334
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 55
8
+ TINY = 58
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
@@ -5,12 +5,14 @@ require 'rails/generators/active_record'
5
5
  require 'fancy_gets'
6
6
 
7
7
  module Brick
8
- # Auto-generates migrations
8
+ # Auto-generates migration files
9
9
  class MigrationsGenerator < ::Rails::Generators::Base
10
10
  include FancyGets
11
11
  # include ::Rails::Generators::Migration
12
12
 
13
- # SQL types that are the same as their migration data type name: text, integer, bigint, date, boolean, decimal, float
13
+ # Many SQL types are the same as their migration data type name:
14
+ # text, integer, bigint, date, boolean, decimal, float
15
+ # These however are not:
14
16
  SQL_TYPES = { 'character varying' => 'string',
15
17
  'character' => 'string', # %%% Need to put in "limit: 1"
16
18
  'xml' => 'text',
@@ -23,15 +25,7 @@ module Brick
23
25
  'smallint' => 'integer' } # %%% Need to put in "limit: 2"
24
26
  # (Still need to find what "inet" and "json" data types map to.)
25
27
 
26
- # # source_root File.expand_path('templates', __dir__)
27
- # class_option(
28
- # :with_changes,
29
- # type: :boolean,
30
- # default: false,
31
- # desc: 'Add IMPORT_TEMPLATE to model'
32
- # )
33
-
34
- desc 'Auto-generates migrations for an existing database.'
28
+ desc 'Auto-generates migration files for an existing database.'
35
29
 
36
30
  def brick_migrations
37
31
  # If Apartment is active, see if a default schema to analyse is indicated
@@ -47,11 +41,14 @@ module Brick
47
41
  key_type = (ActiveRecord.version < ::Gem::Version.new('5.1') ? 'integer' : 'bigint')
48
42
  is_4x_rails = ActiveRecord.version < ::Gem::Version.new('5.0')
49
43
  ar_version = "[#{ActiveRecord.version.segments[0..1].join('.')}]" unless is_4x_rails
50
- default_mig_path = (mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{::Rails.root}/db/migrate")
51
- if Dir.exist?(mig_path)
44
+ is_insert_versions = true
45
+ is_delete_versions = false
46
+ versions_to_delete_or_append = nil
47
+ if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{::Rails.root}/db/migrate")
52
48
  if Dir["#{mig_path}/**/*.rb"].present?
53
49
  puts "WARNING: migrations folder #{mig_path} appears to already have ruby files present."
54
50
  mig_path2 = "#{::Rails.root}/tmp/brick_migrations"
51
+ is_insert_versions = false unless mig_path == mig_path2
55
52
  if Dir.exist?(mig_path2)
56
53
  if Dir["#{mig_path2}/**/*.rb"].present?
57
54
  puts "As well, temporary folder #{mig_path2} also has ruby files present."
@@ -59,10 +56,15 @@ module Brick
59
56
  mig_path2 = gets_list(list: ['Cancel operation!', "Append migration files into #{mig_path} anyway", mig_path, mig_path2])
60
57
  return if mig_path2.start_with?('Cancel')
61
58
 
59
+ existing_mig_files = Dir["#{mig_path2}/**/*.rb"]
60
+ if (is_insert_versions = mig_path == mig_path2)
61
+ versions_to_delete_or_append = existing_mig_files.map { |ver| ver.split('/').last.split('_').first }
62
+ end
62
63
  if mig_path2.start_with?('Append migration files into ')
63
64
  mig_path2 = mig_path
64
65
  else
65
- Dir["#{mig_path2}/**/*.rb"].each { |rb| File.delete(rb) }
66
+ is_delete_versions = true
67
+ existing_mig_files.each { |rb| File.delete(rb) }
66
68
  end
67
69
  else
68
70
  puts "Using temporary folder #{mig_path2} for created migration files.\n\n"
@@ -82,11 +84,21 @@ module Brick
82
84
 
83
85
  # Generate a list of tables that can be chosen
84
86
  chosen = gets_list(list: tables, chosen: tables.dup)
87
+ schemas = chosen.each_with_object({}) do |v, s|
88
+ schema = if v.split('.').length > 1
89
+ v.first
90
+ end
91
+ s[schema] = nil if schema && [::Brick.default_schema, 'public'].exclude?(schema)
92
+ end
85
93
  # Start the timestamps back the same number of minutes from now as expected number of migrations to create
86
- current_mig_time = Time.now - chosen.length.minutes
94
+ current_mig_time = Time.now - (schemas.length + chosen.length).minutes
87
95
  done = []
88
96
  fks = {}
89
97
  stuck = {}
98
+ indexes = {} # Track index names to make sure things are unique
99
+ built_schemas = {} # Track all built schemas so we can place an appropriate drop_schema command only in the first
100
+ # migration in which that schema is referenced, thereby allowing rollbacks to function properly.
101
+ versions_to_create = [] # Resulting versions to be used when updating the schema_migrations table
90
102
  # Start by making migrations for fringe tables (those with no foreign keys).
91
103
  # Continue layer by layer, creating migrations for tables that reference ones already done, until
92
104
  # no more migrations can be created. (At that point hopefully all tables are accounted for.)
@@ -111,20 +123,28 @@ module Brick
111
123
  end
112
124
  end
113
125
  schema = if (tbl_parts = tbl.split('.')).length > 1
114
- if tbl_parts.first == 'public'
126
+ if tbl_parts.first == (::Brick.default_schema || 'public')
115
127
  tbl_parts.shift
116
128
  nil
117
129
  else
118
130
  tbl_parts.first
119
131
  end
120
132
  end
133
+ unless built_schemas.key?(schema)
134
+ mig = +" def change\n create_schema(:#{schema}) unless schema_exists?(:#{schema})\n end\n"
135
+ migration_file_write(mig_path, "create_db_schema_#{schema}", current_mig_time += 1.minute, ar_version, mig)
136
+ built_schemas[schema] = nil
137
+ end
138
+
121
139
  # %%% For the moment we're skipping polymorphics
122
140
  fkey_cols = relation[:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
123
- mig = +"class Create#{(full_table_name = tbl_parts.join('_')).camelize} < ActiveRecord::Migration#{ar_version}\n"
141
+ # If the primary key is also used as a foreign key, will need to do id: false and then build out
142
+ # a column definition which includes :primary_key -- %%% also using a data type of bigserial or serial
143
+ # if this one has come in as bigint or integer.
144
+ pk_is_also_fk = fkey_cols.any? { |assoc| pkey_cols&.first == assoc[:fk] } ? pkey_cols&.first : nil
124
145
  # Support missing primary key (by adding: ,id: false)
125
- # also integer / uuid / other non-standard data types for primary key
126
- id_option = unless (pkey_col_first = relation[:cols][pkey_cols&.first]&.first) == key_type
127
- unless pkey_cols&.present?
146
+ id_option = if pk_is_also_fk || (pkey_col_first = relation[:cols][pkey_cols&.first]&.first) != key_type
147
+ if pk_is_also_fk || !pkey_cols&.present?
128
148
  ', id: false'
129
149
  else
130
150
  case pkey_col_first
@@ -134,28 +154,36 @@ module Brick
134
154
  ', id: :bigserial'
135
155
  else
136
156
  ", id: :#{SQL_TYPES[pkey_col_first] || pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
137
- end + (pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '')
157
+ end +
158
+ (pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '') +
159
+ (!is_4x_rails && (comment = relation&.fetch(:description, nil))&.present? ? ", comment: #{comment.inspect}" : '')
138
160
  end
139
161
  end
162
+ # Find the ActiveRecord class in order to see if the columns have comments
163
+ unless is_4x_rails
164
+ klass = begin
165
+ tbl.tr('.', '/').singularize.camelize.constantize
166
+ rescue StandardError
167
+ end
168
+ if klass
169
+ unless ActiveRecord::Migration.table_exists?(klass.table_name)
170
+ puts "WARNING: Unable to locate table #{klass.table_name} (for #{klass.name})."
171
+ klass = nil
172
+ end
173
+ end
174
+ end
140
175
  # Refer to this table name as a symbol or dotted string as appropriate
141
- tbl = tbl_parts.length == 1 ? ":#{tbl_parts.first}" : "'#{tbl}'"
142
- mig << " def change\n return unless reverting? || !table_exists?(#{tbl})\n\n"
143
- mig << " create_schema :#{schema} unless schema_exists?(:#{schema})\n" if schema
144
- mig << " create_table #{tbl}#{id_option} do |t|\n"
176
+ tbl_code = tbl_parts.length == 1 ? ":#{tbl_parts.first}" : "'#{tbl}'"
177
+ mig = +" def change\n return unless reverting? || !table_exists?(#{tbl_code})\n\n"
178
+ mig << " create_table #{tbl_code}#{id_option} do |t|\n"
145
179
  possible_ts = [] # Track possible generic timestamps
146
180
  add_fks = [] # Track foreign keys to add after table creation
147
181
  relation[:cols].each do |col, col_type|
148
- next if !id_option&.end_with?('id: false') && pkey_cols.include?(col)
149
-
150
- # See if there are generic timestamps
151
- if (sql_type = SQL_TYPES[col_type.first]) == 'timestamp' &&
152
- ['created_at','updated_at'].include?(col)
153
- possible_ts << [col, !col_type[3]]
154
- next
155
- end
156
-
157
- sql_type ||= col_type.first
182
+ sql_type = SQL_TYPES[col_type.first] || col_type.first
158
183
  suffix = col_type[3] ? +', null: false' : +''
184
+ if !is_4x_rails && klass && (comment = klass.columns_hash.fetch(col, nil)&.comment)&.present?
185
+ suffix << ", comment: #{comment.inspect}"
186
+ end
159
187
  # Determine if this column is used as part of a foreign key
160
188
  if fk = fkey_cols.find { |assoc| col == assoc[:fk] }
161
189
  to_table = fk[:inverse_table].split('.')
@@ -166,10 +194,29 @@ module Brick
166
194
  add_fks << [to_table, column, ::Brick.relations[fk[:inverse_table]]]
167
195
  else
168
196
  suffix << ", type: :#{sql_type}" unless sql_type == key_type
197
+ # Will the resulting default index name be longer than what Postgres allows? (63 characters)
198
+ if (idx_name = ActiveRecord::Base.connection.index_name(tbl, {column: col})).length > 63
199
+ # Try to find a shorter name that hasn't been used yet
200
+ unless indexes.key?(shorter = idx_name[0..62]) ||
201
+ indexes.key?(shorter = idx_name.tr('_', '')[0..62]) ||
202
+ indexes.key?(shorter = idx_name.tr('aeio', '')[0..62])
203
+ puts "Unable to easily find unique name for index #{idx_name} that is shorter than 64 characters,"
204
+ puts "so have resorted to this GUID-based identifier: #{shorter = "#{tbl[0..25]}_#{::SecureRandom.uuid}"}."
205
+ end
206
+ suffix << ", index: { name: '#{shorter || idx_name}' }"
207
+ indexes[shorter || idx_name] = nil
208
+ end
169
209
  mig << " t.references :#{fk[:assoc_name]}#{suffix}, foreign_key: { to_table: #{to_table} }\n"
170
210
  end
171
211
  else
172
- mig << emit_column(sql_type, col, suffix)
212
+ next if !id_option&.end_with?('id: false') && pkey_cols.include?(col)
213
+
214
+ # See if there are generic timestamps
215
+ if sql_type == 'timestamp' && ['created_at','updated_at'].include?(col)
216
+ possible_ts << [col, !col_type[3]]
217
+ else
218
+ mig << emit_column(sql_type, col, suffix)
219
+ end
173
220
  end
174
221
  end
175
222
  if possible_ts.length == 2 && # Both created_at and updated_at
@@ -180,6 +227,11 @@ module Brick
180
227
  possible_ts.each { |ts| emit_column('timestamp', ts.first, nil) }
181
228
  end
182
229
  mig << " end\n"
230
+ if pk_is_also_fk
231
+ mig << " reversible do |dir|\n"
232
+ mig << " dir.up { execute('ALTER TABLE #{tbl} ADD PRIMARY KEY (#{pk_is_also_fk})') }\n"
233
+ mig << " end\n"
234
+ end
183
235
  add_fks.each do |add_fk|
184
236
  is_commented = false
185
237
  # add_fk[2] holds the inverse relation
@@ -189,16 +241,16 @@ module Brick
189
241
  # No official PK, but if coincidentally there's a column of the same name, take a chance on it
190
242
  pk = (add_fk[2][:cols].key?(add_fk[1]) && add_fk[1]) || '???'
191
243
  end
192
- # to_table column
193
- mig << " #{'# ' if is_commented}add_foreign_key #{tbl}, #{add_fk[0]}, column: :#{add_fk[1]}, primary_key: :#{pk}\n"
244
+ # to_table column
245
+ mig << " #{'# ' if is_commented}add_foreign_key #{tbl_code}, #{add_fk[0]}, column: :#{add_fk[1]}, primary_key: :#{pk}\n"
194
246
  end
195
- mig << " end\nend\n"
196
- current_mig_time += 1.minute
197
- File.open("#{mig_path}/#{current_mig_time.strftime('%Y%m%d%H%M00')}_create_#{full_table_name}.rb", "w") { |f| f.write mig }
247
+ mig << " end\n"
248
+ versions_to_create << migration_file_write(mig_path, "create_#{tbl_parts.join('_')}", current_mig_time += 1.minute, ar_version, mig)
198
249
  end
199
250
  done.concat(fringe)
200
251
  chosen -= done
201
252
  end
253
+
202
254
  stuck_counts = Hash.new { |h, k| h[k] = 0 }
203
255
  chosen.each do |leftover|
204
256
  puts "Can't do #{leftover} because:\n #{stuck[leftover].map do |snag|
@@ -209,13 +261,31 @@ module Brick
209
261
  if mig_path.start_with?(cur_path = ::Rails.root.to_s)
210
262
  pretty_mig_path = mig_path[cur_path.length..-1]
211
263
  end
212
- puts "*** Created #{done.length} migration files under #{pretty_mig_path || mig_path} ***"
264
+ puts "\n*** Created #{done.length} migration files under #{pretty_mig_path || mig_path} ***"
213
265
  if (stuck_sorted = stuck_counts.to_a.sort { |a, b| b.last <=> a.last }).length.positive?
214
266
  puts "-----------------------------------------"
215
267
  puts "Unable to create migrations for #{stuck_sorted.length} tables#{
216
268
  ". Here's the top 5 blockers" if stuck_sorted.length > 5
217
269
  }:"
218
270
  pp stuck_sorted[0..4]
271
+ else # Successful, and now we can update the schema_migrations table accordingly
272
+ unless ActiveRecord::Migration.table_exists?(ActiveRecord::Base.schema_migrations_table_name)
273
+ ActiveRecord::SchemaMigration.create_table
274
+ end
275
+ # Remove to_delete - to_create
276
+ if ((versions_to_delete_or_append ||= []) - versions_to_create).present? && is_delete_versions
277
+ ActiveRecord::Base.execute_sql("DELETE FROM #{
278
+ ActiveRecord::Base.schema_migrations_table_name} WHERE version IN (#{
279
+ (versions_to_delete_or_append - versions_to_create).map { |vtd| "'#{vtd}'" }.join(', ')}
280
+ )")
281
+ end
282
+ # Add to_create - to_delete
283
+ if is_insert_versions && ((versions_to_create ||= []) - versions_to_delete_or_append).present?
284
+ ActiveRecord::Base.execute_sql("INSERT INTO #{
285
+ ActiveRecord::Base.schema_migrations_table_name} (version) VALUES #{
286
+ (versions_to_create - versions_to_delete_or_append).map { |vtc| "('#{vtc}')" }.join(', ')
287
+ }")
288
+ end
219
289
  end
220
290
  end
221
291
 
@@ -224,5 +294,14 @@ module Brick
224
294
  def emit_column(type, name, suffix)
225
295
  " t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}#{suffix}\n"
226
296
  end
297
+
298
+ def migration_file_write(mig_path, name, current_mig_time, ar_version, mig)
299
+ File.open("#{mig_path}/#{version = current_mig_time.strftime('%Y%m%d%H%M00')}_#{name}.rb", "w") do |f|
300
+ f.write "class #{name.camelize} < ActiveRecord::Migration#{ar_version}\n"
301
+ f.write mig
302
+ f.write "end\n"
303
+ end
304
+ version
305
+ end
227
306
  end
228
307
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brick
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.55
4
+ version: 1.0.58
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-09 00:00:00.000000000 Z
11
+ date: 2022-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord