declare_schema 1.4.0.colin.9 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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