brick 1.0.54 → 1.0.55

Sign up to get free protection for your applications and to get access to all the features.
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