rails-bigint-pk 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +3 -0
  5. data/.vim/snippets/rspec.snippets +4 -0
  6. data/.vimrc +2 -0
  7. data/Changelog +11 -0
  8. data/Gemfile +4 -0
  9. data/Guardfile +20 -0
  10. data/LICENSE +7 -0
  11. data/Rakefile +6 -0
  12. data/Readme.md +61 -0
  13. data/lib/bigint_pk.rb +92 -0
  14. data/lib/bigint_pk/version.rb +3 -0
  15. data/lib/generators/bigint_pk.rb +5 -0
  16. data/lib/generators/bigint_pk/install_generator.rb +21 -0
  17. data/lib/generators/bigint_pk/templates/bigint_pk.rb +9 -0
  18. data/lib/generators/bigint_pk/templates/migration.rb +20 -0
  19. data/rails-bigint-pk.gemspec +37 -0
  20. data/spec/fixtures/Gemfile +8 -0
  21. data/spec/fixtures/mysql_database.yml +13 -0
  22. data/spec/fixtures/postgresql_database.yml +12 -0
  23. data/spec/fixtures/sqlite3_database.yml +11 -0
  24. data/spec/fixtures/test_rails_app/app/models/empire.rb +2 -0
  25. data/spec/fixtures/test_rails_app/app/models/ruler.rb +4 -0
  26. data/spec/fixtures/test_rails_app/db/migrate/19000101100000_create_empires_and_rulers.rb +10 -0
  27. data/spec/fixtures/test_rails_app/db/migrate/19000102100000_update_rulers_add_favourite_ruler.rb +7 -0
  28. data/spec/integration/generator_spec.rb +40 -0
  29. data/spec/integration/schema_spec.rb +222 -0
  30. data/spec/migration_spec.rb +102 -0
  31. data/spec/monkey_patch/connection_adapters_spec.rb +149 -0
  32. data/spec/spec_helper.rb +61 -0
  33. data/spec/support/cmd.rb +42 -0
  34. data/spec/support/file_utils.rb +7 -0
  35. data/spec/support/logging.rb +5 -0
  36. data/spec/support/rails.rb +32 -0
  37. data/spec/support/rspec.rb +3 -0
  38. metadata +340 -0
@@ -0,0 +1,222 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Migrations', :integration do
4
+ after :each do
5
+ execute_sql %Q{
6
+ delete from rulers;
7
+ delete from empires;
8
+ }
9
+ end
10
+
11
+ describe 'create_table' do
12
+ before :each do
13
+ in_directory( RailsDir ) do
14
+ run 'rails generate bigint_pk:install'
15
+ FileUtils.rm Dir['./db/migrate/**change_keys_to_bigint*'].first
16
+ end
17
+ end
18
+
19
+ def self.they_use_bigint_primary_keys
20
+ describe 'primary keys' do
21
+ they 'default to 64bit' do
22
+ expect{
23
+ execute_sql %Q{
24
+ insert into empires values(#{2 ** 31 - 1});
25
+ insert into empires values(#{2 ** 31});
26
+ }
27
+ }.to_not raise_error
28
+
29
+ expect(
30
+ execute_ruby 'puts Empire.all.collect(&:id)'
31
+ ).to eq ["#{2**31-1}", "#{2**31}"].join("\n")
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.they_use_bigint_foreign_keys
37
+ describe 'foreign keys' do
38
+ they 'can reference 64bit values' do
39
+ expect{
40
+ execute_sql %Q{
41
+ insert into empires values(#{2 ** 31 - 1});
42
+ insert into empires values(#{2 ** 31});
43
+ insert into rulers( empire_id ) values(#{2 ** 31 - 1});
44
+ insert into rulers( empire_id ) values(#{2 ** 31});
45
+ }
46
+ }.to_not raise_error
47
+
48
+ expect(
49
+ execute_ruby 'puts Ruler.all.map{|r| r.empire_id}'
50
+ ).to eq ["#{2**31 - 1}", "#{2**31}"].join("\n")
51
+ end
52
+
53
+ context 'that were added to an existing table' do
54
+ they 'can reference 64bit values' do
55
+ expect{
56
+ execute_sql %Q{
57
+ insert into rulers( id ) values(#{2 ** 31 - 1});
58
+ insert into rulers( id ) values(#{2 ** 31});
59
+
60
+ insert into rulers( favourite_ruler_id ) values(#{2 ** 31 - 1});
61
+ insert into rulers( favourite_ruler_id ) values(#{2 ** 31});
62
+ }
63
+ }.to_not raise_error
64
+
65
+ expect(
66
+ execute_ruby('puts Ruler.all.map{|r| r.favourite_ruler_id}').split
67
+ ).to eq ["#{2**31 - 1}", "#{2**31}"]
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'with a mysql database' do
74
+ before(:each) do
75
+ use_database! :mysql
76
+ rake 'db:migrate'
77
+ end
78
+
79
+ they_use_bigint_primary_keys
80
+ they_use_bigint_foreign_keys
81
+ end
82
+
83
+ context 'with a postgresql database' do
84
+ before(:each) do
85
+ use_database! :postgres
86
+ rake 'db:migrate'
87
+ end
88
+
89
+ they_use_bigint_primary_keys
90
+ they_use_bigint_foreign_keys
91
+ end
92
+
93
+ context 'with a sqlite3 database' do
94
+ before(:each) do
95
+ use_database! :sqlite3
96
+ rake 'db:migrate'
97
+ end
98
+
99
+ they_use_bigint_primary_keys
100
+ they_use_bigint_foreign_keys
101
+ end
102
+ end
103
+
104
+ describe 'existing tables' do
105
+ InitializerFile = "./config/initializers/bigint_pk.rb"
106
+
107
+ before :each do
108
+ in_directory( RailsDir ) do
109
+ run 'rails generate bigint_pk:install'
110
+
111
+ initializer = File.read InitializerFile
112
+ initializer.gsub!( /enabled = true/, 'enabled = false' )
113
+ File.open( InitializerFile, 'w'){|f| f.print initializer }
114
+ end
115
+ end
116
+
117
+ def self.they_have_their_primary_keys_migrated
118
+ they 'have their primary keys migrated' do
119
+ expect{
120
+ execute_sql %Q{
121
+ insert into empires values(#{2 ** 31 - 1});
122
+ insert into empires values(#{2 ** 31});
123
+ }
124
+ }.to raise_error
125
+
126
+ execute_ruby 'Empire.delete_all'
127
+ rake 'db:migrate'
128
+
129
+ expect{
130
+ execute_sql %Q{
131
+ insert into empires values(#{2 ** 31 - 1});
132
+ insert into empires values(#{2 ** 31});
133
+ }
134
+ }.to_not raise_error
135
+ end
136
+ end
137
+
138
+ def self.they_have_their_foreign_keys_migrated
139
+ they 'have their foreign keys migrated' do
140
+ expect{
141
+ execute_sql %Q{
142
+ insert into empires values(#{2 ** 31 - 1});
143
+ insert into empires values(#{2 ** 31});
144
+ insert into rulers( empire_id ) values(#{2 ** 31});
145
+ insert into rulers( empire_id ) values(#{2 ** 31 - 1});
146
+ }
147
+ }.to raise_error
148
+
149
+ execute_ruby 'Ruler.delete_all'
150
+ execute_ruby 'Empire.delete_all'
151
+ rake 'db:migrate'
152
+
153
+ expect{
154
+ execute_sql %Q{
155
+ insert into empires values(#{2 ** 31 - 1});
156
+ insert into empires values(#{2 ** 31});
157
+ insert into rulers( empire_id ) values(#{2 ** 31 - 1});
158
+ insert into rulers( empire_id ) values(#{2 ** 31});
159
+ }
160
+ }.to_not raise_error
161
+
162
+ expect(
163
+ execute_ruby 'puts Ruler.all.collect{|r| [r.id, r.empire.id]}'
164
+ ).to eq [ "1", "#{2**31 - 1}",
165
+ "2", "#{2**31}"].join("\n")
166
+ end
167
+ end
168
+
169
+ context 'with a mysql database' do
170
+ before(:each) do
171
+ use_database! :mysql
172
+ rake 'db:migrate VERSION=19000101100000'
173
+ end
174
+
175
+ they_have_their_primary_keys_migrated
176
+ they_have_their_foreign_keys_migrated
177
+ end
178
+
179
+ context 'with a postgresql database' do
180
+ before(:each) do
181
+ use_database! :postgres
182
+ rake 'db:migrate VERSION=19000101100000'
183
+ end
184
+
185
+ they_have_their_primary_keys_migrated
186
+ they_have_their_foreign_keys_migrated
187
+ end
188
+ end
189
+
190
+ describe "with models & migrations created after bigint migration" do
191
+ before :each do
192
+ in_directory( RailsDir ) do
193
+ run 'rails generate bigint_pk:install'
194
+ run 'rails generate model subject'
195
+ end
196
+ end
197
+
198
+ def self.it_completes_migration
199
+ it "completes the migration" do
200
+ in_directory( RailsDir ) do
201
+ run("rake db:migrate")
202
+ end
203
+ end
204
+ end
205
+
206
+ context "with a mysql database" do
207
+ before(:each) do
208
+ use_database! :mysql
209
+ end
210
+
211
+ it_completes_migration
212
+ end
213
+
214
+ context "with a postgresql database" do
215
+ before(:each) do
216
+ use_database! :postgres
217
+ end
218
+
219
+ it_completes_migration
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+ require 'generators/bigint_pk/templates/migration'
3
+
4
+ describe 'ChangeKeysToBigint' do
5
+ let(:connection) do
6
+ double('Connection').tap do |c|
7
+ c.stub(:quote_table_name){|tn| "`#{tn}`"}
8
+ c.stub(:quote_column_name){|column| "`#{column}`"}
9
+ c.stub(:table_exists?) { true }
10
+ c.stub(:execute){|sql| queries << sql }
11
+ c.stub(:column_exists?) do |table_name, column_name|
12
+ if table_name.to_s == 'coaches' && column_name == 'team_id'
13
+ false
14
+ else
15
+ true
16
+ end
17
+ end
18
+ end
19
+ end
20
+ let(:queries){ [] }
21
+
22
+ before do
23
+ stub_const('Team', double('Team', table_name: 'teams', primary_key: 'id'))
24
+ stub_const('Player', double('Player', table_name: 'players', primary_key: 'id'))
25
+ stub_const('Coach', double('Coach', table_name: 'coaches', primary_key: 'id'))
26
+ Team.stub( reflect_on_all_associations: [])
27
+ Player.stub( reflect_on_all_associations: [
28
+ double('belongs_to', macro: :belongs_to, foreign_key: 'team_id')])
29
+ Coach.stub( reflect_on_all_associations: [
30
+ double('belongs_to', macro: :belongs_to, foreign_key: 'team_id')])
31
+
32
+ stub_const('Rails', double('Rails'))
33
+ Rails.stub_chain 'application.eager_load!'
34
+
35
+ ActiveRecord::Base.stub( subclasses: [ Team, Player, Coach ], connection: connection)
36
+ ActiveRecord::Base.stub_chain('connection_pool.with_connection') do |&prok|
37
+ prok.call connection
38
+ end
39
+ end
40
+
41
+ context 'with a postgres database' do
42
+ before { connection.stub( adapter_name: 'PostgreSQL') }
43
+
44
+ it 'migrates primary keys' do
45
+ ChangeKeysToBigint.migrate :up
46
+ expect( queries ).to include(
47
+ 'ALTER TABLE `teams` ALTER COLUMN `id` TYPE bigint',
48
+ 'ALTER TABLE `players` ALTER COLUMN `id` TYPE bigint',
49
+ 'ALTER TABLE `coaches` ALTER COLUMN `id` TYPE bigint'
50
+ )
51
+ end
52
+
53
+ it 'migrates foreign keys' do
54
+ ChangeKeysToBigint.migrate :up
55
+ expect( queries ).to include(
56
+ 'ALTER TABLE `players` ALTER COLUMN `team_id` TYPE bigint'
57
+ )
58
+ end
59
+
60
+ it 'does not migrate foreign keys that do not exist in database' do
61
+ ChangeKeysToBigint.migrate :up
62
+ expect( queries ).to_not include(
63
+ 'ALTER TABLE `coaches` ALTER COLUMN `team_id` TYPE bigint'
64
+ )
65
+ end
66
+ end
67
+
68
+ context 'with a mysql database' do
69
+ before { connection.stub( adapter_name: 'MySQL') }
70
+
71
+ it 'migrates primary keys' do
72
+ ChangeKeysToBigint.migrate :up
73
+ expect( queries ).to include(
74
+ 'ALTER TABLE `teams` MODIFY COLUMN `id` bigint(20) DEFAULT NULL auto_increment',
75
+ 'ALTER TABLE `players` MODIFY COLUMN `id` bigint(20) DEFAULT NULL auto_increment',
76
+ 'ALTER TABLE `coaches` MODIFY COLUMN `id` bigint(20) DEFAULT NULL auto_increment'
77
+ )
78
+ end
79
+
80
+ it 'migrates foreign keys' do
81
+ ChangeKeysToBigint.migrate :up
82
+ expect( queries ).to include(
83
+ 'ALTER TABLE `players` MODIFY COLUMN `team_id` bigint(20) DEFAULT NULL')
84
+ end
85
+
86
+ it 'does not migrate foreign keys that do not exist in database' do
87
+ ChangeKeysToBigint.migrate :up
88
+ expect( queries ).to_not include(
89
+ 'ALTER TABLE `coaches` MODIFY COLUMN `team_id` bigint(20) DEFAULT NULL'
90
+ )
91
+ end
92
+ end
93
+
94
+ context 'with a sqlite database' do
95
+ before { connection.stub( adapter_name: 'SQLite') }
96
+
97
+ it 'does not migrate any keys' do
98
+ ChangeKeysToBigint.migrate :up
99
+ expect( queries ).to eq []
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
4
+
5
+ describe BigintPk do
6
+ describe '::setup' do
7
+ def reset_connection_adapters!
8
+ if defined? ::ActiveRecord::ConnectionAdapters
9
+ [:PostgreSQLAdapter, :AbstractMysqlAdapter].each do |adapter|
10
+ if ActiveRecord::ConnectionAdapters.const_defined? adapter
11
+ ActiveRecord::ConnectionAdapters.send :remove_const, adapter
12
+ end
13
+ end
14
+ end
15
+ load 'active_record/connection_adapters/postgresql_adapter.rb'
16
+ load 'active_record/connection_adapters/abstract_mysql_adapter.rb'
17
+ end
18
+
19
+ before do
20
+ ActiveRecord::Base.class_eval do
21
+ def self.establish_connection; end
22
+ end
23
+
24
+ reset_connection_adapters!
25
+ BigintPk.setup &prok
26
+ end
27
+
28
+ context 'when enabled' do
29
+ let(:prok){ lambda{|c| c.enabled = true }}
30
+
31
+ it 'updates the default primary key for the postgres adapter' do
32
+ expect(
33
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::
34
+ NATIVE_DATABASE_TYPES[:primary_key]
35
+ ).to eq 'bigserial primary key'
36
+ end
37
+
38
+ it 'updates the default primary key for both mysql adapters' do
39
+ expect(
40
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::
41
+ NATIVE_DATABASE_TYPES[:primary_key]
42
+ ).to eq 'bigint(20) DEFAULT NULL auto_increment PRIMARY KEY'
43
+ end
44
+
45
+ def self.it_makes_references_default_to_64bit
46
+ describe '#references' do
47
+ before { abstract_table_class.any_instance.stub :references_without_default_bigint_fk }
48
+
49
+
50
+ let(:args){ ['some_other_table', options ].compact }
51
+
52
+ context 'when a limit is specified' do
53
+ let(:options){{ limit: 6 }}
54
+
55
+ it 'uses the specified limit' do
56
+ abstract_table.should_receive(:references_without_default_bigint_fk).with(
57
+ 'some_other_table', hash_including( limit: 6 )
58
+ )
59
+ abstract_table.references *args
60
+ end
61
+ end
62
+
63
+ context 'when a limit is not specified' do
64
+ let(:options){}
65
+
66
+ it 'defaults the limit to 8' do
67
+ abstract_table.should_receive(:references_without_default_bigint_fk).with(
68
+ 'some_other_table', hash_including( limit: 8 )
69
+ )
70
+ abstract_table.references *args
71
+ end
72
+ end
73
+
74
+ context 'when reference is polymorphic' do
75
+ context 'when there is not additional options' do
76
+ let(:options){{ polymorphic: true }}
77
+
78
+ it 'should not contain limit' do
79
+ abstract_table.should_receive(:references_without_default_bigint_fk).with(
80
+ 'some_other_table', hash_including( polymorphic: {} )
81
+ )
82
+ abstract_table.references *args
83
+ end
84
+ end
85
+
86
+ context 'when there is common options' do
87
+ let(:options){{ null: false, polymorphic: true }}
88
+
89
+ it "should contain common options" do
90
+ abstract_table.should_receive(:references_without_default_bigint_fk).with(
91
+ 'some_other_table', hash_including( polymorphic: { null: false } )
92
+ )
93
+ abstract_table.references *args
94
+ end
95
+ end
96
+
97
+ context "when there is polymorphic options" do
98
+ let(:options){{ null: false, polymorphic: { limit: 120 } }}
99
+
100
+ it "should contain polymorphic options" do
101
+ abstract_table.should_receive(:references_without_default_bigint_fk).with(
102
+ 'some_other_table', hash_including( polymorphic: { limit: 120 } )
103
+ )
104
+ abstract_table.references *args
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ describe ActiveRecord::ConnectionAdapters::Table do
112
+ let(:abstract_table_class){ ActiveRecord::ConnectionAdapters::TableDefinition }
113
+ let(:abstract_table){ abstract_table_class.new Object.new }
114
+ it_makes_references_default_to_64bit
115
+ end
116
+
117
+ describe ActiveRecord::ConnectionAdapters::TableDefinition do
118
+ let(:connection_adapter_double) do
119
+ double("Connection Adapter").tap do |double|
120
+ double.stub :add_column
121
+ end
122
+ end
123
+ let(:abstract_table_class){ ActiveRecord::ConnectionAdapters::Table }
124
+ let(:abstract_table) do
125
+ abstract_table_class.new 'test_table', connection_adapter_double
126
+ end
127
+ it_makes_references_default_to_64bit
128
+ end
129
+ end
130
+
131
+ context 'when not enabled' do
132
+ let(:prok){ lambda{|c| c.enabled = false }}
133
+
134
+ it 'does not alter the default primary key for the postgres adapter' do
135
+ expect(
136
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::
137
+ NATIVE_DATABASE_TYPES[:primary_key]
138
+ ).to eq 'serial primary key'
139
+ end
140
+
141
+ it 'does not alter the default primary key for either mysql adapters' do
142
+ expect(
143
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::
144
+ NATIVE_DATABASE_TYPES[:primary_key]
145
+ ).to eq 'int(11) DEFAULT NULL auto_increment PRIMARY KEY'
146
+ end
147
+ end
148
+ end
149
+ end