copyable 0.0.1

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