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,63 @@
1
+ require 'active_record'
2
+ require 'active_record/migration'
3
+
4
+ module LowCardTables
5
+ module Helpers
6
+ module SystemHelpers
7
+ def migrate(&block)
8
+ migration_class = Class.new(::ActiveRecord::Migration)
9
+ metaclass = migration_class.class_eval { class << self; self; end }
10
+ metaclass.instance_eval { define_method(:up, &block) }
11
+
12
+ ::ActiveRecord::Migration.suppress_messages do
13
+ migration_class.migrate(:up)
14
+ end
15
+
16
+ LowCardTables::VersionSupport.clear_schema_cache!(::ActiveRecord::Base)
17
+ end
18
+
19
+ def define_model_class(name, table_name, &block)
20
+ model_class = Class.new(::ActiveRecord::Base)
21
+ ::Object.send(:remove_const, name) if ::Object.const_defined?(name)
22
+ ::Object.const_set(name, model_class)
23
+ model_class.table_name = table_name
24
+ model_class.class_eval(&block)
25
+ end
26
+
27
+ def create_standard_system_spec_tables!
28
+ migrate do
29
+ drop_table :lctables_spec_user_statuses rescue nil
30
+ create_table :lctables_spec_user_statuses do |t|
31
+ t.boolean :deleted, :null => false
32
+ t.boolean :deceased
33
+ t.string :gender, :null => false
34
+ t.integer :donation_level
35
+ end
36
+
37
+ add_index :lctables_spec_user_statuses, [ :deleted, :deceased, :gender, :donation_level ], :unique => true, :name => 'index_lctables_spec_user_statuses_on_all'
38
+
39
+ drop_table :lctables_spec_users rescue nil
40
+ create_table :lctables_spec_users do |t|
41
+ t.string :name, :null => false
42
+ t.integer :user_status_id, :null => false, :limit => 2
43
+ end
44
+ end
45
+ end
46
+
47
+ def create_standard_system_spec_models!
48
+ define_model_class(:UserStatus, 'lctables_spec_user_statuses') { is_low_card_table }
49
+ define_model_class(:User, 'lctables_spec_users') { has_low_card_table :status }
50
+ define_model_class(:UserStatusBackdoor, 'lctables_spec_user_statuses') { }
51
+
52
+ ::UserStatus.low_card_cache_expiration :unlimited
53
+ end
54
+
55
+ def drop_standard_system_spec_tables!
56
+ migrate do
57
+ drop_table :lctables_spec_user_statuses rescue nil
58
+ drop_table :lctables_spec_users rescue nil
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,254 @@
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 basic operations" do
6
+ include LowCardTables::Helpers::SystemHelpers
7
+
8
+ before :each do
9
+ @dh = LowCardTables::Helpers::DatabaseHelper.new
10
+ @dh.setup_activerecord!
11
+ end
12
+
13
+ context "with standard setup" do
14
+ before :each do
15
+ create_standard_system_spec_tables!
16
+ create_standard_system_spec_models!
17
+
18
+ @user1 = ::User.new
19
+ @user1.name = 'User1'
20
+ @user1.deleted = false
21
+ @user1.deceased = false
22
+ @user1.gender = 'female'
23
+ @user1.donation_level = 3
24
+ @user1.save!
25
+ end
26
+
27
+ after :each do
28
+ drop_standard_system_spec_tables!
29
+ end
30
+
31
+ it "should say #is_low_card_table? appropriately" do
32
+ ::UserStatus.is_low_card_table?.should be
33
+ ::User.is_low_card_table?.should_not be
34
+
35
+ ::UserStatus.low_card_options.should == { }
36
+ end
37
+
38
+ it "should blow up if you say #has_low_card_table for something that isn't" do
39
+ define_model_class(:UserStatusReferencingError, :lctables_spec_user_statuses) { }
40
+
41
+ lambda do
42
+ define_model_class(:UserReferencingError, :lctables_spec_users) do
43
+ has_low_card_table :status, :class => :UserStatusReferencingError, :foreign_key => :user_status_id
44
+ end
45
+ end.should raise_error(ArgumentError, /UserStatusReferencingError/i)
46
+ end
47
+
48
+ it "should allow setting all options, and create an appropriate row" do
49
+ @user1.should be
50
+
51
+ rows = ::UserStatusBackdoor.all
52
+ rows.length.should == 1
53
+ row = rows[0]
54
+ row.id.should == @user1.user_status_id
55
+ row.deleted.should == false
56
+ row.deceased.should == false
57
+ row.gender.should == 'female'
58
+ row.donation_level.should == 3
59
+
60
+ user1_v2 = User.where(:name => 'User1').first
61
+ user1_v2.deleted.should == false
62
+ user1_v2.deceased.should == false
63
+ user1_v2.gender.should == 'female'
64
+ user1_v2.donation_level.should == 3
65
+ end
66
+
67
+ it "should expose a low-card row, but not with an ID, when read in from the DB" do
68
+ @user1.status.should be
69
+ @user1.status.id.should_not be
70
+
71
+ user1_v2 = User.where(:name => 'User1').first
72
+ user1_v2.should be
73
+ user1_v2.status.should be
74
+ user1_v2.status.id.should_not be
75
+ end
76
+
77
+ it "should not allow re-saving the status to the DB, with or without changes" do
78
+ lambda { @user1.status.save! }.should raise_error
79
+ @user1.deleted = true
80
+ lambda { @user1.status.save! }.should raise_error
81
+ end
82
+
83
+ it "should allow changing a property, and create another row, but only for the final set" do
84
+ previous_status_id = @user1.user_status_id
85
+
86
+ @user1.gender = 'unknown'
87
+ @user1.gender = 'male'
88
+ @user1.donation_level = 1
89
+ @user1.save!
90
+
91
+ rows = ::UserStatusBackdoor.all
92
+ rows.length.should == 2
93
+ new_row = rows.detect { |r| r.id != previous_status_id }
94
+ new_row.id.should == @user1.user_status_id
95
+ new_row.deleted.should == false
96
+ new_row.deceased.should == false
97
+ new_row.gender.should == 'male'
98
+ new_row.donation_level.should == 1
99
+ end
100
+
101
+ it "should allow creating another associated row, and they should be independent, even if they start with the same low-card ID" do
102
+ user2 = ::User.new
103
+ user2.name = 'User2'
104
+ user2.deleted = false
105
+ user2.deceased = false
106
+ user2.gender = 'female'
107
+ user2.donation_level = 3
108
+ user2.save!
109
+
110
+ @user1.user_status_id.should == user2.user_status_id
111
+
112
+ user2.deleted = true
113
+ user2.save!
114
+
115
+ @user1.user_status_id.should_not == user2.user_status_id
116
+ @user1.deleted.should == false
117
+ user2.deleted.should == true
118
+
119
+ user1_v2 = ::User.where(:name => 'User1').first
120
+ user2_v2 = ::User.where(:name => 'User2').first
121
+
122
+ user1_v2.deleted.should == false
123
+ user2_v2.deleted.should == true
124
+ end
125
+
126
+ it "should have an explicit 'assign the low-card ID now' call" do
127
+ user2 = ::User.new
128
+ user2.name = 'User2'
129
+ user2.deleted = false
130
+ user2.deceased = false
131
+ user2.gender = 'female'
132
+ user2.donation_level = 3
133
+
134
+ user2.low_card_update_foreign_keys!
135
+ user2.user_status_id.should be
136
+ user2.user_status_id.should > 0
137
+ user2.user_status_id.should == @user1.user_status_id
138
+
139
+ ::UserStatusBackdoor.count.should == 1
140
+
141
+ user2 = ::User.new
142
+ user2.name = 'User2'
143
+ user2.deleted = false
144
+ user2.deceased = false
145
+ user2.gender = 'female'
146
+ user2.donation_level = 9
147
+
148
+ user2.low_card_update_foreign_keys!
149
+ user2.user_status_id.should be
150
+ user2.user_status_id.should > 0
151
+ user2.user_status_id.should_not == @user1.user_status_id
152
+
153
+ ::UserStatusBackdoor.count.should == 2
154
+ end
155
+
156
+ it "should let you assign the low-card ID directly, and remap the associated object when that happens" do
157
+ previous_status_id = @user1.user_status_id
158
+
159
+ @user1.deleted = true
160
+ @user1.save!
161
+
162
+ new_status_id = @user1.user_status_id
163
+ new_status_id.should_not == previous_status_id
164
+
165
+ @user1.deceased = true
166
+
167
+ @user1.deleted.should == true
168
+ @user1.deceased.should == true
169
+
170
+ @user1.user_status_id = previous_status_id
171
+
172
+ @user1.user_status_id.should == previous_status_id
173
+ @user1.user_status_id.should_not == new_status_id
174
+
175
+ @user1.deleted.should == false
176
+ @user1.deceased.should == false
177
+ @user1.gender.should == 'female'
178
+ @user1.donation_level.should == 3
179
+ end
180
+
181
+ it "should blow up if there are too many rows in the low-card table" do
182
+ class ::UserStatus
183
+ is_low_card_table :max_row_count => 10
184
+ end
185
+
186
+ 15.times do |i|
187
+ bd = ::UserStatusBackdoor.new
188
+ bd.deleted = false
189
+ bd.deceased = false
190
+ bd.gender = 'female'
191
+ bd.donation_level = i + 10
192
+ bd.save!
193
+ end
194
+
195
+ lambda do
196
+ ::UserStatus.low_card_flush_cache!
197
+ ::UserStatus.low_card_all_rows
198
+ end.should raise_error(LowCardTables::Errors::LowCardTooManyRowsError, /11/)
199
+ end
200
+ end
201
+
202
+ it "should handle column default values in exactly the same way as ActiveRecord" do
203
+ migrate do
204
+ drop_table :lctables_spec_user_statuses rescue nil
205
+ create_table :lctables_spec_user_statuses do |t|
206
+ t.boolean :deleted, :null => false
207
+ t.boolean :deceased, :default => false
208
+ t.string :gender, :default => 'unknown'
209
+ t.integer :donation_level, :default => 5
210
+ end
211
+
212
+ add_index :lctables_spec_user_statuses, [ :deleted, :deceased, :gender, :donation_level ], :unique => true, :name => 'index_lctables_spec_user_statuses_on_all'
213
+
214
+ drop_table :lctables_spec_users rescue nil
215
+ create_table :lctables_spec_users do |t|
216
+ t.string :name, :null => false
217
+ t.integer :user_status_id, :null => false, :limit => 2
218
+ end
219
+ end
220
+
221
+ create_standard_system_spec_models!
222
+
223
+ user = ::User.new
224
+ user.name = 'Name1'
225
+ user.deleted.should == nil
226
+ user.deceased.should == false
227
+ user.gender.should == 'unknown'
228
+ user.donation_level.should == 5
229
+
230
+ lambda { user.save! }.should raise_error(LowCardTables::Errors::LowCardInvalidLowCardRowsError)
231
+ user.deleted = false
232
+ user.save!
233
+
234
+ user.deleted.should == false
235
+ user.deceased.should == false
236
+ user.gender.should == 'unknown'
237
+ user.donation_level.should == 5
238
+
239
+ user2 = ::User.find(user.id)
240
+ user2.name.should == 'Name1'
241
+ user2.deleted.should == false
242
+ user2.deceased.should == false
243
+ user2.gender.should == 'unknown'
244
+ user2.donation_level.should == 5
245
+
246
+ rows = ::UserStatusBackdoor.all
247
+ rows.length.should == 1
248
+ row = rows[0]
249
+ row.deleted.should == false
250
+ row.deceased.should == false
251
+ row.gender.should == 'unknown'
252
+ row.donation_level.should == 5
253
+ end
254
+ end
@@ -0,0 +1,334 @@
1
+ require 'low_card_tables'
2
+ require 'low_card_tables/helpers/database_helper'
3
+ require 'low_card_tables/helpers/system_helpers'
4
+ require 'low_card_tables/helpers/query_spy_helper'
5
+
6
+ describe "LowCardTables bulk operations" do
7
+ include LowCardTables::Helpers::SystemHelpers
8
+
9
+ before :each do
10
+ @dh = LowCardTables::Helpers::DatabaseHelper.new
11
+ @dh.setup_activerecord!
12
+ end
13
+
14
+ context "with standard setup" do
15
+ before :each do
16
+ create_standard_system_spec_tables!
17
+ create_standard_system_spec_models!
18
+
19
+ # create several low-card rows in our table
20
+ user = User.new
21
+ user.name = 'User1'
22
+ user.deleted = false
23
+ user.deceased = false
24
+ user.gender = 'male'
25
+ user.donation_level = 5
26
+ user.save!
27
+
28
+ @hash1_id = user.user_status_id
29
+
30
+ user.deleted = true
31
+ user.save!
32
+
33
+ @hash2_id = user.user_status_id
34
+
35
+ user.deleted = false
36
+ user.gender = 'female'
37
+ user.donation_level = 9
38
+ user.save!
39
+
40
+ @hash3_id = user.user_status_id
41
+
42
+ @hash1 = { :deleted => false, :deceased => false, :gender => 'male', :donation_level => 5 }.with_indifferent_access
43
+ @hash2 = { :deleted => true, :deceased => false, :gender => 'male', :donation_level => 5 }.with_indifferent_access
44
+ @hash3 = { :deleted => false, :deceased => false, :gender => 'female', :donation_level => 9 }.with_indifferent_access
45
+ @hash4 = { :deleted => false, :deceased => true, :gender => 'female', :donation_level => 3 }.with_indifferent_access
46
+ @hash5 = { :deleted => false, :deceased => true, :gender => 'male', :donation_level => 2 }.with_indifferent_access
47
+ end
48
+
49
+ after :each do
50
+ drop_standard_system_spec_tables!
51
+ end
52
+
53
+ def verify_row(row, deleted, deceased, gender, donation_level)
54
+ row.deleted.should == deleted
55
+ row.deceased.should == deceased
56
+ row.gender.should == gender
57
+ row.donation_level.should == donation_level
58
+ end
59
+
60
+ def verify_by_id(rows, id, deleted, deceased, gender, donation_level)
61
+ row = rows.detect { |r| r.id == id }
62
+ row.should be
63
+ verify_row(row, deleted, deceased, gender, donation_level)
64
+ end
65
+
66
+ def ensure_zero_database_calls(&block)
67
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy('lctables_spec_user_statuses') do |spy|
68
+ ::UserStatus.low_card_all_rows
69
+
70
+ pre_count = spy.call_count
71
+ block.call
72
+ post_count = spy.call_count
73
+ post_count.should == pre_count
74
+ end
75
+ end
76
+
77
+ it "should allow for bulk retrieval of subsets of rows" do
78
+ ensure_zero_database_calls do
79
+ hash_selector_1 = { :deleted => false, :deceased => false }
80
+ hash_selector_2 = { :deceased => false, :donation_level => 5 }
81
+ results = ::UserStatus.low_card_rows_matching([ hash_selector_1, hash_selector_2 ])
82
+
83
+ results.size.should == 2
84
+
85
+ results[hash_selector_1].should be
86
+ results[hash_selector_1].length.should == 2
87
+ results[hash_selector_1].map(&:id).sort.should == [ @hash1_id, @hash3_id ].sort
88
+
89
+ verify_by_id(results[hash_selector_1], @hash1_id, false, false, 'male', 5)
90
+ verify_by_id(results[hash_selector_1], @hash3_id, false, false, 'female', 9)
91
+
92
+ results[hash_selector_2].should be
93
+ results[hash_selector_2].length.should == 2
94
+ results[hash_selector_2].map(&:id).sort.should == [ @hash1_id, @hash2_id ].sort
95
+
96
+ verify_by_id(results[hash_selector_2], @hash1_id, false, false, 'male', 5)
97
+ verify_by_id(results[hash_selector_2], @hash2_id, true, false, 'male', 5)
98
+ end
99
+ end
100
+
101
+ it "should allow for bulk retrieval of subsets of IDs" do
102
+ ensure_zero_database_calls do
103
+ hash_selector_1 = { :deleted => false, :deceased => false }
104
+ hash_selector_2 = { :deceased => false, :donation_level => 5 }
105
+ results = ::UserStatus.low_card_ids_matching([ hash_selector_1, hash_selector_2 ])
106
+
107
+ results.size.should == 2
108
+
109
+ results[hash_selector_1].should be
110
+ results[hash_selector_1].length.should == 2
111
+ results[hash_selector_1].sort.should == [ @hash1_id, @hash3_id ].sort
112
+
113
+ results[hash_selector_2].should be
114
+ results[hash_selector_2].length.should == 2
115
+ results[hash_selector_2].sort.should == [ @hash1_id, @hash2_id ].sort
116
+ end
117
+ end
118
+
119
+ it "should raise an exception if passed invalid values in the hashes" do
120
+ ensure_zero_database_calls do
121
+ lambda { ::UserStatus.low_card_rows_matching([ { :deleted => false, :foo => 1 }]) }.should raise_error(LowCardTables::Errors::LowCardColumnNotPresentError)
122
+ lambda { ::UserStatus.low_card_ids_matching([ { :deleted => false, :foo => 1 }]) }.should raise_error(LowCardTables::Errors::LowCardColumnNotPresentError)
123
+ end
124
+ end
125
+
126
+ it "should raise an exception if there are missing values in the hashes" do
127
+ ensure_zero_database_calls do
128
+ lambda { ::UserStatus.low_card_find_rows_for([ { :deleted => false, :gender => 'male', :donation_level => 5 }]) }.should raise_error(LowCardTables::Errors::LowCardColumnNotSpecifiedError)
129
+ lambda { ::UserStatus.low_card_find_ids_for([ { :deleted => false, :gender => 'male', :donation_level => 5 }]) }.should raise_error(LowCardTables::Errors::LowCardColumnNotSpecifiedError)
130
+ end
131
+ end
132
+
133
+ context "with a table with defaults" do
134
+ before :each do
135
+ migrate do
136
+ drop_table :lctables_spec_bulk_defaults rescue nil
137
+ create_table :lctables_spec_bulk_defaults do |t|
138
+ t.boolean :deleted, :null => false
139
+ t.string :gender, :null => false, :default => 'female'
140
+ t.integer :donation_level, :default => 10
141
+ end
142
+
143
+ add_index :lctables_spec_bulk_defaults, [ :deleted, :gender, :donation_level ], :unique => true, :name => 'index_lctables_spec_bulk_defaults_on_all'
144
+
145
+ drop_table :lctables_spec_users_defaults rescue nil
146
+ create_table :lctables_spec_users_defaults do |t|
147
+ t.string :name, :null => false
148
+ t.integer :user_status_id, :null => false, :limit => 2
149
+ end
150
+ end
151
+
152
+ define_model_class(:UserStatusBulkDefaults, :lctables_spec_bulk_defaults) { is_low_card_table }
153
+ define_model_class(:UserBulkDefaults, :lctables_spec_users_defaults) { has_low_card_table :status, :class => 'UserStatusBulkDefaults', :foreign_key => :user_status_id }
154
+ end
155
+
156
+ after :each do
157
+ migrate do
158
+ drop_table :lctables_spec_bulk_defaults rescue nil
159
+ end
160
+ end
161
+
162
+ it "should fill in missing values in the hashes with defaults when finding rows" do
163
+ u1 = ::UserBulkDefaults.new
164
+ u1.name = 'User 1'
165
+ u1.deleted = false
166
+ u1.save!
167
+
168
+ u1.name.should == 'User 1'
169
+ u1.deleted.should == false
170
+ u1.gender.should == 'female'
171
+ u1.donation_level.should == 10
172
+
173
+ u2 = ::UserBulkDefaults.new
174
+ u2.name = 'User 2'
175
+ u2.deleted = false
176
+ u2.gender = 'female'
177
+ u2.donation_level = 8
178
+ u2.save!
179
+
180
+ status_id_1 = u1.user_status_id
181
+ status_id_1.should be
182
+ status_row_1 = ::UserStatusBulkDefaults.find(status_id_1)
183
+
184
+ status_id_2 = u2.user_status_id
185
+ status_id_2.should be
186
+ status_row_2 = ::UserStatusBulkDefaults.find(status_id_2)
187
+
188
+ lambda { ::UserStatusBulkDefaults.low_card_find_rows_for({ :gender => 'female' }) }.should raise_error(LowCardTables::Errors::LowCardColumnNotSpecifiedError)
189
+ ::UserStatusBulkDefaults.low_card_find_rows_for({ :deleted => false }).should == status_row_1
190
+ ::UserStatusBulkDefaults.low_card_find_rows_for({ :deleted => false, :donation_level => 8 }).should == status_row_2
191
+ end
192
+
193
+ it "should fill in missing values in the hashes with defaults when creating rows" do
194
+ row = ::UserStatusBulkDefaults.low_card_find_or_create_rows_for({ :deleted => false })
195
+ row.should be
196
+ row.id.should be
197
+ row.id.should > 0
198
+
199
+ row.deleted.should == false
200
+ row.gender.should == 'female'
201
+ row.donation_level.should == 10
202
+ end
203
+ end
204
+
205
+ it "should allow for bulk retrieval of rows by IDs" do
206
+ ensure_zero_database_calls do
207
+ results = ::UserStatus.low_card_rows_for_ids([ @hash1_id, @hash3_id ])
208
+ results.size.should == 2
209
+ verify_row(results[@hash1_id], false, false, 'male', 5)
210
+ verify_row(results[@hash3_id], false, false, 'female', 9)
211
+ end
212
+ end
213
+
214
+ it "should raise if asked for an ID that's not present" do
215
+ random_id = 1_000_000 + rand(1_000_000)
216
+
217
+ e = nil
218
+ begin
219
+ ::UserStatus.low_card_rows_for_ids([ @hash1_id, @hash3_id, random_id ])
220
+ rescue => x
221
+ e = x
222
+ end
223
+
224
+ e.should be
225
+ e.class.should == LowCardTables::Errors::LowCardIdNotFoundError
226
+ e.ids.should == [ random_id ]
227
+ end
228
+
229
+ it "should allow for retrieving all rows" do
230
+ ensure_zero_database_calls do
231
+ results = ::UserStatus.low_card_all_rows
232
+ results.size.should == 3
233
+ verify_by_id(results, @hash1_id, false, false, 'male', 5)
234
+ verify_by_id(results, @hash2_id, true, false, 'male', 5)
235
+ verify_by_id(results, @hash3_id, false, false, 'female', 9)
236
+ end
237
+ end
238
+
239
+ it "should allow retrieving an individual row directly" do
240
+ ensure_zero_database_calls do
241
+ row = ::UserStatus.low_card_row_for_id(@hash2_id)
242
+ verify_row(row, true, false, 'male', 5)
243
+ end
244
+ end
245
+
246
+ def row_from_hash(h)
247
+ ::UserStatus.new(h)
248
+ end
249
+
250
+ %w{hash object}.each do |input_type|
251
+ def to_desired_input_type(hashes, type)
252
+ case type
253
+ when 'hash' then hashes
254
+ when 'object' then hashes.map { |h| ::UserStatus.new(h) }
255
+ else raise "Unknown input_type: #{input_type.inspect}"
256
+ end
257
+ end
258
+
259
+ it "should allow for bulk retrieval-and-creation of rows by #{input_type}" do
260
+ input = to_desired_input_type([ @hash1, @hash3, @hash4, @hash5 ], input_type)
261
+
262
+ result = ::UserStatus.low_card_find_or_create_rows_for(input)
263
+ result.size.should == 4
264
+
265
+ result[input[0]].id.should == @hash1_id
266
+ result[@hash2].should be_nil
267
+ result[input[1]].id.should == @hash3_id
268
+
269
+ known_ids = [ @hash1_id, @hash2_id, @hash3_id ]
270
+ known_ids.include?(result[input[2]].id).should_not be
271
+ known_ids.include?(result[input[3]].id).should_not be
272
+
273
+ verify_row(result[input[2]], false, true, 'female', 3)
274
+ verify_row(result[input[3]], false, true, 'male', 2)
275
+
276
+ ::UserStatusBackdoor.count.should == 5
277
+ verify_row(::UserStatusBackdoor.find(result[input[0]].id), false, false, 'male', 5)
278
+ verify_row(::UserStatusBackdoor.find(result[input[1]].id), false, false, 'female', 9)
279
+ verify_row(::UserStatusBackdoor.find(result[input[2]].id), false, true, 'female', 3)
280
+ verify_row(::UserStatusBackdoor.find(result[input[3]].id), false, true, 'male', 2)
281
+ end
282
+
283
+ it "should allow for bulk retrieval-and-creation of IDs by #{input_type}" do
284
+ input = to_desired_input_type([ @hash1, @hash3, @hash4, @hash5 ], input_type)
285
+
286
+ result = ::UserStatus.low_card_find_or_create_ids_for(input)
287
+ result.size.should == 4
288
+
289
+ result[input[0]].should == @hash1_id
290
+ result[@hash2].should be_nil
291
+ result[input[1]].should == @hash3_id
292
+
293
+ known_ids = [ @hash1_id, @hash2_id, @hash3_id ]
294
+ known_ids.include?(result[input[2]]).should_not be
295
+ known_ids.include?(result[input[3]]).should_not be
296
+
297
+ ::UserStatusBackdoor.count.should == 5
298
+ verify_row(::UserStatusBackdoor.find(result[input[0]]), false, false, 'male', 5)
299
+ verify_row(::UserStatusBackdoor.find(result[input[1]]), false, false, 'female', 9)
300
+ verify_row(::UserStatusBackdoor.find(result[input[2]]), false, true, 'female', 3)
301
+ verify_row(::UserStatusBackdoor.find(result[input[3]]), false, true, 'male', 2)
302
+ end
303
+
304
+ it "should allow for bulk retrieval of rows with exact matches by #{input_type}" do
305
+ ensure_zero_database_calls do
306
+ results = ::UserStatus.low_card_find_rows_for([ @hash1, @hash2, @hash3, @hash4, @hash5 ])
307
+ results.size.should == 5
308
+
309
+ verify_row(results[@hash1], false, false, 'male', 5)
310
+ verify_row(results[@hash2], true, false, 'male', 5)
311
+ verify_row(results[@hash3], false, false, 'female', 9)
312
+
313
+ results[@hash4].should be_nil
314
+ results[@hash5].should be_nil
315
+ end
316
+ end
317
+
318
+ it "should allow for bulk retrieval of IDs with exact matches by #{input_type}" do
319
+ ensure_zero_database_calls do
320
+ input = to_desired_input_type([ @hash1, @hash2, @hash3, @hash4, @hash5 ], input_type)
321
+ results = ::UserStatus.low_card_find_ids_for(input)
322
+ results.size.should == 5
323
+
324
+ results[input[0]].should == @hash1_id
325
+ results[input[1]].should == @hash2_id
326
+ results[input[2]].should == @hash3_id
327
+
328
+ results[input[3]].should be_nil
329
+ results[input[4]].should be_nil
330
+ end
331
+ end
332
+ end
333
+ end
334
+ end