brick 1.0.154 → 1.0.156

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: 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