declare_schema 1.4.0.colin.8 → 1.4.0.colin.9

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: ca0ed75221c664e2a5c59bff495a971495922f7f31ea0fb548fb67f0ad0eb639
4
- data.tar.gz: fc76669be291fff9da61353f7f040ba14327e1359f712a963e5865ed948034c8
3
+ metadata.gz: 7e20de5eeec8ea9436d2fd674f5ff33f20888cfb87e17456fd278a38504bbede
4
+ data.tar.gz: 259d8dd463292836e40d3fe07261743e714d588440d8a988183daa32f0c8a4d9
5
5
  SHA512:
6
- metadata.gz: 47b53d7e5306a3b2ada9296494af897738056c1c5e14e35e0b3967daf3176e07cf48c74560e2ccd659d0c1b8bc466e6d29f632f86b25bff568bcc4b6dcf672af
7
- data.tar.gz: 9af422633cae00b32e94bf64d0e6e9445aee83849e4898633384af679b61c877a22ce14f1b20c225d17f4a1546ee3c3a1867e0661516f186717895a658d4b2eb
6
+ metadata.gz: 18dc843b60c81ace18bc692f1e8d76038d50695081ab846b14f0653aa4d6a755666417f4d7190429aa83e9eb9fffba02d61c71b2a5c44ef7dbf69baf401e7e31
7
+ data.tar.gz: 94bf1af85ecdc252e08f6453018edc6a2f216e65aa6d7fce0ab7399058097d6a0e1a834e02d350789e8e6fb3485050979dde543e2036e048cc914dd6ffb5cdad
data/CHANGELOG.md CHANGED
@@ -10,7 +10,12 @@ Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0
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.3] - 2023-01-17
13
+ ## [1.3.4] - 2024-01-18
14
+ ### Fixed
15
+ - Add test for migrating `has_and_belongs_to_many` associations and fix them to properly declare their
16
+ 2 foreign keys as the primary key of the join table, rather than just a unique index.
17
+
18
+ ## [1.3.3] - 2024-01-17
14
19
  ### Fixed
15
20
  - Fix a MySQL 8 bug where MySQL 8+ renames charset 'utf8' to 'utf8mb3' and collation 'utf8_general_ci' to
16
21
  'utf8mb3_unicode_ci'.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (1.4.0.colin.8)
4
+ declare_schema (1.4.0.colin.9)
5
5
  rails (>= 5.0)
6
6
 
7
7
  GEM
@@ -4,15 +4,17 @@ module DeclareSchema
4
4
  module Model
5
5
  class HabtmModelShim
6
6
  class << self
7
- def from_reflection(refl)
8
- new(refl.join_table, [refl.foreign_key, refl.association_foreign_key],
9
- [refl.active_record.table_name, refl.class_name.constantize.table_name])
7
+ def from_reflection(reflection)
8
+ new(reflection.join_table,
9
+ [reflection.foreign_key, reflection.association_foreign_key],
10
+ [reflection.active_record.table_name, reflection.klass.table_name],
11
+ connection: reflection.active_record.connection)
10
12
  end
11
13
  end
12
14
 
13
- attr_reader :join_table, :foreign_keys, :parent_table_names
15
+ attr_reader :join_table, :foreign_keys, :parent_table_names, :connection
14
16
 
15
- def initialize(join_table, foreign_keys, parent_table_names)
17
+ def initialize(join_table, foreign_keys, parent_table_names, connection:)
16
18
  foreign_keys.is_a?(Array) && foreign_keys.size == 2 or
17
19
  raise ArgumentError, "foreign_keys must be <Array[2]>; got #{foreign_keys.inspect}"
18
20
  parent_table_names.is_a?(Array) && parent_table_names.size == 2 or
@@ -20,6 +22,7 @@ module DeclareSchema
20
22
  @join_table = join_table
21
23
  @foreign_keys = foreign_keys.sort # Rails requires these be in alphabetical order
22
24
  @parent_table_names = @foreign_keys == foreign_keys ? parent_table_names : parent_table_names.reverse # match the above sort
25
+ @connection = connection
23
26
  end
24
27
 
25
28
  def _table_options
@@ -41,17 +44,21 @@ module DeclareSchema
41
44
  end
42
45
 
43
46
  def _declared_primary_key
44
- false # no single-column primary key declared
47
+ foreign_keys
45
48
  end
46
49
 
47
- def index_definitions_with_primary_key
48
- @index_definitions_with_primary_key ||= Set.new([
49
- IndexDefinition.new(foreign_keys, name: Model::IndexDefinition::PRIMARY_KEY_NAME, table_name: table_name, unique: true), # creates a primary composite key on both foreign keys
50
- IndexDefinition.new(foreign_keys.last, table_name: table_name, unique: false) # index for queries where we only have the last foreign key
51
- ])
50
+ def index_definitions
51
+ [
52
+ IndexDefinition.new(foreign_keys.last, table_name: table_name, unique: false) # index for queries where we only have the last foreign key
53
+ ]
52
54
  end
53
55
 
54
- alias_method :index_definitions, :index_definitions_with_primary_key
56
+ def index_definitions_with_primary_key
57
+ [
58
+ *index_definitions,
59
+ IndexDefinition.new(foreign_keys, table_name: table_name, name: Model::IndexDefinition::PRIMARY_KEY_NAME, unique: true) # creates a primary composite key on both foreign keys
60
+ ]
61
+ end
55
62
 
56
63
  def ignore_indexes
57
64
  @ignore_indexes ||= Set.new
@@ -16,12 +16,9 @@ module DeclareSchema
16
16
 
17
17
  PRIMARY_KEY_NAME = "PRIMARY"
18
18
 
19
- # Caller needs to pass either name or table_name. The table_name is not remembered; it is just used to compute the
20
- # default name if no name is given.
21
19
  def initialize(columns, table_name:, name: nil, allow_equivalent: false, unique: false, where: nil, length: nil)
22
20
  @table_name = table_name
23
21
  @name = name || self.class.default_index_name(table_name, columns)
24
- @name.to_s == 'index_adverts_on_Advert' and binding.pry
25
22
  @columns = Array.wrap(columns).map(&:to_s)
26
23
  @explicit_name = @name if !allow_equivalent
27
24
  unique.in?([false, true]) or raise ArgumentError, "unique must be true or false: got #{unique.inspect}"
@@ -38,7 +35,7 @@ module DeclareSchema
38
35
  @where = where.start_with?('(') ? where : "(#{where})"
39
36
  end
40
37
 
41
- @length = length
38
+ @length = self.class.normalize_index_length(length, columns: @columns)
42
39
  end
43
40
 
44
41
  class << self
@@ -57,16 +54,7 @@ module DeclareSchema
57
54
  raise "primary key on #{table_name} was not unique on #{primary_key_columns} (was unique=#{index.unique} on #{index.columns})"
58
55
  primary_key_found = true
59
56
  end
60
- length =
61
- case lengths = index.lengths
62
- when {}
63
- nil
64
- when Hash
65
- lengths.size == 1 ? lengths.values.first : lengths
66
- else
67
- lengths
68
- end
69
- new(index.columns, name: index.name, table_name: table_name, unique: index.unique, where: index.where, length: length)
57
+ new(index.columns, name: index.name, table_name: table_name, unique: index.unique, where: index.where, length: index.lengths)
70
58
  end.compact
71
59
 
72
60
  if !primary_key_found
@@ -86,6 +74,26 @@ module DeclareSchema
86
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."
87
75
  end
88
76
 
77
+ # This method normalizes the length option to be either nil or a Hash of Symbol column names to lengths,
78
+ # so that we can safely compare what the user specified with what we get when querying the database schema.
79
+ # @return [Hash<Symbol, nil>]
80
+ def normalize_index_length(length, columns:)
81
+ case length
82
+ when nil, {}
83
+ nil
84
+ when Integer
85
+ if columns.size == 1
86
+ { columns.first.to_sym => length }
87
+ else
88
+ raise ArgumentError, "Index length of Integer only allowed when exactly one column; got #{length.inspect} for #{columns.inspect}"
89
+ end
90
+ when Hash
91
+ length.transform_keys(&:to_sym)
92
+ else
93
+ raise ArgumentError, "Index length must be nil or Integer or a Hash of column names to lengths; got #{length.inspect} for #{columns.inspect}"
94
+ end
95
+ end
96
+
89
97
  private
90
98
 
91
99
  SHA_SUFFIX_LENGTH = 4
@@ -111,31 +111,32 @@ module DeclareSchema
111
111
  column_options[:default] = options.delete(:default) if options.has_key?(:default)
112
112
  if options.has_key?(:limit)
113
113
  options.delete(:limit)
114
- ActiveSupport::Deprecation.warn("belongs_to limit: is deprecated since it is now inferred")
114
+ ActiveSupport::Deprecation.warn("belongs_to #{name.inspect}, limit: is deprecated since it is now inferred")
115
115
  end
116
116
 
117
117
  # index: true means create an index on the foreign key
118
118
  # index: false means do not create an index on the foreign key
119
119
  # index: { ... } means create an index on the foreign key with the given options
120
120
  index_value = options.delete(:index)
121
- if index_value != false || options.has_key?(:unique) || options.has_key?(:allow_equivalent)
122
- index_options = {} # truthy iff we want an index
121
+ if index_value == false # don't create an index
122
+ options.delete(:unique)
123
+ options.delete(:allow_equivalent)
124
+ else
125
+ index_options = {} # create an index
123
126
  case index_value
124
127
  when String, Symbol
125
- Kernel.warn("belongs_to index: 'name' is deprecated; use index: { name: 'name' } instead (in #{name})")
128
+ ActiveSupport::Deprecation.warn("belongs_to #{name.inspect}, index: 'name' is deprecated; use index: { name: 'name' } instead (in #{self.name})")
126
129
  index_options[:name] = index_value.to_s
127
- when false
128
- raise ArgumentError, "belongs_to index: false contradicts others options #{options.inspect} (in #{name})"
129
130
  when true
130
131
  when nil
131
132
  when Hash
132
133
  index_options = index_value
133
134
  else
134
- raise ArgumentError, "belongs_to index: must be true or false or a Hash; got #{index_value.inspect} (in #{name})"
135
+ raise ArgumentError, "belongs_to #{name.inspect}, index: must be true or false or a Hash; got #{index_value.inspect} (in #{self.name})"
135
136
  end
136
137
 
137
138
  if options.has_key?(:unique)
138
- Kernel.warn("belongs_to unique: true|false is deprecated; use index: { unique: true|false } instead (in #{name})")
139
+ ActiveSupport::Deprecation.warn("belongs_to #{name.inspect}, unique: true|false is deprecated; use index: { unique: true|false } instead (in #{self.name})")
139
140
  index_options[:unique] = options.delete(:unique)
140
141
  end
141
142
 
@@ -186,7 +187,7 @@ module DeclareSchema
186
187
  end
187
188
 
188
189
  if ::DeclareSchema.default_generate_foreign_keys && constraint_name != false
189
- constraint(foreign_key_column, constraint_name: constraint_name || index_options&.[](:name), parent_class_name: reflection.klass, dependent: dependent_delete)
190
+ constraint(foreign_key_column, constraint_name: constraint_name || index_options&.[](:name), parent_class_name: reflection.class_name, dependent: dependent_delete)
190
191
  end
191
192
  end
192
193
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "1.4.0.colin.8"
4
+ VERSION = "1.4.0.colin.9"
5
5
  end
@@ -204,8 +204,8 @@ module Generators
204
204
  end
205
205
  end
206
206
  # generate shims for HABTM models
207
- habtm_tables.each do |name, refls|
208
- models_by_table_name[name] = ::DeclareSchema::Model::HabtmModelShim.from_reflection(refls.first)
207
+ habtm_tables.each do |name, reflections|
208
+ models_by_table_name[name] = ::DeclareSchema::Model::HabtmModelShim.from_reflection(reflections.first)
209
209
  end
210
210
  model_table_names = models_by_table_name.keys
211
211
 
@@ -222,8 +222,8 @@ module Generators
222
222
  ::DeclareSchema::SchemaChange::TableRemove.new(t, add_table_back(t))
223
223
  end
224
224
 
225
- creates = to_create.map do |t|
226
- model = models_by_table_name[t]
225
+ creates = to_create.map do |table_name|
226
+ model = models_by_table_name[table_name]
227
227
  disable_auto_increment = model.try(:disable_auto_increment)
228
228
 
229
229
  primary_key_definition =
@@ -240,10 +240,13 @@ module Generators
240
240
  table_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, **table_options_for_model(model))
241
241
  table_options = create_table_options(model, disable_auto_increment)
242
242
 
243
- table_add = ::DeclareSchema::SchemaChange::TableAdd.new(t,
244
- primary_key_definition + field_definitions,
245
- table_options,
246
- sql_options: table_options_definition.settings)
243
+ table_add = ::DeclareSchema::SchemaChange::TableAdd.new(
244
+ table_name,
245
+ primary_key_definition + field_definitions,
246
+ table_options,
247
+ sql_options: table_options_definition.settings
248
+ )
249
+
247
250
  [
248
251
  table_add,
249
252
  *Array((create_indexes(model) if ::DeclareSchema.default_generate_indexing)),
@@ -256,9 +259,9 @@ module Generators
256
259
  fk_changes = []
257
260
  table_options_changes = []
258
261
 
259
- to_change.each do |t|
260
- model = models_by_table_name[t]
261
- table = to_rename.key(t) || model.table_name
262
+ to_change.each do |table_name|
263
+ model = models_by_table_name[table_name]
264
+ table = to_rename.key(table_name) || model.table_name
262
265
  if table.in?(db_tables)
263
266
  change, index_change, fk_change, table_options_change = change_table(model, table)
264
267
  changes << change
@@ -312,6 +315,8 @@ module Generators
312
315
  { id: false }
313
316
  elsif primary_key == "id"
314
317
  { id: :bigint }
318
+ elsif primary_key.is_a?(Array)
319
+ { primary_key: primary_key.map(&:to_sym) }
315
320
  else
316
321
  { primary_key: primary_key.to_sym }
317
322
  end.merge(model._table_options)
@@ -414,7 +414,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
414
414
 
415
415
  # You can specify the index name with index: 'name' [deprecated]
416
416
 
417
- expect(Kernel).to receive(:warn).with(/belongs_to index: 'name' is deprecated; use index: \{ name: 'name' \} instead/i)
417
+ expect(ActiveSupport::Deprecation).to receive(:warn).with(/belongs_to :category, index: 'name' is deprecated; use index: \{ name: 'name' \} instead/i)
418
418
 
419
419
  class Category < ActiveRecord::Base; end
420
420
  class Advert < ActiveRecord::Base
@@ -487,8 +487,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
487
487
 
488
488
  # You can add an index to a field definition
489
489
 
490
- expect(Kernel).to receive(:warn).with(/belongs_to index: 'name' is deprecated; use index: \{ name: 'name' \} instead/i)
491
- expect(Kernel).to receive(:warn).with(/belongs_to unique: true\|false is deprecated; use index: \{ unique: true\|false \} instead/i)
490
+ expect(ActiveSupport::Deprecation).to receive(:warn).with(/belongs_to :category, index: 'name' is deprecated; use index: \{ name: 'name' \} instead/i)
491
+ expect(ActiveSupport::Deprecation).to receive(:warn).with(/belongs_to :category, unique: true\|false is deprecated; use index: \{ unique: true\|false \} instead/i)
492
492
 
493
493
  class Advert < ActiveRecord::Base
494
494
  declare_schema do
@@ -572,7 +572,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
572
572
  expect(Generators::DeclareSchema::Migration::Migrator.run).to(
573
573
  migrate_up(<<~EOS.strip)
574
574
  add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
575
- add_index :adverts, [:title], name: :my_index, length: 10
575
+ add_index :adverts, [:title], name: :my_index, length: { title: 10 }
576
576
  EOS
577
577
  )
578
578
 
@@ -820,6 +820,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
820
820
  expect(User.field_specs.keys).to eq(['company'])
821
821
  expect(User.field_specs['company'].options[:ruby_default]&.call).to eq("BigCorp")
822
822
 
823
+ nuke_model_class(User)
824
+
823
825
  ## validates
824
826
 
825
827
  # DeclareSchema can accept a validates hash in the field options.
@@ -840,6 +842,44 @@ RSpec.describe 'DeclareSchema Migration Generator' do
840
842
  up, _down = Generators::DeclareSchema::Migration::Migrator.run
841
843
  ActiveRecord::Migration.class_eval(up)
842
844
  expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
845
+
846
+ # DeclareSchema supports has_and_belongs_to_many relationships and generates the intersection ("join") table
847
+ # with appropriate primary key, indexes, and foreign keys.
848
+
849
+ class Advertiser < ActiveRecord::Base
850
+ declare_schema do
851
+ string :name, limit: 250
852
+ end
853
+ has_and_belongs_to_many :creatives
854
+ end
855
+ class Creative < ActiveRecord::Base
856
+ declare_schema do
857
+ string :url, limit: 500
858
+ end
859
+ has_and_belongs_to_many :advertisers
860
+ end
861
+
862
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
863
+ migrate_up(<<~EOS.strip)
864
+ create_table :advertisers, id: :bigint#{create_table_charset_and_collation} do |t|
865
+ t.string :name, limit: 250, null: false#{charset_and_collation}
866
+ end
867
+ create_table :advertisers_creatives, primary_key: [:advertiser_id, :creative_id]#{create_table_charset_and_collation} do |t|
868
+ t.integer :advertiser_id, limit: 8, null: false
869
+ t.integer :creative_id, limit: 8, null: false
870
+ end
871
+ create_table :creatives, id: :bigint#{create_table_charset_and_collation} do |t|
872
+ t.string :url, limit: 500, null: false#{charset_and_collation}
873
+ end
874
+ add_index :advertisers_creatives, [:creative_id], name: :index_advertisers_creatives_on_creative_id
875
+ add_foreign_key :advertisers_creatives, :advertisers, column: :advertiser_id, name: :advertisers_creatives_FK1
876
+ add_foreign_key :advertisers_creatives, :creatives, column: :creative_id, name: :advertisers_creatives_FK2
877
+ EOS
878
+ )
879
+
880
+ nuke_model_class(Ad)
881
+ nuke_model_class(Advertiser)
882
+ nuke_model_class(Creative)
843
883
  end
844
884
 
845
885
  context 'models with the same parent foreign key relation' do
@@ -863,7 +903,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
863
903
  end
864
904
  end
865
905
 
866
- it 'will genereate unique constraint names' do
906
+ it 'will generate unique constraint names' do
867
907
  expect(Generators::DeclareSchema::Migration::Migrator.run).to(
868
908
  migrate_up(<<~EOS.strip)
869
909
  create_table :categories, id: :bigint, options: "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin" do |t|
@@ -1160,7 +1200,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
1160
1200
  end
1161
1201
 
1162
1202
  it 'deprecates limit:' do
1163
- expect(ActiveSupport::Deprecation).to receive(:warn).with("belongs_to limit: is deprecated since it is now inferred")
1203
+ expect(ActiveSupport::Deprecation).to receive(:warn).with("belongs_to :ad_category, limit: is deprecated since it is now inferred")
1164
1204
  eval <<~EOS
1165
1205
  class UsingLimit < ActiveRecord::Base
1166
1206
  declare_schema { }
@@ -11,6 +11,7 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
11
11
  let(:join_table) { "customers_users" }
12
12
  let(:foreign_keys) { ["user_id", "customer_id"] }
13
13
  let(:parent_table_names) { ["users", "customers"] }
14
+ let(:connection) { instance_double(ActiveRecord::Base.connection.class, "connection") }
14
15
 
15
16
  before do
16
17
  load File.expand_path('../prepare_testapp.rb', __dir__)
@@ -30,8 +31,11 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
30
31
  foreign_key: foreign_keys.first,
31
32
  association_foreign_key: foreign_keys.last,
32
33
  active_record: User,
33
- class_name: 'Customer') }
34
+ class_name: 'Customer',
35
+ klass: Customer) }
34
36
  it 'returns a new object' do
37
+ expect(User).to receive(:connection).and_return(connection)
38
+
35
39
  result = described_class.from_reflection(reflection)
36
40
 
37
41
  expect(result).to be_a(described_class)
@@ -42,9 +46,7 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
42
46
  end
43
47
 
44
48
  describe 'instance methods' do
45
- let(:connection) { instance_double(ActiveRecord::Base.connection.class, "connection") }
46
-
47
- subject { described_class.new(join_table, foreign_keys, parent_table_names) }
49
+ subject { described_class.new(join_table, foreign_keys, parent_table_names, connection: connection) }
48
50
 
49
51
  describe '#initialize' do
50
52
  it 'stores initialization attributes' do
@@ -53,6 +55,12 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
53
55
  end
54
56
  end
55
57
 
58
+ describe '#connection' do
59
+ it 'returns the connection' do
60
+ expect(subject.connection).to be(connection)
61
+ end
62
+ end
63
+
56
64
  describe '#table_options' do
57
65
  it 'returns empty hash' do
58
66
  expect(subject._table_options).to eq({})
@@ -85,14 +93,14 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
85
93
  end
86
94
 
87
95
  describe '#primary_key' do
88
- it 'returns false' do
89
- expect(subject._declared_primary_key).to eq(false)
96
+ it 'returns false because there is no single-column PK for ActiveRecord to use' do
97
+ expect(subject.primary_key).to eq(false)
90
98
  end
91
99
  end
92
100
 
93
101
  describe '#_declared_primary_key' do
94
- it 'returns false' do
95
- expect(subject._declared_primary_key).to eq(false)
102
+ it 'returns the foreign key pair that are used as the primary key in the database' do
103
+ expect(subject._declared_primary_key).to eq(["customer_id", "user_id"])
96
104
  end
97
105
  end
98
106
 
@@ -101,10 +109,10 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
101
109
  index_definitions = subject.index_definitions_with_primary_key
102
110
  expect(index_definitions.size).to eq(2), index_definitions.inspect
103
111
 
104
- expect(index_definitions.first).to be_a(::DeclareSchema::Model::IndexDefinition)
105
- expect(index_definitions.first.name).to eq('PRIMARY')
106
- expect(index_definitions.first.fields).to eq(foreign_keys.reverse)
107
- expect(index_definitions.first.unique).to be_truthy
112
+ expect(index_definitions.last).to be_a(::DeclareSchema::Model::IndexDefinition)
113
+ expect(index_definitions.last.name).to eq('PRIMARY')
114
+ expect(index_definitions.last.fields).to eq(foreign_keys.reverse)
115
+ expect(index_definitions.last.unique).to be_truthy
108
116
  end
109
117
  end
110
118
 
@@ -127,21 +135,38 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
127
135
  it 'returns two index definitions and does not raise a IndexNameTooLongError' do
128
136
  indexes = subject.index_definitions_with_primary_key.to_a
129
137
  expect(indexes.size).to eq(2), indexes.inspect
130
- expect(indexes.first).to be_a(::DeclareSchema::Model::IndexDefinition)
131
- expect(indexes.first.name).to eq('PRIMARY')
132
- expect(indexes.first.fields).to eq(foreign_keys)
133
- expect(indexes.first.unique).to be_truthy
134
138
  expect(indexes.last).to be_a(::DeclareSchema::Model::IndexDefinition)
135
- expect(indexes.last.name).to eq('index_advertiser_campaigns_tracking_pixels_on_campaign_id')
136
- expect(indexes.last.fields).to eq([foreign_keys.last])
137
- expect(indexes.last.unique).to be_falsey
139
+ expect(indexes.last.name).to eq('PRIMARY')
140
+ expect(indexes.last.fields).to eq(foreign_keys)
141
+ expect(indexes.last.unique).to be_truthy
142
+ expect(indexes.first).to be_a(::DeclareSchema::Model::IndexDefinition)
143
+ expect(indexes.first.name).to eq('index_advertiser_campaigns_tracking_pixels_on_campaign_id')
144
+ expect(indexes.first.fields).to eq([foreign_keys.last])
145
+ expect(indexes.first.unique).to be_falsey
138
146
  end
139
147
  end
140
148
 
141
149
  describe '#index_definitions' do
142
- it 'returns index_definitions_with_primary_key' do
150
+ it 'returns index_definitions' do
143
151
  indexes = subject.index_definitions
152
+ expect(indexes.size).to eq(1), indexes.inspect
153
+ expect(indexes.first.columns).to eq(["user_id"])
154
+ options = [:name, :unique, :where].map { |k| [k, indexes.first.send(k)] }.to_h
155
+ expect(options).to eq(name: "index_customers_users_on_user_id",
156
+ unique: false,
157
+ where: nil)
158
+ end
159
+ end
160
+
161
+ describe '#index_definitions_with_primary_key' do
162
+ it 'returns index_definitions_with_primary_key' do
163
+ indexes = subject.index_definitions_with_primary_key
144
164
  expect(indexes.size).to eq(2), indexes.inspect
165
+ expect(indexes.last.columns).to eq(["customer_id", "user_id"])
166
+ options = [:name, :unique, :where].map { |k| [k, indexes.last.send(k)] }.to_h
167
+ expect(options).to eq(name: "PRIMARY",
168
+ unique: true,
169
+ where: nil)
145
170
  end
146
171
  end
147
172
 
@@ -77,13 +77,14 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
77
77
  let(:options) { { table_name: table_name, length: length } }
78
78
 
79
79
  context 'with integer length' do
80
+ let(:fields) { ['last_name'] }
80
81
  let(:length) { 2 }
81
82
 
82
- it { is_expected.to eq(length) }
83
+ it { is_expected.to eq(last_name: 2) }
83
84
  end
84
85
 
85
86
  context 'with Hash length' do
86
- let(:length) { { name: 2 } }
87
+ let(:length) { { first_name: 2 } }
87
88
 
88
89
  it { is_expected.to eq(length) }
89
90
  end
@@ -91,7 +92,7 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
91
92
 
92
93
  describe '#options' do
93
94
  subject { instance.options }
94
- let(:options) { { name: 'my_index', table_name: table_name, unique: false, where: "(name like 'a%')", length: 10 } }
95
+ let(:options) { { name: 'my_index', table_name: table_name, unique: false, where: "(last_name like 'a%')", length: { last_name: 10, first_name: 5 } } }
95
96
 
96
97
  it { is_expected.to eq(options.except(:table_name)) }
97
98
  end
@@ -163,7 +164,7 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
163
164
  it 'returns the indexes for the model' do
164
165
  expect(subject.map(&:to_key)).to eq([
165
166
  ["index_definition_test_models_on_name", ["name"], { unique: true, where: nil, length: nil }],
166
- (["index_definition_test_models_on_name_partial", ["name"], { unique: false, where: nil, length: 10 }] if defined?(Mysql2)),
167
+ (["index_definition_test_models_on_name_partial", ["name"], { unique: false, where: nil, length: { name: 10 } }] if defined?(Mysql2)),
167
168
  ["PRIMARY", ["id"], { unique: true, where: nil, length: nil }]
168
169
  ].compact)
169
170
  end
@@ -184,7 +185,7 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
184
185
 
185
186
  it 'skips the ignored index' do
186
187
  expect(subject.map(&:to_key)).to eq([
187
- (["index_definition_test_models_on_name_partial", ["name"], { unique: false, where: nil, length: 10 }] if defined?(Mysql2)),
188
+ (["index_definition_test_models_on_name_partial", ["name"], { unique: false, where: nil, length: { name: 10 } }] if defined?(Mysql2)),
188
189
  ["PRIMARY", ["id"], { length: nil, unique: true, where: nil }]
189
190
  ].compact)
190
191
  end
@@ -259,6 +260,69 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
259
260
  end
260
261
  end
261
262
  end
263
+
264
+ describe '.normalize_index_length' do
265
+ let(:columns) { [:last_name] }
266
+ subject { described_class.normalize_index_length(length, columns: columns) }
267
+
268
+ context 'with nil length' do
269
+ let(:length) { nil }
270
+
271
+ it { is_expected.to eq(nil) }
272
+ end
273
+
274
+ context 'when Integer' do
275
+ let(:length) { 10 }
276
+
277
+ it { is_expected.to eq(last_name: length) }
278
+
279
+ context 'with multiple columns' do
280
+ let(:columns) { ["last_name", "first_name"] }
281
+
282
+ it { expect { subject }.to raise_exception(ArgumentError, /Index length of Integer only allowed when exactly one column; got 10 for \["last_name", "first_name"]/i) }
283
+ end
284
+ end
285
+
286
+ context 'when empty Hash' do
287
+ let(:length) { {} }
288
+
289
+ it { is_expected.to eq(nil) }
290
+ end
291
+
292
+ context 'when Hash' do
293
+ let(:length) { { last_name: 10 } }
294
+
295
+ it { is_expected.to eq(length) }
296
+ end
297
+
298
+ context 'when Hash with String key' do
299
+ let(:length) { { "last_name" => 10 } }
300
+
301
+ it { is_expected.to eq(last_name: 10) }
302
+ end
303
+
304
+ context 'with multiple columns' do
305
+ let(:columns) { [:last_name, :first_name] }
306
+
307
+ context 'when Hash with String keys' do
308
+ let(:length) { { "last_name" => 10, "first_name" => 5 } }
309
+
310
+ it { is_expected.to eq(last_name: 10, first_name: 5) }
311
+ end
312
+ end
313
+
314
+ context 'with nil length' do
315
+ let(:length) { nil }
316
+
317
+ it { is_expected.to eq(nil) }
318
+ end
319
+
320
+ context 'with an invalid length' do
321
+ let(:length) { 10.5 }
322
+
323
+ it { expect { subject }.to raise_exception(ArgumentError, /length must be nil or Integer or a Hash of column names to lengths; got 10\.5 for \[:last_name]/i) }
324
+ end
325
+ end
262
326
  end
263
327
  # TODO: fill out remaining tests
264
328
  end
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.8
4
+ version: 1.4.0.colin.9
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-18 00:00:00.000000000 Z
11
+ date: 2024-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails