brick 1.0.54 → 1.0.55

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: '09f9f2f9f7e21a96d29cdad815c069e52280ea068051f27a542541bcefd33e87'
4
- data.tar.gz: 54e032633f930a43d1bc929d05d241367d9ebbdb8010f588b922d1111077524e
3
+ metadata.gz: 430a2dfa5ef0caee9b99bc341ccd42a9d29e8f1d54071284aa2469e1baa0c659
4
+ data.tar.gz: b36e18de169bd9032e6c383548ec30cce06eeb97a35e8c3e4f4da935aff8f603
5
5
  SHA512:
6
- metadata.gz: 56cad221c2b20ac62839c1b7904c8acb9bc392b0779946bbe325c48c8ee5cf00b33413e070674343809ee851804e5f9bbec2d6469a148688c4aecbbcaee3e09d
7
- data.tar.gz: 9757effb2a1d058ae4ccd259b59e0218e002928d90dadfd6ff3d0acd05fb12e27cfcfef30afa3964637e0e4f1e4246b410deba45fb01e452e20056df94820377
6
+ metadata.gz: c51491575b9ee56c3789619bfc74a2d9c0525750687224e94e120cbae42fad8db1abda9d82978115d9266c55fa0c6f914ce273a3613915657d8a0cf4e7fe6aeb
7
+ data.tar.gz: a0646cfdc9a2c844fd1149968d0175346c48835c6245d7fe92d0126d60708cf76514704e29dc3f506d2e06e546c8c995bf7103d6ef42be481bb931b4d2bfa73f
@@ -1316,16 +1316,21 @@ module ActiveRecord::ConnectionHandling
1316
1316
  # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
1317
1317
 
1318
1318
  is_postgres = nil
1319
- schema_sql = 'SELECT NULL AS table_schema;'
1320
1319
  case ActiveRecord::Base.connection.adapter_name
1321
1320
  when 'PostgreSQL'
1322
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
1323
1328
  if (is_multitenant = (multitenancy = ::Brick.config.schema_behavior[:multitenant]) &&
1324
- (sta = multitenancy[:schema_to_analyse]) != 'public')
1329
+ (sta = multitenancy[:schema_to_analyse]) != 'public') &&
1330
+ ::Brick.db_schemas.include?(sta)
1325
1331
  ::Brick.default_schema = schema = sta
1326
1332
  ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
1327
1333
  end
1328
- schema_sql = 'SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;'
1329
1334
  when 'Mysql2'
1330
1335
  ::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
1331
1336
  when 'SQLite'
@@ -1342,16 +1347,7 @@ module ActiveRecord::ConnectionHandling
1342
1347
  puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
1343
1348
  end
1344
1349
 
1345
- unless (db_schemas = ActiveRecord::Base.execute_sql(schema_sql)).is_a?(Array)
1346
- db_schemas = db_schemas.to_a
1347
- end
1348
- unless db_schemas.empty?
1349
- ::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
1350
- row = row.is_a?(String) ? row : row['table_schema']
1351
- # Remove any system schemas
1352
- s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
1353
- end
1354
- end
1350
+ ::Brick.db_schemas ||= []
1355
1351
 
1356
1352
  if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1357
1353
  if (possible_schema = ::Brick.config.schema_behavior&.[](:multitenant)&.[](:schema_to_analyse))
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 54
8
+ TINY = 55
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
@@ -10,11 +10,17 @@ module Brick
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, smallint, date, boolean, decimal, float
14
- SQL_TYPES = { 'character varying' =>'string',
15
- 'timestamp without time zone' =>'timestamp',
16
- 'timestamp with time zone' =>'timestamp',
17
- 'double precision' =>'float' } # might work with 'double'
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"
18
24
  # (Still need to find what "inet" and "json" data types map to.)
19
25
 
20
26
  # # source_root File.expand_path('templates', __dir__)
@@ -38,24 +44,22 @@ module Brick
38
44
  return
39
45
  end
40
46
 
41
- ar_version = unless ActiveRecord.version < ::Gem::Version.new('5.0')
42
- arv = ActiveRecord.version.segments[0..1]
43
- arv.pop if arv&.last == 0
44
- "[#{arv.join('.')}]"
45
- end
46
- default_mig_path = (mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{File.expand_path(__dir__)}/db/migrate")
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")
47
51
  if Dir.exist?(mig_path)
48
52
  if Dir["#{mig_path}/**/*.rb"].present?
49
53
  puts "WARNING: migrations folder #{mig_path} appears to already have ruby files present."
50
- mig_path2 = "#{File.expand_path(__dir__)}/tmp/brick_migrations"
54
+ mig_path2 = "#{::Rails.root}/tmp/brick_migrations"
51
55
  if Dir.exist?(mig_path2)
52
56
  if Dir["#{mig_path2}/**/*.rb"].present?
53
57
  puts "As well, temporary folder #{mig_path2} also has ruby files present."
54
58
  puts "Choose a destination -- all existing .rb files will be removed:"
55
- mig_path2 = gets_list(list: ['Cancel operation!', "Add all migration files to #{mig_path} anyway", mig_path, mig_path2])
59
+ mig_path2 = gets_list(list: ['Cancel operation!', "Append migration files into #{mig_path} anyway", mig_path, mig_path2])
56
60
  return if mig_path2.start_with?('Cancel')
57
61
 
58
- if mig_path2.start_with?('Add all migration files to ')
62
+ if mig_path2.start_with?('Append migration files into ')
59
63
  mig_path2 = mig_path
60
64
  else
61
65
  Dir["#{mig_path2}/**/*.rb"].each { |rb| File.delete(rb) }
@@ -78,7 +82,7 @@ module Brick
78
82
 
79
83
  # Generate a list of tables that can be chosen
80
84
  chosen = gets_list(list: tables, chosen: tables.dup)
81
- # Work from now back the same number of minutes as expected migrations to create
85
+ # Start the timestamps back the same number of minutes from now as expected number of migrations to create
82
86
  current_mig_time = Time.now - chosen.length.minutes
83
87
  done = []
84
88
  fks = {}
@@ -87,48 +91,110 @@ module Brick
87
91
  # Continue layer by layer, creating migrations for tables that reference ones already done, until
88
92
  # no more migrations can be created. (At that point hopefully all tables are accounted for.)
89
93
  while (fringe = chosen.reject do |tbl|
90
- snags = ::Brick.relations[tbl][:fks].select do |_k, v|
94
+ snags = ::Brick.relations.fetch(tbl, nil)&.fetch(:fks, nil)&.select do |_k, v|
91
95
  v[:is_bt] && !v[:polymorphic] &&
92
96
  tbl != v[:inverse_table] && # Ignore self-referencing associations (stuff like "parent_id")
93
97
  !done.include?(v[:inverse_table])
94
98
  end
95
- stuck[tbl] = snags if snags.present?
99
+ stuck[tbl] = snags if snags&.present?
96
100
  end).present?
97
101
  fringe.each do |tbl|
98
- tbl = tbl[7..-1] if tbl.start_with?('public.') # %%% Provide better multitenancy support later
99
- pkey_cols = ::Brick.relations[chosen.first][:pkey].values.flatten
102
+ next unless (relation = ::Brick.relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present?
103
+
104
+ pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ActiveRecord::Base.primary_key].flatten)
105
+ # In case things aren't as standard
106
+ if pkey_cols.empty?
107
+ pkey_cols = if rpk.empty? && relation[:cols][arpk.first]&.first == key_type
108
+ arpk
109
+ elsif rpk.first
110
+ rpk
111
+ end
112
+ end
113
+ schema = if (tbl_parts = tbl.split('.')).length > 1
114
+ if tbl_parts.first == 'public'
115
+ tbl_parts.shift
116
+ nil
117
+ else
118
+ tbl_parts.first
119
+ end
120
+ end
100
121
  # %%% For the moment we're skipping polymorphics
101
- fkey_cols = ::Brick.relations[tbl][:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
102
- mig = +"class Create#{table_name = tbl.camelize} < ActiveRecord::Migration#{ar_version}\n"
103
- # %%% Support missing foreign key (by adding: ,id: false)
104
- # also bigint / uuid / other non-standard data type for primary key
105
- mig << " def change\n create_table :#{tbl} do |t|\n"
122
+ 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"
124
+ # 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?
128
+ ', id: false'
129
+ else
130
+ case pkey_col_first
131
+ when 'integer'
132
+ ', id: :serial'
133
+ when 'bigint'
134
+ ', id: :bigserial'
135
+ else
136
+ ", 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}" : '')
138
+ end
139
+ end
140
+ # 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"
106
145
  possible_ts = [] # Track possible generic timestamps
107
- (relation = ::Brick.relations[tbl])[:cols].each do |col, col_type|
108
- next if pkey_cols.include?(col)
146
+ add_fks = [] # Track foreign keys to add after table creation
147
+ relation[:cols].each do |col, col_type|
148
+ next if !id_option&.end_with?('id: false') && pkey_cols.include?(col)
109
149
 
110
150
  # See if there are generic timestamps
111
- if (sql_type = SQL_TYPES[col_type.first]) == 'timestamp' && ['created_at','updated_at'].include?(col)
112
- possible_ts << col
151
+ if (sql_type = SQL_TYPES[col_type.first]) == 'timestamp' &&
152
+ ['created_at','updated_at'].include?(col)
153
+ possible_ts << [col, !col_type[3]]
113
154
  next
114
155
  end
115
156
 
116
157
  sql_type ||= col_type.first
158
+ suffix = col_type[3] ? +', null: false' : +''
117
159
  # Determine if this column is used as part of a foreign key
118
160
  if fk = fkey_cols.find { |assoc| col == assoc[:fk] }
119
- mig << " t.references :#{fk[:assoc_name]}#{", type: #{sql_type}" unless sql_type == 'integer'}\n"
161
+ to_table = fk[:inverse_table].split('.')
162
+ to_table = to_table.length == 1 ? ":#{to_table.first}" : "'#{fk[:inverse_table]}'"
163
+ if fk[:fk] != "#{fk[:assoc_name].singularize}_id" # Need to do our own foreign_key tricks, not use references?
164
+ column = fk[:fk]
165
+ mig << " t.#{sql_type} :#{column}#{suffix}\n"
166
+ add_fks << [to_table, column, ::Brick.relations[fk[:inverse_table]]]
167
+ else
168
+ suffix << ", type: :#{sql_type}" unless sql_type == key_type
169
+ mig << " t.references :#{fk[:assoc_name]}#{suffix}, foreign_key: { to_table: #{to_table} }\n"
170
+ end
120
171
  else
121
- mig << emit_column(sql_type, col)
172
+ mig << emit_column(sql_type, col, suffix)
122
173
  end
123
174
  end
124
- if possible_ts.length == 2 # Both created_at and updated_at
175
+ if possible_ts.length == 2 && # Both created_at and updated_at
176
+ # Rails 5 and later timestamps default to NOT NULL
177
+ (possible_ts.first.last == is_4x_rails && possible_ts.last.last == is_4x_rails)
125
178
  mig << "\n t.timestamps\n"
126
- else # Just one or the other
127
- possible_ts.each { |ts| emit_column('timestamp', ts) }
179
+ else # Just one or the other, or a nullability mismatch
180
+ possible_ts.each { |ts| emit_column('timestamp', ts.first, nil) }
128
181
  end
129
- mig << " end\n end\nend\n"
182
+ mig << " end\n"
183
+ add_fks.each do |add_fk|
184
+ is_commented = false
185
+ # add_fk[2] holds the inverse relation
186
+ unless (pk = add_fk[2][:pkey].values.flatten&.first)
187
+ is_commented = true
188
+ mig << " # (Unable to create relationship because primary key is missing on table #{add_fk[0]})\n"
189
+ # No official PK, but if coincidentally there's a column of the same name, take a chance on it
190
+ pk = (add_fk[2][:cols].key?(add_fk[1]) && add_fk[1]) || '???'
191
+ 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"
194
+ end
195
+ mig << " end\nend\n"
130
196
  current_mig_time += 1.minute
131
- File.open("#{mig_path}/#{current_mig_time.strftime('%Y%m%d%H%M00')}_create_#{tbl}.rb", "w") { |f| f.write mig }
197
+ File.open("#{mig_path}/#{current_mig_time.strftime('%Y%m%d%H%M00')}_create_#{full_table_name}.rb", "w") { |f| f.write mig }
132
198
  end
133
199
  done.concat(fringe)
134
200
  chosen -= done
@@ -140,7 +206,9 @@ module Brick
140
206
  snag.last[:assoc_name]
141
207
  end.join(', ')}"
142
208
  end
143
- pretty_mig_path = mig_path[cur_path.length] if mig_path.start_with?(cur_path = File.expand_path(__dir__))
209
+ if mig_path.start_with?(cur_path = ::Rails.root.to_s)
210
+ pretty_mig_path = mig_path[cur_path.length..-1]
211
+ end
144
212
  puts "*** Created #{done.length} migration files under #{pretty_mig_path || mig_path} ***"
145
213
  if (stuck_sorted = stuck_counts.to_a.sort { |a, b| b.last <=> a.last }).length.positive?
146
214
  puts "-----------------------------------------"
@@ -153,8 +221,8 @@ module Brick
153
221
 
154
222
  private
155
223
 
156
- def emit_column(type, name)
157
- " t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}\n"
224
+ def emit_column(type, name, suffix)
225
+ " t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}#{suffix}\n"
158
226
  end
159
227
  end
160
228
  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.54
4
+ version: 1.0.55
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-08 00:00:00.000000000 Z
11
+ date: 2022-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord