brick 1.0.53 → 1.0.56
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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b696c53c9d9d359d49de147b06b5342820ca230451c860d097f44bcae3b23bb0
|
4
|
+
data.tar.gz: 7b0f6ff365f918a97d64305eabf70fcfe78cf764514ca22b3731b780da04b22c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43ab6db28554fb7502d0f03267d2f5bab27d310fb988701d371232f6604090cd679767a12097a7c217401b05e938c787844ae11138a2c8143c1e675b93599656
|
7
|
+
data.tar.gz: 8d2e592ba27bba30e100ea23a2bf951d6f3861c422ef355bad7cd38e146c20c2cf53380aa7ee440f40aa2d111c7f33eadc8dc09f73d1689432fff071e04228c4
|
data/lib/brick/extensions.rb
CHANGED
@@ -1289,7 +1289,10 @@ module ActiveRecord::ConnectionHandling
|
|
1289
1289
|
alias _brick_establish_connection establish_connection
|
1290
1290
|
def establish_connection(*args)
|
1291
1291
|
conn = _brick_establish_connection(*args)
|
1292
|
-
|
1292
|
+
begin
|
1293
|
+
_brick_reflect_tables
|
1294
|
+
rescue ActiveRecord::NoDatabaseError
|
1295
|
+
end
|
1293
1296
|
conn
|
1294
1297
|
end
|
1295
1298
|
|
@@ -1313,16 +1316,21 @@ module ActiveRecord::ConnectionHandling
|
|
1313
1316
|
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
1314
1317
|
|
1315
1318
|
is_postgres = nil
|
1316
|
-
schema_sql = 'SELECT NULL AS table_schema;'
|
1317
1319
|
case ActiveRecord::Base.connection.adapter_name
|
1318
1320
|
when 'PostgreSQL'
|
1319
1321
|
is_postgres = true
|
1322
|
+
db_schemas = ActiveRecord::Base.execute_sql('SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;')
|
1323
|
+
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
1324
|
+
row = row.is_a?(String) ? row : row['table_schema']
|
1325
|
+
# Remove any system schemas
|
1326
|
+
s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
|
1327
|
+
end
|
1320
1328
|
if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
|
1321
|
-
(sta = multitenancy[:schema_to_analyse]) != 'public')
|
1329
|
+
(sta = multitenancy[:schema_to_analyse]) != 'public') &&
|
1330
|
+
::Brick.db_schemas.include?(sta)
|
1322
1331
|
::Brick.default_schema = schema = sta
|
1323
1332
|
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
1324
1333
|
end
|
1325
|
-
schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
|
1326
1334
|
when 'Mysql2'
|
1327
1335
|
::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
|
1328
1336
|
when 'SQLite'
|
@@ -1339,16 +1347,7 @@ module ActiveRecord::ConnectionHandling
|
|
1339
1347
|
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
1340
1348
|
end
|
1341
1349
|
|
1342
|
-
|
1343
|
-
db_schemas = db_schemas.to_a
|
1344
|
-
end
|
1345
|
-
unless db_schemas.empty?
|
1346
|
-
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
1347
|
-
row = row.is_a?(String) ? row : row['table_schema']
|
1348
|
-
# Remove any system schemas
|
1349
|
-
s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
|
1350
|
-
end
|
1351
|
-
end
|
1350
|
+
::Brick.db_schemas ||= []
|
1352
1351
|
|
1353
1352
|
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
1354
1353
|
if (possible_schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
|
data/lib/brick/version_number.rb
CHANGED
@@ -7,13 +7,13 @@ module Brick
|
|
7
7
|
class InstallGenerator < ::Rails::Generators::Base
|
8
8
|
# include ::Rails::Generators::Migration
|
9
9
|
|
10
|
-
source_root File.expand_path('templates', __dir__)
|
11
|
-
class_option(
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
)
|
10
|
+
# source_root File.expand_path('templates', __dir__)
|
11
|
+
# class_option(
|
12
|
+
# :with_changes,
|
13
|
+
# type: :boolean,
|
14
|
+
# default: false,
|
15
|
+
# desc: 'Store changeset (diff) with each version'
|
16
|
+
# )
|
17
17
|
|
18
18
|
desc 'Generates an initializer file for configuring Brick'
|
19
19
|
|
@@ -0,0 +1,265 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
require 'rails/generators/active_record'
|
5
|
+
require 'fancy_gets'
|
6
|
+
|
7
|
+
module Brick
|
8
|
+
# Auto-generates migrations
|
9
|
+
class MigrationsGenerator < ::Rails::Generators::Base
|
10
|
+
include FancyGets
|
11
|
+
# include ::Rails::Generators::Migration
|
12
|
+
|
13
|
+
# SQL types that are the same as their migration data type name: text, integer, bigint, date, boolean, decimal, float
|
14
|
+
SQL_TYPES = { 'character varying' => 'string',
|
15
|
+
'character' => 'string', # %%% Need to put in "limit: 1"
|
16
|
+
'xml' => 'text',
|
17
|
+
'bytea' => 'binary',
|
18
|
+
'timestamp without time zone' => 'timestamp',
|
19
|
+
'timestamp with time zone' => 'timestamp',
|
20
|
+
'time without time zone' => 'time',
|
21
|
+
'time with time zone' => 'time',
|
22
|
+
'double precision' => 'float', # might work with 'double'
|
23
|
+
'smallint' => 'integer' } # %%% Need to put in "limit: 2"
|
24
|
+
# (Still need to find what "inet" and "json" data types map to.)
|
25
|
+
|
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.'
|
35
|
+
|
36
|
+
def brick_migrations
|
37
|
+
# If Apartment is active, see if a default schema to analyse is indicated
|
38
|
+
|
39
|
+
# # Load all models
|
40
|
+
# Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
|
41
|
+
|
42
|
+
if (tables = ::Brick.relations.reject { |k, v| v.key?(:isView) && v[:isView] == true }.map(&:first).sort).empty?
|
43
|
+
puts "No tables found in database #{ActiveRecord::Base.connection.current_database}."
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
key_type = (ActiveRecord.version < ::Gem::Version.new('5.1') ? 'integer' : 'bigint')
|
48
|
+
is_4x_rails = ActiveRecord.version < ::Gem::Version.new('5.0')
|
49
|
+
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)
|
52
|
+
if Dir["#{mig_path}/**/*.rb"].present?
|
53
|
+
puts "WARNING: migrations folder #{mig_path} appears to already have ruby files present."
|
54
|
+
mig_path2 = "#{::Rails.root}/tmp/brick_migrations"
|
55
|
+
if Dir.exist?(mig_path2)
|
56
|
+
if Dir["#{mig_path2}/**/*.rb"].present?
|
57
|
+
puts "As well, temporary folder #{mig_path2} also has ruby files present."
|
58
|
+
puts "Choose a destination -- all existing .rb files will be removed:"
|
59
|
+
mig_path2 = gets_list(list: ['Cancel operation!', "Append migration files into #{mig_path} anyway", mig_path, mig_path2])
|
60
|
+
return if mig_path2.start_with?('Cancel')
|
61
|
+
|
62
|
+
if mig_path2.start_with?('Append migration files into ')
|
63
|
+
mig_path2 = mig_path
|
64
|
+
else
|
65
|
+
Dir["#{mig_path2}/**/*.rb"].each { |rb| File.delete(rb) }
|
66
|
+
end
|
67
|
+
else
|
68
|
+
puts "Using temporary folder #{mig_path2} for created migration files.\n\n"
|
69
|
+
end
|
70
|
+
else
|
71
|
+
puts "Creating the temporary folder #{mig_path2} for created migration files.\n\n"
|
72
|
+
Dir.mkdir(mig_path2)
|
73
|
+
end
|
74
|
+
mig_path = mig_path2
|
75
|
+
else
|
76
|
+
puts "Using standard migration folder #{mig_path} for created migration files.\n\n"
|
77
|
+
end
|
78
|
+
else
|
79
|
+
puts "Creating standard ActiveRecord migration folder #{mig_path} to hold new migration files.\n\n"
|
80
|
+
Dir.mkdir(mig_path)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Generate a list of tables that can be chosen
|
84
|
+
chosen = gets_list(list: tables, chosen: tables.dup)
|
85
|
+
# 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
|
87
|
+
done = []
|
88
|
+
fks = {}
|
89
|
+
stuck = {}
|
90
|
+
indexes = {} # Track index names to make sure things are unique
|
91
|
+
versions = [] # Resulting versions to be used when updating the schema_migrations table
|
92
|
+
# Start by making migrations for fringe tables (those with no foreign keys).
|
93
|
+
# Continue layer by layer, creating migrations for tables that reference ones already done, until
|
94
|
+
# no more migrations can be created. (At that point hopefully all tables are accounted for.)
|
95
|
+
while (fringe = chosen.reject do |tbl|
|
96
|
+
snags = ::Brick.relations.fetch(tbl, nil)&.fetch(:fks, nil)&.select do |_k, v|
|
97
|
+
v[:is_bt] && !v[:polymorphic] &&
|
98
|
+
tbl != v[:inverse_table] && # Ignore self-referencing associations (stuff like "parent_id")
|
99
|
+
!done.include?(v[:inverse_table])
|
100
|
+
end
|
101
|
+
stuck[tbl] = snags if snags&.present?
|
102
|
+
end).present?
|
103
|
+
fringe.each do |tbl|
|
104
|
+
next unless (relation = ::Brick.relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present?
|
105
|
+
|
106
|
+
pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ActiveRecord::Base.primary_key].flatten)
|
107
|
+
# In case things aren't as standard
|
108
|
+
if pkey_cols.empty?
|
109
|
+
pkey_cols = if rpk.empty? && relation[:cols][arpk.first]&.first == key_type
|
110
|
+
arpk
|
111
|
+
elsif rpk.first
|
112
|
+
rpk
|
113
|
+
end
|
114
|
+
end
|
115
|
+
schema = if (tbl_parts = tbl.split('.')).length > 1
|
116
|
+
if tbl_parts.first == 'public'
|
117
|
+
tbl_parts.shift
|
118
|
+
nil
|
119
|
+
else
|
120
|
+
tbl_parts.first
|
121
|
+
end
|
122
|
+
end
|
123
|
+
# %%% For the moment we're skipping polymorphics
|
124
|
+
fkey_cols = relation[:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
|
125
|
+
mig = +"class Create#{(full_table_name = tbl_parts.join('_')).camelize} < ActiveRecord::Migration#{ar_version}\n"
|
126
|
+
# Support missing primary key (by adding: ,id: false)
|
127
|
+
# also integer / uuid / other non-standard data types for primary key
|
128
|
+
id_option = unless (pkey_col_first = relation[:cols][pkey_cols&.first]&.first) == key_type
|
129
|
+
unless pkey_cols&.present?
|
130
|
+
', id: false'
|
131
|
+
else
|
132
|
+
case pkey_col_first
|
133
|
+
when 'integer'
|
134
|
+
', id: :serial'
|
135
|
+
when 'bigint'
|
136
|
+
', id: :bigserial'
|
137
|
+
else
|
138
|
+
", id: :#{SQL_TYPES[pkey_col_first] || pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
|
139
|
+
end +
|
140
|
+
(pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '') +
|
141
|
+
(!is_4x_rails && (comment = relation&.fetch(:description, nil))&.present? ? ", comment: #{comment.inspect}" : '')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
# Find the ActiveRecord class in order to see if the columns have comments
|
145
|
+
unless is_4x_rails
|
146
|
+
klass = begin
|
147
|
+
tbl.tr('.', '/').singularize.camelize.constantize
|
148
|
+
rescue StandardError
|
149
|
+
end
|
150
|
+
if klass
|
151
|
+
unless ActiveRecord::Migration.table_exists?(klass.table_name)
|
152
|
+
puts "WARNING: Unable to locate table #{klass.table_name} (for #{klass.name})."
|
153
|
+
klass = nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
# Refer to this table name as a symbol or dotted string as appropriate
|
158
|
+
tbl_code = tbl_parts.length == 1 ? ":#{tbl_parts.first}" : "'#{tbl}'"
|
159
|
+
mig << " def change\n return unless reverting? || !table_exists?(#{tbl_code})\n\n"
|
160
|
+
mig << " create_schema :#{schema} unless schema_exists?(:#{schema})\n" if schema
|
161
|
+
mig << " create_table #{tbl_code}#{id_option} do |t|\n"
|
162
|
+
possible_ts = [] # Track possible generic timestamps
|
163
|
+
add_fks = [] # Track foreign keys to add after table creation
|
164
|
+
relation[:cols].each do |col, col_type|
|
165
|
+
next if !id_option&.end_with?('id: false') && pkey_cols.include?(col)
|
166
|
+
|
167
|
+
# See if there are generic timestamps
|
168
|
+
if (sql_type = SQL_TYPES[col_type.first]) == 'timestamp' &&
|
169
|
+
['created_at','updated_at'].include?(col)
|
170
|
+
possible_ts << [col, !col_type[3]]
|
171
|
+
next
|
172
|
+
end
|
173
|
+
|
174
|
+
sql_type ||= col_type.first
|
175
|
+
suffix = col_type[3] ? +', null: false' : +''
|
176
|
+
if !is_4x_rails && klass && (comment = klass.columns_hash.fetch(col, nil)&.comment)&.present?
|
177
|
+
suffix << ", comment: #{comment.inspect}"
|
178
|
+
end
|
179
|
+
# Determine if this column is used as part of a foreign key
|
180
|
+
if fk = fkey_cols.find { |assoc| col == assoc[:fk] }
|
181
|
+
to_table = fk[:inverse_table].split('.')
|
182
|
+
to_table = to_table.length == 1 ? ":#{to_table.first}" : "'#{fk[:inverse_table]}'"
|
183
|
+
if fk[:fk] != "#{fk[:assoc_name].singularize}_id" # Need to do our own foreign_key tricks, not use references?
|
184
|
+
column = fk[:fk]
|
185
|
+
mig << " t.#{sql_type} :#{column}#{suffix}\n"
|
186
|
+
add_fks << [to_table, column, ::Brick.relations[fk[:inverse_table]]]
|
187
|
+
else
|
188
|
+
suffix << ", type: :#{sql_type}" unless sql_type == key_type
|
189
|
+
# Will the resulting default index name be longer than what Postgres allows? (63 characters)
|
190
|
+
if (idx_name = ActiveRecord::Base.connection.index_name(tbl, {column: col})).length > 63
|
191
|
+
# Try to find a shorter name that hasn't been used yet
|
192
|
+
unless indexes.key?(shorter = idx_name[0..62]) ||
|
193
|
+
indexes.key?(shorter = idx_name.tr('_', '')[0..62]) ||
|
194
|
+
indexes.key?(shorter = idx_name.tr('aeio', '')[0..62])
|
195
|
+
puts "Unable to easily find unique name for index #{idx_name} that is shorter than 64 characters,"
|
196
|
+
puts "so have resorted to this GUID-based identifier: #{shorter = "#{tbl[0..25]}_#{::SecureRandom.uuid}"}."
|
197
|
+
end
|
198
|
+
suffix << ", index: { name: '#{shorter || idx_name}' }"
|
199
|
+
indexes[shorter || idx_name] = nil
|
200
|
+
end
|
201
|
+
mig << " t.references :#{fk[:assoc_name]}#{suffix}, foreign_key: { to_table: #{to_table} }\n"
|
202
|
+
end
|
203
|
+
else
|
204
|
+
mig << emit_column(sql_type, col, suffix)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
if possible_ts.length == 2 && # Both created_at and updated_at
|
208
|
+
# Rails 5 and later timestamps default to NOT NULL
|
209
|
+
(possible_ts.first.last == is_4x_rails && possible_ts.last.last == is_4x_rails)
|
210
|
+
mig << "\n t.timestamps\n"
|
211
|
+
else # Just one or the other, or a nullability mismatch
|
212
|
+
possible_ts.each { |ts| emit_column('timestamp', ts.first, nil) }
|
213
|
+
end
|
214
|
+
mig << " end\n"
|
215
|
+
add_fks.each do |add_fk|
|
216
|
+
is_commented = false
|
217
|
+
# add_fk[2] holds the inverse relation
|
218
|
+
unless (pk = add_fk[2][:pkey].values.flatten&.first)
|
219
|
+
is_commented = true
|
220
|
+
mig << " # (Unable to create relationship because primary key is missing on table #{add_fk[0]})\n"
|
221
|
+
# No official PK, but if coincidentally there's a column of the same name, take a chance on it
|
222
|
+
pk = (add_fk[2][:cols].key?(add_fk[1]) && add_fk[1]) || '???'
|
223
|
+
end
|
224
|
+
# to_table column
|
225
|
+
mig << " #{'# ' if is_commented}add_foreign_key #{tbl_code}, #{add_fk[0]}, column: :#{add_fk[1]}, primary_key: :#{pk}\n"
|
226
|
+
end
|
227
|
+
mig << " end\nend\n"
|
228
|
+
current_mig_time += 1.minute
|
229
|
+
versions << (version = current_mig_time.strftime('%Y%m%d%H%M00'))
|
230
|
+
File.open("#{mig_path}/#{version}_create_#{full_table_name}.rb", "w") { |f| f.write mig }
|
231
|
+
end
|
232
|
+
done.concat(fringe)
|
233
|
+
chosen -= done
|
234
|
+
end
|
235
|
+
stuck_counts = Hash.new { |h, k| h[k] = 0 }
|
236
|
+
chosen.each do |leftover|
|
237
|
+
puts "Can't do #{leftover} because:\n #{stuck[leftover].map do |snag|
|
238
|
+
stuck_counts[snag.last[:inverse_table]] += 1
|
239
|
+
snag.last[:assoc_name]
|
240
|
+
end.join(', ')}"
|
241
|
+
end
|
242
|
+
if mig_path.start_with?(cur_path = ::Rails.root.to_s)
|
243
|
+
pretty_mig_path = mig_path[cur_path.length..-1]
|
244
|
+
end
|
245
|
+
puts "\n*** Created #{done.length} migration files under #{pretty_mig_path || mig_path} ***"
|
246
|
+
if (stuck_sorted = stuck_counts.to_a.sort { |a, b| b.last <=> a.last }).length.positive?
|
247
|
+
puts "-----------------------------------------"
|
248
|
+
puts "Unable to create migrations for #{stuck_sorted.length} tables#{
|
249
|
+
". Here's the top 5 blockers" if stuck_sorted.length > 5
|
250
|
+
}:"
|
251
|
+
pp stuck_sorted[0..4]
|
252
|
+
else # Successful, and now we can update the schema_migrations table accordingly
|
253
|
+
ActiveRecord::Base.execute_sql("INSERT INTO #{ActiveRecord::Base.schema_migrations_table_name} (version) VALUES #{
|
254
|
+
versions.map { |version| "('#{version}')" }.join(', ')
|
255
|
+
}")
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
private
|
260
|
+
|
261
|
+
def emit_column(type, name, suffix)
|
262
|
+
" t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}#{suffix}\n"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
@@ -59,7 +59,6 @@ module Brick
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
models.each do |m| # Find longest name in the list for future use to show lists on the right side of the screen
|
62
|
-
# Strangely this can't be inlined since it assigns to "len"
|
63
62
|
if longest_length < (len = m.name.length)
|
64
63
|
longest_length = len
|
65
64
|
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.56
|
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-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -238,6 +238,7 @@ files:
|
|
238
238
|
- lib/brick/version_number.rb
|
239
239
|
- lib/generators/brick/USAGE
|
240
240
|
- lib/generators/brick/install_generator.rb
|
241
|
+
- lib/generators/brick/migrations_generator.rb
|
241
242
|
- lib/generators/brick/model_generator.rb
|
242
243
|
- lib/generators/brick/templates/add_object_changes_to_versions.rb.erb
|
243
244
|
- lib/generators/brick/templates/create_versions.rb.erb
|