declare_schema 1.4.0.colin.9 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -1
- data/Gemfile.lock +1 -1
- data/lib/declare_schema/model/index_definition.rb +1 -1
- data/lib/declare_schema/model.rb +50 -5
- data/lib/declare_schema/schema_change/column_add.rb +4 -2
- data/lib/declare_schema/version.rb +1 -1
- data/lib/declare_schema.rb +13 -3
- data/lib/generators/declare_schema/migration/migrator.rb +1 -1
- data/spec/lib/declare_schema/migration_generator_spec.rb +48 -0
- data/spec/lib/declare_schema_spec.rb +40 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a00635546beea8512ef5678ea8d182612f6a6e5b099d6a13c4287e52312e8490
|
4
|
+
data.tar.gz: f5ba19f1fd2dc5d3c66923dda796e413f4a3bcd4586764b254c4345602ebb984
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cd37d8cf76460b9af36e1f05c7e5c42f370eae8fec0936e563a6dcd74e7176d1d8c66e6fa464d7e58429be220be0dbb5b6a7f5bf7c19dfb0ff834e53771e806
|
7
|
+
data.tar.gz: '0870d0173821829d81c8033fda33921c8de00f7b1ec5aa2b9da56fb15b7d73b716a14058be0dc2ad595bc2bf2a6b9b66c8cdcbb612dd91fd21d88c0992b0e057'
|
data/CHANGELOG.md
CHANGED
@@ -4,12 +4,21 @@ Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
4
4
|
|
5
5
|
Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
-
## [1.4] -
|
7
|
+
## [1.4.0] - 2024-01-24
|
8
8
|
### Added
|
9
9
|
- Added support for partial indexes with `length:` option.
|
10
10
|
### Changed
|
11
11
|
- Deprecate index: 'name' and unique: true|false in favor of index: { name: 'name', unique: true|false }.
|
12
12
|
|
13
|
+
## [1.3.6] - 2024-01-22
|
14
|
+
### Fixed
|
15
|
+
- Add missing commits around connection: and Array check for composite declared primary key.
|
16
|
+
|
17
|
+
## [1.3.5] - 2024-01-22
|
18
|
+
### Fixed
|
19
|
+
- Make `default_charset=` and `default_collation=` lazy so they don't use the database connection to check the
|
20
|
+
MySQL version. Instead, that is checked the first time `default_charset` or `default_collation` is called.
|
21
|
+
|
13
22
|
## [1.3.4] - 2024-01-18
|
14
23
|
### Fixed
|
15
24
|
- Add test for migrating `has_and_belongs_to_many` associations and fix them to properly declare their
|
data/Gemfile.lock
CHANGED
@@ -18,7 +18,7 @@ module DeclareSchema
|
|
18
18
|
|
19
19
|
def initialize(columns, table_name:, name: nil, allow_equivalent: false, unique: false, where: nil, length: nil)
|
20
20
|
@table_name = table_name
|
21
|
-
@name = name || self.class.default_index_name(table_name, columns)
|
21
|
+
@name = (name || self.class.default_index_name(table_name, columns)).to_s
|
22
22
|
@columns = Array.wrap(columns).map(&:to_s)
|
23
23
|
@explicit_name = @name if !allow_equivalent
|
24
24
|
unique.in?([false, true]) or raise ArgumentError, "unique must be true or false: got #{unique.inspect}"
|
data/lib/declare_schema/model.rb
CHANGED
@@ -50,10 +50,23 @@ module DeclareSchema
|
|
50
50
|
|
51
51
|
module ClassMethods
|
52
52
|
def index(columns, name: nil, allow_equivalent: false, unique: false, where: nil, length: nil)
|
53
|
-
|
53
|
+
index_definition = ::DeclareSchema::Model::IndexDefinition.new(
|
54
54
|
columns,
|
55
55
|
name: name, table_name: table_name, allow_equivalent: allow_equivalent, unique: unique, where: where, length: length
|
56
56
|
)
|
57
|
+
|
58
|
+
if (equivalent = index_definitions.find { index_definition.equivalent?(_1) }) # differs only by name
|
59
|
+
if equivalent == index_definition
|
60
|
+
# identical is always idempotent
|
61
|
+
else
|
62
|
+
# equivalent is idempotent iff allow_equivalent: true passed
|
63
|
+
allow_equivalent or
|
64
|
+
raise ArgumentError, "equivalent index definition found (pass allow_equivalent: true to ignore):\n" \
|
65
|
+
"#{index_definition.inspect}\n#{equivalent.inspect}"
|
66
|
+
end
|
67
|
+
else
|
68
|
+
index_definitions << index_definition
|
69
|
+
end
|
57
70
|
end
|
58
71
|
|
59
72
|
def primary_key_index(*columns)
|
@@ -61,11 +74,13 @@ module DeclareSchema
|
|
61
74
|
end
|
62
75
|
|
63
76
|
def constraint(foreign_key_column, parent_table_name: nil, constraint_name: nil, parent_class_name: nil, dependent: nil)
|
64
|
-
|
77
|
+
constraint_definition = ::DeclareSchema::Model::ForeignKeyDefinition.new(
|
65
78
|
foreign_key_column.to_s,
|
66
79
|
constraint_name: constraint_name,
|
67
80
|
child_table_name: table_name, parent_table_name: parent_table_name, parent_class_name: parent_class_name, dependent: dependent
|
68
81
|
)
|
82
|
+
|
83
|
+
constraint_definitions << constraint_definition # Set<> implements idempotent insert.
|
69
84
|
end
|
70
85
|
|
71
86
|
# tell the migration generator to ignore the named index. Useful for existing indexes, or for indexes
|
@@ -100,7 +115,37 @@ module DeclareSchema
|
|
100
115
|
# 1. creates a FieldSpec for the foreign key
|
101
116
|
# 2. declares an index on the foreign key (optional)
|
102
117
|
# 3. declares a foreign_key constraint (optional)
|
118
|
+
# @param name [Symbol] the name of the association to pass to super
|
119
|
+
# @param scope [Proc] the scope of the association to pass to super
|
120
|
+
# @option options [Boolean] :optional (default: false) whether the foreign key column should be nullable and whether
|
121
|
+
# ActiveRecord should validate presence of the foreign key (passed through to super)
|
122
|
+
# @option options [Boolean] :null (default: inferred from options[:optional]) whether the foreign key column should be nullable
|
123
|
+
# (`null:` should only be passed if it is the inverse of `optional:`; otherwise it is redundant)
|
124
|
+
# @option options [Integer] :limit (default: inferred from the primary key limit:) the limit of the foreign key column size (4 or 8)
|
125
|
+
# @option options [Boolean|Hash<Symbol>] :index (default: true) whether to create an index on the foreign key; can be true or false
|
126
|
+
# or a hash of options to pass to the index declaration, with keys like { name: ..., unique: ... }
|
127
|
+
# @option options [Boolean] :allow_equivalent (default: false) whether to allow an existing index with a different name
|
128
|
+
# @option options [Boolean|String] :constraint (default: true) whether to create a foreign key constraint on the foreign key;
|
129
|
+
# may be true or false or a string to use as the constraint name
|
130
|
+
# @option options [Boolean] :polymorphic (default: false) whether this is a polymorphic belongs_to with a _type column next to
|
131
|
+
# the foreign key _id column (also passed through to super)
|
132
|
+
# @option options [Boolean] :far_end_dependent (default: nil) whether to add a dependent: :delete to the far end of the foreign key
|
133
|
+
# constraint
|
134
|
+
# @option options [String] :foreign_type (default: "#{name}_type") the name prefix for the _type column for a polymorphic belongs_to
|
135
|
+
# (passed through to super)
|
136
|
+
# Other options are passed through to super
|
103
137
|
def belongs_to(name, scope = nil, **options)
|
138
|
+
if options[:null].in?([true, false]) && options[:optional] == options[:null]
|
139
|
+
STDERR.puts("[declare_schema warning] belongs_to #{name.inspect}, null: with the same value as optional: is redundant; omit null: #{options[:null]} (called from #{caller[0]})")
|
140
|
+
elsif !options.has_key?(:optional)
|
141
|
+
case options[:null]
|
142
|
+
when true
|
143
|
+
STDERR.puts("[declare_schema] belongs_to #{name.inspect}, null: true is deprecated in favor of optional: true (called from #{caller[0]})")
|
144
|
+
when false
|
145
|
+
STDERR.puts("[declare_schema] belongs_to #{name.inspect}, null: false is implied and can be omitted (called from #{caller[0]})")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
104
149
|
column_options = {}
|
105
150
|
|
106
151
|
column_options[:null] = if options.has_key?(:null)
|
@@ -125,18 +170,18 @@ module DeclareSchema
|
|
125
170
|
index_options = {} # create an index
|
126
171
|
case index_value
|
127
172
|
when String, Symbol
|
128
|
-
ActiveSupport::Deprecation.warn("belongs_to #{name.inspect}, index: 'name' is deprecated; use index: { name: 'name' } instead (in #{self.name})")
|
173
|
+
ActiveSupport::Deprecation.warn("[declare_schema] belongs_to #{name.inspect}, index: 'name' is deprecated; use index: { name: 'name' } instead (in #{self.name})")
|
129
174
|
index_options[:name] = index_value.to_s
|
130
175
|
when true
|
131
176
|
when nil
|
132
177
|
when Hash
|
133
178
|
index_options = index_value
|
134
179
|
else
|
135
|
-
raise ArgumentError, "belongs_to #{name.inspect}, index: must be true or false or a Hash; got #{index_value.inspect} (in #{self.name})"
|
180
|
+
raise ArgumentError, "[declare_schema] belongs_to #{name.inspect}, index: must be true or false or a Hash; got #{index_value.inspect} (in #{self.name})"
|
136
181
|
end
|
137
182
|
|
138
183
|
if options.has_key?(:unique)
|
139
|
-
ActiveSupport::Deprecation.warn("belongs_to #{name.inspect}, unique: true|false is deprecated; use index: { unique: true|false } instead (in #{self.name})")
|
184
|
+
ActiveSupport::Deprecation.warn("[declare_schema] belongs_to #{name.inspect}, unique: true|false is deprecated; use index: { unique: true|false } instead (in #{self.name})")
|
140
185
|
index_options[:unique] = options.delete(:unique)
|
141
186
|
end
|
142
187
|
|
@@ -6,8 +6,10 @@ module DeclareSchema
|
|
6
6
|
module SchemaChange
|
7
7
|
class ColumnAdd < Base
|
8
8
|
def initialize(table_name, column_name, column_type, **column_options)
|
9
|
-
|
10
|
-
|
9
|
+
table_name.is_a?(String) || table_name.is_a?(Symbol) or raise ArgumentError, "must provide String|Symbol table_name; got #{table_name.inspect}"
|
10
|
+
column_name.is_a?(String) || column_name.is_a?(Symbol) or raise ArgumentError, "must provide String|Symbol column_name; got #{column_name.inspect}"
|
11
|
+
@table_name = table_name
|
12
|
+
@column_name = column_name
|
11
13
|
@column_type = column_type or raise ArgumentError, "must provide column_type"
|
12
14
|
@column_options = column_options
|
13
15
|
end
|
data/lib/declare_schema.rb
CHANGED
@@ -36,7 +36,7 @@ module DeclareSchema
|
|
36
36
|
|
37
37
|
class << self
|
38
38
|
attr_writer :mysql_version
|
39
|
-
attr_reader :
|
39
|
+
attr_reader :default_text_limit, :default_string_limit, :default_null,
|
40
40
|
:default_generate_foreign_keys, :default_generate_indexing, :db_migrate_command,
|
41
41
|
:max_index_and_constraint_name_length
|
42
42
|
|
@@ -81,12 +81,22 @@ module DeclareSchema
|
|
81
81
|
|
82
82
|
def default_charset=(charset)
|
83
83
|
charset.is_a?(String) or raise ArgumentError, "charset must be a string (got #{charset.inspect})"
|
84
|
-
@
|
84
|
+
@default_charset_before_normalization = charset
|
85
|
+
@default_charset = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def default_charset
|
89
|
+
@default_charset ||= normalize_charset(@default_charset_before_normalization)
|
85
90
|
end
|
86
91
|
|
87
92
|
def default_collation=(collation)
|
88
93
|
collation.is_a?(String) or raise ArgumentError, "collation must be a string (got #{collation.inspect})"
|
89
|
-
@
|
94
|
+
@default_collation_before_normalization = collation
|
95
|
+
@default_collation = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def default_collation
|
99
|
+
@default_collation ||= normalize_collation(@default_collation_before_normalization)
|
90
100
|
end
|
91
101
|
|
92
102
|
def default_text_limit=(text_limit)
|
@@ -352,7 +352,7 @@ module Generators
|
|
352
352
|
|
353
353
|
db_columns = model.connection.columns(current_table_name).index_by(&:name)
|
354
354
|
if (pk = model._declared_primary_key.presence)
|
355
|
-
pk_was_in_db_columns = db_columns.delete(pk)
|
355
|
+
pk_was_in_db_columns = pk.is_a?(Array) || db_columns.delete(pk)
|
356
356
|
end
|
357
357
|
|
358
358
|
model_column_names = model.field_specs.keys.map(&:to_s)
|
@@ -1437,5 +1437,53 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
1437
1437
|
end
|
1438
1438
|
end
|
1439
1439
|
end
|
1440
|
+
|
1441
|
+
context 'index' do
|
1442
|
+
before do
|
1443
|
+
class Advert < active_record_base_class.constantize
|
1444
|
+
declare_schema { }
|
1445
|
+
belongs_to :ad_category
|
1446
|
+
end
|
1447
|
+
end
|
1448
|
+
|
1449
|
+
it "is idempotent and doesn't raise" do
|
1450
|
+
expect do
|
1451
|
+
Advert.index [:ad_category_id], name: :index_adverts_on_ad_category_id
|
1452
|
+
end.to_not change { Advert.index_definitions.size }
|
1453
|
+
end
|
1454
|
+
|
1455
|
+
it "when equivalent but not marked to allow, it raises" do
|
1456
|
+
expect do
|
1457
|
+
Advert.index [:ad_category_id], name: :on_ad_category_id
|
1458
|
+
end.to raise_exception(ArgumentError, /equivalent index definition found/i)
|
1459
|
+
end
|
1460
|
+
|
1461
|
+
it "when equivalent and marked to allow, it is idempotent and doesn't raise" do
|
1462
|
+
expect do
|
1463
|
+
Advert.index [:ad_category_id], name: :on_ad_category_id, allow_equivalent: true
|
1464
|
+
end.to_not change { Advert.index_definitions.size }
|
1465
|
+
end
|
1466
|
+
|
1467
|
+
context 'constraint' do
|
1468
|
+
before do
|
1469
|
+
class Advert < active_record_base_class.constantize
|
1470
|
+
declare_schema { }
|
1471
|
+
belongs_to :ad_category
|
1472
|
+
end
|
1473
|
+
end
|
1474
|
+
|
1475
|
+
it "when exactly equal, it is idempotent and doesn't raise" do
|
1476
|
+
expect do
|
1477
|
+
Advert.constraint :ad_category_id, parent_table_name: 'ad_categories', constraint_name: :index_adverts_on_ad_category_id, parent_class_name: 'AdCategory'
|
1478
|
+
end.to_not change { Advert.index_definitions.size }
|
1479
|
+
end
|
1480
|
+
|
1481
|
+
it "when equivalent, it is idempotent and doesn't raise" do
|
1482
|
+
expect do
|
1483
|
+
Advert.constraint :ad_category_id, parent_table_name: 'ad_categories', constraint_name: :constraint_1, parent_class_name: 'AdCategory'
|
1484
|
+
end.to_not change { Advert.index_definitions.size }
|
1485
|
+
end
|
1486
|
+
end
|
1487
|
+
end
|
1440
1488
|
end
|
1441
1489
|
end
|
@@ -37,6 +37,26 @@ RSpec.describe DeclareSchema do
|
|
37
37
|
it { is_expected.to eq("utf8mb3") }
|
38
38
|
end
|
39
39
|
end
|
40
|
+
|
41
|
+
context 'when MySQL version not known yet' do
|
42
|
+
before { described_class.remove_instance_variable('@mysql_version') rescue nil }
|
43
|
+
after { described_class.remove_instance_variable('@mysql_version') rescue nil }
|
44
|
+
|
45
|
+
context 'when set' do
|
46
|
+
let(:connection) { double("connection", select_value: "8.0.21") }
|
47
|
+
|
48
|
+
it "is lazy, so it doesn't use the database connection until read" do
|
49
|
+
expect(ActiveRecord::Base).to receive(:connection) do
|
50
|
+
@connection_called = true
|
51
|
+
connection
|
52
|
+
end
|
53
|
+
described_class.default_charset = "utf8"
|
54
|
+
expect(@connection_called).to be_falsey
|
55
|
+
described_class.default_charset
|
56
|
+
expect(@connection_called).to be_truthy
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
40
60
|
end
|
41
61
|
|
42
62
|
describe '#default_collation' do
|
@@ -81,6 +101,26 @@ RSpec.describe DeclareSchema do
|
|
81
101
|
it { is_expected.to eq("utf8mb3_general_ci") }
|
82
102
|
end
|
83
103
|
end
|
104
|
+
|
105
|
+
context 'when MySQL version not known yet' do
|
106
|
+
before { described_class.remove_instance_variable('@mysql_version') rescue nil }
|
107
|
+
after { described_class.remove_instance_variable('@mysql_version') rescue nil }
|
108
|
+
|
109
|
+
context 'when set' do
|
110
|
+
let(:connection) { double("connection", select_value: "8.0.21") }
|
111
|
+
|
112
|
+
it "is lazy, so it doesn't use the database connection until read" do
|
113
|
+
expect(ActiveRecord::Base).to receive(:connection) do
|
114
|
+
@connection_called = true
|
115
|
+
connection
|
116
|
+
end
|
117
|
+
described_class.default_collation = "utf8_general_ci"
|
118
|
+
expect(@connection_called).to be_falsey
|
119
|
+
described_class.default_collation
|
120
|
+
expect(@connection_called).to be_truthy
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
84
124
|
end
|
85
125
|
|
86
126
|
describe '#default_text_limit' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: declare_schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.4.0
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Invoca Development adapted from hobo_fields by Tom Locke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|