brick 1.0.156 → 1.0.158
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/config.rb +8 -0
- data/lib/brick/extensions.rb +100 -74
- data/lib/brick/frameworks/rails/engine.rb +39 -21
- data/lib/brick/frameworks/rails/form_builder.rb +12 -5
- data/lib/brick/frameworks/rails/form_tags.rb +1 -1
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +31 -19
- data/lib/generators/brick/install_generator.rb +1 -1
- data/lib/generators/brick/migration_builder.rb +341 -0
- data/lib/generators/brick/migrations_generator.rb +6 -327
- data/lib/generators/brick/models_generator.rb +3 -0
- data/lib/generators/brick/seeds_generator.rb +39 -15
- metadata +3 -2
@@ -3,354 +3,33 @@
|
|
3
3
|
require 'rails/generators'
|
4
4
|
require 'rails/generators/active_record'
|
5
5
|
require 'fancy_gets'
|
6
|
+
require 'generators/brick/migration_builder'
|
6
7
|
|
7
8
|
module Brick
|
8
9
|
# Auto-generates migration files
|
9
10
|
class MigrationsGenerator < ::Rails::Generators::Base
|
10
11
|
include FancyGets
|
11
|
-
|
12
|
-
|
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:
|
16
|
-
SQL_TYPES = { 'character varying' => 'string',
|
17
|
-
'character' => 'string', # %%% Need to put in "limit: 1"
|
18
|
-
'xml' => 'text',
|
19
|
-
'bytea' => 'binary',
|
20
|
-
'timestamp without time zone' => 'timestamp',
|
21
|
-
'timestamp with time zone' => 'timestamp',
|
22
|
-
'time without time zone' => 'time',
|
23
|
-
'time with time zone' => 'time',
|
24
|
-
'double precision' => 'float',
|
25
|
-
'smallint' => 'integer', # %%% Need to put in "limit: 2"
|
26
|
-
'ARRAY' => 'string', # Note that we'll also add ", array: true"
|
27
|
-
# Oracle data types
|
28
|
-
'VARCHAR2' => 'string',
|
29
|
-
'CHAR' => 'string',
|
30
|
-
['NUMBER', 22] => 'integer',
|
31
|
-
/^INTERVAL / => 'string', # Time interval stuff like INTERVAL YEAR(2) TO MONTH, INTERVAL '999' DAY(3), etc
|
32
|
-
'XMLTYPE' => 'xml',
|
33
|
-
'RAW' => 'binary',
|
34
|
-
'SDO_GEOMETRY' => 'geometry',
|
35
|
-
# MSSQL data types
|
36
|
-
'int' => 'integer',
|
37
|
-
'nvarchar' => 'string',
|
38
|
-
'nchar' => 'string',
|
39
|
-
'datetime2' => 'timestamp',
|
40
|
-
'bit' => 'boolean',
|
41
|
-
'varbinary' => 'binary',
|
42
|
-
# Sqlite data types
|
43
|
-
'TEXT' => 'text',
|
44
|
-
'' => 'string',
|
45
|
-
'INTEGER' => 'integer',
|
46
|
-
'REAL' => 'float',
|
47
|
-
'BLOB' => 'binary',
|
48
|
-
'TIMESTAMP' => 'timestamp',
|
49
|
-
'DATETIME' => 'timestamp'
|
50
|
-
}
|
51
|
-
# (Still need to find what "inet" and "json" data types map to.)
|
12
|
+
include ::Brick::MigrationBuilder
|
52
13
|
|
53
14
|
desc 'Auto-generates migration files for an existing database.'
|
54
15
|
|
55
16
|
def brick_migrations
|
56
17
|
# If Apartment is active, see if a default schema to analyse is indicated
|
57
18
|
|
58
|
-
|
59
|
-
|
19
|
+
::Brick.mode = :on
|
20
|
+
ActiveRecord::Base.establish_connection
|
60
21
|
|
61
22
|
if (tables = ::Brick.relations.reject { |k, v| v.key?(:isView) && v[:isView] == true }.map(&:first).sort).empty?
|
62
23
|
puts "No tables found in database #{ActiveRecord::Base.connection.current_database}."
|
63
24
|
return
|
64
25
|
end
|
65
26
|
|
66
|
-
|
67
|
-
key_type = ((is_sqlite || ActiveRecord.version < ::Gem::Version.new('5.1')) ? 'integer' : 'bigint')
|
68
|
-
is_4x_rails = ActiveRecord.version < ::Gem::Version.new('5.0')
|
69
|
-
ar_version = "[#{ActiveRecord.version.segments[0..1].join('.')}]" unless is_4x_rails
|
70
|
-
is_insert_versions = true
|
71
|
-
is_delete_versions = false
|
72
|
-
versions_to_delete_or_append = nil
|
73
|
-
if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{::Rails.root}/db/migrate")
|
74
|
-
if Dir["#{mig_path}/**/*.rb"].present?
|
75
|
-
puts "WARNING: migrations folder #{mig_path} appears to already have ruby files present."
|
76
|
-
mig_path2 = "#{::Rails.root}/tmp/brick_migrations"
|
77
|
-
is_insert_versions = false unless mig_path == mig_path2
|
78
|
-
if Dir.exist?(mig_path2)
|
79
|
-
if Dir["#{mig_path2}/**/*.rb"].present?
|
80
|
-
puts "As well, temporary folder #{mig_path2} also has ruby files present."
|
81
|
-
puts "Choose a destination -- all existing .rb files will be removed:"
|
82
|
-
mig_path2 = gets_list(list: ['Cancel operation!', "Append migration files into #{mig_path} anyway", mig_path, mig_path2])
|
83
|
-
return if mig_path2.start_with?('Cancel')
|
84
|
-
|
85
|
-
existing_mig_files = Dir["#{mig_path2}/**/*.rb"]
|
86
|
-
if (is_insert_versions = mig_path == mig_path2)
|
87
|
-
versions_to_delete_or_append = existing_mig_files.map { |ver| ver.split('/').last.split('_').first }
|
88
|
-
end
|
89
|
-
if mig_path2.start_with?('Append migration files into ')
|
90
|
-
mig_path2 = mig_path
|
91
|
-
else
|
92
|
-
is_delete_versions = true
|
93
|
-
existing_mig_files.each { |rb| File.delete(rb) }
|
94
|
-
end
|
95
|
-
else
|
96
|
-
puts "Using temporary folder #{mig_path2} for created migration files.\n\n"
|
97
|
-
end
|
98
|
-
else
|
99
|
-
puts "Creating the temporary folder #{mig_path2} for created migration files.\n\n"
|
100
|
-
Dir.mkdir(mig_path2)
|
101
|
-
end
|
102
|
-
mig_path = mig_path2
|
103
|
-
else
|
104
|
-
puts "Using standard migration folder #{mig_path} for created migration files.\n\n"
|
105
|
-
end
|
106
|
-
else
|
107
|
-
puts "Creating standard ActiveRecord migration folder #{mig_path} to hold new migration files.\n\n"
|
108
|
-
Dir.mkdir(mig_path)
|
109
|
-
end
|
27
|
+
mig_path, is_insert_versions, is_delete_versions = ::Brick::MigrationBuilder.check_folder
|
110
28
|
|
111
29
|
# Generate a list of tables that can be chosen
|
112
30
|
chosen = gets_list(list: tables, chosen: tables.dup)
|
113
|
-
schemas = chosen.each_with_object({}) do |v, s|
|
114
|
-
if (v_parts = v.split('.')).length > 1
|
115
|
-
s[v_parts.first] = nil unless [::Brick.default_schema, 'public'].include?(v_parts.first)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
# Start the timestamps back the same number of minutes from now as expected number of migrations to create
|
119
|
-
current_mig_time = Time.now - (schemas.length + chosen.length).minutes
|
120
|
-
done = []
|
121
|
-
fks = {}
|
122
|
-
stuck = {}
|
123
|
-
indexes = {} # Track index names to make sure things are unique
|
124
|
-
built_schemas = {} # Track all built schemas so we can place an appropriate drop_schema command only in the first
|
125
|
-
# migration in which that schema is referenced, thereby allowing rollbacks to function properly.
|
126
|
-
versions_to_create = [] # Resulting versions to be used when updating the schema_migrations table
|
127
|
-
ar_base = Object.const_defined?(:ApplicationRecord) ? ApplicationRecord : Class.new(ActiveRecord::Base)
|
128
|
-
# Start by making migrations for fringe tables (those with no foreign keys).
|
129
|
-
# Continue layer by layer, creating migrations for tables that reference ones already done, until
|
130
|
-
# no more migrations can be created. (At that point hopefully all tables are accounted for.)
|
131
|
-
while (fringe = chosen.reject do |tbl|
|
132
|
-
snag_fks = []
|
133
|
-
snags = ::Brick.relations.fetch(tbl, nil)&.fetch(:fks, nil)&.select do |_k, v|
|
134
|
-
v[:is_bt] && !v[:polymorphic] &&
|
135
|
-
tbl != v[:inverse_table] && # Ignore self-referencing associations (stuff like "parent_id")
|
136
|
-
!done.include?(v[:inverse_table]) &&
|
137
|
-
::Brick.config.ignore_migration_fks.exclude?(snag_fk = "#{tbl}.#{v[:fk]}") &&
|
138
|
-
snag_fks << snag_fk
|
139
|
-
end
|
140
|
-
if snags&.present?
|
141
|
-
# puts snag_fks.inspect
|
142
|
-
stuck[tbl] = snags
|
143
|
-
end
|
144
|
-
end).present?
|
145
|
-
fringe.each do |tbl|
|
146
|
-
next unless (relation = ::Brick.relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present?
|
147
|
-
|
148
|
-
pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ar_base.primary_key].flatten.sort)
|
149
|
-
# In case things aren't as standard
|
150
|
-
if pkey_cols.empty?
|
151
|
-
pkey_cols = if rpk.empty? && relation[:cols][arpk.first]&.first == key_type
|
152
|
-
arpk
|
153
|
-
elsif rpk.first
|
154
|
-
rpk
|
155
|
-
end
|
156
|
-
end
|
157
|
-
schema = if (tbl_parts = tbl.split('.')).length > 1
|
158
|
-
if tbl_parts.first == (::Brick.default_schema || 'public')
|
159
|
-
tbl_parts.shift
|
160
|
-
nil
|
161
|
-
else
|
162
|
-
tbl_parts.first
|
163
|
-
end
|
164
|
-
end
|
165
|
-
unless schema.blank? || built_schemas.key?(schema)
|
166
|
-
mig = +" def change\n create_schema(:#{schema}) unless schema_exists?(:#{schema})\n end\n"
|
167
|
-
migration_file_write(mig_path, "create_db_schema_#{schema.underscore}", current_mig_time += 1.minute, ar_version, mig)
|
168
|
-
built_schemas[schema] = nil
|
169
|
-
end
|
170
31
|
|
171
|
-
|
172
|
-
fkey_cols = relation[:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
|
173
|
-
# If the primary key is also used as a foreign key, will need to do id: false and then build out
|
174
|
-
# a column definition which includes :primary_key -- %%% also using a data type of bigserial or serial
|
175
|
-
# if this one has come in as bigint or integer.
|
176
|
-
pk_is_also_fk = fkey_cols.any? { |assoc| pkey_cols&.first == assoc[:fk] } ? pkey_cols&.first : nil
|
177
|
-
# Support missing primary key (by adding: , id: false)
|
178
|
-
id_option = if pk_is_also_fk || !pkey_cols&.present?
|
179
|
-
needs_serial_col = true
|
180
|
-
+', id: false'
|
181
|
-
elsif ((pkey_col_first = (col_def = relation[:cols][pkey_cols&.first])&.first) &&
|
182
|
-
(pkey_col_first = SQL_TYPES[pkey_col_first] || SQL_TYPES[col_def&.[](0..1)] ||
|
183
|
-
SQL_TYPES.find { |r| r.first.is_a?(Regexp) && pkey_col_first =~ r.first }&.last ||
|
184
|
-
pkey_col_first
|
185
|
-
) != key_type
|
186
|
-
)
|
187
|
-
case pkey_col_first
|
188
|
-
when 'integer'
|
189
|
-
+', id: :serial'
|
190
|
-
when 'bigint'
|
191
|
-
+', id: :bigserial'
|
192
|
-
else
|
193
|
-
+", id: :#{pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
|
194
|
-
end +
|
195
|
-
(pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '')
|
196
|
-
end
|
197
|
-
if !id_option && pkey_cols.sort != arpk
|
198
|
-
id_option = +", primary_key: :#{pkey_cols.first}"
|
199
|
-
end
|
200
|
-
if !is_4x_rails && (comment = relation&.fetch(:description, nil))&.present?
|
201
|
-
(id_option ||= +'') << ", comment: #{comment.inspect}"
|
202
|
-
end
|
203
|
-
# Find the ActiveRecord class in order to see if the columns have comments
|
204
|
-
unless is_4x_rails
|
205
|
-
klass = begin
|
206
|
-
tbl.tr('.', '/').singularize.camelize.constantize
|
207
|
-
rescue StandardError
|
208
|
-
end
|
209
|
-
if klass
|
210
|
-
unless ActiveRecord::Migration.table_exists?(klass.table_name)
|
211
|
-
puts "WARNING: Unable to locate table #{klass.table_name} (for #{klass.name})."
|
212
|
-
klass = nil
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
# Refer to this table name as a symbol or dotted string as appropriate
|
217
|
-
tbl_code = tbl_parts.length == 1 ? ":#{tbl_parts.first}" : "'#{tbl}'"
|
218
|
-
mig = +" def change\n return unless reverting? || !table_exists?(#{tbl_code})\n\n"
|
219
|
-
mig << " create_table #{tbl_code}#{id_option} do |t|\n"
|
220
|
-
possible_ts = [] # Track possible generic timestamps
|
221
|
-
add_fks = [] # Track foreign keys to add after table creation
|
222
|
-
relation[:cols].each do |col, col_type|
|
223
|
-
sql_type = SQL_TYPES[col_type.first] || SQL_TYPES[col_type[0..1]] ||
|
224
|
-
SQL_TYPES.find { |r| r.first.is_a?(Regexp) && col_type.first =~ r.first }&.last ||
|
225
|
-
col_type.first
|
226
|
-
suffix = col_type[3] || pkey_cols&.include?(col) ? +', null: false' : +''
|
227
|
-
suffix << ', array: true' if (col_type.first == 'ARRAY')
|
228
|
-
if !is_4x_rails && klass && (comment = klass.columns_hash.fetch(col, nil)&.comment)&.present?
|
229
|
-
suffix << ", comment: #{comment.inspect}"
|
230
|
-
end
|
231
|
-
# Determine if this column is used as part of a foreign key
|
232
|
-
if (fk = fkey_cols.find { |assoc| col == assoc[:fk] })
|
233
|
-
to_table = fk[:inverse_table].split('.')
|
234
|
-
to_table = to_table.length == 1 ? ":#{to_table.first}" : "'#{fk[:inverse_table]}'"
|
235
|
-
if needs_serial_col && pkey_cols&.include?(col) && (new_serial_type = {'integer' => 'serial', 'bigint' => 'bigserial'}[sql_type])
|
236
|
-
sql_type = new_serial_type
|
237
|
-
needs_serial_col = false
|
238
|
-
end
|
239
|
-
if fk[:fk] != "#{fk[:assoc_name].singularize}_id" # Need to do our own foreign_key tricks, not use references?
|
240
|
-
column = fk[:fk]
|
241
|
-
mig << emit_column(sql_type, column, suffix)
|
242
|
-
add_fks << [to_table, column, ::Brick.relations[fk[:inverse_table]]]
|
243
|
-
else
|
244
|
-
suffix << ", type: :#{sql_type}" unless sql_type == key_type
|
245
|
-
# Will the resulting default index name be longer than what Postgres allows? (63 characters)
|
246
|
-
if (idx_name = ActiveRecord::Base.connection.index_name(tbl, {column: col})).length > 63
|
247
|
-
# Try to find a shorter name that hasn't been used yet
|
248
|
-
unless indexes.key?(shorter = idx_name[0..62]) ||
|
249
|
-
indexes.key?(shorter = idx_name.tr('_', '')[0..62]) ||
|
250
|
-
indexes.key?(shorter = idx_name.tr('aeio', '')[0..62])
|
251
|
-
puts "Unable to easily find unique name for index #{idx_name} that is shorter than 64 characters,"
|
252
|
-
puts "so have resorted to this GUID-based identifier: #{shorter = "#{tbl[0..25]}_#{::SecureRandom.uuid}"}."
|
253
|
-
end
|
254
|
-
suffix << ", index: { name: '#{shorter || idx_name}' }"
|
255
|
-
indexes[shorter || idx_name] = nil
|
256
|
-
end
|
257
|
-
primary_key = ::Brick.relations[fk[:inverse_table]][:class_name]&.constantize&.primary_key
|
258
|
-
mig << " t.references :#{fk[:assoc_name]}#{suffix}, foreign_key: { to_table: #{to_table}#{", primary_key: :#{primary_key}" if primary_key != ar_base.primary_key} }\n"
|
259
|
-
end
|
260
|
-
else
|
261
|
-
next if !id_option&.end_with?('id: false') && pkey_cols&.include?(col)
|
262
|
-
|
263
|
-
# See if there are generic timestamps
|
264
|
-
if sql_type == 'timestamp' && ['created_at','updated_at'].include?(col)
|
265
|
-
possible_ts << [col, !col_type[3]]
|
266
|
-
else
|
267
|
-
mig << emit_column(sql_type, col, suffix)
|
268
|
-
end
|
269
|
-
end
|
270
|
-
end
|
271
|
-
if possible_ts.length == 2 && # Both created_at and updated_at
|
272
|
-
# Rails 5 and later timestamps default to NOT NULL
|
273
|
-
(possible_ts.first.last == is_4x_rails && possible_ts.last.last == is_4x_rails)
|
274
|
-
mig << "\n t.timestamps\n"
|
275
|
-
else # Just one or the other, or a nullability mismatch
|
276
|
-
possible_ts.each { |ts| emit_column('timestamp', ts.first, nil) }
|
277
|
-
end
|
278
|
-
mig << " end\n"
|
279
|
-
if pk_is_also_fk
|
280
|
-
mig << " reversible do |dir|\n"
|
281
|
-
mig << " dir.up { execute('ALTER TABLE #{tbl} ADD PRIMARY KEY (#{pk_is_also_fk})') }\n"
|
282
|
-
mig << " end\n"
|
283
|
-
end
|
284
|
-
add_fks.each do |add_fk|
|
285
|
-
is_commented = false
|
286
|
-
# add_fk[2] holds the inverse relation
|
287
|
-
unless (pk = add_fk[2][:pkey].values.flatten&.first)
|
288
|
-
is_commented = true
|
289
|
-
mig << " # (Unable to create relationship because primary key is missing on table #{add_fk[0]})\n"
|
290
|
-
# No official PK, but if coincidentally there's a column of the same name, take a chance on it
|
291
|
-
pk = (add_fk[2][:cols].key?(add_fk[1]) && add_fk[1]) || '???'
|
292
|
-
end
|
293
|
-
# to_table column
|
294
|
-
mig << " #{'# ' if is_commented}add_foreign_key #{tbl_code}, #{add_fk[0]}, column: :#{add_fk[1]}, primary_key: :#{pk}\n"
|
295
|
-
end
|
296
|
-
mig << " end\n"
|
297
|
-
versions_to_create << migration_file_write(mig_path, "create_#{tbl_parts.map(&:underscore).join('_')}", current_mig_time += 1.minute, ar_version, mig)
|
298
|
-
end
|
299
|
-
done.concat(fringe)
|
300
|
-
chosen -= done
|
301
|
-
end
|
302
|
-
|
303
|
-
stuck_counts = Hash.new { |h, k| h[k] = 0 }
|
304
|
-
chosen.each do |leftover|
|
305
|
-
puts "Can't do #{leftover} because:\n #{stuck[leftover].map do |snag|
|
306
|
-
stuck_counts[snag.last[:inverse_table]] += 1
|
307
|
-
snag.last[:assoc_name]
|
308
|
-
end.join(', ')}"
|
309
|
-
end
|
310
|
-
if mig_path.start_with?(cur_path = ::Rails.root.to_s)
|
311
|
-
pretty_mig_path = mig_path[cur_path.length..-1]
|
312
|
-
end
|
313
|
-
puts "\n*** Created #{done.length} migration files under #{pretty_mig_path || mig_path} ***"
|
314
|
-
if (stuck_sorted = stuck_counts.to_a.sort { |a, b| b.last <=> a.last }).length.positive?
|
315
|
-
puts "-----------------------------------------"
|
316
|
-
puts "Unable to create migrations for #{stuck_sorted.length} tables#{
|
317
|
-
". Here's the top 5 blockers" if stuck_sorted.length > 5
|
318
|
-
}:"
|
319
|
-
pp stuck_sorted[0..4]
|
320
|
-
else # Successful, and now we can update the schema_migrations table accordingly
|
321
|
-
unless ActiveRecord::Migration.table_exists?(ActiveRecord::Base.schema_migrations_table_name)
|
322
|
-
ActiveRecord::SchemaMigration.create_table
|
323
|
-
end
|
324
|
-
# Remove to_delete - to_create
|
325
|
-
if ((versions_to_delete_or_append ||= []) - versions_to_create).present? && is_delete_versions
|
326
|
-
ActiveRecord::Base.execute_sql("DELETE FROM #{
|
327
|
-
ActiveRecord::Base.schema_migrations_table_name} WHERE version IN (#{
|
328
|
-
(versions_to_delete_or_append - versions_to_create).map { |vtd| "'#{vtd}'" }.join(', ')}
|
329
|
-
)")
|
330
|
-
end
|
331
|
-
# Add to_create - to_delete
|
332
|
-
if is_insert_versions && ((versions_to_create ||= []) - versions_to_delete_or_append).present?
|
333
|
-
ActiveRecord::Base.execute_sql("INSERT INTO #{
|
334
|
-
ActiveRecord::Base.schema_migrations_table_name} (version) VALUES #{
|
335
|
-
(versions_to_create - versions_to_delete_or_append).map { |vtc| "('#{vtc}')" }.join(', ')
|
336
|
-
}")
|
337
|
-
end
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
private
|
342
|
-
|
343
|
-
def emit_column(type, name, suffix)
|
344
|
-
" t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}#{suffix}\n"
|
345
|
-
end
|
346
|
-
|
347
|
-
def migration_file_write(mig_path, name, current_mig_time, ar_version, mig)
|
348
|
-
File.open("#{mig_path}/#{version = current_mig_time.strftime('%Y%m%d%H%M00')}_#{name}.rb", "w") do |f|
|
349
|
-
f.write "class #{name.camelize} < ActiveRecord::Migration#{ar_version}\n"
|
350
|
-
f.write mig
|
351
|
-
f.write "end\n"
|
352
|
-
end
|
353
|
-
version
|
32
|
+
::Brick::MigrationBuilder.generate_migrations(chosen, mig_path, is_insert_versions, is_delete_versions)
|
354
33
|
end
|
355
34
|
end
|
356
35
|
end
|
@@ -9,25 +9,46 @@ module Brick
|
|
9
9
|
|
10
10
|
desc 'Auto-generates a seeds file from existing data.'
|
11
11
|
|
12
|
+
SeedModel = Struct.new(:table_name, :klass, :is_brick)
|
13
|
+
SeedModel.define_method(:to_s) do
|
14
|
+
"#{klass.name}#{' (brick-generated)' if is_brick}"
|
15
|
+
end
|
16
|
+
|
12
17
|
def brick_seeds
|
13
18
|
# %%% If Apartment is active and there's no schema_to_analyse, ask which schema they want
|
14
19
|
|
15
20
|
::Brick.mode = :on
|
16
21
|
ActiveRecord::Base.establish_connection
|
17
22
|
|
18
|
-
|
19
|
-
|
23
|
+
# Load all models
|
24
|
+
::Brick.eager_load_classes
|
25
|
+
|
26
|
+
# Generate a list of viable models that can be chosen
|
27
|
+
# First start with any existing models that have been defined ...
|
28
|
+
existing_models = ActiveRecord::Base.descendants.each_with_object({}) do |m, s|
|
29
|
+
s[m.table_name] = SeedModel.new(m.table_name, m, false) if !m.abstract_class? && m.table_exists?
|
30
|
+
end
|
31
|
+
models = (existing_models.values +
|
32
|
+
# ... then add models which can be auto-built by Brick
|
33
|
+
::Brick.relations.reject do |k, v|
|
34
|
+
(v.key?(:isView) && v[:isView] == true) || existing_models.key?(k)
|
35
|
+
end.map { |k, v| SeedModel.new(k, v[:class_name].constantize, true) }
|
36
|
+
).sort { |a, b| a.to_s <=> b.to_s }
|
37
|
+
if models.empty?
|
38
|
+
puts "No viable models found for database #{ActiveRecord::Base.connection.current_database}."
|
20
39
|
return
|
21
40
|
end
|
22
41
|
|
23
42
|
if File.exist?(seed_file_path = "#{::Rails.root}/db/seeds.rb")
|
24
|
-
puts "WARNING: seeds file #{seed_file_path} appears to already be present
|
43
|
+
puts "WARNING: seeds file #{seed_file_path} appears to already be present.\nOverwrite?"
|
44
|
+
return unless gets_list(list: ['No', 'Yes']) == 'Yes'
|
45
|
+
|
46
|
+
puts "\n"
|
25
47
|
end
|
26
48
|
|
27
|
-
|
28
|
-
chosen = gets_list(list: tables, chosen: tables.dup)
|
49
|
+
chosen = gets_list(list: models, chosen: models.dup)
|
29
50
|
schemas = chosen.each_with_object({}) do |v, s|
|
30
|
-
if (v_parts = v.split('.')).length > 1
|
51
|
+
if (v_parts = v.table_name.split('.')).length > 1
|
31
52
|
s[v_parts.first] = nil unless [::Brick.default_schema, 'public'].include?(v_parts.first)
|
32
53
|
end
|
33
54
|
end
|
@@ -40,12 +61,13 @@ module Brick
|
|
40
61
|
# Start by making entries for fringe models (those with no foreign keys).
|
41
62
|
# Continue layer by layer, creating entries for models that reference ones already done, until
|
42
63
|
# no more entries can be created. (At that point hopefully all models are accounted for.)
|
43
|
-
while (fringe = chosen.reject do |
|
64
|
+
while (fringe = chosen.reject do |seed_model|
|
65
|
+
tbl = seed_model.table_name
|
44
66
|
snag_fks = []
|
45
67
|
snags = ::Brick.relations.fetch(tbl, nil)&.fetch(:fks, nil)&.select do |_k, v|
|
46
68
|
v[:is_bt] && !v[:polymorphic] &&
|
47
69
|
tbl != v[:inverse_table] && # Ignore self-referencing associations (stuff like "parent_id")
|
48
|
-
!done.
|
70
|
+
!done.any? { |done_seed_model| done_seed_model.table_name == v[:inverse_table] } &&
|
49
71
|
::Brick.config.ignore_migration_fks.exclude?(snag_fk = "#{tbl}.#{v[:fk]}") &&
|
50
72
|
snag_fks << snag_fk
|
51
73
|
end
|
@@ -53,12 +75,14 @@ module Brick
|
|
53
75
|
# puts snag_fks.inspect
|
54
76
|
stuck[tbl] = snags
|
55
77
|
end
|
56
|
-
end
|
78
|
+
end
|
79
|
+
).present?
|
57
80
|
seeds << "\n"
|
58
|
-
fringe.each do |
|
81
|
+
fringe.each do |seed_model|
|
82
|
+
tbl = seed_model.table_name
|
59
83
|
next unless ::Brick.config.exclude_tables.exclude?(tbl) &&
|
60
84
|
(relation = ::Brick.relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present? &&
|
61
|
-
(klass =
|
85
|
+
(klass = seed_model.klass).table_exists?
|
62
86
|
|
63
87
|
pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ar_base.primary_key].flatten.sort)
|
64
88
|
# In case things aren't as standard
|
@@ -88,7 +112,7 @@ module Brick
|
|
88
112
|
klass.order(*pkey_cols).each do |obj|
|
89
113
|
unless has_rows
|
90
114
|
has_rows = true
|
91
|
-
seeds << " puts 'Seeding: #{
|
115
|
+
seeds << " puts 'Seeding: #{seed_model.klass.name}'\n"
|
92
116
|
end
|
93
117
|
is_empty = false
|
94
118
|
pk_val = obj.send(pkey_cols.first)
|
@@ -108,9 +132,9 @@ module Brick
|
|
108
132
|
data << "#{col}: #{val.inspect}"
|
109
133
|
end
|
110
134
|
end
|
111
|
-
seeds << "#{tbl.gsub('.', '__')}_#{brick_escape(pk_val)} = #{
|
135
|
+
seeds << "#{tbl.gsub('.', '__')}_#{brick_escape(pk_val)} = #{seed_model.klass.name}.create(#{(fk_vals + data).join(', ')})\n"
|
112
136
|
end
|
113
|
-
seeds << " # (Skipping #{
|
137
|
+
seeds << " # (Skipping #{seed_model.klass.name} as it has no rows)\n" unless has_rows
|
114
138
|
File.open(seed_file_path, "w") { |f| f.write seeds }
|
115
139
|
end
|
116
140
|
done.concat(fringe)
|
@@ -118,7 +142,7 @@ module Brick
|
|
118
142
|
end
|
119
143
|
stuck_counts = Hash.new { |h, k| h[k] = 0 }
|
120
144
|
chosen.each do |leftover|
|
121
|
-
puts "Can't do #{leftover} because:\n #{stuck[leftover].map do |snag|
|
145
|
+
puts "Can't do #{leftover.klass.name} because:\n #{stuck[leftover.table_name].map do |snag|
|
122
146
|
stuck_counts[snag.last[:inverse_table]] += 1
|
123
147
|
snag.last[:assoc_name]
|
124
148
|
end.join(', ')}"
|
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.158
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-07-
|
11
|
+
date: 2023-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -255,6 +255,7 @@ files:
|
|
255
255
|
- lib/brick/version_number.rb
|
256
256
|
- lib/generators/brick/USAGE
|
257
257
|
- lib/generators/brick/install_generator.rb
|
258
|
+
- lib/generators/brick/migration_builder.rb
|
258
259
|
- lib/generators/brick/migrations_generator.rb
|
259
260
|
- lib/generators/brick/models_generator.rb
|
260
261
|
- lib/generators/brick/seeds_generator.rb
|