declare_schema 2.0.0 → 2.1.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +19 -0
  3. data/.devcontainer/boot.sh +1 -0
  4. data/.devcontainer/devcontainer.json +55 -0
  5. data/.devcontainer/docker-compose.yml +40 -0
  6. data/.github/workflows/declare_schema_build.yml +35 -11
  7. data/Appraisals +2 -10
  8. data/CHANGELOG.md +5 -0
  9. data/CODE-OF-CONDUCT.md +16 -0
  10. data/CONTRIBUTING.md +46 -0
  11. data/Gemfile +6 -1
  12. data/Gemfile.lock +87 -77
  13. data/README.md +38 -14
  14. data/Rakefile +5 -15
  15. data/catalog-info.yaml +35 -0
  16. data/config/brakeman.ignore +2 -2
  17. data/gemfiles/{rails_6_1_sqlite3.gemfile → rails_6_1.gemfile} +3 -0
  18. data/gemfiles/{rails_7_0_sqlite3.gemfile → rails_7_0.gemfile} +3 -0
  19. data/gemfiles/{rails_7_1_sqlite3.gemfile → rails_7_1.gemfile} +3 -0
  20. data/gemfiles/{rails_7_0_mysql2.gemfile → rails_7_2.gemfile} +4 -1
  21. data/lib/declare_schema/command.rb +2 -8
  22. data/lib/declare_schema/model/column.rb +1 -1
  23. data/lib/declare_schema/model/foreign_key_definition.rb +9 -12
  24. data/lib/declare_schema/model/index_definition.rb +16 -8
  25. data/lib/declare_schema/model.rb +10 -14
  26. data/lib/declare_schema/schema_change/base.rb +4 -0
  27. data/lib/declare_schema/schema_change/primary_key_change.rb +19 -6
  28. data/lib/declare_schema/version.rb +1 -1
  29. data/lib/declare_schema.rb +20 -8
  30. data/lib/generators/declare_schema/migration/migration_generator.rb +15 -2
  31. data/lib/generators/declare_schema/migration/migrator.rb +16 -9
  32. data/spec/fixtures/migrations/mysql2/will_generate_unique_constraint_names_rails_6.txt +15 -0
  33. data/spec/fixtures/migrations/mysql2/will_generate_unique_constraint_names_rails_7.txt +15 -0
  34. data/spec/fixtures/migrations/postgresql/will_generate_unique_constraint_names_rails_6.txt +15 -0
  35. data/spec/fixtures/migrations/postgresql/will_generate_unique_constraint_names_rails_7.txt +15 -0
  36. data/spec/fixtures/migrations/sqlite3/will_generate_unique_constraint_names_rails_6.txt +15 -0
  37. data/spec/fixtures/migrations/sqlite3/will_generate_unique_constraint_names_rails_7.txt +15 -0
  38. data/spec/lib/declare_schema/api_spec.rb +1 -3
  39. data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +3 -3
  40. data/spec/lib/declare_schema/field_spec_spec.rb +68 -45
  41. data/spec/lib/declare_schema/generator_spec.rb +2 -4
  42. data/spec/lib/declare_schema/interactive_primary_key_spec.rb +3 -10
  43. data/spec/lib/declare_schema/migration_generator_spec.rb +248 -249
  44. data/spec/lib/declare_schema/model/column_spec.rb +89 -41
  45. data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +14 -8
  46. data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +4 -9
  47. data/spec/lib/declare_schema/model/index_definition_spec.rb +18 -10
  48. data/spec/lib/declare_schema/model/table_options_definition_spec.rb +11 -11
  49. data/spec/lib/declare_schema/schema_change/base_spec.rb +5 -7
  50. data/spec/lib/declare_schema/schema_change/column_add_spec.rb +1 -3
  51. data/spec/lib/declare_schema/schema_change/column_change_spec.rb +1 -3
  52. data/spec/lib/declare_schema/schema_change/column_remove_spec.rb +1 -3
  53. data/spec/lib/declare_schema/schema_change/column_rename_spec.rb +1 -3
  54. data/spec/lib/declare_schema/schema_change/foreign_key_add_spec.rb +1 -3
  55. data/spec/lib/declare_schema/schema_change/foreign_key_remove_spec.rb +1 -3
  56. data/spec/lib/declare_schema/schema_change/index_add_spec.rb +1 -3
  57. data/spec/lib/declare_schema/schema_change/index_remove_spec.rb +1 -3
  58. data/spec/lib/declare_schema/schema_change/primary_key_change_spec.rb +39 -15
  59. data/spec/lib/declare_schema/schema_change/table_add_spec.rb +1 -3
  60. data/spec/lib/declare_schema/schema_change/table_change_spec.rb +1 -3
  61. data/spec/lib/declare_schema/schema_change/table_remove_spec.rb +1 -3
  62. data/spec/lib/declare_schema/schema_change/table_rename_spec.rb +1 -3
  63. data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +0 -4
  64. data/spec/spec_helper.rb +3 -0
  65. data/spec/support/adapter_specific_test_helpers.rb +25 -0
  66. data/spec/{lib/declare_schema → support}/prepare_testapp.rb +3 -1
  67. data/spec/support/test_app_spec_helpers.rb +7 -0
  68. metadata +22 -9
  69. data/gemfiles/rails_6_1_mysql2.gemfile +0 -23
  70. data/gemfiles/rails_7_1_mysql2.gemfile +0 -23
data/README.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  Declare your Rails/ActiveRecord model schemas and have database migrations generated for you!
4
4
 
5
+ The `DeclareSchema` gem provides a DSL for declaring your model schemas in a block, and then generates the corresponding database migration for you. It also provides a way to configure the default schema for all models, and to ignore certain tables.
6
+
7
+ Currently tested against:
8
+ - Ruby 3.0+
9
+ - Rails 6.1+
10
+ - MySQL 5.7
11
+ - SQLite
12
+ - PostgreSQL 16
13
+
14
+ ## Installation
15
+
16
+ Install the `DeclareSchema` gem directly:
17
+ ```
18
+ $ gem install declare_schema
19
+ ```
20
+ or add it to your `bundler` Gemfile:
21
+ ```
22
+ gem 'declare_schema'
23
+ ```
24
+
5
25
  ## Example
6
26
 
7
27
  Make a model and declare your schema within a `declare_schema do ... end` block:
@@ -115,6 +135,8 @@ The following `index` options are supported:
115
135
  - `length` (integer or hash) - The partial index length(s). If an integer is provided, it is used as the length for all columns. If a hash is provided, it is used to specify the length for individual columns, where the column names are given as `Symbol` hash keys.
116
136
  - `where` (string) - The subset index predicate.
117
137
 
138
+ **Note:** The `DeclareSchema.max_index_and_constraint_name_length` setting is ignored when using `PostgreSQL` since that database does not have a limit on the length of index names, and requires globally unique index names.
139
+
118
140
  ## Usage without Rails
119
141
 
120
142
  When using `DeclareSchema` without Rails, you can use the `declare_schema/rake` task to generate the migration file.
@@ -314,6 +336,9 @@ DeclareSchema.max_index_and_constraint_name_length = 64
314
336
  If you know that your migrations will only be used on a database type with a different limit, you can
315
337
  adjust this configuration value. A `nil` value means "unlimited".
316
338
 
339
+ **Note:** This setting is ignored when using `PostgreSQL` since that database does not have a limit on the length of index names,
340
+ and requires globally unique index names.
341
+
317
342
  ## Declaring Character Set and Collation
318
343
  _Note: This feature currently only works for MySQL database configurations._
319
344
 
@@ -367,19 +392,18 @@ class Comment < ActiveRecord::Base
367
392
  end
368
393
  ```
369
394
 
370
- ## Installing
395
+ ## Contributions
371
396
 
372
- Install the `DeclareSchema` gem directly:
373
- ```
374
- $ gem install declare_schema
375
- ```
376
- or add it to your `bundler` Gemfile:
377
- ```
378
- gem 'declare_schema'
379
- ```
380
- ## Testing
381
- To run tests:
382
- ```
383
- rake test:prepare_testapp[force]
384
- rake test:all
397
+ Contributions to this project are always welcome. Please thoroughly read our [Contribution Guidelines](CONTRIBUTING.md) before starting any work.
398
+
399
+ ### Local Development
400
+
401
+ Depending on the database you are developing for, you may need to install the appropriate database client and server in order to appropriately test your changes. To help with this, we've provided a [DevContainer](https://github.com/devcontainers) configuration that will set up a development environment for you. To use this, you will need to have Docker installed on your machine.
402
+
403
+ ### Running Tests Locally
404
+ To run tests locally, you need to prepare a test application using the specific adapter you'd like to test against. For example, to test against MySQL:
405
+
406
+ ```bash
407
+ bundle exec rake test:prepare_testapp[mysql,true]
408
+ bundle exec rake test:all
385
409
  ```
data/Rakefile CHANGED
@@ -26,24 +26,14 @@ namespace "test" do
26
26
  task all: :spec
27
27
 
28
28
  desc "Prepare a rails application for testing"
29
- task :prepare_testapp, :force do |_t, args|
29
+ task :prepare_testapp, [:adapter, :force] do |_t, args|
30
30
  if args.force || !File.directory?(TESTAPP_PATH)
31
31
  FileUtils.remove_entry_secure(TESTAPP_PATH, true)
32
- sh %(#{BIN} new #{TESTAPP_PATH} --skip-wizard --skip-bundle --api)
32
+ sh %(#{BIN} new #{TESTAPP_PATH} --skip-wizard --skip-bundle --api -d #{args.adapter})
33
33
  FileUtils.chdir(TESTAPP_PATH)
34
- begin
35
- require 'mysql2'
36
- if ENV['MYSQL_PORT']
37
- sh "(echo 'H';
38
- echo '1,$s/localhost/127.0.0.1/';
39
- echo '/host:/';
40
- echo 'a';
41
- echo ' port: #{ENV['MYSQL_PORT']}';
42
- echo '.';
43
- echo w;
44
- echo q) | ed #{TESTAPP_PATH}/config/database.yml || echo ed failed!"
45
- end
46
- rescue LoadError
34
+ if args.adapter == 'mysql'
35
+ sh "sed -i -e 's/host:.*/host: <%= ENV[\"MYSQL_HOST\"].presence || \"localhost\" %>/g' #{TESTAPP_PATH}/config/database.yml || echo sed failed!"
36
+ sh "sed -i -e 's/password:.*/password: <%= ENV[\"MYSQL_PASSWORD\"].presence %>/g' #{TESTAPP_PATH}/config/database.yml || echo sed failed!"
47
37
  end
48
38
  sh "bundle install"
49
39
  sh "(echo '';
data/catalog-info.yaml ADDED
@@ -0,0 +1,35 @@
1
+ # This file is partially auto-generated by the invoca-backstage-tools gem
2
+ # The following fields should not be edited manually as they are auto-generated
3
+ # based on the contents of the repo:
4
+ # - metadata.name
5
+ # - metadata.title
6
+ # - metadata.description
7
+ # - annotations.github.com/project-slug
8
+ # - invoca.com/version-repository-location
9
+ # - invoca.com/version-repository-name
10
+ # - spec.type
11
+ # - spec.owner
12
+ ---
13
+ apiVersion: backstage.io/v1alpha1
14
+ kind: Component
15
+ metadata:
16
+ name: declare_schema-gem
17
+ title: DeclareSchema
18
+ annotations:
19
+ buildkite.com/project-slug: Invoca/declare_schema
20
+ github.com/project-slug: invoca/declare_schema
21
+ invoca.com/version-repository-location: rubygems
22
+ invoca.com/version-repository-name: declare_schema
23
+ endoflife.date/products: ruby@3.1
24
+ tags:
25
+ - ruby
26
+ - open-source
27
+ - gem
28
+ - executable
29
+ description: Declare your Rails/active_record model schemas and have database migrations
30
+ generated for you!
31
+ spec:
32
+ type: library
33
+ lifecycle: production
34
+ owner: octothorpe
35
+ dependsOn: []
@@ -26,11 +26,11 @@
26
26
  {
27
27
  "warning_type": "Command Injection",
28
28
  "warning_code": 14,
29
- "fingerprint": "601f18b634f67229235d6e91434d1ea4c3c845876c4786d27380034e66178ee6",
29
+ "fingerprint": "6b90f8dd199afbdf79cc8c4d00a0853e0696b067d00c0fe93071e31b69de8628",
30
30
  "check_name": "Execute",
31
31
  "message": "Possible command injection",
32
32
  "file": "lib/declare_schema/command.rb",
33
- "line": 51,
33
+ "line": 45,
34
34
  "link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
35
35
  "code": "system(\"rails new #{\"new\"} #{(args * \" \")} -m #{File.join(Dir.tmpdir, \"declare_schema_app_template\")}#{begin\n (require(\"mysql2\")\n \" -d mysql\")\nrescue LoadError\n # do nothing\nend}\")",
36
36
  "render_path": null,
@@ -11,8 +11,11 @@ gem "pry-byebug"
11
11
  gem "rails", "~> 6.1.0"
12
12
  gem "responders"
13
13
  gem "rspec"
14
+ gem "rspec-its"
14
15
  gem "rubocop"
15
16
  gem "yard"
17
+ gem "mysql2", "~> 0.5"
18
+ gem "pg", "~> 1.1"
16
19
  gem "sqlite3", "~> 1.4"
17
20
 
18
21
  group :testapp do
@@ -11,8 +11,11 @@ gem "pry-byebug"
11
11
  gem "rails", "~> 7.0.0"
12
12
  gem "responders"
13
13
  gem "rspec"
14
+ gem "rspec-its"
14
15
  gem "rubocop"
15
16
  gem "yard"
17
+ gem "mysql2", "~> 0.5"
18
+ gem "pg", "~> 1.1"
16
19
  gem "sqlite3", "~> 1.4"
17
20
 
18
21
  group :testapp do
@@ -11,8 +11,11 @@ gem "pry-byebug"
11
11
  gem "rails", "~> 7.1.0"
12
12
  gem "responders"
13
13
  gem "rspec"
14
+ gem "rspec-its"
14
15
  gem "rubocop"
15
16
  gem "yard"
17
+ gem "mysql2", "~> 0.5"
18
+ gem "pg", "~> 1.1"
16
19
  gem "sqlite3", "~> 1.4"
17
20
 
18
21
  group :testapp do
@@ -8,12 +8,15 @@ gem "mail"
8
8
  gem "net-smtp"
9
9
  gem "pry"
10
10
  gem "pry-byebug"
11
- gem "rails", "~> 7.0.0"
11
+ gem "rails", "~> 7.2.0"
12
12
  gem "responders"
13
13
  gem "rspec"
14
+ gem "rspec-its"
14
15
  gem "rubocop"
15
16
  gem "yard"
16
17
  gem "mysql2", "~> 0.5"
18
+ gem "pg", "~> 1.1"
19
+ gem "sqlite3", "~> 1.4"
17
20
 
18
21
  group :testapp do
19
22
  gem "bootsnap", ">= 1.1.0", require: false
@@ -41,14 +41,8 @@ module DeclareSchema
41
41
  file.puts ["gem '#{gem}', '>= #{version}'", (gemfile_options.inspect unless gemfile_options.empty?)].compact.join(', ')
42
42
  end
43
43
  puts "Generating Rails infrastructure..."
44
- database_option =
45
- begin
46
- require 'mysql2'
47
- ' -d mysql'
48
- rescue LoadError
49
- end
50
- puts("rails new #{app_name} #{args * ' '} -m #{template_path}#{database_option}")
51
- system("rails new #{app_name} #{args * ' '} -m #{template_path}#{database_option}")
44
+ puts("rails new #{app_name} #{args * ' '} -m #{template_path}")
45
+ system("rails new #{app_name} #{args * ' '} -m #{template_path}")
52
46
  File.delete(template_path)
53
47
 
54
48
  when /^(g|generate|destroy)$/
@@ -52,7 +52,7 @@ module DeclareSchema
52
52
 
53
53
  def deserialize_default_value(column, type, default_value)
54
54
  type or raise ArgumentError, "must pass type; got #{type.inspect}"
55
- cast_type = ActiveRecord::Base.connection.send(:lookup_cast_type, type.to_s) or
55
+ cast_type = ActiveRecord::Base.connection.lookup_cast_type_from_column(column) or
56
56
  raise "cast_type not found for #{type}"
57
57
  cast_type.deserialize(default_value)
58
58
  end
@@ -31,19 +31,16 @@ module DeclareSchema
31
31
  end
32
32
 
33
33
  class << self
34
+ # TODO: I think we might just be able to start using the AR built in moving forward
34
35
  def for_table(child_table_name, connection, dependent: nil)
35
- show_create_table = connection.select_rows("show create table #{connection.quote_table_name(child_table_name)}").first.last
36
- constraints = show_create_table.split("\n").map { |line| line.strip if line['CONSTRAINT'] }.compact
37
-
38
- constraints.map do |fkc|
39
- constraint_name, foreign_key_column, parent_table_name = fkc.match(/CONSTRAINT `([^`]*)` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)`/).captures
40
- dependent_value = :delete if dependent || fkc['ON DELETE CASCADE']
41
-
42
- new(foreign_key_column,
43
- constraint_name: constraint_name,
44
- child_table_name: child_table_name,
45
- parent_table_name: parent_table_name,
46
- dependent: dependent_value)
36
+ connection.foreign_keys(child_table_name).map do |fkc|
37
+ new(
38
+ fkc.column,
39
+ constraint_name: fkc.name,
40
+ child_table_name: fkc.from_table,
41
+ parent_table_name: fkc.to_table,
42
+ dependent: dependent || fkc.on_delete == :cascade ? :delete : nil
43
+ )
47
44
  end
48
45
  end
49
46
  end
@@ -64,14 +64,11 @@ module DeclareSchema
64
64
  end
65
65
 
66
66
  def default_index_name(table_name, columns)
67
- index_name = nil
68
- [:long_index_name, :short_index_name].find do |method_name|
69
- index_name = send(method_name, table_name, columns)
70
- if DeclareSchema.max_index_and_constraint_name_length.nil? || index_name.length <= DeclareSchema.max_index_and_constraint_name_length
71
- break index_name
72
- end
73
- end or raise IndexNameTooLongError,
74
- "Default index name '#{index_name}' exceeds configured limit of #{DeclareSchema.max_index_and_constraint_name_length} characters. Use the `name:` option to give it a shorter name, or adjust DeclareSchema.max_index_and_constraint_name_length if you know your database can accept longer names."
67
+ if DeclareSchema.current_adapter == 'postgresql'
68
+ long_index_name(table_name, columns)
69
+ else
70
+ longest_valid_index_name(table_name, columns)
71
+ end
75
72
  end
76
73
 
77
74
  # This method normalizes the length option to be either nil or a Hash of Symbol column names to lengths,
@@ -108,6 +105,17 @@ module DeclareSchema
108
105
  end
109
106
  end
110
107
 
108
+ def longest_valid_index_name(table_name, columns)
109
+ index_name = nil
110
+ [:long_index_name, :short_index_name].find do |method_name|
111
+ index_name = send(method_name, table_name, columns)
112
+ if DeclareSchema.max_index_and_constraint_name_length.nil? || index_name.length <= DeclareSchema.max_index_and_constraint_name_length
113
+ break index_name
114
+ end
115
+ end or raise IndexNameTooLongError,
116
+ "Default index name '#{index_name}' exceeds configured limit of #{DeclareSchema.max_index_and_constraint_name_length} characters. Use the `name:` option to give it a shorter name, or adjust DeclareSchema.max_index_and_constraint_name_length if you know your database can accept longer names." # rubocop:disable Layout/LineLength
117
+ end
118
+
111
119
  def long_index_name(table_name, columns)
112
120
  "index_#{table_name}_on_#{Array(columns).join("_and_")}"
113
121
  end
@@ -157,7 +157,7 @@ module DeclareSchema
157
157
  column_options[:default] = options.delete(:default) if options.has_key?(:default)
158
158
  if options.has_key?(:limit)
159
159
  options.delete(:limit)
160
- ActiveSupport::Deprecation.warn("belongs_to #{name.inspect}, limit: is deprecated since it is now inferred")
160
+ DeclareSchema.deprecator.warn("belongs_to #{name.inspect}, limit: is deprecated since it is now inferred")
161
161
  end
162
162
 
163
163
  # index: true means create an index on the foreign key
@@ -171,7 +171,7 @@ module DeclareSchema
171
171
  index_options = {} # create an index
172
172
  case index_value
173
173
  when String, Symbol
174
- ActiveSupport::Deprecation.warn("[declare_schema] belongs_to #{name.inspect}, index: 'name' is deprecated; use index: { name: 'name' } instead (in #{self.name})")
174
+ DeclareSchema.deprecator.warn("[declare_schema] belongs_to #{name.inspect}, index: 'name' is deprecated; use index: { name: 'name' } instead (in #{self.name})")
175
175
  index_options[:name] = index_value.to_s
176
176
  when true
177
177
  when nil
@@ -182,7 +182,7 @@ module DeclareSchema
182
182
  end
183
183
 
184
184
  if options.has_key?(:unique)
185
- ActiveSupport::Deprecation.warn("[declare_schema] belongs_to #{name.inspect}, unique: true|false is deprecated; use index: { unique: true|false } instead (in #{self.name})")
185
+ DeclareSchema.deprecator.warn("[declare_schema] belongs_to #{name.inspect}, unique: true|false is deprecated; use index: { unique: true|false } instead (in #{self.name})")
186
186
  index_options[:unique] = options.delete(:unique)
187
187
  end
188
188
 
@@ -358,24 +358,20 @@ module DeclareSchema
358
358
  end
359
359
 
360
360
  def _add_formatting_for_field(name, type)
361
- if (type_class = DeclareSchema.to_class(type))
362
- if "format".in?(type_class.instance_methods)
363
- before_validation do |record|
364
- record.send("#{name}=", record.send(name)&.format)
365
- end
361
+ if (type_class = DeclareSchema.to_class(type)) && "format".in?(type_class.instance_methods)
362
+ before_validation do |record|
363
+ record.send("#{name}=", record.send(name)&.format)
366
364
  end
367
365
  end
368
366
  end
369
367
 
370
368
  def _add_index_for_field(column_name, args, **options)
371
- if (index_name = options.delete(:index))
372
- index_opts =
373
- {
374
- unique: args.include?(:unique) || !!options.delete(:unique)
375
- }
369
+ if (index_config = options.delete(:index))
370
+ index_opts = index_config.is_a?(Hash) ? index_config : {}
371
+ index_opts[:unique] ||= args.include?(:unique) || !!options.delete(:unique)
376
372
 
377
373
  # support index: true declaration
378
- index_opts[:name] = index_name unless index_name == true
374
+ index_opts[:name] = index_config unless index_config == true || index_config.is_a?(Hash)
379
375
  index([column_name], **index_opts)
380
376
  end
381
377
  end
@@ -33,6 +33,10 @@ module DeclareSchema
33
33
 
34
34
  private
35
35
 
36
+ def current_adapter(model_class = ActiveRecord::Base)
37
+ DeclareSchema.current_adapter(model_class)
38
+ end
39
+
36
40
  def spacing(command)
37
41
  if command["\n"]
38
42
  "\n\n"
@@ -5,7 +5,7 @@ require_relative 'base'
5
5
  module DeclareSchema
6
6
  module SchemaChange
7
7
  class PrimaryKeyChange < Base
8
- def initialize(table_name, old_column_names, new_column_names)
8
+ def initialize(table_name, old_column_names, new_column_names) # rubocop:disable Lint/MissingSuper
9
9
  @table_name = table_name
10
10
  @old_column_names = old_column_names.presence
11
11
  @new_column_names = new_column_names.presence
@@ -22,11 +22,24 @@ module DeclareSchema
22
22
  private
23
23
 
24
24
  def alter_primary_key(old_col_names, new_col_names)
25
- drop_command = "DROP PRIMARY KEY" if old_col_names
26
- add_command = "ADD PRIMARY KEY (#{new_col_names.join(', ')})" if new_col_names
27
- commands = [drop_command, add_command].compact.join(', ')
28
- statement = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(@table_name)} #{commands}"
29
- "execute #{statement.inspect}"
25
+ if current_adapter == 'postgresql'
26
+ [].tap do |commands|
27
+ if old_col_names
28
+ drop_command = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(@table_name)} DROP CONSTRAINT #{@table_name}_pkey;"
29
+ commands << "execute #{drop_command.inspect}"
30
+ end
31
+ if new_col_names
32
+ add_command = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(@table_name)} ADD PRIMARY KEY (#{new_col_names.join(', ')});"
33
+ commands << "execute #{add_command.inspect}"
34
+ end
35
+ end.join("\n")
36
+ else
37
+ drop_command = "DROP PRIMARY KEY" if old_col_names
38
+ add_command = "ADD PRIMARY KEY (#{new_col_names.join(', ')})" if new_col_names
39
+ commands = [drop_command, add_command].compact.join(', ')
40
+ statement = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(@table_name)} #{commands}"
41
+ "execute #{statement.inspect}"
42
+ end
30
43
  end
31
44
  end
32
45
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "2.0.0"
4
+ VERSION = "2.1.0.pre.1"
5
5
  end
@@ -24,14 +24,14 @@ module DeclareSchema
24
24
 
25
25
  SEMVER_8 = Gem::Version.new('8.0.0').freeze
26
26
 
27
- @default_charset = "utf8mb4"
28
- @default_collation = "utf8mb4_bin"
29
- @default_text_limit = 0xffff_ffff
30
- @default_string_limit = nil
31
- @default_null = false
32
- @default_generate_foreign_keys = true
33
- @default_generate_indexing = true
34
- @db_migrate_command = "bundle exec rails db:migrate"
27
+ @default_charset = "utf8mb4"
28
+ @default_collation = "utf8mb4_bin"
29
+ @default_text_limit = 0xffff_ffff
30
+ @default_string_limit = nil
31
+ @default_null = false
32
+ @default_generate_foreign_keys = true
33
+ @default_generate_indexing = true
34
+ @db_migrate_command = "bundle exec rails db:migrate"
35
35
  @max_index_and_constraint_name_length = 64 # limit for MySQL
36
36
 
37
37
  class << self
@@ -146,6 +146,18 @@ module DeclareSchema
146
146
  length.is_a?(Integer) || length.nil? or raise ArgumentError, "max_index_and_constraint_name_length must be an Integer or nil (meaning unlimited)"
147
147
  @max_index_and_constraint_name_length = length
148
148
  end
149
+
150
+ def deprecator
151
+ @deprecator ||= ActiveSupport::Deprecation.new('3.0', 'DeclareSchema')
152
+ end
153
+
154
+ def current_adapter(model_class = ActiveRecord::Base)
155
+ if Rails::VERSION::MAJOR >= 7
156
+ model_class.connection_db_config.adapter
157
+ else
158
+ model_class.connection_config[:adapter]
159
+ end
160
+ end
149
161
  end
150
162
  end
151
163
 
@@ -107,7 +107,13 @@ module DeclareSchema
107
107
  end
108
108
 
109
109
  def load_migrations
110
- if ActiveSupport.version >= Gem::Version.new('7.1.0')
110
+ if ActiveSupport.version >= Gem::Version.new('7.2.0')
111
+ ActiveRecord::MigrationContext.new(
112
+ ActiveRecord::Migrator.migrations_paths,
113
+ ActiveRecord::SchemaMigration.new(ActiveRecord::Base.connection_pool),
114
+ ActiveRecord::InternalMetadata.new(ActiveRecord::Base.connection_pool)
115
+ ).migrations
116
+ elsif ActiveSupport.version >= Gem::Version.new('7.1.0')
111
117
  ActiveRecord::MigrationContext.new(
112
118
  ActiveRecord::Migrator.migrations_paths,
113
119
  ActiveRecord::Base.connection.schema_migration,
@@ -120,7 +126,14 @@ module DeclareSchema
120
126
 
121
127
  def load_pending_migrations
122
128
  migrations = load_migrations
123
- if ActiveSupport.version >= Gem::Version.new('7.1.0')
129
+ if ActiveSupport.version >= Gem::Version.new('7.2.0')
130
+ ActiveRecord::Migrator.new(
131
+ :up,
132
+ migrations,
133
+ ActiveRecord::SchemaMigration.new(ActiveRecord::Base.connection_pool),
134
+ ActiveRecord::InternalMetadata.new(ActiveRecord::Base.connection_pool)
135
+ ).pending_migrations
136
+ elsif ActiveSupport.version >= Gem::Version.new('7.1.0')
124
137
  ActiveRecord::Migrator.new(
125
138
  :up,
126
139
  migrations,
@@ -303,7 +303,7 @@ module Generators
303
303
  private
304
304
 
305
305
  def up_and_down_migrations(migration_commands)
306
- up = migration_commands.map(&:up ).select(&:present?)
306
+ up = migration_commands.map(&:up).select(&:present?)
307
307
  down = migration_commands.map(&:down).select(&:present?).reverse
308
308
 
309
309
  [up * "\n", down * "\n"]
@@ -314,7 +314,7 @@ module Generators
314
314
  if primary_key.blank? || disable_auto_increment
315
315
  { id: false }
316
316
  elsif primary_key == "id"
317
- { id: :bigint }
317
+ { id: current_adapter == 'postgresql' ? :bigserial : :bigint }
318
318
  elsif primary_key.is_a?(Array)
319
319
  { primary_key: primary_key.map(&:to_sym) }
320
320
  else
@@ -322,14 +322,18 @@ module Generators
322
322
  end.merge(model._table_options)
323
323
  end
324
324
 
325
+ def current_adapter(model_class = ActiveRecord::Base)
326
+ ::DeclareSchema.current_adapter(model_class)
327
+ end
328
+
325
329
  def table_options_for_model(model)
326
- if ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
327
- {}
328
- else
330
+ if current_adapter == 'mysql2'
329
331
  {
330
332
  charset: model._table_options&.[](:charset) || ::DeclareSchema.default_charset,
331
333
  collation: model._table_options&.[](:collation) || ::DeclareSchema.default_collation
332
334
  }
335
+ else
336
+ {}
333
337
  end
334
338
  end
335
339
 
@@ -601,12 +605,15 @@ module Generators
601
605
  end
602
606
  end
603
607
 
604
- SchemaDumper = ActiveRecord::ConnectionAdapters::SchemaDumper
605
-
606
-
607
608
  def add_table_back(table)
608
609
  dumped_schema_stream = StringIO.new
609
- SchemaDumper.send(:new, ActiveRecord::Base.connection).send(:table, table, dumped_schema_stream)
610
+ dumper = case current_adapter
611
+ when 'postgresql'
612
+ ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaDumper.create(ActiveRecord::Base.connection, {})
613
+ else
614
+ ActiveRecord::ConnectionAdapters::SchemaDumper.create(ActiveRecord::Base.connection, {})
615
+ end
616
+ dumper.send(:table, table, dumped_schema_stream)
610
617
 
611
618
  dumped_schema = dumped_schema_stream.string.strip.gsub!("\n ", "\n")
612
619
  if connection.class.name.match?(/mysql/i)
@@ -0,0 +1,15 @@
1
+ create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
2
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
3
+ end
4
+ create_table :advertisers, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
5
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
6
+ t.integer :category_id, limit: 8, null: false
7
+ end
8
+ create_table :affiliates, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
9
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
10
+ t.integer :category_id, limit: 8, null: false
11
+ end
12
+ add_index :advertisers, [:category_id], name: :index_advertisers_on_category_id
13
+ add_index :affiliates, [:category_id], name: :index_affiliates_on_category_id
14
+ add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
15
+ add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
@@ -0,0 +1,15 @@
1
+ create_table :affiliates, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
2
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
3
+ t.integer :category_id, limit: 8, null: false
4
+ end
5
+ create_table :advertisers, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
6
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
7
+ t.integer :category_id, limit: 8, null: false
8
+ end
9
+ create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
10
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
11
+ end
12
+ add_index :affiliates, [:category_id], name: :index_affiliates_on_category_id
13
+ add_index :advertisers, [:category_id], name: :index_advertisers_on_category_id
14
+ add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
15
+ add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
@@ -0,0 +1,15 @@
1
+ create_table :categories, id: :bigserial do |t|
2
+ t.string :name, limit: 250, null: true
3
+ end
4
+ create_table :advertisers, id: :bigserial do |t|
5
+ t.string :name, limit: 250, null: true
6
+ t.integer :category_id, limit: 8, null: false
7
+ end
8
+ create_table :affiliates, id: :bigserial do |t|
9
+ t.string :name, limit: 250, null: true
10
+ t.integer :category_id, limit: 8, null: false
11
+ end
12
+ add_index :advertisers, [:category_id], name: :index_advertisers_on_category_id
13
+ add_index :affiliates, [:category_id], name: :index_affiliates_on_category_id
14
+ add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
15
+ add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
@@ -0,0 +1,15 @@
1
+ create_table :affiliates, id: :bigserial do |t|
2
+ t.string :name, limit: 250, null: true
3
+ t.integer :category_id, limit: 8, null: false
4
+ end
5
+ create_table :advertisers, id: :bigserial do |t|
6
+ t.string :name, limit: 250, null: true
7
+ t.integer :category_id, limit: 8, null: false
8
+ end
9
+ create_table :categories, id: :bigserial do |t|
10
+ t.string :name, limit: 250, null: true
11
+ end
12
+ add_index :affiliates, [:category_id], name: :index_affiliates_on_category_id
13
+ add_index :advertisers, [:category_id], name: :index_advertisers_on_category_id
14
+ add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
15
+ add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
@@ -0,0 +1,15 @@
1
+ create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
2
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
3
+ end
4
+ create_table :advertisers, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
5
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
6
+ t.integer :category_id, limit: 8, null: false
7
+ end
8
+ create_table :affiliates, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
9
+ t.string :name, limit: 250, null: true, charset: "utf8mb4", collation: "utf8mb4_bin"
10
+ t.integer :category_id, limit: 8, null: false
11
+ end
12
+ add_index :advertisers, [:category_id], name: :index_advertisers_on_category_id
13
+ add_index :affiliates, [:category_id], name: :index_affiliates_on_category_id
14
+ add_foreign_key :advertisers, :categories, column: :category_id, name: :index_advertisers_on_category_id
15
+ add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id