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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e20de5eeec8ea9436d2fd674f5ff33f20888cfb87e17456fd278a38504bbede
4
- data.tar.gz: 259d8dd463292836e40d3fe07261743e714d588440d8a988183daa32f0c8a4d9
3
+ metadata.gz: a00635546beea8512ef5678ea8d182612f6a6e5b099d6a13c4287e52312e8490
4
+ data.tar.gz: f5ba19f1fd2dc5d3c66923dda796e413f4a3bcd4586764b254c4345602ebb984
5
5
  SHA512:
6
- metadata.gz: 18dc843b60c81ace18bc692f1e8d76038d50695081ab846b14f0653aa4d6a755666417f4d7190429aa83e9eb9fffba02d61c71b2a5c44ef7dbf69baf401e7e31
7
- data.tar.gz: 94bf1af85ecdc252e08f6453018edc6a2f216e65aa6d7fce0ab7399058097d6a0e1a834e02d350789e8e6fb3485050979dde543e2036e048cc914dd6ffb5cdad
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] - Unreleased
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (1.4.0.colin.9)
4
+ declare_schema (1.4.0)
5
5
  rails (>= 5.0)
6
6
 
7
7
  GEM
@@ -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}"
@@ -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
- index_definitions << ::DeclareSchema::Model::IndexDefinition.new(
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
- constraint_definitions << ::DeclareSchema::Model::ForeignKeyDefinition.new(
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
- @table_name = table_name or raise ArgumentError, "must provide table_name"
10
- @column_name = column_name or raise ArgumentError, "must provide column_name"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "1.4.0.colin.9"
4
+ VERSION = "1.4.0"
5
5
  end
@@ -36,7 +36,7 @@ module DeclareSchema
36
36
 
37
37
  class << self
38
38
  attr_writer :mysql_version
39
- attr_reader :default_charset, :default_collation, :default_text_limit, :default_string_limit, :default_null,
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
- @default_charset = normalize_charset(charset)
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
- @default_collation = normalize_collation(collation)
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.colin.9
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-20 00:00:00.000000000 Z
11
+ date: 2024-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails