declare_schema 0.9.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/declare_schema_build.yml +1 -1
  3. data/CHANGELOG.md +32 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +12 -2
  6. data/lib/declare_schema.rb +12 -1
  7. data/lib/declare_schema/dsl.rb +39 -0
  8. data/lib/declare_schema/extensions/active_record/fields_declaration.rb +22 -3
  9. data/lib/declare_schema/model.rb +4 -6
  10. data/lib/declare_schema/model/column.rb +1 -1
  11. data/lib/declare_schema/model/foreign_key_definition.rb +6 -11
  12. data/lib/declare_schema/model/index_definition.rb +1 -20
  13. data/lib/declare_schema/schema_change/all.rb +22 -0
  14. data/lib/declare_schema/schema_change/base.rb +45 -0
  15. data/lib/declare_schema/schema_change/column_add.rb +27 -0
  16. data/lib/declare_schema/schema_change/column_change.rb +32 -0
  17. data/lib/declare_schema/schema_change/column_remove.rb +20 -0
  18. data/lib/declare_schema/schema_change/column_rename.rb +23 -0
  19. data/lib/declare_schema/schema_change/foreign_key_add.rb +25 -0
  20. data/lib/declare_schema/schema_change/foreign_key_remove.rb +20 -0
  21. data/lib/declare_schema/schema_change/index_add.rb +33 -0
  22. data/lib/declare_schema/schema_change/index_remove.rb +20 -0
  23. data/lib/declare_schema/schema_change/primary_key_change.rb +33 -0
  24. data/lib/declare_schema/schema_change/table_add.rb +37 -0
  25. data/lib/declare_schema/schema_change/table_change.rb +36 -0
  26. data/lib/declare_schema/schema_change/table_remove.rb +22 -0
  27. data/lib/declare_schema/schema_change/table_rename.rb +22 -0
  28. data/lib/declare_schema/version.rb +1 -1
  29. data/lib/generators/declare_schema/migration/USAGE +14 -24
  30. data/lib/generators/declare_schema/migration/migration_generator.rb +40 -38
  31. data/lib/generators/declare_schema/migration/migrator.rb +175 -177
  32. data/lib/generators/declare_schema/migration/templates/migration.rb.erb +3 -3
  33. data/lib/generators/declare_schema/support/model.rb +4 -4
  34. data/spec/lib/declare_schema/api_spec.rb +8 -8
  35. data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +41 -15
  36. data/spec/lib/declare_schema/field_spec_spec.rb +2 -2
  37. data/spec/lib/declare_schema/generator_spec.rb +5 -5
  38. data/spec/lib/declare_schema/interactive_primary_key_spec.rb +117 -28
  39. data/spec/lib/declare_schema/migration_generator_spec.rb +1990 -843
  40. data/spec/lib/declare_schema/model/column_spec.rb +49 -23
  41. data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +158 -57
  42. data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +0 -2
  43. data/spec/lib/declare_schema/model/index_definition_spec.rb +189 -78
  44. data/spec/lib/declare_schema/model/table_options_definition_spec.rb +75 -11
  45. data/spec/lib/declare_schema/schema_change/base_spec.rb +75 -0
  46. data/spec/lib/declare_schema/schema_change/column_add_spec.rb +30 -0
  47. data/spec/lib/declare_schema/schema_change/column_change_spec.rb +33 -0
  48. data/spec/lib/declare_schema/schema_change/column_remove_spec.rb +30 -0
  49. data/spec/lib/declare_schema/schema_change/column_rename_spec.rb +28 -0
  50. data/spec/lib/declare_schema/schema_change/foreign_key_add_spec.rb +29 -0
  51. data/spec/lib/declare_schema/schema_change/foreign_key_remove_spec.rb +29 -0
  52. data/spec/lib/declare_schema/schema_change/index_add_spec.rb +56 -0
  53. data/spec/lib/declare_schema/schema_change/index_remove_spec.rb +29 -0
  54. data/spec/lib/declare_schema/schema_change/primary_key_change_spec.rb +69 -0
  55. data/spec/lib/declare_schema/schema_change/table_add_spec.rb +50 -0
  56. data/spec/lib/declare_schema/schema_change/table_change_spec.rb +30 -0
  57. data/spec/lib/declare_schema/schema_change/table_remove_spec.rb +27 -0
  58. data/spec/lib/declare_schema/schema_change/table_rename_spec.rb +27 -0
  59. data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +59 -11
  60. data/spec/spec_helper.rb +1 -1
  61. data/spec/support/acceptance_spec_helpers.rb +2 -2
  62. metadata +35 -6
  63. data/test_responses.txt +0 -2
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails'
4
-
5
3
  begin
6
4
  require 'mysql2'
7
5
  rescue LoadError
@@ -16,7 +14,7 @@ RSpec.describe DeclareSchema::Model::Column do
16
14
 
17
15
  describe 'class methods' do
18
16
  describe '.native_type?' do
19
- if Rails::VERSION::MAJOR >= 5
17
+ if ActiveSupport::VERSION::MAJOR >= 5
20
18
  let(:native_types) { [:string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean, :json] }
21
19
  else
22
20
  let(:native_types) { [:string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean] }
@@ -65,9 +63,7 @@ RSpec.describe DeclareSchema::Model::Column do
65
63
  end
66
64
 
67
65
  describe '.deserialize_default_value' do
68
- require 'rails'
69
-
70
- if ::Rails::VERSION::MAJOR >= 5
66
+ if ::ActiveSupport::VERSION::MAJOR >= 5
71
67
  it 'deserializes :boolean' do
72
68
  expect(described_class.deserialize_default_value(nil, :boolean, 'true')).to eq(true)
73
69
  expect(described_class.deserialize_default_value(nil, :boolean, 'false')).to eq(false)
@@ -85,14 +81,6 @@ RSpec.describe DeclareSchema::Model::Column do
85
81
  end
86
82
 
87
83
  describe 'instance methods' do
88
- before do
89
- class ColumnTestModel < ActiveRecord::Base
90
- fields do
91
- title :string, limit: 127, null: false
92
- count :integer, null: false
93
- end
94
- end
95
- end
96
84
  let(:model) { ColumnTestModel }
97
85
  let(:type) { :integer }
98
86
  let(:current_table_name) { model.table_name }
@@ -108,18 +96,56 @@ RSpec.describe DeclareSchema::Model::Column do
108
96
  sql_type_metadata: {}) }
109
97
  subject { described_class.new(model, current_table_name, column) }
110
98
 
111
- describe '#type' do
112
- it 'returns type' do
113
- expect(subject.type).to eq(type)
99
+ context 'Using fields' do
100
+ before do
101
+ class ColumnTestModel < ActiveRecord::Base
102
+ fields do
103
+ title :string, limit: 127, null: false
104
+ count :integer, null: false
105
+ end
106
+ end
107
+ end
108
+
109
+ describe '#type' do
110
+ it 'returns type' do
111
+ expect(subject.type).to eq(type)
112
+ end
113
+ end
114
+
115
+ describe '#schema_attributes' do
116
+ it 'returns a hash with relevant key/values' do
117
+ if defined?(Mysql2)
118
+ expect(subject.schema_attributes).to eq(type: :integer, null: false, limit: 4)
119
+ else
120
+ expect(subject.schema_attributes).to eq(type: :integer, null: false)
121
+ end
122
+ end
114
123
  end
115
124
  end
116
125
 
117
- describe '#schema_attributes' do
118
- it 'returns a hash with relevant key/values' do
119
- if defined?(Mysql2)
120
- expect(subject.schema_attributes).to eq(type: :integer, null: false, limit: 4)
121
- else
122
- expect(subject.schema_attributes).to eq(type: :integer, null: false)
126
+ context 'Using declare_schema' do
127
+ before do
128
+ class ColumnTestModel < ActiveRecord::Base
129
+ declare_schema do
130
+ string :title, limit: 127, null: false
131
+ integer :count, null: false
132
+ end
133
+ end
134
+ end
135
+
136
+ describe '#type' do
137
+ it 'returns type' do
138
+ expect(subject.type).to eq(type)
139
+ end
140
+ end
141
+
142
+ describe '#schema_attributes' do
143
+ it 'returns a hash with relevant key/values' do
144
+ if defined?(Mysql2)
145
+ expect(subject.schema_attributes).to eq(type: :integer, null: false, limit: 4)
146
+ else
147
+ expect(subject.schema_attributes).to eq(type: :integer, null: false)
148
+ end
123
149
  end
124
150
  end
125
151
  end
@@ -3,89 +3,190 @@
3
3
  require_relative '../../../../lib/declare_schema/model/foreign_key_definition'
4
4
 
5
5
  RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
6
- before do
7
- load File.expand_path('../prepare_testapp.rb', __dir__)
6
+ let(:model_class) { Network }
7
+
8
+ context 'Using fields' do
9
+ before do
10
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
11
 
9
- class Network < ActiveRecord::Base
10
- fields do
11
- name :string, limit: 127, index: true
12
+ class Network < ActiveRecord::Base
13
+ fields do
14
+ name :string, limit: 127, index: true
12
15
 
13
- timestamps
16
+ timestamps
17
+ end
14
18
  end
15
19
  end
16
- end
17
-
18
- let(:model_class) { Network }
19
20
 
20
- describe 'instance methods' do
21
- let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
22
- let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
23
- let(:foreign_key) { :network_id }
24
- let(:options) { {} }
25
- subject { described_class.new(model, foreign_key, options)}
21
+ describe 'instance methods' do
22
+ let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
23
+ let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
24
+ let(:foreign_key) { :network_id }
25
+ let(:options) { {} }
26
+ subject { described_class.new(model, foreign_key, options)}
26
27
 
27
- before do
28
- allow(connection).to receive(:index_name).with('models', column: 'network_id') { 'on_network_id' }
29
- end
30
-
31
- describe '#initialize' do
32
- it 'normalizes symbols to strings' do
33
- expect(subject.foreign_key).to eq('network_id')
34
- expect(subject.parent_table_name).to eq('networks')
28
+ before do
29
+ allow(model.connection).to receive(:index_name).with(any_args) { 'index_on_network_id' }
35
30
  end
36
31
 
37
- context 'when most options passed' do
38
- let(:options) { { parent_table: :networks, foreign_key: :the_network_id, index_name: :index_on_network_id } }
39
-
32
+ describe '#initialize' do
40
33
  it 'normalizes symbols to strings' do
41
34
  expect(subject.foreign_key).to eq('network_id')
42
- expect(subject.foreign_key_name).to eq('the_network_id')
43
35
  expect(subject.parent_table_name).to eq('networks')
44
- expect(subject.foreign_key).to eq('network_id')
45
- expect(subject.constraint_name).to eq('index_on_network_id')
46
- expect(subject.on_delete_cascade).to be_falsey
47
36
  end
48
- end
49
37
 
50
- context 'when all options passed' do
51
- let(:foreign_key) { nil }
52
- let(:options) { { parent_table: :networks, foreign_key: :the_network_id, index_name: :index_on_network_id,
53
- constraint_name: :constraint_1, dependent: :delete } }
38
+ context 'when most options passed' do
39
+ let(:options) { { parent_table: :networks, foreign_key: :the_network_id } }
40
+
41
+ it 'normalizes symbols to strings' do
42
+ expect(subject.foreign_key).to eq('network_id')
43
+ expect(subject.foreign_key_name).to eq('the_network_id')
44
+ expect(subject.parent_table_name).to eq('networks')
45
+ expect(subject.foreign_key).to eq('network_id')
46
+ expect(subject.constraint_name).to eq('index_on_network_id')
47
+ expect(subject.on_delete_cascade).to be_falsey
48
+ end
49
+ end
54
50
 
55
- it 'normalizes symbols to strings' do
56
- expect(subject.foreign_key).to be_nil
57
- expect(subject.foreign_key_name).to eq('the_network_id')
58
- expect(subject.parent_table_name).to eq('networks')
59
- expect(subject.constraint_name).to eq('constraint_1')
60
- expect(subject.on_delete_cascade).to be_truthy
51
+ context 'when all options passed' do
52
+ let(:foreign_key) { nil }
53
+ let(:options) { { parent_table: :networks, foreign_key: :the_network_id, constraint_name: :constraint_1, dependent: :delete } }
54
+
55
+ it 'normalizes symbols to strings' do
56
+ expect(subject.foreign_key).to be_nil
57
+ expect(subject.foreign_key_name).to eq('the_network_id')
58
+ expect(subject.parent_table_name).to eq('networks')
59
+ expect(subject.constraint_name).to eq('constraint_1')
60
+ expect(subject.on_delete_cascade).to be_truthy
61
+ end
62
+ end
63
+
64
+ context 'when constraint name passed as empty string' do
65
+ let(:options) { { constraint_name: "" } }
66
+ it 'defaults to rails constraint name' do
67
+ expect(subject.constraint_name).to eq("index_on_network_id")
68
+ end
69
+ end
70
+
71
+ context 'when no constraint name passed' do
72
+ it 'defaults to rails constraint name' do
73
+ expect(subject.constraint_name).to eq("index_on_network_id")
74
+ end
61
75
  end
62
76
  end
63
77
  end
64
78
 
65
- describe '#to_add_statement' do
66
- it 'returns add_foreign_key command' do
67
- expect(subject.to_add_statement).to eq('add_foreign_key("models", "networks", column: "network_id", name: "on_network_id")')
79
+ describe 'class << self' do
80
+ let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
81
+ let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
82
+ let(:old_table_name) { 'networks' }
83
+ before do
84
+ allow(connection).to receive(:quote_table_name).with('networks') { 'networks' }
85
+ allow(connection).to receive(:select_rows) { [['CONSTRAINT `constraint` FOREIGN KEY (`network_id`) REFERENCES `networks` (`id`)']] }
86
+ allow(connection).to receive(:index_name).with('models', column: 'network_id') { }
87
+ end
88
+
89
+ describe '.for_model' do
90
+ subject { described_class.for_model(model, old_table_name) }
91
+
92
+ it 'returns new object' do
93
+ expect(subject.size).to eq(1), subject.inspect
94
+ expect(subject.first).to be_kind_of(described_class)
95
+ expect(subject.first.foreign_key).to eq('network_id')
96
+ end
68
97
  end
69
98
  end
70
99
  end
71
100
 
72
- describe 'class << self' do
73
- let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
74
- let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
75
- let(:old_table_name) { 'networks' }
101
+ context 'Using declare_schema' do
76
102
  before do
77
- allow(connection).to receive(:quote_table_name).with('networks') { 'networks' }
78
- allow(connection).to receive(:select_rows) { [['CONSTRAINT `constraint` FOREIGN KEY (`network_id`) REFERENCES `networks` (`id`)']] }
79
- allow(connection).to receive(:index_name).with('models', column: 'network_id') { }
103
+ load File.expand_path('../prepare_testapp.rb', __dir__)
104
+
105
+ class Network < ActiveRecord::Base
106
+ declare_schema do
107
+ string :name, limit: 127, index: true
108
+
109
+ timestamps
110
+ end
111
+ end
112
+ end
113
+
114
+ describe 'instance methods' do
115
+ let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
116
+ let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
117
+ let(:foreign_key) { :network_id }
118
+ let(:options) { {} }
119
+ subject { described_class.new(model, foreign_key, options)}
120
+
121
+ before do
122
+ allow(model.connection).to receive(:index_name).with(any_args) { 'index_on_network_id' }
123
+ end
124
+
125
+ describe '#initialize' do
126
+ it 'normalizes symbols to strings' do
127
+ expect(subject.foreign_key).to eq('network_id')
128
+ expect(subject.parent_table_name).to eq('networks')
129
+ end
130
+
131
+ context 'when most options passed' do
132
+ let(:options) { { parent_table: :networks, foreign_key: :the_network_id } }
133
+
134
+ it 'normalizes symbols to strings' do
135
+ expect(subject.foreign_key).to eq('network_id')
136
+ expect(subject.foreign_key_name).to eq('the_network_id')
137
+ expect(subject.parent_table_name).to eq('networks')
138
+ expect(subject.foreign_key).to eq('network_id')
139
+ expect(subject.constraint_name).to eq('index_on_network_id')
140
+ expect(subject.on_delete_cascade).to be_falsey
141
+ end
142
+ end
143
+
144
+ context 'when all options passed' do
145
+ let(:foreign_key) { nil }
146
+ let(:options) { { parent_table: :networks, foreign_key: :the_network_id, constraint_name: :constraint_1, dependent: :delete } }
147
+
148
+ it 'normalizes symbols to strings' do
149
+ expect(subject.foreign_key).to be_nil
150
+ expect(subject.foreign_key_name).to eq('the_network_id')
151
+ expect(subject.parent_table_name).to eq('networks')
152
+ expect(subject.constraint_name).to eq('constraint_1')
153
+ expect(subject.on_delete_cascade).to be_truthy
154
+ end
155
+ end
156
+
157
+ context 'when constraint name passed as empty string' do
158
+ let(:options) { { constraint_name: "" } }
159
+ it 'defaults to rails constraint name' do
160
+ expect(subject.constraint_name).to eq("index_on_network_id")
161
+ end
162
+ end
163
+
164
+ context 'when no constraint name passed' do
165
+ it 'defaults to rails constraint name' do
166
+ expect(subject.constraint_name).to eq("index_on_network_id")
167
+ end
168
+ end
169
+ end
80
170
  end
81
171
 
82
- describe '.for_model' do
83
- subject { described_class.for_model(model, old_table_name) }
172
+ describe 'class << self' do
173
+ let(:connection) { instance_double(ActiveRecord::Base.connection.class) }
174
+ let(:model) { instance_double('Model', table_name: 'models', connection: connection) }
175
+ let(:old_table_name) { 'networks' }
176
+ before do
177
+ allow(connection).to receive(:quote_table_name).with('networks') { 'networks' }
178
+ allow(connection).to receive(:select_rows) { [['CONSTRAINT `constraint` FOREIGN KEY (`network_id`) REFERENCES `networks` (`id`)']] }
179
+ allow(connection).to receive(:index_name).with('models', column: 'network_id') { }
180
+ end
181
+
182
+ describe '.for_model' do
183
+ subject { described_class.for_model(model, old_table_name) }
84
184
 
85
- it 'returns new object' do
86
- expect(subject.size).to eq(1), subject.inspect
87
- expect(subject.first).to be_kind_of(described_class)
88
- expect(subject.first.foreign_key).to eq('network_id')
185
+ it 'returns new object' do
186
+ expect(subject.size).to eq(1), subject.inspect
187
+ expect(subject.first).to be_kind_of(described_class)
188
+ expect(subject.first.foreign_key).to eq('network_id')
189
+ end
89
190
  end
90
191
  end
91
192
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails'
4
-
5
3
  begin
6
4
  require 'mysql2'
7
5
  rescue LoadError
@@ -10,119 +10,230 @@ require_relative '../../../../lib/declare_schema/model/index_definition'
10
10
  # unless you manually create any Sqlite tables with UNIQUE constraints.
11
11
 
12
12
  RSpec.describe DeclareSchema::Model::IndexDefinition do
13
- before do
14
- load File.expand_path('../prepare_testapp.rb', __dir__)
13
+ let(:model_class) { IndexDefinitionTestModel }
14
+
15
+ context 'Using fields' do
16
+ before do
17
+ load File.expand_path('../prepare_testapp.rb', __dir__)
15
18
 
16
- class IndexDefinitionTestModel < ActiveRecord::Base
17
- fields do
18
- name :string, limit: 127, index: true
19
+ class IndexDefinitionTestModel < ActiveRecord::Base
20
+ fields do
21
+ name :string, limit: 127, index: true
19
22
 
20
- timestamps
23
+ timestamps
24
+ end
21
25
  end
22
- end
23
26
 
24
- class IndexDefinitionCompoundIndexModel < ActiveRecord::Base
25
- fields do
26
- fk1_id :integer
27
- fk2_id :integer
27
+ class IndexDefinitionCompoundIndexModel < ActiveRecord::Base
28
+ fields do
29
+ fk1_id :integer
30
+ fk2_id :integer
28
31
 
29
- timestamps
32
+ timestamps
33
+ end
30
34
  end
31
35
  end
32
- end
33
36
 
34
- let(:model_class) { IndexDefinitionTestModel }
37
+ describe 'instance methods' do
38
+ let(:model) { model_class.new }
39
+ subject { declared_class.new(model_class) }
35
40
 
36
- describe 'instance methods' do
37
- let(:model) { model_class.new }
38
- subject { declared_class.new(model_class) }
41
+ it 'has index_definitions' do
42
+ expect(model_class.index_definitions).to be_kind_of(Array)
43
+ expect(model_class.index_definitions.map(&:name)).to eq(['on_name'])
44
+ expect([:name, :fields, :unique].map { |attr| model_class.index_definitions[0].send(attr)}).to eq(
45
+ ['on_name', ['name'], false]
46
+ )
47
+ end
39
48
 
40
- it 'has index_definitions' do
41
- expect(model_class.index_definitions).to be_kind_of(Array)
42
- expect(model_class.index_definitions.map(&:name)).to eq(['on_name'])
43
- expect([:name, :fields, :unique].map { |attr| model_class.index_definitions[0].send(attr)}).to eq(
44
- ['on_name', ['name'], false]
45
- )
49
+ it 'has index_definitions_with_primary_key' do
50
+ expect(model_class.index_definitions_with_primary_key).to be_kind_of(Array)
51
+ result = model_class.index_definitions_with_primary_key.sort_by(&:name)
52
+ expect(result.map(&:name)).to eq(['PRIMARY', 'on_name'])
53
+ expect([:name, :fields, :unique].map { |attr| result[0].send(attr)}).to eq(
54
+ ['PRIMARY', ['id'], true]
55
+ )
56
+ expect([:name, :fields, :unique].map { |attr| result[1].send(attr)}).to eq(
57
+ ['on_name', ['name'], false]
58
+ )
59
+ end
46
60
  end
47
61
 
48
- it 'has index_definitions_with_primary_key' do
49
- expect(model_class.index_definitions_with_primary_key).to be_kind_of(Array)
50
- result = model_class.index_definitions_with_primary_key.sort_by(&:name)
51
- expect(result.map(&:name)).to eq(['PRIMARY', 'on_name'])
52
- expect([:name, :fields, :unique].map { |attr| result[0].send(attr)}).to eq(
53
- ['PRIMARY', ['id'], true]
54
- )
55
- expect([:name, :fields, :unique].map { |attr| result[1].send(attr)}).to eq(
56
- ['on_name', ['name'], false]
57
- )
62
+ describe 'class methods' do
63
+ describe 'index_name' do
64
+ it 'works with a single column' do
65
+ expect(described_class.index_name('parent_id')).to eq('on_parent_id')
66
+ end
67
+
68
+ it 'works with many columns' do
69
+ expect(described_class.index_name(['a', 'b', 'c'])).to eq('on_a_and_b_and_c')
70
+ end
71
+ end
72
+
73
+ context 'with a migrated database' do
74
+ before do
75
+ ActiveRecord::Base.connection.execute <<~EOS
76
+ CREATE TABLE index_definition_test_models (
77
+ id INTEGER NOT NULL PRIMARY KEY,
78
+ name #{if defined?(SQLite3) then 'TEXT' else 'VARCHAR(255)' end} NOT NULL
79
+ )
80
+ EOS
81
+ ActiveRecord::Base.connection.execute <<~EOS
82
+ CREATE UNIQUE INDEX index_definition_test_models_on_name ON index_definition_test_models(name)
83
+ EOS
84
+ ActiveRecord::Base.connection.execute <<~EOS
85
+ CREATE TABLE index_definition_compound_index_models (
86
+ fk1_id INTEGER NOT NULL,
87
+ fk2_id INTEGER NOT NULL,
88
+ PRIMARY KEY (fk1_id, fk2_id)
89
+ )
90
+ EOS
91
+ ActiveRecord::Base.connection.schema_cache.clear!
92
+ end
93
+
94
+ describe 'for_model' do
95
+ subject { described_class.for_model(model_class) }
96
+
97
+ context 'with single-column PK' do
98
+ it 'returns the indexes for the model' do
99
+ expect(subject.size).to eq(2), subject.inspect
100
+ expect([:name, :columns, :unique].map { |attr| subject[0].send(attr) }).to eq(
101
+ ['index_definition_test_models_on_name', ['name'], true]
102
+ )
103
+ expect([:name, :columns, :unique].map { |attr| subject[1].send(attr) }).to eq(
104
+ ['PRIMARY', ['id'], true]
105
+ )
106
+ end
107
+ end
108
+
109
+ context 'with compound-column PK' do
110
+ let(:model_class) { IndexDefinitionCompoundIndexModel }
111
+
112
+ it 'returns the indexes for the model' do
113
+ if ActiveSupport::VERSION::MAJOR < 5
114
+ expect(model_class.connection).to receive(:primary_key).with('index_definition_compound_index_models').and_return(nil)
115
+ connection_stub = instance_double(ActiveRecord::Base.connection.class, "connection")
116
+ expect(connection_stub).to receive(:indexes).
117
+ with('index_definition_compound_index_models').
118
+ and_return([DeclareSchema::Model::IndexDefinition.new(model_class, ['fk1_id', 'fk2_id'], name: 'PRIMARY')])
119
+
120
+ expect(model_class.connection).to receive(:dup).and_return(connection_stub)
121
+ end
122
+
123
+ expect(subject.size).to eq(1), subject.inspect
124
+ expect([:name, :columns, :unique].map { |attr| subject[0].send(attr) }).to eq(
125
+ ['PRIMARY', ['fk1_id', 'fk2_id'], true]
126
+ )
127
+ end
128
+ end
129
+ end
130
+ end
58
131
  end
59
132
  end
60
133
 
61
- describe 'class methods' do
62
- describe 'index_name' do
63
- it 'works with a single column' do
64
- expect(described_class.index_name('parent_id')).to eq('on_parent_id')
134
+ context 'Using declare_schema' do
135
+ before do
136
+ load File.expand_path('../prepare_testapp.rb', __dir__)
137
+
138
+ class IndexDefinitionTestModel < ActiveRecord::Base
139
+ declare_schema do
140
+ string :name, limit: 127, index: true
141
+
142
+ timestamps
143
+ end
65
144
  end
66
145
 
67
- it 'works with many columns' do
68
- expect(described_class.index_name(['a', 'b', 'c'])).to eq('on_a_and_b_and_c')
146
+ class IndexDefinitionCompoundIndexModel < ActiveRecord::Base
147
+ declare_schema do
148
+ integer :fk1_id
149
+ integer :fk2_id
150
+
151
+ timestamps
152
+ end
69
153
  end
70
154
  end
71
155
 
72
- context 'with a migrated database' do
73
- before do
74
- ActiveRecord::Base.connection.execute <<~EOS
156
+ describe 'instance methods' do
157
+ let(:model) { model_class.new }
158
+ subject { declared_class.new(model_class) }
159
+
160
+ it 'has index_definitions' do
161
+ expect(model_class.index_definitions).to be_kind_of(Array)
162
+ expect(model_class.index_definitions.map(&:name)).to eq(['on_name'])
163
+ expect([:name, :fields, :unique].map { |attr| model_class.index_definitions[0].send(attr)}).to eq(
164
+ ['on_name', ['name'], false]
165
+ )
166
+ end
167
+
168
+ it 'has index_definitions_with_primary_key' do
169
+ expect(model_class.index_definitions_with_primary_key).to be_kind_of(Array)
170
+ result = model_class.index_definitions_with_primary_key.sort_by(&:name)
171
+ expect(result.map(&:name)).to eq(['PRIMARY', 'on_name'])
172
+ expect([:name, :fields, :unique].map { |attr| result[0].send(attr)}).to eq(
173
+ ['PRIMARY', ['id'], true]
174
+ )
175
+ expect([:name, :fields, :unique].map { |attr| result[1].send(attr)}).to eq(
176
+ ['on_name', ['name'], false]
177
+ )
178
+ end
179
+ end
180
+
181
+ describe 'class << self' do
182
+ context 'with a migrated database' do
183
+ before do
184
+ ActiveRecord::Base.connection.execute <<~EOS
75
185
  CREATE TABLE index_definition_test_models (
76
186
  id INTEGER NOT NULL PRIMARY KEY,
77
- name #{if defined?(Sqlite3) then 'TEXT' else 'VARCHAR(255)' end} NOT NULL
187
+ name #{if defined?(SQLite3) then 'TEXT' else 'VARCHAR(255)' end} NOT NULL
78
188
  )
79
- EOS
80
- ActiveRecord::Base.connection.execute <<~EOS
189
+ EOS
190
+ ActiveRecord::Base.connection.execute <<~EOS
81
191
  CREATE UNIQUE INDEX index_definition_test_models_on_name ON index_definition_test_models(name)
82
- EOS
83
- ActiveRecord::Base.connection.execute <<~EOS
192
+ EOS
193
+ ActiveRecord::Base.connection.execute <<~EOS
84
194
  CREATE TABLE index_definition_compound_index_models (
85
195
  fk1_id INTEGER NOT NULL,
86
196
  fk2_id INTEGER NOT NULL,
87
197
  PRIMARY KEY (fk1_id, fk2_id)
88
198
  )
89
- EOS
90
- ActiveRecord::Base.connection.schema_cache.clear!
91
- end
92
-
93
- describe 'for_model' do
94
- subject { described_class.for_model(model_class) }
199
+ EOS
200
+ ActiveRecord::Base.connection.schema_cache.clear!
201
+ end
95
202
 
96
- context 'with single-column PK' do
97
- it 'returns the indexes for the model' do
98
- expect(subject.size).to eq(2), subject.inspect
99
- expect([:name, :columns, :unique].map { |attr| subject[0].send(attr) }).to eq(
100
- ['index_definition_test_models_on_name', ['name'], true]
101
- )
102
- expect([:name, :columns, :unique].map { |attr| subject[1].send(attr) }).to eq(
103
- ['PRIMARY', ['id'], true]
104
- )
203
+ describe 'for_model' do
204
+ subject { described_class.for_model(model_class) }
205
+
206
+ context 'with single-column PK' do
207
+ it 'returns the indexes for the model' do
208
+ expect(subject.size).to eq(2), subject.inspect
209
+ expect([:name, :columns, :unique].map { |attr| subject[0].send(attr) }).to eq(
210
+ ['index_definition_test_models_on_name', ['name'], true]
211
+ )
212
+ expect([:name, :columns, :unique].map { |attr| subject[1].send(attr) }).to eq(
213
+ ['PRIMARY', ['id'], true]
214
+ )
215
+ end
105
216
  end
106
- end
107
217
 
108
- context 'with compound-column PK' do
109
- let(:model_class) { IndexDefinitionCompoundIndexModel }
218
+ context 'with compound-column PK' do
219
+ let(:model_class) { IndexDefinitionCompoundIndexModel }
110
220
 
111
- it 'returns the indexes for the model' do
112
- if Rails::VERSION::MAJOR < 5
113
- expect(model_class.connection).to receive(:primary_key).with('index_definition_compound_index_models').and_return(nil)
114
- connection_stub = instance_double(ActiveRecord::Base.connection.class, "connection")
115
- expect(connection_stub).to receive(:indexes).
116
- with('index_definition_compound_index_models').
117
- and_return([DeclareSchema::Model::IndexDefinition.new(model_class, ['fk1_id', 'fk2_id'], name: 'PRIMARY')])
221
+ it 'returns the indexes for the model' do
222
+ if ActiveSupport::VERSION::MAJOR < 5
223
+ expect(model_class.connection).to receive(:primary_key).with('index_definition_compound_index_models').and_return(nil)
224
+ connection_stub = instance_double(ActiveRecord::Base.connection.class, "connection")
225
+ expect(connection_stub).to receive(:indexes).
226
+ with('index_definition_compound_index_models').
227
+ and_return([DeclareSchema::Model::IndexDefinition.new(model_class, ['fk1_id', 'fk2_id'], name: 'PRIMARY')])
118
228
 
119
- expect(model_class.connection).to receive(:dup).and_return(connection_stub)
120
- end
229
+ expect(model_class.connection).to receive(:dup).and_return(connection_stub)
230
+ end
121
231
 
122
- expect(subject.size).to eq(1), subject.inspect
123
- expect([:name, :columns, :unique].map { |attr| subject[0].send(attr) }).to eq(
124
- ['PRIMARY', ['fk1_id', 'fk2_id'], true]
125
- )
232
+ expect(subject.size).to eq(1), subject.inspect
233
+ expect([:name, :columns, :unique].map { |attr| subject[0].send(attr) }).to eq(
234
+ ['PRIMARY', ['fk1_id', 'fk2_id'], true]
235
+ )
236
+ end
126
237
  end
127
238
  end
128
239
  end