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

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: 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