low_card_tables 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +59 -0
  4. data/Gemfile +17 -0
  5. data/LICENSE +21 -0
  6. data/README.md +75 -0
  7. data/Rakefile +6 -0
  8. data/lib/low_card_tables.rb +72 -0
  9. data/lib/low_card_tables/active_record/base.rb +55 -0
  10. data/lib/low_card_tables/active_record/migrations.rb +223 -0
  11. data/lib/low_card_tables/active_record/relation.rb +35 -0
  12. data/lib/low_card_tables/active_record/scoping.rb +87 -0
  13. data/lib/low_card_tables/errors.rb +74 -0
  14. data/lib/low_card_tables/has_low_card_table/base.rb +114 -0
  15. data/lib/low_card_tables/has_low_card_table/low_card_association.rb +273 -0
  16. data/lib/low_card_tables/has_low_card_table/low_card_associations_manager.rb +143 -0
  17. data/lib/low_card_tables/has_low_card_table/low_card_dynamic_method_manager.rb +224 -0
  18. data/lib/low_card_tables/has_low_card_table/low_card_objects_manager.rb +80 -0
  19. data/lib/low_card_tables/low_card_table/base.rb +184 -0
  20. data/lib/low_card_tables/low_card_table/cache.rb +214 -0
  21. data/lib/low_card_tables/low_card_table/cache_expiration/exponential_cache_expiration_policy.rb +151 -0
  22. data/lib/low_card_tables/low_card_table/cache_expiration/fixed_cache_expiration_policy.rb +23 -0
  23. data/lib/low_card_tables/low_card_table/cache_expiration/has_cache_expiration.rb +100 -0
  24. data/lib/low_card_tables/low_card_table/cache_expiration/no_caching_expiration_policy.rb +13 -0
  25. data/lib/low_card_tables/low_card_table/cache_expiration/unlimited_cache_expiration_policy.rb +13 -0
  26. data/lib/low_card_tables/low_card_table/row_collapser.rb +175 -0
  27. data/lib/low_card_tables/low_card_table/row_manager.rb +681 -0
  28. data/lib/low_card_tables/low_card_table/table_unique_index.rb +134 -0
  29. data/lib/low_card_tables/version.rb +4 -0
  30. data/lib/low_card_tables/version_support.rb +52 -0
  31. data/low_card_tables.gemspec +69 -0
  32. data/spec/low_card_tables/helpers/database_helper.rb +148 -0
  33. data/spec/low_card_tables/helpers/query_spy_helper.rb +47 -0
  34. data/spec/low_card_tables/helpers/system_helpers.rb +63 -0
  35. data/spec/low_card_tables/system/basic_system_spec.rb +254 -0
  36. data/spec/low_card_tables/system/bulk_system_spec.rb +334 -0
  37. data/spec/low_card_tables/system/caching_system_spec.rb +531 -0
  38. data/spec/low_card_tables/system/migrations_system_spec.rb +747 -0
  39. data/spec/low_card_tables/system/options_system_spec.rb +581 -0
  40. data/spec/low_card_tables/system/queries_system_spec.rb +142 -0
  41. data/spec/low_card_tables/system/validations_system_spec.rb +88 -0
  42. data/spec/low_card_tables/unit/active_record/base_spec.rb +53 -0
  43. data/spec/low_card_tables/unit/active_record/migrations_spec.rb +207 -0
  44. data/spec/low_card_tables/unit/active_record/relation_spec.rb +47 -0
  45. data/spec/low_card_tables/unit/active_record/scoping_spec.rb +101 -0
  46. data/spec/low_card_tables/unit/has_low_card_table/base_spec.rb +79 -0
  47. data/spec/low_card_tables/unit/has_low_card_table/low_card_association_spec.rb +287 -0
  48. data/spec/low_card_tables/unit/has_low_card_table/low_card_associations_manager_spec.rb +190 -0
  49. data/spec/low_card_tables/unit/has_low_card_table/low_card_dynamic_method_manager_spec.rb +234 -0
  50. data/spec/low_card_tables/unit/has_low_card_table/low_card_objects_manager_spec.rb +70 -0
  51. data/spec/low_card_tables/unit/low_card_table/base_spec.rb +207 -0
  52. data/spec/low_card_tables/unit/low_card_table/cache_expiration/exponential_cache_expiration_policy_spec.rb +128 -0
  53. data/spec/low_card_tables/unit/low_card_table/cache_expiration/fixed_cache_expiration_policy_spec.rb +25 -0
  54. data/spec/low_card_tables/unit/low_card_table/cache_expiration/has_cache_expiration_policy_spec.rb +100 -0
  55. data/spec/low_card_tables/unit/low_card_table/cache_expiration/no_caching_expiration_policy_spec.rb +14 -0
  56. data/spec/low_card_tables/unit/low_card_table/cache_expiration/unlimited_cache_expiration_policy_spec.rb +14 -0
  57. data/spec/low_card_tables/unit/low_card_table/cache_spec.rb +282 -0
  58. data/spec/low_card_tables/unit/low_card_table/row_collapser_spec.rb +109 -0
  59. data/spec/low_card_tables/unit/low_card_table/row_manager_spec.rb +918 -0
  60. data/spec/low_card_tables/unit/low_card_table/table_unique_index_spec.rb +117 -0
  61. metadata +206 -0
@@ -0,0 +1,142 @@
1
+ require 'low_card_tables'
2
+ require 'low_card_tables/helpers/database_helper'
3
+ require 'low_card_tables/helpers/system_helpers'
4
+
5
+ describe "LowCardTables query support" do
6
+ include LowCardTables::Helpers::SystemHelpers
7
+
8
+ def create_user!(name, deleted, deceased, gender, donation_level)
9
+ out = ::User.new
10
+ out.name = name
11
+ out.deleted = deleted
12
+ out.deceased = deceased
13
+ out.gender = gender
14
+ out.donation_level = donation_level
15
+ out.save!
16
+ out
17
+ end
18
+
19
+ before :each do
20
+ @dh = LowCardTables::Helpers::DatabaseHelper.new
21
+ @dh.setup_activerecord!
22
+
23
+ create_standard_system_spec_tables!
24
+ create_standard_system_spec_models!
25
+
26
+ @user1 = create_user!('User1', false, false, 'female', 10)
27
+ @user2 = create_user!('User2', true, false, 'female', 10)
28
+ @user3 = create_user!('User3', false, true, 'female', 10)
29
+ @user4 = create_user!('User4', false, false, 'male', 10)
30
+ @user5 = create_user!('User5', false, false, 'female', 8)
31
+ end
32
+
33
+ after :each do
34
+ drop_standard_system_spec_tables!
35
+ end
36
+
37
+ def check_user_ids(users, expected_users)
38
+ users.to_a.map(&:id).sort.should == expected_users.to_a.map(&:id).sort
39
+ end
40
+
41
+ it "should allow 'where' clauses that use the association name" do
42
+ check_user_ids(::User.where(:status => { :deleted => false }), [ @user1, @user3, @user4, @user5 ])
43
+ end
44
+
45
+ it "should allow 'where' clauses that use the foreign-key name" do
46
+ check_user_ids(::User.where(:user_status_id => ::UserStatus.low_card_ids_matching({ :deleted => false })), [ @user1, @user3, @user4, @user5 ])
47
+ end
48
+
49
+ it "should allow 'where' clauses that use delegated properties directly" do
50
+ check_user_ids(::User.where(:deleted => false), [ @user1, @user3, @user4, @user5 ])
51
+ end
52
+
53
+ it "should allow 'where' clauses that combine low-card and non-low-card properties" do
54
+ check_user_ids(::User.where(:deleted => false, :name => 'User1'), [ @user1 ])
55
+ check_user_ids(::User.where(:deleted => false, :name => 'User2'), [ ])
56
+ end
57
+
58
+ it "should allow 'where' clauses that use delegated properties with differently-prefixed names directly" do
59
+ define_model_class(:User2, :lctables_spec_users) { has_low_card_table :status, :prefix => :foo, :class => ::UserStatus, :foreign_key => :user_status_id }
60
+
61
+ lambda { check_user_ids(::User2.where(:deleted => false), [ ]) }.should raise_error(::ActiveRecord::StatementInvalid)
62
+ check_user_ids(::User2.where(:foo_deleted => false), [ @user1, @user3, @user4, @user5 ])
63
+ end
64
+
65
+ it "should not allow 'where' clauses that use non-delegated properties" do
66
+ define_model_class(:User3, :lctables_spec_users) { has_low_card_table :status, :delegate => [ :deceased ], :class => ::UserStatus, :foreign_key => :user_status_id }
67
+
68
+ lambda { check_user_ids(::User3.where(:deleted => false), [ ]) }.should raise_error(::ActiveRecord::StatementInvalid)
69
+ end
70
+
71
+ it "should compose 'where' clauses correctly" do
72
+ check_user_ids(::User.where(:deleted => false).where(:gender => 'male'), [ @user4 ])
73
+ end
74
+
75
+ it "should compose delegated and specified clauses correctly" do
76
+ check_user_ids(::User.where(:deleted => false, :status => { :gender => 'male'}), [ @user4 ])
77
+ end
78
+
79
+ it "should allow using low-card properties in the default scope" do
80
+ LowCardTables::VersionSupport.define_default_scope(::User, :deleted => false)
81
+ check_user_ids(::User.all, [ @user1, @user3, @user4, @user5 ])
82
+ end
83
+
84
+ it "should allow using low-card properties in arbitrary scopes and the default scope" do
85
+ LowCardTables::VersionSupport.define_default_scope(::User, :deleted => false)
86
+ class ::User < ::ActiveRecord::Base
87
+ scope :foo, lambda { where(:gender => 'female') }
88
+ scope :bar, lambda { where(:status => { :deceased => false }) }
89
+ end
90
+
91
+ check_user_ids(::User.all, [ @user1, @user3, @user4, @user5 ])
92
+ check_user_ids(::User.foo, [ @user1, @user3, @user5 ])
93
+ check_user_ids(::User.bar, [ @user1, @user4, @user5 ])
94
+ check_user_ids(::User.foo.bar, [ @user1, @user5 ])
95
+ check_user_ids(::User.bar.foo, [ @user1, @user5 ])
96
+ end
97
+
98
+ it "should pick up new low-card rows when using a low-card property in a scope" do
99
+ LowCardTables::VersionSupport.define_default_scope(::User, nil)
100
+ class ::User < ::ActiveRecord::Base
101
+ scope :foo, lambda { where(:deceased => false) }
102
+ scope :bar, lambda { where(:gender => 'female') }
103
+ end
104
+
105
+ check_user_ids(::User.all, [ @user1, @user2, @user3, @user4, @user5 ])
106
+ check_user_ids(::User.foo, [ @user1, @user2, @user4, @user5 ])
107
+ check_user_ids(::User.bar, [ @user1, @user2, @user3, @user5 ])
108
+
109
+ @user6 = create_user!('User6', false, false, 'female', 7)
110
+ [ @user1, @user2, @user3, @user4, @user5 ].map(&:user_status_id).include?(@user6.user_status_id).should_not be
111
+
112
+ check_user_ids(::User.all, [ @user1, @user2, @user3, @user4, @user5, @user6 ])
113
+ check_user_ids(::User.foo, [ @user1, @user2, @user4, @user5, @user6 ])
114
+ check_user_ids(::User.bar, [ @user1, @user2, @user3, @user5, @user6 ])
115
+ end
116
+
117
+ it "should blow up if you constrain on low-card foreign keys in a static scope" do
118
+ lambda do
119
+ LowCardTables::VersionSupport.define_default_scope(::User, nil)
120
+ class ::User < ::ActiveRecord::Base
121
+ scope :foo, where(:deleted => false)
122
+ end
123
+ end.should raise_error(LowCardTables::Errors::LowCardStaticScopeError, /user_status_id/mi)
124
+
125
+ lambda do
126
+ LowCardTables::VersionSupport.define_default_scope(::User, nil)
127
+ class ::User < ::ActiveRecord::Base
128
+ scope :foo, where(:user_status_id => ::UserStatus.low_card_ids_matching(:deleted => false))
129
+ end
130
+ end.should raise_error(LowCardTables::Errors::LowCardStaticScopeError, /user_status_id/mi)
131
+ end
132
+
133
+
134
+ it "should not blow up if you constrain on other things in a static scope" do
135
+ LowCardTables::VersionSupport.define_default_scope(::User, nil)
136
+ class ::User < ::ActiveRecord::Base
137
+ scope :foo, where(:name => %w{foo bar})
138
+ end
139
+
140
+ ::User.first.should be
141
+ end
142
+ end
@@ -0,0 +1,88 @@
1
+ require 'low_card_tables'
2
+ require 'low_card_tables/helpers/database_helper'
3
+ require 'low_card_tables/helpers/system_helpers'
4
+
5
+ describe "LowCardTables validation support" do
6
+ include LowCardTables::Helpers::SystemHelpers
7
+
8
+ before :each do
9
+ @dh = LowCardTables::Helpers::DatabaseHelper.new
10
+ @dh.setup_activerecord!
11
+
12
+ create_standard_system_spec_tables!
13
+ create_standard_system_spec_models!
14
+
15
+ class ::UserStatus
16
+ validates :gender, :inclusion => { :in => %w{male female other} }
17
+ end
18
+
19
+ class ::User
20
+ validates :donation_level, :numericality => { :greater_than_or_equal_to => 0, :less_than_or_equal_to => 10 }
21
+ end
22
+
23
+ @user1 = ::User.new
24
+ @user1.name = 'User1'
25
+ @user1.deleted = false
26
+ @user1.deceased = false
27
+ @user1.gender = 'female'
28
+ @user1.donation_level = 3
29
+ @user1.save!
30
+ end
31
+
32
+ after :each do
33
+ drop_standard_system_spec_tables!
34
+ end
35
+
36
+ it "should allow validations on the low-card table that are enforced" do
37
+ @user1.gender = 'amazing'
38
+ e = nil
39
+
40
+ begin
41
+ @user1.save!
42
+ rescue => x
43
+ e = x
44
+ end
45
+
46
+ e.should be
47
+ e.class.should == LowCardTables::Errors::LowCardInvalidLowCardRowsError
48
+ e.message.should match(/lctables_spec_user_statuses/mi)
49
+ e.message.should match(/validation/mi)
50
+ e.message.should match(/gender/mi)
51
+ e.message.should match(/gender is not included in the list/mi)
52
+ e.message.should match(/amazing/mi)
53
+ end
54
+
55
+ it "should allow the associated table to validate low-card data" do
56
+ @user1.donation_level = 40
57
+ e = nil
58
+
59
+ begin
60
+ @user1.save!
61
+ rescue => x
62
+ e = x
63
+ end
64
+
65
+ e.should be
66
+ e.class.should == ::ActiveRecord::RecordInvalid
67
+ e.message.should match(/donation level/mi)
68
+ e.message.should match(/less than or equal to 10/mi)
69
+ end
70
+
71
+ it "should gracefully handle database-level rejection of a new low-card row" do
72
+ @user1.deleted = nil
73
+ e = nil
74
+
75
+ begin
76
+ @user1.save!
77
+ rescue => x
78
+ e = x
79
+ end
80
+
81
+ e.should be
82
+ e.class.should == LowCardTables::Errors::LowCardInvalidLowCardRowsError
83
+ e.message.should match(/lctables_spec_user_statuses/mi)
84
+ e.message.should match(/gender/mi)
85
+ e.message.should match(/nil/mi)
86
+ e.message.should match(/ActiveRecord::StatementInvalid/mi)
87
+ end
88
+ end
@@ -0,0 +1,53 @@
1
+ require 'low_card_tables'
2
+
3
+ describe LowCardTables::ActiveRecord::Base do
4
+ before :each do
5
+ @klass = Class.new
6
+ @klass.send(:include, LowCardTables::ActiveRecord::Base)
7
+ end
8
+
9
+ it "should include LowCardTables::LowCardTable::Base appropriately and respond to #is_low_card_table? appropriately" do
10
+ @klass.ancestors.include?(LowCardTables::LowCardTable::Base).should_not be
11
+ @klass.is_low_card_table?.should_not be
12
+
13
+ opts = Hash.new
14
+
15
+ @klass.is_low_card_table(opts)
16
+ @klass.is_low_card_table?.should be
17
+ @klass.ancestors.include?(LowCardTables::LowCardTable::Base).should be
18
+ @klass.low_card_options.should be(opts)
19
+
20
+ # ...and again:
21
+ opts = Hash.new
22
+
23
+ @klass.is_low_card_table(opts)
24
+ @klass.is_low_card_table?.should be
25
+ @klass.ancestors.include?(LowCardTables::LowCardTable::Base).should be
26
+ @klass.low_card_options.should be(opts)
27
+ end
28
+
29
+ it "should include LowCardTables::HasLowCardTable::Base appropriately and respond to #has_any_low_card_tables? appropriately" do
30
+ @klass.ancestors.include?(LowCardTables::HasLowCardTable::Base).should_not be
31
+ @klass.has_any_low_card_tables?.should_not be
32
+
33
+ name = :foo
34
+ opts = Hash.new
35
+
36
+ mock_associations_manager = double('LowCardAssociationsManager')
37
+ expect(mock_associations_manager).to receive(:has_low_card_table).with(name, opts).once
38
+ expect(LowCardTables::HasLowCardTable::LowCardAssociationsManager).to receive(:new).and_return(mock_associations_manager)
39
+
40
+ @klass.has_low_card_table(name, opts)
41
+ @klass.has_any_low_card_tables?.should be
42
+ @klass.ancestors.include?(LowCardTables::HasLowCardTable::Base).should be
43
+
44
+ name = :bar
45
+ opts = Hash.new
46
+
47
+ expect(mock_associations_manager).to receive(:has_low_card_table).with(name, opts).once
48
+
49
+ @klass.has_low_card_table(name, opts)
50
+ @klass.has_any_low_card_tables?.should be
51
+ @klass.ancestors.include?(LowCardTables::HasLowCardTable::Base).should be
52
+ end
53
+ end
@@ -0,0 +1,207 @@
1
+ require 'low_card_tables'
2
+
3
+ describe LowCardTables::ActiveRecord::Migrations do
4
+ class MockMigrationClass
5
+ attr_reader :calls
6
+
7
+ def initialize
8
+ @calls = [ ]
9
+ end
10
+
11
+ %w{add_column remove_column}.each do |method_name|
12
+ class_eval %{
13
+ def #{method_name}(*args)
14
+ record_call(:#{method_name}, args)
15
+ end}
16
+ end
17
+
18
+ %w{create_table change_table}.each do |method_name|
19
+ class_eval %{
20
+ def #{method_name}(*args, &block)
21
+ record_call(:#{method_name}, args, &block)
22
+ instance_eval(&block)
23
+ end}
24
+ end
25
+
26
+ private
27
+ def record_call(name, args, &block)
28
+ @calls << { :name => name, :args => args, :block => block }
29
+ end
30
+
31
+ include LowCardTables::ActiveRecord::Migrations
32
+ end
33
+
34
+ before :each do
35
+ @migration = MockMigrationClass.new
36
+ @opts = { }
37
+ @proc = lambda { |*args| }
38
+ end
39
+
40
+ it "should pass through correctly for :create_table" do
41
+ @migration.create_table(:foo, @opts, &@proc)
42
+ @migration.calls.should == [ { :name => :create_table, :args => [ :foo, @opts ], :block => @proc } ]
43
+ end
44
+
45
+ context "with mock ::Rails" do
46
+ before :each do
47
+ rails_class = Object.new
48
+ Object.send(:remove_const, :Rails) if Object.const_defined?(:Rails)
49
+ Object.const_set(:Rails, rails_class)
50
+
51
+ application = Object.new
52
+
53
+ expect(rails_class).to receive(:application).at_least(:once).and_return(application)
54
+ expect(application).to receive(:eager_load!).once
55
+ end
56
+
57
+ context "without mock low-card model" do
58
+ it "should create a temporary low-card model if :low_card => true" do
59
+ @opts[:low_card] = true
60
+
61
+ temp_class = Class.new
62
+ expect(Class).to receive(:new).once.with(::ActiveRecord::Base).and_return(temp_class).ordered
63
+ expect(temp_class).to receive(:table_name=).once.with(:foo).ordered
64
+ expect(temp_class).to receive(:is_low_card_table).once.with().ordered
65
+ expect(temp_class).to receive(:reset_column_information).once.ordered
66
+
67
+ expect(temp_class).to receive(:low_card_remove_unique_index!).once.ordered
68
+
69
+ expect(temp_class).to receive(:reset_column_information).at_least(2).times.ordered
70
+ expect(temp_class).to receive(:low_card_value_column_names).twice.ordered.and_return([ 'x', 'y' ])
71
+
72
+ expect(LowCardTables::VersionSupport).to receive(:clear_schema_cache!).once.ordered.with(temp_class)
73
+
74
+ expect(temp_class).to receive(:low_card_ensure_has_unique_index!).once.with(true).ordered
75
+
76
+ @migration.create_table(:foo, @opts, &@proc)
77
+ @migration.calls.should == [ { :name => :create_table, :args => [ :foo, { } ], :block => @proc } ]
78
+ end
79
+ end
80
+
81
+ context "with mock low-card model" do
82
+ before :each do
83
+ non_low_card_class = Object.new
84
+ @low_card_class = Object.new
85
+
86
+ expect(non_low_card_class).to receive(:table_name).and_return('bar')
87
+ expect(@low_card_class).to receive(:table_name).and_return('foo')
88
+
89
+ expect(@low_card_class).to receive(:is_low_card_table?).and_return(true)
90
+ expect(@low_card_class).to receive(:name).at_least(:once).and_return('Whatever')
91
+
92
+ expect(::ActiveRecord::Base).to receive(:descendants).and_return([ non_low_card_class, @low_card_class ])
93
+ end
94
+
95
+ %w{add_column remove_column create_table change_table}.each do |method_name|
96
+ context "#{method_name}" do
97
+ before :each do
98
+ @method = method_name.to_sym
99
+ @args = case @method
100
+ when :create_table then [ :foo, { :bar => :baz } ]
101
+ when :change_table then [ :foo ]
102
+ when :add_column then [ :foo, :bar, :integer, { :null => false } ]
103
+ when :remove_column then [ :foo, :bar ]
104
+ else raise "unknown method_name #{method_name.inspect}"
105
+ end
106
+
107
+ @proc = case @method
108
+ when :create_table, :change_table then lambda { |*args| }
109
+ else nil
110
+ end
111
+ end
112
+
113
+ def add_option(args, hash)
114
+ if args[-1].kind_of?(Hash)
115
+ args[-1].merge!(hash)
116
+ else
117
+ args << hash
118
+ end
119
+ end
120
+
121
+ def remove_low_card_options(args)
122
+ out = args.dup
123
+ if out[-1].kind_of?(Hash)
124
+ out[-1].delete_if { |k,v| k.to_s =~ /^low_card/ }
125
+ out.pop if out[-1].size == 0
126
+ end
127
+ out
128
+ end
129
+
130
+ it "should call #eager_load, pick up an AR descendant properly, and enforce the index" do
131
+ expect(@low_card_class).to receive(:low_card_remove_unique_index!).once.ordered
132
+
133
+ expect(@low_card_class).to receive(:reset_column_information).at_least(2).times.ordered
134
+ expect(@low_card_class).to receive(:low_card_value_column_names).twice.ordered.and_return([ 'x', 'y' ])
135
+
136
+ expect(LowCardTables::VersionSupport).to receive(:clear_schema_cache!).once.ordered.with(@low_card_class)
137
+
138
+ expect(@low_card_class).to receive(:low_card_ensure_has_unique_index!).once.with(true).ordered
139
+
140
+ @migration.send(@method, *@args, &@proc)
141
+ @migration.calls.should == [ { :name => @method, :args => @args, :block => @proc } ]
142
+ end
143
+
144
+ it "should not reinstitute the index if :low_card_collapse_rows => true" do
145
+ add_option(@args, :low_card_collapse_rows => false)
146
+
147
+ expect(@low_card_class).to receive(:low_card_remove_unique_index!).once.ordered
148
+
149
+ expect(@low_card_class).to receive(:reset_column_information).at_least(2).times.ordered
150
+ expect(@low_card_class).to receive(:low_card_value_column_names).twice.ordered.and_return([ 'x', 'y' ])
151
+
152
+ expect(LowCardTables::VersionSupport).to receive(:clear_schema_cache!).once.ordered.with(@low_card_class)
153
+
154
+ @migration.send(@method, *@args, &@proc)
155
+
156
+ expected_args = remove_low_card_options(@args)
157
+ @migration.calls.should == [ { :name => @method, :args => expected_args, :block => @proc } ]
158
+ end
159
+
160
+ it "should detect removed columns" do
161
+ add_option(@args, :low_card_foo => :bar)
162
+
163
+ expect(@low_card_class).to receive(:low_card_remove_unique_index!).once.ordered
164
+
165
+ expect(@low_card_class).to receive(:reset_column_information).at_least(2).times.ordered
166
+ expect(@low_card_class).to receive(:low_card_value_column_names).once.ordered.and_return([ 'x', 'y' ])
167
+
168
+ expect(LowCardTables::VersionSupport).to receive(:clear_schema_cache!).once.ordered.with(@low_card_class)
169
+
170
+ expect(@low_card_class).to receive(:low_card_value_column_names).once.ordered.and_return([ 'y' ])
171
+ expect(@low_card_class).to receive(:low_card_collapse_rows_and_update_referrers!).once.ordered.with(:low_card_foo => :bar)
172
+
173
+ expect(@low_card_class).to receive(:low_card_ensure_has_unique_index!).once.with(true).ordered
174
+
175
+ @migration.send(@method, *@args, &@proc)
176
+ expected_args = remove_low_card_options(@args)
177
+ @migration.calls.should == [ { :name => @method, :args => expected_args, :block => @proc } ]
178
+ end
179
+ end
180
+ end
181
+
182
+ it "should not do anything twice if calls are nested" do
183
+ @opts[:foo] = :bar
184
+
185
+ expect(@low_card_class).to receive(:low_card_remove_unique_index!).once.ordered
186
+
187
+ expect(@low_card_class).to receive(:reset_column_information).at_least(2).times.ordered
188
+ expect(@low_card_class).to receive(:low_card_value_column_names).twice.ordered.and_return([ 'x', 'y' ])
189
+
190
+ expect(LowCardTables::VersionSupport).to receive(:clear_schema_cache!).once.ordered.with(@low_card_class)
191
+
192
+ expect(@low_card_class).to receive(:low_card_ensure_has_unique_index!).once.with(true).ordered
193
+
194
+ inner_opts = { :a => :b, :low_card_foo => :bar }
195
+ @proc = lambda do |*args|
196
+ remove_column :bar, :baz, inner_opts
197
+ end
198
+
199
+ @migration.create_table(:foo, @opts, &@proc)
200
+ @migration.calls.should == [
201
+ { :name => :create_table, :args => [ :foo, { :foo => :bar } ], :block => @proc },
202
+ { :name => :remove_column, :args => [ :bar, :baz, { :a => :b } ], :block => nil }
203
+ ]
204
+ end
205
+ end
206
+ end
207
+ end