brick 1.0.157 → 1.0.158

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: 995a300b0971af02a39ed75857ce349c99720068555cbb1d6311a75d9a13f2b8
4
- data.tar.gz: e9153dac19552fc8dd581774f89b9ed682ac040b5a4dc0d47f4b28fe079ca36d
3
+ metadata.gz: 326ec09b1c28cf7c54e54bae9ea6ceed7d7e476a177be0a41af14e04f7cc3d11
4
+ data.tar.gz: 0507c22a2acf86c58737ecfedeb1a1f5ed6979b5263b28c6d6e77b0e9500ab7c
5
5
  SHA512:
6
- metadata.gz: 4c0c8553d5ef266aa6e947d6f0dd9ee4ae23d64118cd3c69565345747be0afa4de63be999976bd8e4fed55ebec2f3551aa87a4f4b84dee0a3dced188d23260f6
7
- data.tar.gz: a7e0dbdd27c5d0905d7acf0a7a3f6fb5e254556ef65a0633a3021c395664c65f152afd3cecc53992c0f5da0095a1246b10d14898c34d82c60fc1cec298e62a68
6
+ metadata.gz: d6803593a5c3e17ef40005c405017ed4e7519024ca172a077ad377c201a7b2e2caf2a8b4bc5a0ef63f091124a44199ffd3b386db7d6b5d51352f2e3327537d81
7
+ data.tar.gz: 486b0a15422b0d3150b575c8d19c24cc3434e3fd8f35335d2e490e1e69e423995a2032de913def73b4f4df0e2789c6f4af7d61930db5cc144d0a0cb76be3ebdc
@@ -89,6 +89,13 @@ module ActiveRecord
89
89
  def brick_foreign_type(assoc)
90
90
  reflect_on_association(assoc).foreign_type || "#{assoc}_type"
91
91
  end
92
+
93
+ def _brick_all_fields
94
+ rtans = if respond_to?(:rich_text_association_names)
95
+ rich_text_association_names&.map { |rtan| rtan.to_s.start_with?('rich_text_') ? rtan[10..-1] : rtan }
96
+ end
97
+ columns_hash.keys.map(&:to_sym) + (rtans || [])
98
+ end
92
99
  end
93
100
 
94
101
  def self._brick_primary_key(relation = nil)
@@ -357,7 +364,7 @@ module ActiveRecord
357
364
  # Support nested attributes which use the friendly_id gem
358
365
  assoc.klass._brick_nested_friendly_id if Object.const_defined?('FriendlyId') &&
359
366
  assoc.klass.instance_variable_get(:@friendly_id_config)
360
- new_attrib_text = assoc.klass._brick_find_permits(assoc, (new_permits = assoc.klass.columns_hash.keys.map(&:to_sym)), done_permits)
367
+ new_attrib_text = assoc.klass._brick_find_permits(assoc, (new_permits = assoc.klass._brick_all_fields), done_permits)
361
368
  new_permits << :_destroy
362
369
  current_permits << { "#{assoc.name}_attributes".to_sym => new_permits }
363
370
  s << "#{assoc.name}_attributes: #{new_attrib_text}"
@@ -2152,13 +2159,7 @@ class Object
2152
2159
  code << " end\n"
2153
2160
  self.define_method :new do
2154
2161
  _schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
2155
- if (new_obj = model.new).respond_to?(:serializable_hash)
2156
- # Convert any Filename objects with nil into an empty string so that #encode can be called on them
2157
- new_obj.serializable_hash.each do |k, v|
2158
- new_obj.send("#{k}=", ActiveStorage::Filename.new('')) if v.is_a?(ActiveStorage::Filename) && !v.instance_variable_get(:@filename)
2159
- end if Object.const_defined?('ActiveStorage')
2160
- end
2161
- new_obj.attribute_names.each do |a|
2162
+ new_params = model.attribute_names.each_with_object({}) do |a, s|
2162
2163
  if (val = params["__#{a}"])
2163
2164
  # val = case new_obj.class.column_for_attribute(a).type
2164
2165
  # when :datetime, :date, :time, :timestamp
@@ -2166,9 +2167,15 @@ class Object
2166
2167
  # else
2167
2168
  # val
2168
2169
  # end
2169
- new_obj.send("#{a}=", val)
2170
+ s[a] = val
2170
2171
  end
2171
2172
  end
2173
+ if (new_obj = model.new(new_params)).respond_to?(:serializable_hash)
2174
+ # Convert any Filename objects with nil into an empty string so that #encode can be called on them
2175
+ new_obj.serializable_hash.each do |k, v|
2176
+ new_obj.send("#{k}=", ActiveStorage::Filename.new('')) if v.is_a?(ActiveStorage::Filename) && !v.instance_variable_get(:@filename)
2177
+ end if Object.const_defined?('ActiveStorage')
2178
+ end
2172
2179
  instance_variable_set("@#{singular_table_name}".to_sym, new_obj)
2173
2180
  add_csp_hash
2174
2181
  end
@@ -2310,7 +2317,7 @@ class Object
2310
2317
 
2311
2318
  if is_need_params
2312
2319
  code << " def #{params_name}\n"
2313
- permits_txt = model._brick_find_permits(model, permits = model.columns_hash.keys.map(&:to_sym))
2320
+ permits_txt = model._brick_find_permits(model, permits = model._brick_all_fields)
2314
2321
  code << " params.require(:#{require_name = model.name.underscore.tr('/', '_')
2315
2322
  }).permit(#{permits_txt.map(&:inspect).join(', ')})\n"
2316
2323
  code << " end\n"
@@ -713,12 +713,9 @@ window.addEventListener(\"popstate\", linkSchemas);
713
713
  next unless @_brick_model.instance_methods.include?(through) &&
714
714
  (associative = @_brick_model._br_associatives.fetch(hm.first, nil))
715
715
 
716
- tbl_nm = if (source = hm_assoc.source_reflection).macro == :has_many
717
- source.inverse_of&.name # For HM -> HM style HMT
718
- else # belongs_to or has_one
719
- hm_assoc.through_reflection&.name # for standard HMT, which is HM -> BT
720
- end
721
- # If there is no inverse available for the source belongs_to association, make one based on the class name
716
+ # Should handle standard HMT, which is HM -> BT, as well as HM -> HM style HMT
717
+ tbl_nm = hm_assoc.source_reflection&.inverse_of&.name
718
+ # If there is no inverse available for the source belongs_to association, infer one based on the class name
722
719
  unless tbl_nm
723
720
  tbl_nm = associative.class_name.underscore
724
721
  tbl_nm.slice!(0) if tbl_nm[0] == '/'
@@ -1679,12 +1676,22 @@ end
1679
1676
  @#{obj_name}.send(\"#\{model.brick_foreign_type(v.first)}=\", v[1].first&.first&.name)
1680
1677
  end
1681
1678
  end if @#{obj_name}.new_record?
1682
- @#{obj_name}.attributes.each do |k, val|
1683
- next if !(col = #{model_name}.columns_hash[k]) ||
1684
- (#{(pk.map(&:to_s) || []).inspect}.include?(k) && !bts.key?(k)) ||
1685
- ::Brick.config.metadata_columns.include?(k) %>
1679
+ rtans = #{model_name}.rich_text_association_names if #{model_name}.respond_to?(:rich_text_association_names)
1680
+ (#{model_name}.column_names + (rtans || [])).each do |k|
1681
+ next if (#{(pk.map(&:to_s) || []).inspect}.include?(k) && !bts.key?(k)) ||
1682
+ ::Brick.config.metadata_columns.include?(k)
1683
+
1684
+ col = #{model_name}.columns_hash[k]
1685
+ if !col && rtans&.include?(k)
1686
+ k = k[10..-1] if k.start_with?('rich_text_')
1687
+ col = (rt_col ||= ActiveRecord::ConnectionAdapters::Column.new(
1688
+ '', nil, ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: 'varchar', type: :text)
1689
+ )
1690
+ )
1691
+ end
1692
+ val = @#{obj_name}.attributes[k] %>
1686
1693
  <tr>
1687
- <th class=\"show-field\"<%= \" title=\\\"#\{col.comment}\\\"\".html_safe if col.respond_to?(:comment) && !col.comment.blank? %>>
1694
+ <th class=\"show-field\"<%= \" title=\\\"#\{col&.comment}\\\"\".html_safe if col&.respond_to?(:comment) && !col&.comment.blank? %>>
1688
1695
  <% has_fields = true
1689
1696
  if (bt = bts[k])
1690
1697
  # Add a final member in this array with descriptive options to be used in <select> drop-downs
@@ -1716,7 +1723,7 @@ end
1716
1723
  collection&.brick_(:each) do |obj|
1717
1724
  option_detail << [
1718
1725
  obj.brick_descrip(
1719
- descrip_cols&.first&.map { |col| obj.send(col.last) },
1726
+ descrip_cols&.first&.map { |col2| obj.send(col2.last) },
1720
1727
  obj_pk
1721
1728
  ), obj.send(obj_pk)
1722
1729
  ]
@@ -20,7 +20,14 @@ module Brick::Rails::FormBuilder
20
20
 
21
21
  html_options[:prompt] = "Select #{bt_name}"
22
22
  out << self.select(method.to_sym, bt[3], { value: val || '^^^brick_NULL^^^' }, html_options)
23
- bt_link = if (bt_obj = bt_class&.find_by(bt_pair[1] => val))
23
+ bt_obj = nil
24
+ begin
25
+ bt_obj = bt_class&.find_by(bt_pair[1] => val)
26
+ rescue ActiveRecord::SubclassNotFound => e
27
+ # %%% Would be cool to indicate to the user that a subclass is missing.
28
+ # Its name starts at: e.message.index('failed to locate the subclass: ') + 31
29
+ end
30
+ bt_link = if bt_obj
24
31
  bt_path = template.send(
25
32
  "#{bt_class.base_class._brick_index(:singular)}_path".to_sym,
26
33
  bt_obj.send(bt_class.primary_key.to_sym)
@@ -100,7 +107,7 @@ module Brick::Rails::FormBuilder
100
107
  end
101
108
  # Because there are so danged many quotes in JSON, escape them specially by converting to backticks.
102
109
  # (and previous to this, escape backticks with our own goofy code of ^^br_btick__ )
103
- out << (json_field = self.hidden_field(method.to_sym, { class: 'jsonpicker', value: val_str.gsub('`', '^^br_btick__').tr('\"', '`').html_safe }))
110
+ out << (json_field = self.hidden_field(method.to_sym, { class: 'jsonpicker', value: val_str&.gsub('`', '^^br_btick__')&.tr('\"', '`')&.html_safe }))
104
111
  out << "<div id=\"_br_json_#{self.field_id(method)}\"></div>"
105
112
  else
106
113
  is_revert = false
@@ -157,7 +157,7 @@ module Brick::Rails::FormTags
157
157
  out << link_to(ho_txt, send("#{hm_klass.base_class._brick_index(:singular)}_path".to_sym, ho_id))
158
158
  end
159
159
  elsif obj.respond_to?(ct_col = hms_col[1].to_sym) && (ct = obj.send(ct_col)&.to_i)&.positive?
160
- predicates = hms_col[2].each_with_object({}) { |v, s| s[v.first] = v.last.is_a?(String) ? v.last : obj.send(v.last) }
160
+ predicates = hms_col[2].each_with_object({}) { |v, s| s["__#{v.first}"] = v.last.is_a?(String) ? v.last : obj.send(v.last) }
161
161
  predicates.each { |k, v| predicates[k] = klass.name if v == '[sti_type]' }
162
162
  out << "#{link_to("#{ct || 'View'} #{hms_col.first}",
163
163
  send("#{hm_klass._brick_index}_path".to_sym, predicates))}\n"
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 157
8
+ TINY = 158
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
@@ -221,8 +221,8 @@ module Brick
221
221
  hm_models = ActiveRecord::Base.descendants.select do |m|
222
222
  m.reflect_on_all_associations.any? { |assoc| !assoc.belongs_to? && assoc.options[:as]&.to_sym == a.name }
223
223
  end
224
- # No need to include subclassed models if their parent is already in the list
225
- hm_models.reject! { |m| hm_models.any? { |parent| parent != m && m < parent } }
224
+ # No need to include models with no table, or subclassed models if their parent is already in the list
225
+ hm_models.reject! { |m| !m.table_exists? || hm_models.any? { |parent| parent != m && m < parent } }
226
226
  if hm_models.empty?
227
227
  puts "Missing any real indication as to which models \"has_many\" this polymorphic BT in model #{a.active_record.name}:"
228
228
  puts " belongs_to :#{a.name}, polymorphic: true"
@@ -28,7 +28,7 @@ module Brick
28
28
  relations = ::Brick.relations
29
29
  if is_brick_file
30
30
  # Need to remove any currently-existing additional_references so that it doesn't cloud the discovery process:
31
- ::Brick.config.additional_references.each do |ar|
31
+ ::Brick.config.additional_references&.each do |ar|
32
32
  if (fks = relations.fetch(ar[0], nil)&.fetch(:fks, nil))
33
33
  fks.delete(fks.find { |k, v| v[:is_bt] && k.start_with?('(brick) ') && v[:fk] == ar[1] }&.first)
34
34
  end
@@ -0,0 +1,341 @@
1
+ module Brick
2
+ module MigrationBuilder
3
+ # Many SQL types are the same as their migration data type name:
4
+ # text, integer, bigint, date, boolean, decimal, float
5
+ # These however are not:
6
+ SQL_TYPES = { 'character varying' => 'string',
7
+ 'character' => 'string', # %%% Need to put in "limit: 1"
8
+ 'xml' => 'text',
9
+ 'bytea' => 'binary',
10
+ 'timestamp without time zone' => 'timestamp',
11
+ 'timestamp with time zone' => 'timestamp',
12
+ 'time without time zone' => 'time',
13
+ 'time with time zone' => 'time',
14
+ 'double precision' => 'float',
15
+ 'smallint' => 'integer', # %%% Need to put in "limit: 2"
16
+ 'ARRAY' => 'string', # Note that we'll also add ", array: true"
17
+ # Oracle data types
18
+ 'VARCHAR2' => 'string',
19
+ 'CHAR' => 'string',
20
+ ['NUMBER', 22] => 'integer',
21
+ /^INTERVAL / => 'string', # Time interval stuff like INTERVAL YEAR(2) TO MONTH, INTERVAL '999' DAY(3), etc
22
+ 'XMLTYPE' => 'xml',
23
+ 'RAW' => 'binary',
24
+ 'SDO_GEOMETRY' => 'geometry',
25
+ # MSSQL data types
26
+ 'int' => 'integer',
27
+ 'nvarchar' => 'string',
28
+ 'nchar' => 'string',
29
+ 'datetime2' => 'timestamp',
30
+ 'bit' => 'boolean',
31
+ 'varbinary' => 'binary',
32
+ # Sqlite data types
33
+ 'TEXT' => 'text',
34
+ '' => 'string',
35
+ 'INTEGER' => 'integer',
36
+ 'REAL' => 'float',
37
+ 'BLOB' => 'binary',
38
+ 'TIMESTAMP' => 'timestamp',
39
+ 'DATETIME' => 'timestamp'
40
+ }
41
+ # (Still need to find what "inet" and "json" data types map to.)
42
+
43
+ class << self
44
+ def check_folder(is_insert_versions = true, is_delete_versions = false)
45
+ versions_to_delete_or_append = nil
46
+ if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{::Rails.root}/db/migrate")
47
+ if Dir["#{mig_path}/**/*.rb"].present?
48
+ puts "WARNING: migrations folder #{mig_path} appears to already have ruby files present."
49
+ mig_path2 = "#{::Rails.root}/tmp/brick_migrations"
50
+ is_insert_versions = false unless mig_path == mig_path2
51
+ if Dir.exist?(mig_path2)
52
+ if Dir["#{mig_path2}/**/*.rb"].present?
53
+ puts "As well, temporary folder #{mig_path2} also has ruby files present."
54
+ puts "Choose a destination -- all existing .rb files will be removed:"
55
+ mig_path2 = gets_list(list: ['Cancel operation!', "Append migration files into #{mig_path} anyway", mig_path, mig_path2])
56
+ return if mig_path2.start_with?('Cancel')
57
+
58
+ existing_mig_files = Dir["#{mig_path2}/**/*.rb"]
59
+ if (is_insert_versions = mig_path == mig_path2)
60
+ versions_to_delete_or_append = existing_mig_files.map { |ver| ver.split('/').last.split('_').first }
61
+ end
62
+ if mig_path2.start_with?('Append migration files into ')
63
+ mig_path2 = mig_path
64
+ else
65
+ is_delete_versions = true
66
+ existing_mig_files.each { |rb| File.delete(rb) }
67
+ end
68
+ else
69
+ puts "Using temporary folder #{mig_path2} for created migration files.\n\n"
70
+ end
71
+ else
72
+ puts "Creating the temporary folder #{mig_path2} for created migration files.\n\n"
73
+ Dir.mkdir(mig_path2)
74
+ end
75
+ mig_path = mig_path2
76
+ else
77
+ puts "Using standard migration folder #{mig_path} for created migration files.\n\n"
78
+ end
79
+ else
80
+ puts "Creating standard ActiveRecord migration folder #{mig_path} to hold new migration files.\n\n"
81
+ Dir.mkdir(mig_path)
82
+ end
83
+ [mig_path, is_insert_versions, is_delete_versions]
84
+ end
85
+
86
+ def generate_migrations(chosen, mig_path, is_insert_versions, is_delete_versions, relations = ::Brick.relations)
87
+ is_sqlite = ActiveRecord::Base.connection.adapter_name == 'SQLite'
88
+ key_type = ((is_sqlite || ActiveRecord.version < ::Gem::Version.new('5.1')) ? 'integer' : 'bigint')
89
+ is_4x_rails = ActiveRecord.version < ::Gem::Version.new('5.0')
90
+ ar_version = "[#{ActiveRecord.version.segments[0..1].join('.')}]" unless is_4x_rails
91
+
92
+ schemas = chosen.each_with_object({}) do |v, s|
93
+ if (v_parts = v.split('.')).length > 1
94
+ s[v_parts.first] = nil unless [::Brick.default_schema, 'public'].include?(v_parts.first)
95
+ end
96
+ end
97
+ # Start the timestamps back the same number of minutes from now as expected number of migrations to create
98
+ current_mig_time = Time.now - (schemas.length + chosen.length).minutes
99
+ done = []
100
+ fks = {}
101
+ stuck = {}
102
+ indexes = {} # Track index names to make sure things are unique
103
+ built_schemas = {} # Track all built schemas so we can place an appropriate drop_schema command only in the first
104
+ # migration in which that schema is referenced, thereby allowing rollbacks to function properly.
105
+ versions_to_create = [] # Resulting versions to be used when updating the schema_migrations table
106
+ ar_base = Object.const_defined?(:ApplicationRecord) ? ApplicationRecord : Class.new(ActiveRecord::Base)
107
+ # Start by making migrations for fringe tables (those with no foreign keys).
108
+ # Continue layer by layer, creating migrations for tables that reference ones already done, until
109
+ # no more migrations can be created. (At that point hopefully all tables are accounted for.)
110
+ while (fringe = chosen.reject do |tbl|
111
+ snag_fks = []
112
+ snags = relations.fetch(tbl, nil)&.fetch(:fks, nil)&.select do |_k, v|
113
+ v[:is_bt] && !v[:polymorphic] &&
114
+ tbl != v[:inverse_table] && # Ignore self-referencing associations (stuff like "parent_id")
115
+ !done.include?(v[:inverse_table]) &&
116
+ ::Brick.config.ignore_migration_fks.exclude?(snag_fk = "#{tbl}.#{v[:fk]}") &&
117
+ snag_fks << snag_fk
118
+ end
119
+ if snags&.present?
120
+ # puts snag_fks.inspect
121
+ stuck[tbl] = snags
122
+ end
123
+ end).present?
124
+ fringe.each do |tbl|
125
+ next unless (relation = relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present?
126
+
127
+ pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ar_base.primary_key].flatten.sort)
128
+ # In case things aren't as standard
129
+ if pkey_cols.empty?
130
+ pkey_cols = if rpk.empty? && relation[:cols][arpk.first]&.first == key_type
131
+ arpk
132
+ elsif rpk.first
133
+ rpk
134
+ end
135
+ end
136
+ schema = if (tbl_parts = tbl.split('.')).length > 1
137
+ if tbl_parts.first == (::Brick.default_schema || 'public')
138
+ tbl_parts.shift
139
+ nil
140
+ else
141
+ tbl_parts.first
142
+ end
143
+ end
144
+ unless schema.blank? || built_schemas.key?(schema)
145
+ mig = +" def change\n create_schema(:#{schema}) unless schema_exists?(:#{schema})\n end\n"
146
+ migration_file_write(mig_path, "create_db_schema_#{schema.underscore}", current_mig_time += 1.minute, ar_version, mig)
147
+ built_schemas[schema] = nil
148
+ end
149
+
150
+ # %%% For the moment we're skipping polymorphics
151
+ fkey_cols = relation[:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
152
+ # If the primary key is also used as a foreign key, will need to do id: false and then build out
153
+ # a column definition which includes :primary_key -- %%% also using a data type of bigserial or serial
154
+ # if this one has come in as bigint or integer.
155
+ pk_is_also_fk = fkey_cols.any? { |assoc| pkey_cols&.first == assoc[:fk] } ? pkey_cols&.first : nil
156
+ # Support missing primary key (by adding: , id: false)
157
+ id_option = if pk_is_also_fk || !pkey_cols&.present?
158
+ needs_serial_col = true
159
+ +', id: false'
160
+ elsif ((pkey_col_first = (col_def = relation[:cols][pkey_cols&.first])&.first) &&
161
+ (pkey_col_first = SQL_TYPES[pkey_col_first] || SQL_TYPES[col_def&.[](0..1)] ||
162
+ SQL_TYPES.find { |r| r.first.is_a?(Regexp) && pkey_col_first =~ r.first }&.last ||
163
+ pkey_col_first
164
+ ) != key_type
165
+ )
166
+ case pkey_col_first
167
+ when 'integer'
168
+ +', id: :serial'
169
+ when 'bigint'
170
+ +', id: :bigserial'
171
+ else
172
+ +", id: :#{pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
173
+ end +
174
+ (pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '')
175
+ end
176
+ if !id_option && pkey_cols.sort != arpk
177
+ id_option = +", primary_key: :#{pkey_cols.first}"
178
+ end
179
+ if !is_4x_rails && (comment = relation&.fetch(:description, nil))&.present?
180
+ (id_option ||= +'') << ", comment: #{comment.inspect}"
181
+ end
182
+ # Find the ActiveRecord class in order to see if the columns have comments
183
+ unless is_4x_rails
184
+ klass = begin
185
+ tbl.tr('.', '/').singularize.camelize.constantize
186
+ rescue StandardError
187
+ end
188
+ if klass
189
+ unless ActiveRecord::Migration.table_exists?(klass.table_name)
190
+ puts "WARNING: Unable to locate table #{klass.table_name} (for #{klass.name})."
191
+ klass = nil
192
+ end
193
+ end
194
+ end
195
+ # Refer to this table name as a symbol or dotted string as appropriate
196
+ tbl_code = tbl_parts.length == 1 ? ":#{tbl_parts.first}" : "'#{tbl}'"
197
+ mig = +" def change\n return unless reverting? || !table_exists?(#{tbl_code})\n\n"
198
+ mig << " create_table #{tbl_code}#{id_option} do |t|\n"
199
+ possible_ts = [] # Track possible generic timestamps
200
+ add_fks = [] # Track foreign keys to add after table creation
201
+ relation[:cols].each do |col, col_type|
202
+ sql_type = SQL_TYPES[col_type.first] || SQL_TYPES[col_type[0..1]] ||
203
+ SQL_TYPES.find { |r| r.first.is_a?(Regexp) && col_type.first =~ r.first }&.last ||
204
+ col_type.first
205
+ suffix = col_type[3] || pkey_cols&.include?(col) ? +', null: false' : +''
206
+ suffix << ', array: true' if (col_type.first == 'ARRAY')
207
+ if !is_4x_rails && klass && (comment = klass.columns_hash.fetch(col, nil)&.comment)&.present?
208
+ suffix << ", comment: #{comment.inspect}"
209
+ end
210
+ # Determine if this column is used as part of a foreign key
211
+ if (fk = fkey_cols.find { |assoc| col == assoc[:fk] })
212
+ to_table = fk[:inverse_table].split('.')
213
+ to_table = to_table.length == 1 ? ":#{to_table.first}" : "'#{fk[:inverse_table]}'"
214
+ if needs_serial_col && pkey_cols&.include?(col) && (new_serial_type = {'integer' => 'serial', 'bigint' => 'bigserial'}[sql_type])
215
+ sql_type = new_serial_type
216
+ needs_serial_col = false
217
+ end
218
+ if fk[:fk] != "#{fk[:assoc_name].singularize}_id" # Need to do our own foreign_key tricks, not use references?
219
+ column = fk[:fk]
220
+ mig << emit_column(sql_type, column, suffix)
221
+ add_fks << [to_table, column, relations[fk[:inverse_table]]]
222
+ else
223
+ suffix << ", type: :#{sql_type}" unless sql_type == key_type
224
+ # Will the resulting default index name be longer than what Postgres allows? (63 characters)
225
+ if (idx_name = ActiveRecord::Base.connection.index_name(tbl, {column: col})).length > 63
226
+ # Try to find a shorter name that hasn't been used yet
227
+ unless indexes.key?(shorter = idx_name[0..62]) ||
228
+ indexes.key?(shorter = idx_name.tr('_', '')[0..62]) ||
229
+ indexes.key?(shorter = idx_name.tr('aeio', '')[0..62])
230
+ puts "Unable to easily find unique name for index #{idx_name} that is shorter than 64 characters,"
231
+ puts "so have resorted to this GUID-based identifier: #{shorter = "#{tbl[0..25]}_#{::SecureRandom.uuid}"}."
232
+ end
233
+ suffix << ", index: { name: '#{shorter || idx_name}' }"
234
+ indexes[shorter || idx_name] = nil
235
+ end
236
+ primary_key = nil
237
+ begin
238
+ primary_key = relations[fk[:inverse_table]][:class_name]&.constantize&.primary_key
239
+ rescue NameError => e
240
+ primary_key = ar_base.primary_key
241
+ end
242
+ 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"
243
+ end
244
+ else
245
+ next if !id_option&.end_with?('id: false') && pkey_cols&.include?(col)
246
+
247
+ # See if there are generic timestamps
248
+ if sql_type == 'timestamp' && ['created_at','updated_at'].include?(col)
249
+ possible_ts << [col, !col_type[3]]
250
+ else
251
+ mig << emit_column(sql_type, col, suffix)
252
+ end
253
+ end
254
+ end
255
+ if possible_ts.length == 2 && # Both created_at and updated_at
256
+ # Rails 5 and later timestamps default to NOT NULL
257
+ (possible_ts.first.last == is_4x_rails && possible_ts.last.last == is_4x_rails)
258
+ mig << "\n t.timestamps\n"
259
+ else # Just one or the other, or a nullability mismatch
260
+ possible_ts.each { |ts| emit_column('timestamp', ts.first, nil) }
261
+ end
262
+ mig << " end\n"
263
+ if pk_is_also_fk
264
+ mig << " reversible do |dir|\n"
265
+ mig << " dir.up { execute('ALTER TABLE #{tbl} ADD PRIMARY KEY (#{pk_is_also_fk})') }\n"
266
+ mig << " end\n"
267
+ end
268
+ add_fks.each do |add_fk|
269
+ is_commented = false
270
+ # add_fk[2] holds the inverse relation
271
+ unless (pk = add_fk[2][:pkey].values.flatten&.first)
272
+ is_commented = true
273
+ mig << " # (Unable to create relationship because primary key is missing on table #{add_fk[0]})\n"
274
+ # No official PK, but if coincidentally there's a column of the same name, take a chance on it
275
+ pk = (add_fk[2][:cols].key?(add_fk[1]) && add_fk[1]) || '???'
276
+ end
277
+ # to_table column
278
+ mig << " #{'# ' if is_commented}add_foreign_key #{tbl_code}, #{add_fk[0]}, column: :#{add_fk[1]}, primary_key: :#{pk}\n"
279
+ end
280
+ mig << " end\n"
281
+ versions_to_create << migration_file_write(mig_path, "create_#{tbl_parts.map(&:underscore).join('_')}", current_mig_time += 1.minute, ar_version, mig)
282
+ end
283
+ done.concat(fringe)
284
+ chosen -= done
285
+ end
286
+
287
+ stuck_counts = Hash.new { |h, k| h[k] = 0 }
288
+ chosen.each do |leftover|
289
+ puts "Can't do #{leftover} because:\n #{stuck[leftover].map do |snag|
290
+ stuck_counts[snag.last[:inverse_table]] += 1
291
+ snag.last[:assoc_name]
292
+ end.join(', ')}"
293
+ end
294
+ if mig_path.start_with?(cur_path = ::Rails.root.to_s)
295
+ pretty_mig_path = mig_path[cur_path.length..-1]
296
+ end
297
+ puts "\n*** Created #{done.length} migration files under #{pretty_mig_path || mig_path} ***"
298
+ if (stuck_sorted = stuck_counts.to_a.sort { |a, b| b.last <=> a.last }).length.positive?
299
+ puts "-----------------------------------------"
300
+ puts "Unable to create migrations for #{stuck_sorted.length} tables#{
301
+ ". Here's the top 5 blockers" if stuck_sorted.length > 5
302
+ }:"
303
+ pp stuck_sorted[0..4]
304
+ else # Successful, and now we can update the schema_migrations table accordingly
305
+ unless ActiveRecord::Migration.table_exists?(ActiveRecord::Base.schema_migrations_table_name)
306
+ ActiveRecord::SchemaMigration.create_table
307
+ end
308
+ # Remove to_delete - to_create
309
+ if ((versions_to_delete_or_append ||= []) - versions_to_create).present? && is_delete_versions
310
+ ActiveRecord::Base.execute_sql("DELETE FROM #{
311
+ ActiveRecord::Base.schema_migrations_table_name} WHERE version IN (#{
312
+ (versions_to_delete_or_append - versions_to_create).map { |vtd| "'#{vtd}'" }.join(', ')}
313
+ )")
314
+ end
315
+ # Add to_create - to_delete
316
+ if is_insert_versions && ((versions_to_create ||= []) - versions_to_delete_or_append).present?
317
+ ActiveRecord::Base.execute_sql("INSERT INTO #{
318
+ ActiveRecord::Base.schema_migrations_table_name} (version) VALUES #{
319
+ (versions_to_create - versions_to_delete_or_append).map { |vtc| "('#{vtc}')" }.join(', ')
320
+ }")
321
+ end
322
+ end
323
+ end
324
+
325
+ private
326
+
327
+ def emit_column(type, name, suffix)
328
+ " t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}#{suffix}\n"
329
+ end
330
+
331
+ def migration_file_write(mig_path, name, current_mig_time, ar_version, mig)
332
+ File.open("#{mig_path}/#{version = current_mig_time.strftime('%Y%m%d%H%M00')}_#{name}.rb", "w") do |f|
333
+ f.write "class #{name.camelize} < ActiveRecord::Migration#{ar_version}\n"
334
+ f.write mig
335
+ f.write "end\n"
336
+ end
337
+ version
338
+ end
339
+ end
340
+ end
341
+ end
@@ -3,52 +3,13 @@
3
3
  require 'rails/generators'
4
4
  require 'rails/generators/active_record'
5
5
  require 'fancy_gets'
6
+ require 'generators/brick/migration_builder'
6
7
 
7
8
  module Brick
8
9
  # Auto-generates migration files
9
10
  class MigrationsGenerator < ::Rails::Generators::Base
10
11
  include FancyGets
11
- # include ::Rails::Generators::Migration
12
-
13
- # Many SQL types are the same as their migration data type name:
14
- # text, integer, bigint, date, boolean, decimal, float
15
- # These however are not:
16
- SQL_TYPES = { 'character varying' => 'string',
17
- 'character' => 'string', # %%% Need to put in "limit: 1"
18
- 'xml' => 'text',
19
- 'bytea' => 'binary',
20
- 'timestamp without time zone' => 'timestamp',
21
- 'timestamp with time zone' => 'timestamp',
22
- 'time without time zone' => 'time',
23
- 'time with time zone' => 'time',
24
- 'double precision' => 'float',
25
- 'smallint' => 'integer', # %%% Need to put in "limit: 2"
26
- 'ARRAY' => 'string', # Note that we'll also add ", array: true"
27
- # Oracle data types
28
- 'VARCHAR2' => 'string',
29
- 'CHAR' => 'string',
30
- ['NUMBER', 22] => 'integer',
31
- /^INTERVAL / => 'string', # Time interval stuff like INTERVAL YEAR(2) TO MONTH, INTERVAL '999' DAY(3), etc
32
- 'XMLTYPE' => 'xml',
33
- 'RAW' => 'binary',
34
- 'SDO_GEOMETRY' => 'geometry',
35
- # MSSQL data types
36
- 'int' => 'integer',
37
- 'nvarchar' => 'string',
38
- 'nchar' => 'string',
39
- 'datetime2' => 'timestamp',
40
- 'bit' => 'boolean',
41
- 'varbinary' => 'binary',
42
- # Sqlite data types
43
- 'TEXT' => 'text',
44
- '' => 'string',
45
- 'INTEGER' => 'integer',
46
- 'REAL' => 'float',
47
- 'BLOB' => 'binary',
48
- 'TIMESTAMP' => 'timestamp',
49
- 'DATETIME' => 'timestamp'
50
- }
51
- # (Still need to find what "inet" and "json" data types map to.)
12
+ include ::Brick::MigrationBuilder
52
13
 
53
14
  desc 'Auto-generates migration files for an existing database.'
54
15
 
@@ -63,294 +24,12 @@ module Brick
63
24
  return
64
25
  end
65
26
 
66
- is_sqlite = ActiveRecord::Base.connection.adapter_name == 'SQLite'
67
- key_type = ((is_sqlite || ActiveRecord.version < ::Gem::Version.new('5.1')) ? 'integer' : 'bigint')
68
- is_4x_rails = ActiveRecord.version < ::Gem::Version.new('5.0')
69
- ar_version = "[#{ActiveRecord.version.segments[0..1].join('.')}]" unless is_4x_rails
70
- is_insert_versions = true
71
- is_delete_versions = false
72
- versions_to_delete_or_append = nil
73
- if Dir.exist?(mig_path = ActiveRecord::Migrator.migrations_paths.first || "#{::Rails.root}/db/migrate")
74
- if Dir["#{mig_path}/**/*.rb"].present?
75
- puts "WARNING: migrations folder #{mig_path} appears to already have ruby files present."
76
- mig_path2 = "#{::Rails.root}/tmp/brick_migrations"
77
- is_insert_versions = false unless mig_path == mig_path2
78
- if Dir.exist?(mig_path2)
79
- if Dir["#{mig_path2}/**/*.rb"].present?
80
- puts "As well, temporary folder #{mig_path2} also has ruby files present."
81
- puts "Choose a destination -- all existing .rb files will be removed:"
82
- mig_path2 = gets_list(list: ['Cancel operation!', "Append migration files into #{mig_path} anyway", mig_path, mig_path2])
83
- return if mig_path2.start_with?('Cancel')
84
-
85
- existing_mig_files = Dir["#{mig_path2}/**/*.rb"]
86
- if (is_insert_versions = mig_path == mig_path2)
87
- versions_to_delete_or_append = existing_mig_files.map { |ver| ver.split('/').last.split('_').first }
88
- end
89
- if mig_path2.start_with?('Append migration files into ')
90
- mig_path2 = mig_path
91
- else
92
- is_delete_versions = true
93
- existing_mig_files.each { |rb| File.delete(rb) }
94
- end
95
- else
96
- puts "Using temporary folder #{mig_path2} for created migration files.\n\n"
97
- end
98
- else
99
- puts "Creating the temporary folder #{mig_path2} for created migration files.\n\n"
100
- Dir.mkdir(mig_path2)
101
- end
102
- mig_path = mig_path2
103
- else
104
- puts "Using standard migration folder #{mig_path} for created migration files.\n\n"
105
- end
106
- else
107
- puts "Creating standard ActiveRecord migration folder #{mig_path} to hold new migration files.\n\n"
108
- Dir.mkdir(mig_path)
109
- end
27
+ mig_path, is_insert_versions, is_delete_versions = ::Brick::MigrationBuilder.check_folder
110
28
 
111
29
  # Generate a list of tables that can be chosen
112
30
  chosen = gets_list(list: tables, chosen: tables.dup)
113
- schemas = chosen.each_with_object({}) do |v, s|
114
- if (v_parts = v.split('.')).length > 1
115
- s[v_parts.first] = nil unless [::Brick.default_schema, 'public'].include?(v_parts.first)
116
- end
117
- end
118
- # Start the timestamps back the same number of minutes from now as expected number of migrations to create
119
- current_mig_time = Time.now - (schemas.length + chosen.length).minutes
120
- done = []
121
- fks = {}
122
- stuck = {}
123
- indexes = {} # Track index names to make sure things are unique
124
- built_schemas = {} # Track all built schemas so we can place an appropriate drop_schema command only in the first
125
- # migration in which that schema is referenced, thereby allowing rollbacks to function properly.
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)
128
- # Start by making migrations for fringe tables (those with no foreign keys).
129
- # Continue layer by layer, creating migrations for tables that reference ones already done, until
130
- # no more migrations can be created. (At that point hopefully all tables are accounted for.)
131
- while (fringe = chosen.reject do |tbl|
132
- snag_fks = []
133
- snags = ::Brick.relations.fetch(tbl, nil)&.fetch(:fks, nil)&.select do |_k, v|
134
- v[:is_bt] && !v[:polymorphic] &&
135
- tbl != v[:inverse_table] && # Ignore self-referencing associations (stuff like "parent_id")
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
143
- end
144
- end).present?
145
- fringe.each do |tbl|
146
- next unless (relation = ::Brick.relations.fetch(tbl, nil))&.fetch(:cols, nil)&.present?
147
-
148
- pkey_cols = (rpk = relation[:pkey].values.flatten) & (arpk = [ar_base.primary_key].flatten.sort)
149
- # In case things aren't as standard
150
- if pkey_cols.empty?
151
- pkey_cols = if rpk.empty? && relation[:cols][arpk.first]&.first == key_type
152
- arpk
153
- elsif rpk.first
154
- rpk
155
- end
156
- end
157
- schema = if (tbl_parts = tbl.split('.')).length > 1
158
- if tbl_parts.first == (::Brick.default_schema || 'public')
159
- tbl_parts.shift
160
- nil
161
- else
162
- tbl_parts.first
163
- end
164
- end
165
- unless schema.blank? || built_schemas.key?(schema)
166
- mig = +" def change\n create_schema(:#{schema}) unless schema_exists?(:#{schema})\n end\n"
167
- migration_file_write(mig_path, "create_db_schema_#{schema.underscore}", current_mig_time += 1.minute, ar_version, mig)
168
- built_schemas[schema] = nil
169
- end
170
31
 
171
- # %%% For the moment we're skipping polymorphics
172
- fkey_cols = relation[:fks].values.select { |assoc| assoc[:is_bt] && !assoc[:polymorphic] }
173
- # If the primary key is also used as a foreign key, will need to do id: false and then build out
174
- # a column definition which includes :primary_key -- %%% also using a data type of bigserial or serial
175
- # if this one has come in as bigint or integer.
176
- pk_is_also_fk = fkey_cols.any? { |assoc| pkey_cols&.first == assoc[:fk] } ? pkey_cols&.first : nil
177
- # Support missing primary key (by adding: , id: false)
178
- id_option = if pk_is_also_fk || !pkey_cols&.present?
179
- needs_serial_col = true
180
- +', id: false'
181
- elsif ((pkey_col_first = (col_def = relation[:cols][pkey_cols&.first])&.first) &&
182
- (pkey_col_first = SQL_TYPES[pkey_col_first] || SQL_TYPES[col_def&.[](0..1)] ||
183
- SQL_TYPES.find { |r| r.first.is_a?(Regexp) && pkey_col_first =~ r.first }&.last ||
184
- pkey_col_first
185
- ) != key_type
186
- )
187
- case pkey_col_first
188
- when 'integer'
189
- +', id: :serial'
190
- when 'bigint'
191
- +', id: :bigserial'
192
- else
193
- +", id: :#{pkey_col_first}" # Something like: id: :integer, primary_key: :businessentityid
194
- end +
195
- (pkey_cols.first ? ", primary_key: :#{pkey_cols.first}" : '')
196
- end
197
- if !id_option && pkey_cols.sort != arpk
198
- id_option = +", primary_key: :#{pkey_cols.first}"
199
- end
200
- if !is_4x_rails && (comment = relation&.fetch(:description, nil))&.present?
201
- (id_option ||= +'') << ", comment: #{comment.inspect}"
202
- end
203
- # Find the ActiveRecord class in order to see if the columns have comments
204
- unless is_4x_rails
205
- klass = begin
206
- tbl.tr('.', '/').singularize.camelize.constantize
207
- rescue StandardError
208
- end
209
- if klass
210
- unless ActiveRecord::Migration.table_exists?(klass.table_name)
211
- puts "WARNING: Unable to locate table #{klass.table_name} (for #{klass.name})."
212
- klass = nil
213
- end
214
- end
215
- end
216
- # Refer to this table name as a symbol or dotted string as appropriate
217
- tbl_code = tbl_parts.length == 1 ? ":#{tbl_parts.first}" : "'#{tbl}'"
218
- mig = +" def change\n return unless reverting? || !table_exists?(#{tbl_code})\n\n"
219
- mig << " create_table #{tbl_code}#{id_option} do |t|\n"
220
- possible_ts = [] # Track possible generic timestamps
221
- add_fks = [] # Track foreign keys to add after table creation
222
- relation[:cols].each do |col, col_type|
223
- sql_type = SQL_TYPES[col_type.first] || SQL_TYPES[col_type[0..1]] ||
224
- SQL_TYPES.find { |r| r.first.is_a?(Regexp) && col_type.first =~ r.first }&.last ||
225
- col_type.first
226
- suffix = col_type[3] || pkey_cols&.include?(col) ? +', null: false' : +''
227
- suffix << ', array: true' if (col_type.first == 'ARRAY')
228
- if !is_4x_rails && klass && (comment = klass.columns_hash.fetch(col, nil)&.comment)&.present?
229
- suffix << ", comment: #{comment.inspect}"
230
- end
231
- # Determine if this column is used as part of a foreign key
232
- if (fk = fkey_cols.find { |assoc| col == assoc[:fk] })
233
- to_table = fk[:inverse_table].split('.')
234
- to_table = to_table.length == 1 ? ":#{to_table.first}" : "'#{fk[:inverse_table]}'"
235
- if needs_serial_col && pkey_cols&.include?(col) && (new_serial_type = {'integer' => 'serial', 'bigint' => 'bigserial'}[sql_type])
236
- sql_type = new_serial_type
237
- needs_serial_col = false
238
- end
239
- if fk[:fk] != "#{fk[:assoc_name].singularize}_id" # Need to do our own foreign_key tricks, not use references?
240
- column = fk[:fk]
241
- mig << emit_column(sql_type, column, suffix)
242
- add_fks << [to_table, column, ::Brick.relations[fk[:inverse_table]]]
243
- else
244
- suffix << ", type: :#{sql_type}" unless sql_type == key_type
245
- # Will the resulting default index name be longer than what Postgres allows? (63 characters)
246
- if (idx_name = ActiveRecord::Base.connection.index_name(tbl, {column: col})).length > 63
247
- # Try to find a shorter name that hasn't been used yet
248
- unless indexes.key?(shorter = idx_name[0..62]) ||
249
- indexes.key?(shorter = idx_name.tr('_', '')[0..62]) ||
250
- indexes.key?(shorter = idx_name.tr('aeio', '')[0..62])
251
- puts "Unable to easily find unique name for index #{idx_name} that is shorter than 64 characters,"
252
- puts "so have resorted to this GUID-based identifier: #{shorter = "#{tbl[0..25]}_#{::SecureRandom.uuid}"}."
253
- end
254
- suffix << ", index: { name: '#{shorter || idx_name}' }"
255
- indexes[shorter || idx_name] = nil
256
- end
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"
259
- end
260
- else
261
- next if !id_option&.end_with?('id: false') && pkey_cols&.include?(col)
262
-
263
- # See if there are generic timestamps
264
- if sql_type == 'timestamp' && ['created_at','updated_at'].include?(col)
265
- possible_ts << [col, !col_type[3]]
266
- else
267
- mig << emit_column(sql_type, col, suffix)
268
- end
269
- end
270
- end
271
- if possible_ts.length == 2 && # Both created_at and updated_at
272
- # Rails 5 and later timestamps default to NOT NULL
273
- (possible_ts.first.last == is_4x_rails && possible_ts.last.last == is_4x_rails)
274
- mig << "\n t.timestamps\n"
275
- else # Just one or the other, or a nullability mismatch
276
- possible_ts.each { |ts| emit_column('timestamp', ts.first, nil) }
277
- end
278
- mig << " end\n"
279
- if pk_is_also_fk
280
- mig << " reversible do |dir|\n"
281
- mig << " dir.up { execute('ALTER TABLE #{tbl} ADD PRIMARY KEY (#{pk_is_also_fk})') }\n"
282
- mig << " end\n"
283
- end
284
- add_fks.each do |add_fk|
285
- is_commented = false
286
- # add_fk[2] holds the inverse relation
287
- unless (pk = add_fk[2][:pkey].values.flatten&.first)
288
- is_commented = true
289
- mig << " # (Unable to create relationship because primary key is missing on table #{add_fk[0]})\n"
290
- # No official PK, but if coincidentally there's a column of the same name, take a chance on it
291
- pk = (add_fk[2][:cols].key?(add_fk[1]) && add_fk[1]) || '???'
292
- end
293
- # to_table column
294
- mig << " #{'# ' if is_commented}add_foreign_key #{tbl_code}, #{add_fk[0]}, column: :#{add_fk[1]}, primary_key: :#{pk}\n"
295
- end
296
- mig << " end\n"
297
- versions_to_create << migration_file_write(mig_path, "create_#{tbl_parts.map(&:underscore).join('_')}", current_mig_time += 1.minute, ar_version, mig)
298
- end
299
- done.concat(fringe)
300
- chosen -= done
301
- end
302
-
303
- stuck_counts = Hash.new { |h, k| h[k] = 0 }
304
- chosen.each do |leftover|
305
- puts "Can't do #{leftover} because:\n #{stuck[leftover].map do |snag|
306
- stuck_counts[snag.last[:inverse_table]] += 1
307
- snag.last[:assoc_name]
308
- end.join(', ')}"
309
- end
310
- if mig_path.start_with?(cur_path = ::Rails.root.to_s)
311
- pretty_mig_path = mig_path[cur_path.length..-1]
312
- end
313
- puts "\n*** Created #{done.length} migration files under #{pretty_mig_path || mig_path} ***"
314
- if (stuck_sorted = stuck_counts.to_a.sort { |a, b| b.last <=> a.last }).length.positive?
315
- puts "-----------------------------------------"
316
- puts "Unable to create migrations for #{stuck_sorted.length} tables#{
317
- ". Here's the top 5 blockers" if stuck_sorted.length > 5
318
- }:"
319
- pp stuck_sorted[0..4]
320
- else # Successful, and now we can update the schema_migrations table accordingly
321
- unless ActiveRecord::Migration.table_exists?(ActiveRecord::Base.schema_migrations_table_name)
322
- ActiveRecord::SchemaMigration.create_table
323
- end
324
- # Remove to_delete - to_create
325
- if ((versions_to_delete_or_append ||= []) - versions_to_create).present? && is_delete_versions
326
- ActiveRecord::Base.execute_sql("DELETE FROM #{
327
- ActiveRecord::Base.schema_migrations_table_name} WHERE version IN (#{
328
- (versions_to_delete_or_append - versions_to_create).map { |vtd| "'#{vtd}'" }.join(', ')}
329
- )")
330
- end
331
- # Add to_create - to_delete
332
- if is_insert_versions && ((versions_to_create ||= []) - versions_to_delete_or_append).present?
333
- ActiveRecord::Base.execute_sql("INSERT INTO #{
334
- ActiveRecord::Base.schema_migrations_table_name} (version) VALUES #{
335
- (versions_to_create - versions_to_delete_or_append).map { |vtc| "('#{vtc}')" }.join(', ')
336
- }")
337
- end
338
- end
339
- end
340
-
341
- private
342
-
343
- def emit_column(type, name, suffix)
344
- " t.#{type.start_with?('numeric') ? 'decimal' : type} :#{name}#{suffix}\n"
345
- end
346
-
347
- def migration_file_write(mig_path, name, current_mig_time, ar_version, mig)
348
- File.open("#{mig_path}/#{version = current_mig_time.strftime('%Y%m%d%H%M00')}_#{name}.rb", "w") do |f|
349
- f.write "class #{name.camelize} < ActiveRecord::Migration#{ar_version}\n"
350
- f.write mig
351
- f.write "end\n"
352
- end
353
- version
32
+ ::Brick::MigrationBuilder.generate_migrations(chosen, mig_path, is_insert_versions, is_delete_versions)
354
33
  end
355
34
  end
356
35
  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.157
4
+ version: 1.0.158
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-07-27 00:00:00.000000000 Z
11
+ date: 2023-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -255,6 +255,7 @@ files:
255
255
  - lib/brick/version_number.rb
256
256
  - lib/generators/brick/USAGE
257
257
  - lib/generators/brick/install_generator.rb
258
+ - lib/generators/brick/migration_builder.rb
258
259
  - lib/generators/brick/migrations_generator.rb
259
260
  - lib/generators/brick/models_generator.rb
260
261
  - lib/generators/brick/seeds_generator.rb