brick 1.0.154 → 1.0.156

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: 647f34b463dc92515c7eb04331726a808b5ea69dd2bbd6ef99c9d6c769868565
4
- data.tar.gz: 22ae7fa65845e65a7848f5cfff0fbb52a864b0161783c3f7d28cfb82ba1f88e5
3
+ metadata.gz: e001ce24602c8fef817c24a0eb88e014e990d98828474acd66d868b50dd68692
4
+ data.tar.gz: 8378593754641ffc86059672fe6148ba767d91a867361b21ec8377a82903af6b
5
5
  SHA512:
6
- metadata.gz: 84034f48010bd159804a19cb865a9cad04ba5efe89bb1e807a7031393d9a64a33d9ca9802f991565f4d73753df087d200099052dde59e4127c6b94370456ec09
7
- data.tar.gz: 57b3cca20e566ef0d4ac72a45f24b025feea45421417b23cbe7cb683877ac228463f5f348f931a6024b9cdf750dde6aa6c49293e1ae1a596a70a0d8f3c128d6c
6
+ metadata.gz: 84fa7d9831dfca26b9b08ef46bd852c3d4f96e1f8420ff086526f97964090b3f95d71b5d66d9ff7ba47ec5f74e50938c9714731d21d0c59ac2365305ab8826bc
7
+ data.tar.gz: cf790bbb66852142fe07cb1619517043dc3ef8c47b0f020b22bc635cd902fface9b52631507b2fdd9520b604c98eae00b58a1bad29ace185c8d342e79e99a062
data/lib/brick/config.rb CHANGED
@@ -380,6 +380,14 @@ module Brick
380
380
  @mutex.synchronize { @always_load_fields = field_set }
381
381
  end
382
382
 
383
+ def ignore_migration_fks
384
+ @mutex.synchronize { @ignore_migration_fks || [] }
385
+ end
386
+
387
+ def ignore_migration_fks=(relations)
388
+ @mutex.synchronize { @ignore_migration_fks = relations }
389
+ end
390
+
383
391
  # Add status page showing all resources and what files have been built out for them
384
392
  def add_status
385
393
  true
@@ -1464,7 +1464,6 @@ class Object
1464
1464
  rescue StandardError => ex
1465
1465
  ::ActiveRecord::Base
1466
1466
  end))
1467
-
1468
1467
  end
1469
1468
  hmts = nil
1470
1469
  code = +"class #{full_name} < #{base_model.name}\n"
@@ -1489,6 +1488,10 @@ class Object
1489
1488
  end
1490
1489
  # Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
1491
1490
  code << " self.table_name = '#{self.table_name = matching}'\n" if inheritable_name || self.table_name != matching
1491
+ if (inh_col = ::Brick.config.sti_type_column.find { |_k, v| v.include?(matching) }&.first)
1492
+ self.inheritance_column = inh_col
1493
+ code << " self.inheritance_column = '#{inh_col}'\n"
1494
+ end
1492
1495
 
1493
1496
  # Override models backed by a view so they return true for #is_view?
1494
1497
  # (Dynamically-created controllers and view templates for such models will then act in a read-only way)
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 154
8
+ TINY = 156
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
data/lib/brick.rb CHANGED
@@ -1159,11 +1159,16 @@ ActiveSupport.on_load(:active_record) do
1159
1159
  # :singleton-method:
1160
1160
  # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
1161
1161
  # dates and times from the database. This is set to :utc by default.
1162
- unless respond_to?(:default_timezone)
1163
- puts "ADDING!!! 4.w"
1162
+ unless ::ActiveRecord.respond_to?(:default_timezone) || respond_to?(:default_timezone)
1164
1163
  mattr_accessor :default_timezone, instance_writer: false
1165
1164
  self.default_timezone = :utc
1166
1165
  end
1166
+
1167
+ unless respond_to?(:primary_abstract_class)
1168
+ def self.primary_abstract_class
1169
+ self.abstract_class = true
1170
+ end
1171
+ end
1167
1172
  end
1168
1173
 
1169
1174
  # Rails < 4.0 cannot do #find_by, #find_or_create_by, or do #pluck on multiple columns, so here are the patches:
@@ -23,7 +23,8 @@ module Brick
23
23
  'time with time zone' => 'time',
24
24
  'double precision' => 'float',
25
25
  'smallint' => 'integer', # %%% Need to put in "limit: 2"
26
- # # Oracle data types
26
+ 'ARRAY' => 'string', # Note that we'll also add ", array: true"
27
+ # Oracle data types
27
28
  'VARCHAR2' => 'string',
28
29
  'CHAR' => 'string',
29
30
  ['NUMBER', 22] => 'integer',
@@ -123,21 +124,27 @@ module Brick
123
124
  built_schemas = {} # Track all built schemas so we can place an appropriate drop_schema command only in the first
124
125
  # migration in which that schema is referenced, thereby allowing rollbacks to function properly.
125
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)
126
128
  # Start by making migrations for fringe tables (those with no foreign keys).
127
129
  # Continue layer by layer, creating migrations for tables that reference ones already done, until
128
130
  # no more migrations can be created. (At that point hopefully all tables are accounted for.)
129
131
  while (fringe = chosen.reject do |tbl|
132
+ snag_fks = []
130
133
  snags = ::Brick.relations.fetch(tbl, nil)&.fetch(:fks, nil)&.select do |_k, v|
131
134
  v[:is_bt] && !v[:polymorphic] &&
132
135
  tbl != v[:inverse_table] && # Ignore self-referencing associations (stuff like "parent_id")
133
- !done.include?(v[:inverse_table])
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
134
143
  end
135
- stuck[tbl] = snags if snags&.present?
136
144
  end).present?
137
145
  fringe.each do |tbl|
138
146
  next unless (relation = ::Brick.relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present?
139
147
 
140
- ar_base = Object.const_defined?(:ApplicationRecord) ? ApplicationRecord : Class.new(ActiveRecord::Base)
141
148
  pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ar_base.primary_key].flatten.sort)
142
149
  # In case things aren't as standard
143
150
  if pkey_cols.empty?
@@ -170,7 +177,7 @@ module Brick
170
177
  # Support missing primary key (by adding: , id: false)
171
178
  id_option = if pk_is_also_fk || !pkey_cols&.present?
172
179
  needs_serial_col = true
173
- ', id: false'
180
+ +', id: false'
174
181
  elsif ((pkey_col_first = (col_def = relation[:cols][pkey_cols&.first])&.first) &&
175
182
  (pkey_col_first = SQL_TYPES[pkey_col_first] || SQL_TYPES[col_def&.[](0..1)] ||
176
183
  SQL_TYPES.find { |r| r.first.is_a?(Regexp) && pkey_col_first =~ r.first }&.last ||
@@ -179,16 +186,16 @@ module Brick
179
186
  )
180
187
  case pkey_col_first
181
188
  when 'integer'
182
- ', id: :serial'
189
+ +', id: :serial'
183
190
  when 'bigint'
184
- ', id: :bigserial'
191
+ +', id: :bigserial'
185
192
  else
186
- ", id: :#{pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
193
+ +", id: :#{pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
187
194
  end +
188
195
  (pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '')
189
196
  end
190
197
  if !id_option && pkey_cols.sort != arpk
191
- id_option = ", primary_key: :#{pkey_cols.first}"
198
+ id_option = +", primary_key: :#{pkey_cols.first}"
192
199
  end
193
200
  if !is_4x_rails && (comment = relation&.fetch(:description, nil))&.present?
194
201
  (id_option ||= +'') << ", comment: #{comment.inspect}"
@@ -217,6 +224,7 @@ module Brick
217
224
  SQL_TYPES.find { |r| r.first.is_a?(Regexp) && col_type.first =~ r.first }&.last ||
218
225
  col_type.first
219
226
  suffix = col_type[3] || pkey_cols&.include?(col) ? +', null: false' : +''
227
+ suffix << ', array: true' if (col_type.first == 'ARRAY')
220
228
  if !is_4x_rails && klass && (comment = klass.columns_hash.fetch(col, nil)&.comment)&.present?
221
229
  suffix << ", comment: #{comment.inspect}"
222
230
  end
@@ -246,7 +254,8 @@ module Brick
246
254
  suffix << ", index: { name: '#{shorter || idx_name}' }"
247
255
  indexes[shorter || idx_name] = nil
248
256
  end
249
- mig << " t.references :#{fk[:assoc_name]}#{suffix}, foreign_key: { to_table: #{to_table} }\n"
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"
250
259
  end
251
260
  else
252
261
  next if !id_option&.end_with?('id: false') && pkey_cols&.include?(col)
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'fancy_gets'
5
+
6
+ module Brick
7
+ class SeedsGenerator < ::Rails::Generators::Base
8
+ include FancyGets
9
+
10
+ desc 'Auto-generates a seeds file from existing data.'
11
+
12
+ def brick_seeds
13
+ # %%% If Apartment is active and there's no schema_to_analyse, ask which schema they want
14
+
15
+ ::Brick.mode = :on
16
+ ActiveRecord::Base.establish_connection
17
+
18
+ if (tables = ::Brick.relations.reject { |k, v| v.key?(:isView) && v[:isView] == true }.map(&:first).sort).empty?
19
+ puts "No tables found in database #{ActiveRecord::Base.connection.current_database}."
20
+ return
21
+ end
22
+
23
+ if File.exist?(seed_file_path = "#{::Rails.root}/db/seeds.rb")
24
+ puts "WARNING: seeds file #{seed_file_path} appears to already be present."
25
+ end
26
+
27
+ # Generate a list of tables that can be chosen
28
+ chosen = gets_list(list: tables, chosen: tables.dup)
29
+ schemas = chosen.each_with_object({}) do |v, s|
30
+ if (v_parts = v.split('.')).length > 1
31
+ s[v_parts.first] = nil unless [::Brick.default_schema, 'public'].include?(v_parts.first)
32
+ end
33
+ end
34
+ seeds = +"# Seeds file for #{ActiveRecord::Base.connection.current_database}:\n"
35
+ done = []
36
+ fks = {}
37
+ stuck = {}
38
+ indexes = {} # Track index names to make sure things are unique
39
+ ar_base = Object.const_defined?(:ApplicationRecord) ? ApplicationRecord : Class.new(ActiveRecord::Base)
40
+ # Start by making entries for fringe models (those with no foreign keys).
41
+ # Continue layer by layer, creating entries for models that reference ones already done, until
42
+ # no more entries can be created. (At that point hopefully all models are accounted for.)
43
+ while (fringe = chosen.reject do |tbl|
44
+ snag_fks = []
45
+ snags = ::Brick.relations.fetch(tbl, nil)&.fetch(:fks, nil)&.select do |_k, v|
46
+ v[:is_bt] && !v[:polymorphic] &&
47
+ tbl != v[:inverse_table] && # Ignore self-referencing associations (stuff like "parent_id")
48
+ !done.include?(v[:inverse_table]) &&
49
+ ::Brick.config.ignore_migration_fks.exclude?(snag_fk = "#{tbl}.#{v[:fk]}") &&
50
+ snag_fks << snag_fk
51
+ end
52
+ if snags&.present?
53
+ # puts snag_fks.inspect
54
+ stuck[tbl] = snags
55
+ end
56
+ end).present?
57
+ seeds << "\n"
58
+ fringe.each do |tbl|
59
+ next unless ::Brick.config.exclude_tables.exclude?(tbl) &&
60
+ (relation = ::Brick.relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present? &&
61
+ (klass = Object.const_get(class_name = relation[:class_name])).table_exists?
62
+
63
+ pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ar_base.primary_key].flatten.sort)
64
+ # In case things aren't as standard
65
+ if pkey_cols.empty?
66
+ pkey_cols = if rpk.empty? # && relation[:cols][arpk.first]&.first == key_type
67
+ arpk
68
+ elsif rpk.first
69
+ rpk
70
+ end
71
+ end
72
+ schema = if (tbl_parts = tbl.split('.')).length > 1
73
+ if tbl_parts.first == (::Brick.default_schema || 'public')
74
+ tbl_parts.shift
75
+ nil
76
+ else
77
+ tbl_parts.first
78
+ end
79
+ end
80
+
81
+ # %%% For the moment we're skipping polymorphics
82
+ fkeys = relation[:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
83
+ # Refer to this table name as a symbol or dotted string as appropriate
84
+ # tbl_code = tbl_parts.length == 1 ? ":#{tbl_parts.first}" : "'#{tbl}'"
85
+
86
+ has_rows = false
87
+ is_empty = true
88
+ klass.order(*pkey_cols).each do |obj|
89
+ unless has_rows
90
+ has_rows = true
91
+ seeds << " puts 'Seeding: #{class_name}'\n"
92
+ end
93
+ is_empty = false
94
+ pk_val = obj.send(pkey_cols.first)
95
+ fk_vals = []
96
+ data = []
97
+ relation[:cols].each do |col, col_type|
98
+ next if !(fk = fkeys.find { |assoc| col == assoc[:fk] }) &&
99
+ pkey_cols.include?(col)
100
+
101
+ if (val = obj.send(col)) && (val.is_a?(Time) || val.is_a?(Date))
102
+ val = val.to_s
103
+ end
104
+ if fk
105
+ inv_tbl = fk[:inverse_table].gsub('.', '__')
106
+ fk_vals << "#{fk[:assoc_name]}: #{inv_tbl}_#{brick_escape(val)}" if val
107
+ else
108
+ data << "#{col}: #{val.inspect}"
109
+ end
110
+ end
111
+ seeds << "#{tbl.gsub('.', '__')}_#{brick_escape(pk_val)} = #{class_name}.create(#{(fk_vals + data).join(', ')})\n"
112
+ end
113
+ seeds << " # (Skipping #{class_name} as it has no rows)\n" unless has_rows
114
+ File.open(seed_file_path, "w") { |f| f.write seeds }
115
+ end
116
+ done.concat(fringe)
117
+ chosen -= done
118
+ end
119
+ stuck_counts = Hash.new { |h, k| h[k] = 0 }
120
+ chosen.each do |leftover|
121
+ puts "Can't do #{leftover} because:\n #{stuck[leftover].map do |snag|
122
+ stuck_counts[snag.last[:inverse_table]] += 1
123
+ snag.last[:assoc_name]
124
+ end.join(', ')}"
125
+ end
126
+ puts "\n*** Created seeds for #{done.length} models in db/seeds.rb ***"
127
+ if (stuck_sorted = stuck_counts.to_a.sort { |a, b| b.last <=> a.last }).length.positive?
128
+ puts "-----------------------------------------"
129
+ puts "Unable to create migrations for #{stuck_sorted.length} tables#{
130
+ ". Here's the top 5 blockers" if stuck_sorted.length > 5
131
+ }:"
132
+ pp stuck_sorted[0..4]
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ def brick_escape(val)
139
+ val = val.to_s if val.is_a?(Date) || val.is_a?(Time) # Accommodate when for whatever reason a primary key is a date or time
140
+ case val
141
+ when String
142
+ ret = +''
143
+ val.each_char do |ch|
144
+ if ch < '0' || (ch > '9' && ch < 'A') || ch > 'Z'
145
+ ret << (ch == '_' ? ch : "x#{'K'.unpack('H*')[0]}")
146
+ else
147
+ ret << ch
148
+ end
149
+ end
150
+ ret
151
+ else
152
+ val
153
+ end
154
+ end
155
+ end
156
+ 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.154
4
+ version: 1.0.156
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-06-29 00:00:00.000000000 Z
11
+ date: 2023-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -257,6 +257,7 @@ files:
257
257
  - lib/generators/brick/install_generator.rb
258
258
  - lib/generators/brick/migrations_generator.rb
259
259
  - lib/generators/brick/models_generator.rb
260
+ - lib/generators/brick/seeds_generator.rb
260
261
  - lib/generators/brick/templates/add_object_changes_to_versions.rb.erb
261
262
  - lib/generators/brick/templates/create_versions.rb.erb
262
263
  homepage: https://github.com/lorint/brick