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,47 @@
1
+ require 'low_card_tables'
2
+
3
+ describe LowCardTables::ActiveRecord::Relation do
4
+ before :each do
5
+ module ArRelationBaseSpecModule
6
+ def where(*args)
7
+ where_calls << args
8
+ :where_called
9
+ end
10
+
11
+ def where_calls
12
+ @where_calls ||= [ ]
13
+ end
14
+ end
15
+
16
+ @obj = Object.new
17
+ class << @obj
18
+ include ArRelationBaseSpecModule
19
+ include LowCardTables::ActiveRecord::Relation
20
+ end
21
+ end
22
+
23
+ it "should pass through if has_any_low_card_tables? is false" do
24
+ expect(@obj).to receive(:has_any_low_card_tables?).once.and_return(false)
25
+ @obj.where(:a, :b, :c).should == :where_called
26
+ @obj.where_calls.should == [ [ :a, :b, :c ] ]
27
+ end
28
+
29
+ context "with has_any_low_card_tables? == true" do
30
+ before :each do
31
+ expect(@obj).to receive(:has_any_low_card_tables?).once.and_return(true)
32
+ end
33
+
34
+ it "should pass through if passed :_low_card_direct => true" do
35
+ @obj.where(:a => :b, :c => :d, :_low_card_direct => true).should == :where_called
36
+ @obj.where_calls.should == [ [ { :a => :b, :c => :d } ] ]
37
+ end
38
+
39
+ it "should delegate otherwise" do
40
+ dmm = double('_low_card_dynamic_method_manager')
41
+ expect(@obj).to receive(:_low_card_dynamic_method_manager).once.and_return(dmm)
42
+ expect(dmm).to receive(:scope_from_query).once.with(@obj, :foo => :bar, :bar => :baz).and_return :yo_ho_ho
43
+ @obj.where(:foo => :bar, :bar => :baz).should == :yo_ho_ho
44
+ @obj.where_calls.length.should == 0
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,101 @@
1
+ require 'low_card_tables'
2
+ require 'active_support'
3
+
4
+ describe LowCardTables::ActiveRecord::Scoping do
5
+ before :each do
6
+ module ArScopingBaseSpecModule
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def scope(*args, &block)
11
+ scope_calls << { :args => args, :block => block }
12
+ :scope_called
13
+ end
14
+
15
+ def scope_calls
16
+ @scope_calls ||= [ ]
17
+ end
18
+
19
+ def reset!
20
+ @scope_calls = [ ]
21
+ end
22
+ end
23
+ end
24
+
25
+ class ArScopingTestClass
26
+ include ArScopingBaseSpecModule
27
+ include LowCardTables::ActiveRecord::Scoping
28
+ end
29
+
30
+ ArScopingTestClass.reset!
31
+ end
32
+
33
+ it "should pass through if given a block" do
34
+ proc = lambda { }
35
+ ArScopingTestClass.scope(:foo, :bar => :baz, &proc).should == :scope_called
36
+ ArScopingTestClass.scope_calls.should == [ { :args => [ :foo, { :bar => :baz } ], :block => proc } ]
37
+ end
38
+
39
+ it "should pass through if there are no low-card tables" do
40
+ expect(ArScopingTestClass).to receive(:has_any_low_card_tables?).and_return(false)
41
+ ArScopingTestClass.scope(:foo, :bar => :baz).should == :scope_called
42
+ ArScopingTestClass.scope_calls.should == [ { :args => [ :foo, { :bar => :baz } ], :block => nil } ]
43
+ end
44
+
45
+ it "should pass through if it isn't handed a Relation" do
46
+ expect(ArScopingTestClass).to receive(:has_any_low_card_tables?).and_return(true)
47
+ ArScopingTestClass.scope(:foo, :bar => :baz).should == :scope_called
48
+ ArScopingTestClass.scope_calls.should == [ { :args => [ :foo, { :bar => :baz } ], :block => nil } ]
49
+ end
50
+
51
+ context "with low-card tables and a Relation" do
52
+ before :each do
53
+ expect(ArScopingTestClass).to receive(:has_any_low_card_tables?).and_return(true)
54
+ @relation = Object.new
55
+ expect(@relation).to receive(:kind_of?).with(::ActiveRecord::Relation).and_return(true)
56
+
57
+ @associations = [ Object.new, Object.new ]
58
+ expect(@associations[0]).to receive(:foreign_key_column_name).at_least(:once).and_return("fk1")
59
+ expect(@associations[1]).to receive(:foreign_key_column_name).at_least(:once).and_return("fk2")
60
+
61
+ @lcam = double('low_card_associations_manager')
62
+ expect(ArScopingTestClass).to receive(:_low_card_associations_manager).at_least(:once).and_return(@lcam)
63
+ expect(@lcam).to receive(:associations).at_least(:once).and_return(@associations)
64
+ end
65
+
66
+ it "should be fine as long as you don't use any of the foreign keys" do
67
+ where_values = [ "foo=a", "bar=b" ]
68
+ expect(@relation).to receive(:where_values).and_return(where_values)
69
+
70
+ ArScopingTestClass.scope(:foo, @relation).should == :scope_called
71
+ ArScopingTestClass.scope_calls.should == [ { :args => [ :foo, @relation ], :block => nil } ]
72
+ end
73
+
74
+ it "should blow up if you do use any of the foreign keys" do
75
+ where_values = [ "foo=a", "fk2=b" ]
76
+ expect(@relation).to receive(:where_values).and_return(where_values)
77
+
78
+ lambda { ArScopingTestClass.scope(:foo, @relation) }.should raise_error(LowCardTables::Errors::LowCardStaticScopeError, /fk2/i)
79
+ ArScopingTestClass.scope_calls.should == [ { :args => [ :foo, @relation ], :block => nil } ]
80
+ end
81
+
82
+ it "should blow up if you do use any of the foreign keys, with #to_sql necessary" do
83
+ wv2 = Object.new
84
+ expect(wv2).to receive(:to_sql).at_least(:once).and_return("fk2=b")
85
+ where_values = [ "foo=a", wv2 ]
86
+ expect(@relation).to receive(:where_values).and_return(where_values)
87
+
88
+ lambda { ArScopingTestClass.scope(:foo, @relation) }.should raise_error(LowCardTables::Errors::LowCardStaticScopeError, /fk2/i)
89
+ ArScopingTestClass.scope_calls.should == [ { :args => [ :foo, @relation ], :block => nil } ]
90
+ end
91
+
92
+ it "should skip where values that aren't a String and don't respond to :to_sql" do
93
+ wv2 = Object.new
94
+ where_values = [ "foo=a", wv2 ]
95
+ expect(@relation).to receive(:where_values).and_return(where_values)
96
+
97
+ ArScopingTestClass.scope(:foo, @relation).should == :scope_called
98
+ ArScopingTestClass.scope_calls.should == [ { :args => [ :foo, @relation ], :block => nil } ]
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,79 @@
1
+ require 'low_card_tables'
2
+
3
+ describe LowCardTables::HasLowCardTable::Base do
4
+ before :each do
5
+ @spec_class = Class.new
6
+ @spec_class.class_eval do
7
+ include LowCardTables::HasLowCardTable::Base
8
+ end
9
+ end
10
+
11
+ it "should always has_any_low_card_tables?" do
12
+ @spec_class.has_any_low_card_tables?.should == true
13
+ end
14
+
15
+ context "with a low-card associations manager" do
16
+ before :each do
17
+ @lcam = double("low_card_associations_manager")
18
+ expect(LowCardTables::HasLowCardTable::LowCardAssociationsManager).to receive(:new).once.with(@spec_class).and_return(@lcam)
19
+ end
20
+
21
+ it "should create and maintain one low-card associations manager" do
22
+ @spec_class._low_card_associations_manager.should be(@lcam)
23
+ @spec_class._low_card_associations_manager.should be(@lcam)
24
+ end
25
+
26
+ %w{has_low_card_table _low_card_association _low_card_update_collapsed_rows low_card_value_collapsing_update_scheme}.each do |method_name|
27
+ it "should forward ##{method_name} to the LCAM" do
28
+ args = [ :foo, { :bar => :baz } ]
29
+ rv = Object.new
30
+ expect(@lcam).to receive(method_name).once.with(*args).and_return(rv)
31
+
32
+ @spec_class.send(method_name, *args).should be(rv)
33
+ end
34
+ end
35
+
36
+ it "should forward low_card_update_foreign_keys! to the LCAM" do
37
+ instance = @spec_class.new
38
+ rv = Object.new
39
+
40
+ expect(@lcam).to receive(:low_card_update_foreign_keys!).once.with(instance).and_return(rv)
41
+
42
+ instance.low_card_update_foreign_keys!.should be(rv)
43
+ end
44
+ end
45
+
46
+ it "should create and maintain one low-card dynamic methods manager" do
47
+ @lcdmm = double("low_card_dynamic_methods_manager")
48
+ expect(LowCardTables::HasLowCardTable::LowCardDynamicMethodManager).to receive(:new).once.with(@spec_class).and_return(@lcdmm)
49
+
50
+ @spec_class._low_card_dynamic_method_manager.should be(@lcdmm)
51
+ @spec_class._low_card_dynamic_method_manager.should be(@lcdmm)
52
+ end
53
+
54
+ it "should create and maintain one low-card dynamic methods module" do
55
+ class HasLowCardTableBaseDynamicMethodsModuleTest
56
+ include LowCardTables::HasLowCardTable::Base
57
+ end
58
+
59
+ HasLowCardTableBaseDynamicMethodsModuleTest.ancestors.detect { |a| a.name =~ /LowCardDynamicMethods/i }.should_not be
60
+
61
+ mod = HasLowCardTableBaseDynamicMethodsModuleTest._low_card_dynamic_methods_module
62
+ mod.class.should == ::Module
63
+ HasLowCardTableBaseDynamicMethodsModuleTest.ancestors.include?(mod).should be
64
+ HasLowCardTableBaseDynamicMethodsModuleTest.ancestors.detect { |a| a.name =~ /LowCardDynamicMethods/i }.should be
65
+
66
+ HasLowCardTableBaseDynamicMethodsModuleTest.const_get(:LowCardDynamicMethods).should be(mod)
67
+
68
+ HasLowCardTableBaseDynamicMethodsModuleTest._low_card_dynamic_methods_module.should be(mod)
69
+ end
70
+
71
+ it "should create and maintain one low-card objects manager" do
72
+ lcom = double("low_card_objects_manager")
73
+ obj = @spec_class.new
74
+ expect(LowCardTables::HasLowCardTable::LowCardObjectsManager).to receive(:new).once.with(obj).and_return(lcom)
75
+
76
+ obj._low_card_objects_manager.should be(lcom)
77
+ obj._low_card_objects_manager.should be(lcom)
78
+ end
79
+ end
@@ -0,0 +1,287 @@
1
+ require 'low_card_tables'
2
+
3
+ describe LowCardTables::HasLowCardTable::LowCardAssociation do
4
+ class ::ModelClassNameAscName; end
5
+
6
+ before :each do
7
+ @model_class = Class.new
8
+ allow(@model_class).to receive(:name).and_return('model_class_name')
9
+
10
+ col1 = double("column_1")
11
+ allow(col1).to receive(:name).and_return("id")
12
+ col2 = double("column_2")
13
+ allow(col2).to receive(:name).and_return("name")
14
+ @col3 = double("column_3")
15
+ allow(@col3).to receive(:name).and_return("model_class_name_asc_name_id")
16
+
17
+ allow(@model_class).to receive(:columns).and_return([ col1, col2, @col3 ])
18
+
19
+ allow(ModelClassNameAscName).to receive(:is_low_card_table?).and_return(true)
20
+ end
21
+
22
+ describe "instantiation, #foreign_key_column_name, #association_name, and #low_card_class" do
23
+ context "with a referred-to class" do
24
+ before :each do
25
+ expect(ModelClassNameAscName).to receive(:low_card_referred_to_by).once.with(@model_class)
26
+ end
27
+
28
+ it "should create a new, simple instance correctly, and tell the referred-to class" do
29
+ association = LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :asc_name, { })
30
+
31
+ association.association_name.should == 'asc_name'
32
+ association.foreign_key_column_name.should == 'model_class_name_asc_name_id'
33
+ association.low_card_class.should be(::ModelClassNameAscName)
34
+ end
35
+
36
+ describe "options" do
37
+ {
38
+ ::ModelClassNameAscName => 'Class object',
39
+ 'ModelClassNameAscName' => 'String',
40
+ :ModelClassNameAscName => 'Symbol',
41
+ :model_class_name_asc_name => 'Symbol (underscored)',
42
+ 'model_class_name_asc_name' => 'String (underscored)'
43
+ }.each do |input, description|
44
+ it "should allow setting the referred-to class name by #{description}" do
45
+ allow(@col3).to receive(:name).and_return("model_class_name_foobar_id")
46
+ association = LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :foobar, { :class => input })
47
+
48
+ association.association_name.should == 'foobar'
49
+ association.foreign_key_column_name.should == 'model_class_name_foobar_id'
50
+ association.low_card_class.should be(::ModelClassNameAscName)
51
+ end
52
+ end
53
+
54
+ it "should allow setting the foreign key" do
55
+ association = LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :foobar, { :class => ModelClassNameAscName, :foreign_key => :model_class_name_asc_name_id })
56
+
57
+ association.association_name.should == 'foobar'
58
+ association.foreign_key_column_name.should == 'model_class_name_asc_name_id'
59
+ association.low_card_class.should be(::ModelClassNameAscName)
60
+ end
61
+ end
62
+ end
63
+
64
+ it "should fail instantiation if the foreign key specified isn't a column" do
65
+ lambda do
66
+ LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :foobar,
67
+ { :class => ModelClassNameAscName, :foreign_key => :bogus_id })
68
+ end.should raise_error(ArgumentError, /bogus_id/i)
69
+ end
70
+
71
+ it "should fail instantiation if the foreign key inferred isn't a column" do
72
+ allow(@col3).to receive(:name).and_return("whatever")
73
+ lambda do
74
+ LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :foobar,
75
+ { :class => ModelClassNameAscName })
76
+ end.should raise_error(ArgumentError, /model_class_name_foobar_id/i)
77
+ end
78
+
79
+ it "should fail instantiation if the class inferred can't be found" do
80
+ allow(@col3).to receive(:name).and_return("model_class_name_yohoho_id")
81
+ lambda do
82
+ LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :yohoho, { })
83
+ end.should raise_error(ArgumentError, /ModelClassNameYohoho/i)
84
+ end
85
+
86
+ it "should fail instantiation if the class specified can't be found" do
87
+ allow(@col3).to receive(:name).and_return("model_class_name_yohoho_id")
88
+ lambda do
89
+ LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :yohoho, { :class => :FooBar })
90
+ end.should raise_error(ArgumentError, /FooBar/i)
91
+ end
92
+
93
+ it "should fail instantiation if the class specified isn't a Class" do
94
+ ::Object.const_set(:FooBar1, "hi")
95
+
96
+ allow(@col3).to receive(:name).and_return("model_class_name_yohoho_id")
97
+ lambda do
98
+ LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :yohoho, { :class => :FooBar1 })
99
+ end.should raise_error(ArgumentError, /\"hi\"/i)
100
+ end
101
+
102
+ it "should fail instantiation if the class specified doesn't respond to is_low_card_table" do
103
+ klass = Class.new
104
+
105
+ allow(@col3).to receive(:name).and_return("model_class_name_yohoho_id")
106
+ lambda do
107
+ LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :yohoho, { :class => klass })
108
+ end.should raise_error(ArgumentError, /is_low_card_table/i)
109
+ end
110
+
111
+ it "should fail instantiation if the class specified isn't a low-card table Class" do
112
+ klass = Class.new
113
+ expect(klass).to receive(:is_low_card_table?).and_return(false)
114
+
115
+ allow(@col3).to receive(:name).and_return("model_class_name_yohoho_id")
116
+ lambda do
117
+ LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :yohoho, { :class => klass })
118
+ end.should raise_error(ArgumentError, /is_low_card_table/i)
119
+ end
120
+ end
121
+
122
+ describe "#delegated_method_names" do
123
+ def check_delegated_method_names(options, expected_results)
124
+ expect(ModelClassNameAscName).to receive(:low_card_referred_to_by).once.with(@model_class)
125
+ expect(ModelClassNameAscName).to receive(:low_card_value_column_names).and_return(%w{foo bar baz})
126
+ association = LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :asc_name, options)
127
+ association.delegated_method_names.sort.should == expected_results.sort
128
+ end
129
+
130
+ it "should contain all methods by default" do
131
+ check_delegated_method_names({ }, %w{foo bar baz})
132
+ end
133
+
134
+ it "should contain no methods if :delegate => false" do
135
+ check_delegated_method_names({ :delegate => false }, [ ])
136
+ end
137
+
138
+ it "should contain only specified methods if specified" do
139
+ check_delegated_method_names({ :delegate => %w{bar baz} }, %w{bar baz})
140
+ end
141
+
142
+ it "should contain only a single method if specified (Symbol)" do
143
+ check_delegated_method_names({ :delegate => :bar }, %w{bar})
144
+ end
145
+
146
+ it "should contain only a single method if specified (String)" do
147
+ check_delegated_method_names({ :delegate => 'bar' }, %w{bar})
148
+ end
149
+
150
+ it "should exclude certain methods if specified" do
151
+ check_delegated_method_names({ :delegate => { :except => %w{bar baz} } }, %w{foo})
152
+ end
153
+
154
+ it "should blow up if passed anything else" do
155
+ lambda do
156
+ check_delegated_method_names({ :delegate => { :a => :b } }, %w{foo})
157
+ end.should raise_error(ArgumentError)
158
+
159
+ lambda do
160
+ check_delegated_method_names({ :delegate => 123 }, %w{foo})
161
+ end.should raise_error(ArgumentError)
162
+ end
163
+ end
164
+
165
+ describe "#class_method_name_to_low_card_method_name_map" do
166
+ def check_class_method_name_to_low_card_method_name_map(options, expected_results)
167
+ expect(ModelClassNameAscName).to receive(:low_card_referred_to_by).once.with(@model_class)
168
+ expect(ModelClassNameAscName).to receive(:low_card_value_column_names).and_return(%w{foo bar baz})
169
+ association = LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :asc_name, options)
170
+
171
+ association.class_method_name_to_low_card_method_name_map.should == expected_results
172
+ end
173
+
174
+ it "should map all methods to their same names by default" do
175
+ check_class_method_name_to_low_card_method_name_map({ }, {
176
+ 'foo' => 'foo', 'foo=' => 'foo=', 'bar' => 'bar', 'bar=' => 'bar=', 'baz' => 'baz', 'baz=' => 'baz=' })
177
+ end
178
+
179
+ it "should exclude methods if requested" do
180
+ check_class_method_name_to_low_card_method_name_map({ :delegate => %w{foo bar} }, {
181
+ 'foo' => 'foo', 'foo=' => 'foo=', 'bar' => 'bar', 'bar=' => 'bar=' })
182
+ end
183
+
184
+ it "should prefix methods if requested" do
185
+ check_class_method_name_to_low_card_method_name_map({ :prefix => true }, {
186
+ 'asc_name_foo' => 'foo', 'asc_name_foo=' => 'foo=', 'asc_name_bar' => 'bar', 'asc_name_bar=' => 'bar=',
187
+ 'asc_name_baz' => 'baz', 'asc_name_baz=' => 'baz=' })
188
+ end
189
+
190
+ it "should prefix methods with a given string if requested" do
191
+ check_class_method_name_to_low_card_method_name_map({ :prefix => :quux }, {
192
+ 'quux_foo' => 'foo', 'quux_foo=' => 'foo=', 'quux_bar' => 'bar', 'quux_bar=' => 'bar=',
193
+ 'quux_baz' => 'baz', 'quux_baz=' => 'baz=' })
194
+ end
195
+ end
196
+
197
+ context "with a valid instance" do
198
+ before :each do
199
+ expect(ModelClassNameAscName).to receive(:low_card_referred_to_by).once.with(@model_class)
200
+ @association = LowCardTables::HasLowCardTable::LowCardAssociation.new(@model_class, :asc_name, { })
201
+ end
202
+
203
+ describe "#create_low_card_object_for" do
204
+ it "should fail if passed something of the wrong class" do
205
+ lambda { @association.create_low_card_object_for("foo") }.should raise_error(/foo/i)
206
+ end
207
+
208
+ it "should return an empty object if there's no ID" do
209
+ instance = @model_class.new
210
+ expect(instance).to receive(:[]).with('model_class_name_asc_name_id').and_return(nil)
211
+
212
+ rv = Object.new
213
+ expect(ModelClassNameAscName).to receive(:new).once.and_return(rv)
214
+
215
+ obj = @association.create_low_card_object_for(instance)
216
+ obj.should be(rv)
217
+ end
218
+
219
+ it "should return a duplicated object if there is an ID" do
220
+ instance = @model_class.new
221
+ expect(instance).to receive(:[]).with('model_class_name_asc_name_id').and_return(12345)
222
+
223
+ template = Object.new
224
+ expect(ModelClassNameAscName).to receive(:low_card_row_for_id).once.with(12345).and_return(template)
225
+
226
+ rv = Object.new
227
+ expect(template).to receive(:dup).once.and_return(rv)
228
+ expect(rv).to receive(:id=).once.with(nil)
229
+
230
+ obj = @association.create_low_card_object_for(instance)
231
+ obj.should be(rv)
232
+ end
233
+ end
234
+
235
+ describe "#update_collapsed_rows" do
236
+ before :each do
237
+ @map = { :foo => [ :bar, :baz ] }
238
+ end
239
+
240
+ it "should just call it if it's callable" do
241
+ scheme = Object.new
242
+ expect(scheme).to receive(:call).once.with(@map)
243
+
244
+ @association.update_collapsed_rows(@map, scheme)
245
+ end
246
+
247
+ it "should do nothing if it's :none" do
248
+ @association.update_collapsed_rows(@map, :none)
249
+ end
250
+
251
+ # We deliberately don't spec, here, the case where we update in batches; we leave it to the system tests.
252
+ # A unit test would just test some insanely specific and long combination of ActiveRecord calls, and it wouldn't
253
+ # be a useful test anyway, since you're vastly more likely to make errors in figuring out what the DB needs --
254
+ # and you'd just spec those errors as well.
255
+ end
256
+
257
+ describe "#update_foreign_key!" do
258
+ before :each do
259
+ @model_instance = double("model_instance")
260
+ lcom = double("low_card_objects_manager")
261
+ low_card_object = double("low_card_object")
262
+
263
+ expect(@model_instance).to receive(:_low_card_objects_manager).and_return(lcom)
264
+ expect(lcom).to receive(:object_for).with(@association).and_return(low_card_object)
265
+
266
+ expect(ModelClassNameAscName).to receive(:low_card_value_column_names).and_return(%w{foo bar baz})
267
+ allow(low_card_object).to receive(:[]) { |name| "foo#{name}" }
268
+
269
+ expect(ModelClassNameAscName).to receive(:low_card_find_or_create_ids_for).with({
270
+ 'foo' => 'foofoo', 'bar' => 'foobar', 'baz' => 'foobaz' }).and_return(12345)
271
+ end
272
+
273
+ it "should change the ID when necessary" do
274
+ expect(@model_instance).to receive(:[]).with("model_class_name_asc_name_id").and_return(345)
275
+ expect(@model_instance).to receive(:[]=).with("model_class_name_asc_name_id", 12345)
276
+
277
+ @association.update_foreign_key!(@model_instance)
278
+ end
279
+
280
+ it "should not change the ID if it's not necessary" do
281
+ expect(@model_instance).to receive(:[]).with("model_class_name_asc_name_id").and_return(12345)
282
+
283
+ @association.update_foreign_key!(@model_instance)
284
+ end
285
+ end
286
+ end
287
+ end