low_card_tables 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,190 @@
1
+ require 'low_card_tables'
2
+
3
+ describe LowCardTables::HasLowCardTable::LowCardAssociationsManager do
4
+ describe "instantiation" do
5
+ it "should require a Class that descends from ActiveRecord::Base" do
6
+ klass = Class.new(String)
7
+
8
+ lambda do
9
+ LowCardTables::HasLowCardTable::LowCardAssociationsManager.new(klass)
10
+ end.should raise_error(ArgumentError)
11
+ end
12
+
13
+ it "should require a Class that is not itself a low-card class" do
14
+ klass = Class.new(::ActiveRecord::Base)
15
+ allow(klass).to receive(:is_low_card_table?).and_return(true)
16
+
17
+ lambda do
18
+ LowCardTables::HasLowCardTable::LowCardAssociationsManager.new(klass)
19
+ end.should raise_error(ArgumentError)
20
+ end
21
+ end
22
+
23
+ context "with a normal model class" do
24
+ before :each do
25
+ @model_class = Class.new
26
+ allow(@model_class).to receive(:superclass).and_return(::ActiveRecord::Base)
27
+ allow(@model_class).to receive(:is_low_card_table?).and_return(false)
28
+ expect(@model_class).to receive(:before_save).once.with(:low_card_update_foreign_keys!)
29
+
30
+ @manager = LowCardTables::HasLowCardTable::LowCardAssociationsManager.new(@model_class)
31
+ end
32
+
33
+ it "should have no associations by default" do
34
+ @manager.associations.should == [ ]
35
+ end
36
+
37
+ it "should have a default :low_card_value_collapsing_update_scheme" do
38
+ @manager.low_card_value_collapsing_update_scheme.should == :default
39
+ end
40
+
41
+ describe "#low_card_value_collapsing_update_scheme" do
42
+ it "should return :default by default" do
43
+ @manager.low_card_value_collapsing_update_scheme.should == :default
44
+ end
45
+
46
+ it "should be settable to :none or :default" do
47
+ @manager.low_card_value_collapsing_update_scheme :none
48
+ @manager.low_card_value_collapsing_update_scheme.should == :none
49
+ @manager.low_card_value_collapsing_update_scheme :default
50
+ @manager.low_card_value_collapsing_update_scheme.should == :default
51
+ end
52
+
53
+ it "should be settable to a positive integer" do
54
+ @manager.low_card_value_collapsing_update_scheme 1
55
+ @manager.low_card_value_collapsing_update_scheme.should == 1
56
+ @manager.low_card_value_collapsing_update_scheme 345
57
+ @manager.low_card_value_collapsing_update_scheme.should == 345
58
+
59
+ lambda { @manager.low_card_value_collapsing_update_scheme 0 }.should raise_error(ArgumentError)
60
+ lambda { @manager.low_card_value_collapsing_update_scheme -27 }.should raise_error(ArgumentError)
61
+ end
62
+
63
+ it "should be settable to something that responds to :call" do
64
+ callable = double("callable")
65
+ allow(callable).to receive(:call)
66
+
67
+ @manager.low_card_value_collapsing_update_scheme callable
68
+ @manager.low_card_value_collapsing_update_scheme.should be(callable)
69
+ end
70
+
71
+ it "should not be settable to anything else" do
72
+ lambda { @manager.low_card_value_collapsing_update_scheme "foo" }.should raise_error(ArgumentError)
73
+ end
74
+ end
75
+
76
+ describe "#has_low_card_table" do
77
+ it "should require a non-blank association name" do
78
+ lambda { @manager.has_low_card_table(nil) }.should raise_error(ArgumentError)
79
+ lambda { @manager.has_low_card_table("") }.should raise_error(ArgumentError)
80
+ lambda { @manager.has_low_card_table(" ") }.should raise_error(ArgumentError)
81
+ end
82
+
83
+ context "with one association" do
84
+ before :each do
85
+ options = { :a => :b, :c => :d }
86
+
87
+ @association = double("low_card_association")
88
+ expect(LowCardTables::HasLowCardTable::LowCardAssociation).to receive(:new).once.with(@model_class, 'foo', options).and_return(@association)
89
+
90
+ lcdmm = double("low_card_dynamic_method_manager")
91
+ expect(@model_class).to receive(:_low_card_dynamic_method_manager).at_least(:once).and_return(lcdmm)
92
+
93
+ expect(lcdmm).to receive(:sync_methods!).at_least(:once)
94
+
95
+ @manager.has_low_card_table(:foo, options)
96
+ end
97
+
98
+ it "should create a new association and add it to the list" do
99
+ @manager.associations.should == [ @association ]
100
+ end
101
+
102
+ it "should remove any previous associations with the same name" do
103
+ new_options = { :x => :y, :z => :a }
104
+ allow(@association).to receive(:association_name).and_return("foo")
105
+
106
+ association2 = double("association2")
107
+
108
+ expect(LowCardTables::HasLowCardTable::LowCardAssociation).to receive(:new).once.with(@model_class, 'foo', new_options).and_return(association2)
109
+
110
+ @manager.has_low_card_table('foo', new_options)
111
+ @manager.associations.should == [ association2 ]
112
+ end
113
+
114
+ context "and another association" do
115
+ before :each do
116
+ new_options = { :x => :y, :z => :a }
117
+ allow(@association).to receive(:association_name).and_return("foo")
118
+
119
+ @association2 = double("association2")
120
+ allow(@association2).to receive(:association_name).and_return("bar")
121
+
122
+ expect(LowCardTables::HasLowCardTable::LowCardAssociation).to receive(:new).once.with(@model_class, 'bar', new_options).and_return(@association2)
123
+
124
+ @manager.has_low_card_table('bar', new_options)
125
+ @manager.associations.should == [ @association, @association2 ]
126
+ end
127
+
128
+ it "should retrieve them by name" do
129
+ @manager._low_card_association("foo").should == @association
130
+ @manager._low_card_association("bar").should == @association2
131
+ @manager.maybe_low_card_association("foo").should == @association
132
+ @manager.maybe_low_card_association("bar").should == @association2
133
+ end
134
+
135
+ it "should do the right thing when they're not found" do
136
+ @manager.maybe_low_card_association("baz").should_not be
137
+ lambda { @manager._low_card_association("baz") }.should raise_error(LowCardTables::Errors::LowCardAssociationNotFoundError, /bar[\s,]+foo/)
138
+ end
139
+
140
+ it "should update all foreign keys on #low_card_update_foreign_keys!" do
141
+ model_instance = double("model_instance")
142
+ allow(model_instance).to receive(:kind_of?).with(@model_class).and_return(true)
143
+
144
+ @association.should receive(:update_foreign_key!).once.with(model_instance)
145
+ @association2.should receive(:update_foreign_key!).once.with(model_instance)
146
+
147
+ @manager.low_card_update_foreign_keys!(model_instance)
148
+ end
149
+
150
+ it "should blow up if passed something that isn't of the correct class to #low_card_update_foreign_keys!" do
151
+ model_instance = double("model_instance")
152
+ allow(model_instance).to receive(:kind_of?).with(@model_class).and_return(false)
153
+
154
+ lambda { @manager.low_card_update_foreign_keys!(model_instance) }.should raise_error(ArgumentError)
155
+ end
156
+
157
+ describe "_low_card_update_collapsed_rows" do
158
+ it "should call #update_collapsed_rows on associations that match the low-card model class passed" do
159
+ @manager.low_card_value_collapsing_update_scheme 345
160
+
161
+ low_card_class = double("low_card_class")
162
+ other_low_card_class = double("other_low_card_class")
163
+
164
+ collapse_map = double("collapse_map")
165
+
166
+ allow(@association).to receive(:low_card_class).and_return(other_low_card_class)
167
+ allow(@association2).to receive(:low_card_class).and_return(low_card_class)
168
+
169
+ expect(@association2).to receive(:update_collapsed_rows).with(collapse_map, 345).once
170
+
171
+ @manager._low_card_update_collapsed_rows(low_card_class, collapse_map)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ describe "#low_card_column_information_reset!" do
179
+ it "should call through to the model class" do
180
+ low_card_model = double("low_card_model")
181
+
182
+ lcdmm = double("low_card_dynamic_method_manager")
183
+ expect(@model_class).to receive(:_low_card_dynamic_method_manager).and_return(lcdmm)
184
+ expect(lcdmm).to receive(:sync_methods!).once
185
+
186
+ @manager.low_card_column_information_reset!(low_card_model)
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,234 @@
1
+ require 'low_card_tables'
2
+
3
+ describe LowCardTables::HasLowCardTable::LowCardDynamicMethodManager do
4
+ before :each do
5
+ @model_class = double("model_class")
6
+ @lcam = double("low_card_associations_manager")
7
+ allow(@model_class).to receive(:_low_card_associations_manager).and_return(@lcam)
8
+
9
+ @methods_module = Module.new
10
+ allow(@model_class).to receive(:_low_card_dynamic_methods_module).and_return(@methods_module)
11
+
12
+ @manager = LowCardTables::HasLowCardTable::LowCardDynamicMethodManager.new(@model_class)
13
+ end
14
+
15
+ context "with two associations and installed methods" do
16
+ before :each do
17
+ @association1 = double("association1")
18
+ allow(@association1).to receive(:association_name).and_return("foo")
19
+ allow(@association1).to receive(:foreign_key_column_name).and_return("a1fk")
20
+ allow(@association1).to receive(:class_method_name_to_low_card_method_name_map).and_return({
21
+ 'cm1' => 'lc1m1', 'cm2' => 'lc1m2' })
22
+
23
+ @association2 = double("association2")
24
+ allow(@association2).to receive(:association_name).and_return("bar")
25
+ allow(@association2).to receive(:foreign_key_column_name).and_return("a2fk")
26
+ allow(@association2).to receive(:class_method_name_to_low_card_method_name_map).and_return({
27
+ 'cm2' => 'lc2m1', 'cm3' => 'lc2m2' })
28
+
29
+ allow(@lcam).to receive(:associations).and_return([ @association1, @association2 ])
30
+
31
+ @manager.sync_methods!
32
+ end
33
+
34
+ describe "#scope_from_query" do
35
+ before :each do
36
+ @base_scope = double("base_scope")
37
+ @end_scope = double("end_scope")
38
+ end
39
+
40
+ it "should pass through non-low-card constraints" do
41
+ expect(@base_scope).to receive(:where).once.with({ :name => 'bonk', :_low_card_direct => true }).and_return(@end_scope)
42
+ @manager.scope_from_query(@base_scope, { :name => 'bonk' }).should be(@end_scope)
43
+ end
44
+
45
+ it "should apply low-card constraints" do
46
+ low_card_class_1 = double("low_card_class_1")
47
+ allow(@association1).to receive(:low_card_class).and_return(low_card_class_1)
48
+ expect(low_card_class_1).to receive(:low_card_ids_matching).with({ 'lc1m1' => false }).and_return([ 3, 9, 12 ])
49
+
50
+ expect(@base_scope).to receive(:where).once.with("a1fk IN (:ids)", { :ids => [ 3, 9, 12 ] }).and_return(@end_scope)
51
+ @manager.scope_from_query(@base_scope, { :cm1 => false }).should be(@end_scope)
52
+ end
53
+
54
+ it "should apply multiple low-card constraints combined with non-low-card constraints" do
55
+ low_card_class_1 = double("low_card_class_1")
56
+ allow(@association1).to receive(:low_card_class).and_return(low_card_class_1)
57
+ expect(low_card_class_1).to receive(:low_card_ids_matching).with({ 'lc1m1' => false }).and_return([ 3, 9, 12 ])
58
+
59
+ low_card_class_2 = double("low_card_class_2")
60
+ allow(@association2).to receive(:low_card_class).and_return(low_card_class_2)
61
+ expect(low_card_class_2).to receive(:low_card_ids_matching).with({ 'lc2m2' => [ :a, :b ], 'lc2m1' => 'yohoho' }).and_return([ 4, 6, 8 ])
62
+
63
+ mid_scope = double("mid_scope")
64
+
65
+ # expect(@base_scope).to receive(:where).once.with("a1fk IN (:ids)", { :ids => [ 3, 9, 12 ] }).and_return(mid_scope)
66
+ # expect(mid_scope).to receive(:where).once.with("a2fk IN (:ids)", { :ids => [ 4, 6, 8 ] }).and_return(@end_scope)
67
+
68
+ base_args = mid_args = nil
69
+
70
+ expect(@base_scope).to receive(:where).once { |*args| base_args = args; mid_scope }
71
+ expect(mid_scope).to receive(:where).once { |*args| mid_args = args; @end_scope }
72
+
73
+ @manager.scope_from_query(@base_scope, { :cm1 => false, :cm3 => [ :a, :b ], :bar => { 'lc2m1' => "yohoho" } }).should be(@end_scope)
74
+
75
+ # Perfectly valid for this to happen in either order
76
+ if base_args[0] =~ /^a1fk/
77
+ base_args.should == [ 'a1fk IN (:ids)', { :ids => [ 3, 9, 12 ]}]
78
+ mid_args.should == [ 'a2fk IN (:ids)', { :ids => [ 4, 6, 8 ]}]
79
+ else
80
+ base_args.should == [ 'a2fk IN (:ids)', { :ids => [ 4, 6, 8 ]}]
81
+ mid_args.should == [ 'a1fk IN (:ids)', { :ids => [ 3, 9, 12 ]}]
82
+ end
83
+ end
84
+
85
+ it "should apply low-card constraints in combination with direct foreign-key constraints" do
86
+ low_card_class_1 = double("low_card_class_1")
87
+ allow(@association1).to receive(:low_card_class).and_return(low_card_class_1)
88
+ expect(low_card_class_1).to receive(:low_card_ids_matching).with({ 'lc1m1' => false }).and_return([ 3, 9, 12 ])
89
+
90
+ mid_scope = double("mid_scope")
91
+
92
+ expect(@base_scope).to receive(:where).once.with({ :a2fk => [ 1, 3, 12 ], :_low_card_direct => true }).and_return(mid_scope)
93
+ expect(mid_scope).to receive(:where).once.with("a1fk IN (:ids)", { :ids => [ 3, 9, 12 ] }).and_return(@end_scope)
94
+
95
+ @manager.scope_from_query(@base_scope, { :cm1 => false, :a2fk => [ 1, 3, 12] }).should be(@end_scope)
96
+ end
97
+ end
98
+
99
+ describe "method delegation and invocation" do
100
+ before :each do
101
+ @invoked_object = double("invoked_object")
102
+ @low_card_object = double("low_card_object")
103
+ @args = double("args")
104
+ @rv = double("rv")
105
+
106
+ allow(@invoked_object).to receive(:kind_of?).with(@model_class).and_return(true)
107
+ end
108
+
109
+ def check_invocation(method_name, association_name, low_card_method_name)
110
+ expect(@invoked_object).to receive(association_name).and_return(@low_card_object)
111
+ expect(@low_card_object).to receive(low_card_method_name).with(@args).and_return(@rv)
112
+
113
+ @methods_module.instance_methods.map(&:to_s).include?(method_name.to_s).should be
114
+
115
+ @manager.run_low_card_method(@invoked_object, method_name, @args).should be(@rv)
116
+ end
117
+
118
+ context "after changing associations" do
119
+ before :each do
120
+ @manager.sync_methods!
121
+
122
+ @association3 = double("association3")
123
+ allow(@association3).to receive(:association_name).and_return("baz")
124
+ allow(@association3).to receive(:foreign_key_column_name).and_return("a3fk")
125
+ allow(@association3).to receive(:class_method_name_to_low_card_method_name_map).and_return({
126
+ 'cm2' => 'lc3m1', 'cm4' => 'lc3m2' })
127
+
128
+ allow(@lcam).to receive(:associations).and_return([ @association1, @association3 ])
129
+
130
+ @manager.sync_methods!
131
+ end
132
+
133
+ it "should run the right method for cm1" do
134
+ check_invocation("cm1", "foo", "lc1m1")
135
+ end
136
+
137
+ it "should run the right method for cm2" do
138
+ check_invocation("cm2", "baz", "lc3m1")
139
+ end
140
+
141
+ it "should run the right method for cm3" do
142
+ @methods_module.instance_methods.map(&:to_s).include?("cm3").should_not be
143
+
144
+ lambda { @manager.run_low_card_method(@invoked_object, "cm3", @args) }.should raise_error(NameError, /cm3/)
145
+ end
146
+
147
+ it "should run the right method for cm4" do
148
+ check_invocation("cm4", "baz", "lc3m2")
149
+ end
150
+ end
151
+
152
+ it "should run the right method for cm1" do
153
+ check_invocation("cm1", "foo", "lc1m1")
154
+ end
155
+
156
+ it "should run the right method for cm2" do
157
+ check_invocation("cm2", "bar", "lc2m1")
158
+ end
159
+
160
+ it "should run the right method for cm3" do
161
+ check_invocation("cm3", "bar", "lc2m2")
162
+ end
163
+
164
+ def check_association(method_name, association)
165
+ lcom = double("low_card_objects_manager")
166
+ expect(@invoked_object).to receive(:_low_card_objects_manager).and_return(lcom)
167
+ expect(lcom).to receive(:object_for).with(association).and_return(@low_card_object)
168
+
169
+ @methods_module.instance_methods.map(&:to_s).include?(method_name).should be
170
+
171
+ @manager.run_low_card_method(@invoked_object, method_name, [ ]).should be(@low_card_object)
172
+ end
173
+
174
+ it "should return the right association for foo" do
175
+ check_association("foo", @association1)
176
+ end
177
+
178
+ it "should return the right association for bar" do
179
+ check_association("bar", @association2)
180
+ end
181
+
182
+ def check_foreign_key_get(method_name, association)
183
+ lcom = double("low_card_objects_manager")
184
+ expect(@invoked_object).to receive(:_low_card_objects_manager).and_return(lcom)
185
+ expect(lcom).to receive(:foreign_key_for).with(association).and_return(12345)
186
+
187
+ @methods_module.instance_methods.map(&:to_s).include?(method_name).should be
188
+
189
+ @manager.run_low_card_method(@invoked_object, method_name, [ ]).should == 12345
190
+ end
191
+
192
+ it "should return the right foreign key for a1fk" do
193
+ check_foreign_key_get("a1fk", @association1)
194
+ end
195
+
196
+ it "should return the right foreign key for a2fk" do
197
+ check_foreign_key_get("a2fk", @association2)
198
+ end
199
+
200
+ def check_foreign_key_set(method_name, association)
201
+ args = double("args")
202
+ lcom = double("low_card_objects_manager")
203
+ expect(@invoked_object).to receive(:_low_card_objects_manager).and_return(lcom)
204
+ expect(lcom).to receive(:set_foreign_key_for).with(association, args)
205
+
206
+ @methods_module.instance_methods.map(&:to_s).include?(method_name).should be
207
+
208
+ @manager.run_low_card_method(@invoked_object, method_name, args)
209
+ end
210
+
211
+ it "should set the right foreign key for a1fk" do
212
+ check_foreign_key_set("a1fk=", @association1)
213
+ end
214
+
215
+ it "should set the right foreign key for a2fk" do
216
+ check_foreign_key_set("a2fk=", @association2)
217
+ end
218
+
219
+ it "should blow up if asked to invoke a method that doesn't exist" do
220
+ lambda do
221
+ @manager.run_low_card_method(@invoked_object, "quux", [ ])
222
+ end.should raise_error(/quux/i)
223
+ end
224
+
225
+ it "should blow up if given an object of the wrong class" do
226
+ allow(@invoked_object).to receive(:kind_of?).with(@model_class).and_return(false)
227
+
228
+ lambda do
229
+ @manager.run_low_card_method(@invoked_object, "foo", [ ])
230
+ end.should raise_error(ArgumentError)
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,70 @@
1
+ require 'low_card_tables'
2
+
3
+ describe LowCardTables::HasLowCardTable::LowCardObjectsManager do
4
+ before :each do
5
+ @model_instance = double("model_instance")
6
+ @manager = LowCardTables::HasLowCardTable::LowCardObjectsManager.new(@model_instance)
7
+
8
+ @model_class = double("model_class")
9
+ allow(@model_instance).to receive(:class).and_return(@model_class)
10
+
11
+ @lcam = double("low_card_associations_manager")
12
+ allow(@model_class).to receive(:_low_card_associations_manager).and_return(@lcam)
13
+
14
+ @association1 = double("LowCardAssociation")
15
+ allow(@association1).to receive(:association_name).and_return("foo")
16
+ allow(@association1).to receive(:foreign_key_column_name).and_return("blahblah_id")
17
+ end
18
+
19
+ describe "#object_for" do
20
+ it "should call through to the association to create the object, and only once" do
21
+ associated_object = double("associated_object")
22
+
23
+ expect(@lcam).to receive(:_low_card_association).with("foo").and_return(@association1)
24
+ expect(@association1).to receive(:create_low_card_object_for).once.with(@model_instance).and_return(associated_object)
25
+
26
+ @manager.object_for(@association1).should be(associated_object)
27
+ @manager.object_for(@association1).should be(associated_object)
28
+ end
29
+
30
+ it "should maintain multiple associcated objects separately" do
31
+ associated_object_1 = double("associated_object_1")
32
+ associated_object_2 = double("associated_object_2")
33
+
34
+ @association2 = double("LowCardAssociation2")
35
+ allow(@association2).to receive(:association_name).and_return("bar")
36
+
37
+ expect(@lcam).to receive(:_low_card_association).with("foo").and_return(@association1)
38
+ expect(@association1).to receive(:create_low_card_object_for).once.with(@model_instance).and_return(associated_object_1)
39
+
40
+ expect(@lcam).to receive(:_low_card_association).with("bar").and_return(@association2)
41
+ expect(@association2).to receive(:create_low_card_object_for).once.with(@model_instance).and_return(associated_object_2)
42
+
43
+ @manager.object_for(@association1).should be(associated_object_1)
44
+ @manager.object_for(@association2).should be(associated_object_2)
45
+ @manager.object_for(@association1).should be(associated_object_1)
46
+ @manager.object_for(@association2).should be(associated_object_2)
47
+ end
48
+ end
49
+
50
+ describe "foreign-key support" do
51
+ it "should call through to the model instance on get" do
52
+ expect(@model_instance).to receive(:[]).with("blahblah_id").and_return(12345)
53
+ @manager.foreign_key_for(@association1).should == 12345
54
+ end
55
+
56
+ it "should call through to the model instance on set, invalidate the object, and return the new value" do
57
+ associated_object_1 = double("associated_object_1")
58
+ expect(@lcam).to receive(:_low_card_association).with("foo").at_least(:once).and_return(@association1)
59
+ expect(@association1).to receive(:create_low_card_object_for).once.with(@model_instance).and_return(associated_object_1)
60
+ @manager.object_for(@association1).should be(associated_object_1)
61
+
62
+ expect(@model_instance).to receive(:[]=).with("blahblah_id", 23456)
63
+ @manager.set_foreign_key_for(@association1, 23456).should == 23456
64
+
65
+ associated_object_2 = double("associated_object_2")
66
+ expect(@association1).to receive(:create_low_card_object_for).once.with(@model_instance).and_return(associated_object_2)
67
+ @manager.object_for(@association1).should be(associated_object_2)
68
+ end
69
+ end
70
+ end