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,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