copyable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +264 -0
  8. data/Rakefile +7 -0
  9. data/copyable.gemspec +28 -0
  10. data/lib/copyable.rb +32 -0
  11. data/lib/copyable/config.rb +19 -0
  12. data/lib/copyable/copy_registry.rb +50 -0
  13. data/lib/copyable/copyable_extension.rb +118 -0
  14. data/lib/copyable/declarations/after_copy.rb +15 -0
  15. data/lib/copyable/declarations/associations.rb +116 -0
  16. data/lib/copyable/declarations/columns.rb +34 -0
  17. data/lib/copyable/declarations/declaration.rb +15 -0
  18. data/lib/copyable/declarations/declarations.rb +14 -0
  19. data/lib/copyable/declarations/disable_all_callbacks_and_observers_except_validate.rb +9 -0
  20. data/lib/copyable/declarations/main.rb +32 -0
  21. data/lib/copyable/exceptions.rb +10 -0
  22. data/lib/copyable/model_hooks.rb +40 -0
  23. data/lib/copyable/option_checker.rb +23 -0
  24. data/lib/copyable/railtie.rb +9 -0
  25. data/lib/copyable/saver.rb +19 -0
  26. data/lib/copyable/single_copy_enforcer.rb +68 -0
  27. data/lib/copyable/syntax_checking/association_checker.rb +41 -0
  28. data/lib/copyable/syntax_checking/column_checker.rb +43 -0
  29. data/lib/copyable/syntax_checking/completeness_checker.rb +27 -0
  30. data/lib/copyable/syntax_checking/declaration_checker.rb +26 -0
  31. data/lib/copyable/syntax_checking/declaration_stubber.rb +18 -0
  32. data/lib/copyable/syntax_checking/syntax_checker.rb +15 -0
  33. data/lib/copyable/version.rb +3 -0
  34. data/lib/tasks/copyable.rake +55 -0
  35. data/spec/config_spec.rb +132 -0
  36. data/spec/copy_registry_spec.rb +55 -0
  37. data/spec/copyable_after_copy_spec.rb +28 -0
  38. data/spec/copyable_associations_spec.rb +366 -0
  39. data/spec/copyable_columns_spec.rb +116 -0
  40. data/spec/copyable_spec.rb +7 -0
  41. data/spec/create_copy_spec.rb +136 -0
  42. data/spec/deep_structure_copy_spec.rb +169 -0
  43. data/spec/helper/copyable_spec_helper.rb +15 -0
  44. data/spec/helper/test_models.rb +136 -0
  45. data/spec/helper/test_tables.rb +135 -0
  46. data/spec/model_hooks_spec.rb +66 -0
  47. data/spec/spec_helper.rb +29 -0
  48. data/spec/stress_test_spec.rb +261 -0
  49. data/spec/syntax_checking/association_checker_spec.rb +80 -0
  50. data/spec/syntax_checking/column_checker_spec.rb +49 -0
  51. data/spec/syntax_checking/declaration_checker_spec.rb +58 -0
  52. data/spec/syntax_checking_spec.rb +258 -0
  53. data/spec/transaction_spec.rb +78 -0
  54. metadata +200 -0
@@ -0,0 +1,80 @@
1
+ require_relative '../helper/copyable_spec_helper'
2
+
3
+ describe Copyable::AssociationChecker do
4
+
5
+ it 'should not throw an error if all associations are present' do
6
+ block = Proc.new do
7
+ associations({
8
+ copyable_pets: :copy,
9
+ })
10
+ end
11
+ association_checker = Copyable::AssociationChecker.new(CopyablePetFood)
12
+ expect { association_checker.verify!(block) }.to_not raise_error
13
+ end
14
+
15
+ it 'should throw an error if unrecognized associations are present' do
16
+ block = Proc.new do
17
+ associations({
18
+ copyable_pets: :copy,
19
+ what_is_this: :copy,
20
+ })
21
+ end
22
+ association_checker = Copyable::AssociationChecker.new(CopyablePetFood)
23
+ expect { association_checker.verify!(block) }.to raise_error(Copyable::AssociationError)
24
+ end
25
+
26
+ it 'should not require "has many through" or "has one through" relationships to be listed' do
27
+ block = Proc.new do
28
+ associations({
29
+ copyable_toys: :copy,
30
+ copyable_pet_tag: :copy,
31
+ copyable_pet_profile: :copy,
32
+ copyable_pet_foods: :copy,
33
+ copyable_pet_sitting_patronages: :copy,
34
+ })
35
+ end
36
+ association_checker = Copyable::AssociationChecker.new(CopyablePet)
37
+ expect { association_checker.verify!(block) }.to_not raise_error
38
+ end
39
+
40
+ it 'should not require "belongs to" relationships to be listed' do
41
+ block = Proc.new do
42
+ associations({
43
+ copyable_address: :copy,
44
+ })
45
+ end
46
+ association_checker = Copyable::AssociationChecker.new(CopyablePetProfile)
47
+ expect { association_checker.verify!(block) }.to_not raise_error
48
+ end
49
+
50
+ it 'should require "has one" relationships to be listed' do
51
+ block = Proc.new do
52
+ associations({
53
+ # MISSING copyable_address: :copy,
54
+ })
55
+ end
56
+ association_checker = Copyable::AssociationChecker.new(CopyablePetProfile)
57
+ expect { association_checker.verify!(block) }.to raise_error(Copyable::AssociationError)
58
+ end
59
+
60
+ it 'should require "has many" relationships to be listed' do
61
+ block = Proc.new do
62
+ associations({
63
+ # MISSING copyable_pet_sitting_patronages: :copy,
64
+ })
65
+ end
66
+ association_checker = Copyable::AssociationChecker.new(CopyablePetSitter)
67
+ expect { association_checker.verify!(block) }.to raise_error(Copyable::AssociationError)
68
+ end
69
+
70
+ it 'should require "has and belongs to many" relationships to be listed' do
71
+ block = Proc.new do
72
+ associations({
73
+ # MISSING copyable_pets: :copy,
74
+ })
75
+ end
76
+ association_checker = Copyable::AssociationChecker.new(CopyablePetFood)
77
+ expect { association_checker.verify!(block) }.to raise_error(Copyable::AssociationError)
78
+ end
79
+
80
+ end
@@ -0,0 +1,49 @@
1
+ require_relative '../helper/copyable_spec_helper'
2
+
3
+ describe Copyable::ColumnChecker do
4
+
5
+ it 'should not throw an error if all columns are present' do
6
+ block = Proc.new do
7
+ columns({
8
+ name: :copy,
9
+ kind: :copy,
10
+ birth_year: :copy,
11
+ })
12
+ end
13
+ column_checker = Copyable::ColumnChecker.new(CopyablePet)
14
+ expect { column_checker.verify!(block) }.to_not raise_error
15
+ end
16
+
17
+ it 'should throw an error if columns are empty' do
18
+ block = Proc.new do
19
+ columns({})
20
+ end
21
+ column_checker = Copyable::ColumnChecker.new(CopyablePet)
22
+ expect { column_checker.verify!(block) }.to raise_error(Copyable::ColumnError)
23
+ end
24
+
25
+ it 'should throw an error if a column is missing' do
26
+ block = Proc.new do
27
+ columns({
28
+ name: :copy,
29
+ birth_year: :copy,
30
+ })
31
+ end
32
+ column_checker = Copyable::ColumnChecker.new(CopyablePet)
33
+ expect { column_checker.verify!(block) }.to raise_error(Copyable::ColumnError)
34
+ end
35
+
36
+ it 'should throw an error if an unrecognized column is present' do
37
+ block = Proc.new do
38
+ columns({
39
+ name: :copy,
40
+ kind: :copy,
41
+ intruder: :copy,
42
+ birth_year: :copy,
43
+ })
44
+ end
45
+ column_checker = Copyable::ColumnChecker.new(CopyablePet)
46
+ expect { column_checker.verify!(block) }.to raise_error(Copyable::ColumnError)
47
+ end
48
+
49
+ end
@@ -0,0 +1,58 @@
1
+ require_relative '../helper/copyable_spec_helper'
2
+
3
+ describe Copyable::DeclarationChecker do
4
+
5
+ it 'should throw an error if there are no declarations' do
6
+ block = Proc.new {}
7
+ expect { subject.verify!(block) }.to raise_error(Copyable::DeclarationError)
8
+ end
9
+
10
+ it 'should not throw an error if all required declarations are present' do
11
+ block = Proc.new do
12
+ disable_all_callbacks_and_observers_except_validate
13
+ columns
14
+ associations
15
+ end
16
+ expect { subject.verify!(block) }.to_not raise_error
17
+ end
18
+
19
+ it 'should not throw an error if all declarations are present' do
20
+ block = Proc.new do
21
+ disable_all_callbacks_and_observers_except_validate
22
+ columns
23
+ associations
24
+ after_copy
25
+ end
26
+ expect { subject.verify!(block) }.to_not raise_error
27
+ end
28
+
29
+ it 'should throw an error if an unknown declaration is present' do
30
+ block = Proc.new do
31
+ disable_all_callbacks_and_observers_except_validate
32
+ columns
33
+ unknown
34
+ associations
35
+ after_copy
36
+ end
37
+ expect { subject.verify!(block) }.to raise_error(Copyable::DeclarationError)
38
+ end
39
+
40
+ it 'should throw an error if a required declaration is missing' do
41
+ block = Proc.new do
42
+ columns
43
+ associations
44
+ end
45
+ expect { subject.verify!(block) }.to raise_error(Copyable::DeclarationError)
46
+ block = Proc.new do
47
+ disable_all_callbacks_and_observers_except_validate
48
+ associations
49
+ end
50
+ expect { subject.verify!(block) }.to raise_error(Copyable::DeclarationError)
51
+ block = Proc.new do
52
+ disable_all_callbacks_and_observers_except_validate
53
+ columns
54
+ end
55
+ expect { subject.verify!(block) }.to raise_error(Copyable::DeclarationError)
56
+ end
57
+
58
+ end
@@ -0,0 +1,258 @@
1
+ require_relative 'helper/copyable_spec_helper'
2
+
3
+ # Even though we have good specs that test the syntax checker classes
4
+ # individually, we still need the specs in this file to make sure the syntax
5
+ # checking is hooked up properly to the copyable declaration.
6
+
7
+ describe 'syntax checking' do
8
+
9
+
10
+
11
+ #*****************************************************************************
12
+ # COPYABLE
13
+
14
+ context 'copyable' do
15
+ it 'should throw an error when passed no block' do
16
+ model_definition = lambda do
17
+ undefine_copyable_in CopyableCoin
18
+ class CopyableCoin < ActiveRecord::Base
19
+ copyable
20
+ end
21
+ end
22
+ expect(model_definition).to raise_error(Copyable::CopyableError)
23
+ end
24
+
25
+ it 'should throw an error when passed an empty block' do
26
+ model_definition = lambda do
27
+ undefine_copyable_in CopyableCoin
28
+ class CopyableCoin < ActiveRecord::Base
29
+ copyable do
30
+ end
31
+ end
32
+ end
33
+ expect(model_definition).to raise_error(Copyable::DeclarationError)
34
+ end
35
+
36
+ it 'should not throw an error when all required declarations are present' do
37
+ model_definition = lambda do
38
+ undefine_copyable_in CopyableCoin
39
+ class CopyableCoin < ActiveRecord::Base
40
+ copyable do
41
+ disable_all_callbacks_and_observers_except_validate
42
+ columns({
43
+ kind: :copy,
44
+ year: :copy,
45
+ })
46
+ associations({
47
+ })
48
+ end
49
+ end
50
+ end
51
+ expect(model_definition).to_not raise_error
52
+ end
53
+
54
+ it 'should not throw an error when all declarations are present' do
55
+ model_definition = lambda do
56
+ undefine_copyable_in CopyableCoin
57
+ class CopyableCoin < ActiveRecord::Base
58
+ copyable do
59
+ disable_all_callbacks_and_observers_except_validate
60
+ columns({
61
+ kind: :copy,
62
+ year: :copy,
63
+ })
64
+ associations({
65
+ })
66
+ after_copy do |original_model, new_model|
67
+ end
68
+ end
69
+ end
70
+ end
71
+ expect(model_definition).to_not raise_error
72
+ end
73
+
74
+ it 'should throw an error when an unknown declaration is present' do
75
+ model_definition = lambda do
76
+ undefine_copyable_in CopyableCoin
77
+ class CopyableCoin < ActiveRecord::Base
78
+ copyable do
79
+ disable_all_callbacks_and_observers_except_validate
80
+ columns({
81
+ kind: :copy,
82
+ year: :copy,
83
+ })
84
+ associations({
85
+ })
86
+ what_the_heck_is_this_doing_here
87
+ end
88
+ end
89
+ end
90
+ expect(model_definition).to raise_error(Copyable::DeclarationError)
91
+ end
92
+
93
+ it 'should throw an error if a required declaration is missing' do
94
+ model_definition = lambda do
95
+ undefine_copyable_in CopyableCoin
96
+ class CopyableCoin < ActiveRecord::Base
97
+ copyable do
98
+ # MISSING disable_all_callbacks_and_observers_except_validate
99
+ columns({
100
+ kind: :copy,
101
+ year: :copy,
102
+ })
103
+ associations({
104
+ })
105
+ end
106
+ end
107
+ end
108
+ expect(model_definition).to raise_error(Copyable::DeclarationError)
109
+ model_definition = lambda do
110
+ undefine_copyable_in CopyableCoin
111
+ class CopyableCoin < ActiveRecord::Base
112
+ copyable do
113
+ disable_all_callbacks_and_observers_except_validate
114
+ # MISSING columns({
115
+ # kind: :copy,
116
+ # year: :copy,
117
+ # })
118
+ associations({
119
+ })
120
+ end
121
+ end
122
+ end
123
+ expect(model_definition).to raise_error(Copyable::DeclarationError)
124
+ model_definition = lambda do
125
+ undefine_copyable_in CopyableCoin
126
+ class CopyableCoin < ActiveRecord::Base
127
+ copyable do
128
+ disable_all_callbacks_and_observers_except_validate
129
+ columns({
130
+ kind: :copy,
131
+ year: :copy,
132
+ })
133
+ # MISSING associations({
134
+ # })
135
+ end
136
+ end
137
+ end
138
+ expect(model_definition).to raise_error(Copyable::DeclarationError)
139
+ end
140
+ end
141
+
142
+
143
+
144
+ #*****************************************************************************
145
+ # COLUMNS
146
+
147
+ context 'copyable:columns' do
148
+ context 'when missing columns' do
149
+ before(:each) do
150
+ @model_definition = lambda do
151
+ undefine_copyable_in CopyableCoin
152
+ class CopyableCoin < ActiveRecord::Base
153
+ copyable do
154
+ disable_all_callbacks_and_observers_except_validate
155
+ columns({
156
+ kind: :copy,
157
+ })
158
+ associations({
159
+ })
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ it 'should throw an error' do
166
+ expect(@model_definition).to raise_error(Copyable::ColumnError)
167
+ end
168
+ end
169
+
170
+ context 'with unknown columns' do
171
+ before(:each) do
172
+ @model_definition = lambda do
173
+ undefine_copyable_in CopyableCoin
174
+ class CopyableCoin < ActiveRecord::Base
175
+ copyable do
176
+ disable_all_callbacks_and_observers_except_validate
177
+ columns({
178
+ what_is_this_column_doing_here: :copy,
179
+ kind: :copy,
180
+ year: :copy,
181
+ })
182
+ associations({
183
+ })
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ it 'should throw an error' do
190
+ expect(@model_definition).to raise_error(Copyable::ColumnError)
191
+ end
192
+ end
193
+ end
194
+
195
+
196
+
197
+ #*****************************************************************************
198
+ # ASSOCIATIONS
199
+
200
+ context 'copyable:associations' do
201
+ context 'when missing associations' do
202
+ before(:each) do
203
+ @model_definition = lambda do
204
+ class CopyablePet < ActiveRecord::Base
205
+ copyable do
206
+ disable_all_callbacks_and_observers_except_validate
207
+ columns({
208
+ name: :copy,
209
+ kind: :copy,
210
+ birth_year: :copy,
211
+ })
212
+ associations({
213
+ # MISSING copyable_toys: :copy,
214
+ copyable_pet_tag: :copy,
215
+ copyable_pet_profile: :copy,
216
+ copyable_pet_foods: :copy,
217
+ copyable_pet_sitting_patronages: :copy,
218
+ })
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ it 'should throw an error' do
225
+ expect(@model_definition).to raise_error(Copyable::AssociationError)
226
+ end
227
+ end
228
+
229
+ context 'with unknown associations' do
230
+ before(:each) do
231
+ @model_definition = lambda do
232
+ class CopyablePet < ActiveRecord::Base
233
+ copyable do
234
+ disable_all_callbacks_and_observers_except_validate
235
+ columns({
236
+ name: :copy,
237
+ kind: :copy,
238
+ birth_year: :copy,
239
+ })
240
+ associations({
241
+ this_assoc_should_not_be_here: :copy,
242
+ copyable_toys: :copy,
243
+ copyable_pet_tag: :copy,
244
+ copyable_pet_profile: :copy,
245
+ copyable_pet_foods: :copy,
246
+ copyable_pet_sitting_patronages: :copy,
247
+ })
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ it 'should throw an error' do
254
+ expect(@model_definition).to raise_error(Copyable::AssociationError)
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,78 @@
1
+ require_relative 'helper/copyable_spec_helper'
2
+
3
+ describe 'failing' do
4
+ before(:each) do
5
+
6
+ #***************************************************************************
7
+ # Define all of the copyable declarations.
8
+
9
+ undefine_copyable_in CopyableVehicle
10
+ class CopyableVehicle < ActiveRecord::Base
11
+ copyable do
12
+ disable_all_callbacks_and_observers_except_validate
13
+ columns({
14
+ name: :copy,
15
+ copyable_owner_id: :copy,
16
+ })
17
+ associations({
18
+ copyable_amenities: :copy,
19
+ })
20
+ end
21
+ end
22
+
23
+ undefine_copyable_in CopyableAmenity
24
+ class CopyableAmenity < ActiveRecord::Base
25
+ copyable do
26
+ disable_all_callbacks_and_observers_except_validate
27
+ columns({
28
+ name: :copy,
29
+ copyable_vehicle_id: :copy,
30
+ copyable_owner_id: :copy,
31
+ })
32
+ associations({
33
+ copyable_warranty: :copy,
34
+ })
35
+ end
36
+ end
37
+
38
+ undefine_copyable_in CopyableWarranty
39
+ class CopyableWarranty < ActiveRecord::Base
40
+ copyable do
41
+ disable_all_callbacks_and_observers_except_validate
42
+ columns({
43
+ name: lambda { |orig| raise Copyable::CopyableError.new("intentional error") },
44
+ copyable_amenity_id: :copy,
45
+ })
46
+ associations({
47
+ })
48
+ end
49
+ end
50
+
51
+ #***************************************************************************
52
+ # Create the models.
53
+
54
+ @vehicle1 = CopyableVehicle.create!(name: 'Corvette')
55
+ @amenity1 = CopyableAmenity.create!(name: 'moon roof', copyable_vehicle: @vehicle1)
56
+ @warranty1 = CopyableWarranty.create!(name: 'moon roof warranty', copyable_amenity: @amenity1)
57
+ @amenity2 = CopyableAmenity.create!(name: 'twitter', copyable_vehicle: @vehicle1)
58
+ @warranty2 = CopyableWarranty.create!(name: 'twitter warranty', copyable_amenity: @amenity2)
59
+ @amenity3 = CopyableAmenity.create!(name: 'jazz', copyable_vehicle: @vehicle1)
60
+ @warranty3 = CopyableWarranty.create!(name: 'jazz warranty', copyable_amenity: @amenity3)
61
+ end
62
+
63
+ it 'should be transactional' do
64
+ expect(CopyableVehicle.count).to eq(1)
65
+ expect(CopyableAmenity.count).to eq(3)
66
+ expect(CopyableWarranty.count).to eq(3)
67
+ begin
68
+ @vehicle2 = @vehicle1.create_copy!
69
+ rescue Copyable::CopyableError
70
+ # swallow the intentional error
71
+ end
72
+ # transaction rollback should prevent any new records from actually
73
+ # being generated
74
+ expect(CopyableVehicle.count).to eq(1)
75
+ expect(CopyableAmenity.count).to eq(3)
76
+ expect(CopyableWarranty.count).to eq(3)
77
+ end
78
+ end