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 +4 -4
- data/lib/brick/version_number.rb +1 -1
- data/lib/generators/brick/migrations_generator.rb +121 -42
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 508d41fa8b75089e0467ece56ab53fc78b651f3951eabe62d108a62fa8b0f35e
|
4
|
+
data.tar.gz: dcd419560d850fb2014aab3076a6a80f5a555320ac2c2183e3381a916d94dbc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49c90c7fbb1845e151f91b9df7f3b639d587e6239dae5e7f31306807a2b4dc7ac83436d78d46839accfea7c614fd2a94f2460443ea3daae424d280fae17cac77
|
7
|
+
data.tar.gz: 7dd02fd9ab902cfe6359b6be95101ec3a89c47d8b7bc71692ab4537f5ffeb3a24a4ee73063fc10b3d4197ce59fd5c94380f7534f66647d8c03dd512c6ef0c334
|
data/lib/brick/version_number.rb
CHANGED
@@ -5,12 +5,14 @@ require 'rails/generators/active_record'
|
|
5
5
|
require 'fancy_gets'
|
6
6
|
|
7
7
|
module Brick
|
8
|
-
# Auto-generates
|
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
|
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
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
126
|
-
|
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 +
|
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
|
-
|
142
|
-
mig
|
143
|
-
mig << "
|
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
|
-
|
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
|
-
|
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
|
-
#
|
193
|
-
mig << " #{'# ' if is_commented}add_foreign_key #{
|
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\
|
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.
|
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-
|
11
|
+
date: 2022-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|