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,135 @@
1
+ module Copyable
2
+ class TestTables
3
+
4
+ def self.create!
5
+ return if @already_created_tables
6
+ ActiveRecord::Migration.class_eval do
7
+ suppress_messages do
8
+
9
+ # All tables are prefaced with "copyable_" to avoid name collisions
10
+ # with other tables in the test database.
11
+
12
+ create_table :copyable_albums, force: true do |t|
13
+ t.string :name, null: false
14
+ t.timestamps
15
+ end
16
+
17
+ create_table :copyable_amenities, force: true do |t|
18
+ t.string :name
19
+ t.integer :copyable_vehicle_id
20
+ t.integer :copyable_owner_id
21
+ t.timestamps
22
+ end
23
+
24
+ create_table :copyable_addresses, force: true do |t|
25
+ t.string :address1, null: false
26
+ t.string :address2
27
+ t.string :city, null: false
28
+ t.string :state, null: false
29
+ t.integer :copyable_pet_profile_id, null: false
30
+ t.timestamps
31
+ end
32
+
33
+ create_table :copyable_cars, force: true do |t|
34
+ t.string :make, null: false
35
+ t.string :model, null: false
36
+ t.integer :year, null: false
37
+ t.timestamps
38
+ end
39
+
40
+ create_table :copyable_coins, force: true do |t|
41
+ t.string :kind
42
+ t.integer :year
43
+ t.timestamps
44
+ end
45
+
46
+ create_table :copyable_owners, force: true do |t|
47
+ t.string :name, null: false
48
+ t.timestamps
49
+ end
50
+
51
+ create_table :copyable_pet_foods, force: true do |t|
52
+ t.string :name, null: false
53
+ t.timestamps
54
+ end
55
+
56
+ create_table :copyable_pet_foods_pets, force: true, id: false do |t|
57
+ t.integer :copyable_pet_id, null: false
58
+ t.integer :copyable_pet_food_id, null: false
59
+ end
60
+
61
+ create_table :copyable_pet_profiles, force: true do |t|
62
+ t.string :description, null: false
63
+ t.string :nickname
64
+ t.integer :copyable_pet_id, null: false
65
+ t.timestamps
66
+ end
67
+
68
+ create_table :copyable_pet_sitters, force: true do |t|
69
+ t.string :name, null: false
70
+ t.timestamps
71
+ end
72
+
73
+ create_table :copyable_pet_sitting_patronages, force: true do |t|
74
+ t.integer :copyable_pet_id, null: false
75
+ t.integer :copyable_pet_sitter_id, null: false
76
+ t.timestamps
77
+ end
78
+
79
+ create_table :copyable_pet_tags, force: true do |t|
80
+ t.string :registered_name, null: false
81
+ t.integer :copyable_pet_id, null: false
82
+ t.timestamps
83
+ end
84
+
85
+ create_table :copyable_pets, force: true do |t|
86
+ t.string :name, null: false
87
+ t.string :kind, null: false
88
+ t.integer :birth_year
89
+ t.timestamps
90
+ end
91
+
92
+ create_table :copyable_pictures, force: true do |t|
93
+ t.string :name
94
+ t.integer :imageable_id
95
+ t.string :imageable_type
96
+ t.integer :picture_album_id
97
+ t.timestamps
98
+ end
99
+
100
+ create_table :copyable_products, force: true do |t|
101
+ t.string :name
102
+ t.timestamps
103
+ end
104
+
105
+ create_table :copyable_toys, force: true do |t|
106
+ t.string :name
107
+ t.string :kind
108
+ t.integer :copyable_pet_id
109
+ t.timestamps
110
+ end
111
+
112
+ create_table :copyable_trees, force: true do |t|
113
+ t.string :kind
114
+ t.timestamps
115
+ end
116
+
117
+ create_table :copyable_vehicles, force: true do |t|
118
+ t.string :name
119
+ t.integer :copyable_owner_id
120
+ t.timestamps
121
+ end
122
+
123
+ create_table :copyable_warranties, force: true do |t|
124
+ t.string :name
125
+ t.integer :copyable_amenity_id
126
+ t.timestamps
127
+ end
128
+
129
+ end
130
+ end
131
+ @already_created_tables = true
132
+ end
133
+
134
+ end
135
+ end
@@ -0,0 +1,66 @@
1
+ require_relative 'helper/copyable_spec_helper'
2
+
3
+ describe Copyable::ModelHooks do
4
+ context 'callbacks' do
5
+
6
+ # Note: the relevant callbacks are defined in CopyableTree in helper/test_models.rb
7
+
8
+ describe '.disable!' do
9
+ before(:each) do
10
+ puts "Dennis before!"
11
+ Copyable::ModelHooks.disable!(CopyableTree)
12
+ end
13
+ after(:each) do
14
+ puts "Dennis after"
15
+ Copyable::ModelHooks.reenable!(CopyableTree)
16
+ end
17
+ it 'should prevent callbacks from executing' do
18
+ puts "Dennis 1"
19
+ expect {
20
+ tree = CopyableTree.create!(kind: 'magnolia')
21
+ }.to_not raise_error
22
+ puts "Dennis 2"
23
+ end
24
+ it 'should not prevent model actions from executing' do
25
+ puts "Dennis 4"
26
+ expect(CopyableTree.count).to eq(0)
27
+ tree = CopyableTree.create!(kind: 'magnolia')
28
+ expect(CopyableTree.count).to eq(1)
29
+ end
30
+ end
31
+
32
+ describe '.reenable!' do
33
+ it 'should allow callbacks to execute again' do
34
+ Copyable::ModelHooks.disable!(CopyableTree)
35
+ Copyable::ModelHooks.reenable!(CopyableTree)
36
+ expect {
37
+ tree = CopyableTree.create!(kind: 'magnolia')
38
+ }.to raise_error(RuntimeError, "callback2 called")
39
+ end
40
+ end
41
+ end
42
+
43
+ context 'observers' do
44
+
45
+ # Note: the relevant model and observer class is defined in helper/test_models.rb
46
+
47
+ describe '.disable!' do
48
+ before(:each) do
49
+ Copyable::ModelHooks.disable!(CopyableCar)
50
+ end
51
+ after(:each) do
52
+ Copyable::ModelHooks.reenable!(CopyableCar)
53
+ end
54
+ it 'should prevent observers from executing' do
55
+ expect {
56
+ car = CopyableCar.create!(make: 'Ferrari', model: 'California', year: 2009)
57
+ }.to_not raise_error
58
+ end
59
+ it 'should not prevent model actions from executing' do
60
+ expect(CopyableCar.count).to eq(0)
61
+ car = CopyableCar.create!(make: 'Ferrari', model: 'California', year: 2009)
62
+ expect(CopyableCar.count).to eq(1)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,29 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'active_support/all'
3
+ require 'active_support/testing/time_helpers'
4
+ require 'database_cleaner'
5
+
6
+ Bundler.require(:default, :test)
7
+
8
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
9
+
10
+ RSpec.configure do |config|
11
+
12
+ config.before(:suite) do
13
+ DatabaseCleaner.clean_with(:truncation)
14
+ end
15
+
16
+ config.before(:each) do
17
+ DatabaseCleaner.strategy = :transaction
18
+ end
19
+
20
+ config.before(:each) do
21
+ DatabaseCleaner.start
22
+ end
23
+
24
+ config.append_after(:each) do
25
+ DatabaseCleaner.clean
26
+ end
27
+
28
+ end
29
+
@@ -0,0 +1,261 @@
1
+ require_relative 'helper/copyable_spec_helper'
2
+
3
+ describe 'a model hierarchy with lots of variations in copyable behavior' do
4
+ before(:each) do
5
+
6
+ #***************************************************************************
7
+ # Define all of the copyable declarations.
8
+
9
+ undefine_copyable_in CopyablePet
10
+ class CopyablePet < ActiveRecord::Base
11
+ copyable do
12
+ disable_all_callbacks_and_observers_except_validate
13
+ columns({
14
+ name: :copy,
15
+ kind: :copy,
16
+ birth_year: lambda { |orig| orig.birth_year + 1 },
17
+ })
18
+ associations({
19
+ copyable_toys: :do_not_copy,
20
+ copyable_pet_tag: :copy,
21
+ copyable_pet_profile: :copy,
22
+ copyable_pet_foods: :copy_only_habtm_join_records,
23
+ copyable_pet_sitting_patronages: :copy,
24
+ })
25
+ end
26
+ end
27
+
28
+ undefine_copyable_in CopyableToy
29
+ class CopyableToy < ActiveRecord::Base
30
+ copyable do
31
+ disable_all_callbacks_and_observers_except_validate
32
+ columns({
33
+ name: :copy,
34
+ kind: :copy,
35
+ copyable_pet_id: :copy,
36
+ })
37
+ associations({
38
+ })
39
+ end
40
+ end
41
+
42
+ undefine_copyable_in CopyablePetTag
43
+ class CopyablePetTag < ActiveRecord::Base
44
+ copyable do
45
+ disable_all_callbacks_and_observers_except_validate
46
+ columns({
47
+ registered_name: :copy,
48
+ copyable_pet_id: :copy,
49
+ })
50
+ associations({
51
+ })
52
+ end
53
+ end
54
+
55
+ undefine_copyable_in CopyablePetFood
56
+ class CopyablePetFood < ActiveRecord::Base
57
+ copyable do
58
+ disable_all_callbacks_and_observers_except_validate
59
+ columns({
60
+ name: :copy,
61
+ })
62
+ associations({
63
+ copyable_pets: :copy_only_habtm_join_records,
64
+ })
65
+ end
66
+ end
67
+
68
+ undefine_copyable_in CopyablePetSitter
69
+ class CopyablePetSitter < ActiveRecord::Base
70
+ copyable do
71
+ disable_all_callbacks_and_observers_except_validate
72
+ columns({
73
+ name: :copy,
74
+ })
75
+ associations({
76
+ copyable_pet_sitting_patronages: :copy,
77
+ })
78
+ after_copy do |original_model, new_model|
79
+ new_model.name = new_model.name + '*'
80
+ new_model.save!
81
+ end
82
+ end
83
+ end
84
+
85
+ undefine_copyable_in CopyablePetSittingPatronage
86
+ class CopyablePetSittingPatronage < ActiveRecord::Base
87
+ copyable do
88
+ disable_all_callbacks_and_observers_except_validate
89
+ columns({
90
+ copyable_pet_id: :copy,
91
+ copyable_pet_sitter_id: :copy,
92
+ })
93
+ associations({
94
+ })
95
+ end
96
+ end
97
+
98
+ undefine_copyable_in CopyablePetProfile
99
+ class CopyablePetProfile < ActiveRecord::Base
100
+ copyable do
101
+ disable_all_callbacks_and_observers_except_validate
102
+ columns({
103
+ description: lambda { |orig| 'overwrite' },
104
+ nickname: :do_not_copy,
105
+ copyable_pet_id: :copy,
106
+ })
107
+ associations({
108
+ copyable_address: :copy,
109
+ })
110
+ end
111
+ end
112
+
113
+ undefine_copyable_in CopyableAddress
114
+ class CopyableAddress < ActiveRecord::Base
115
+ copyable do
116
+ disable_all_callbacks_and_observers_except_validate
117
+ columns({
118
+ address1: :copy,
119
+ address2: :copy,
120
+ city: :copy,
121
+ state: :copy,
122
+ copyable_pet_profile_id: :copy,
123
+ })
124
+ associations({
125
+ })
126
+ end
127
+ end
128
+
129
+ #***************************************************************************
130
+ # Create the models.
131
+
132
+ # foods
133
+ @chow_mix = CopyablePetFood.create!(name: 'chow mix')
134
+ @mushy_food = CopyablePetFood.create!(name: 'mushy food')
135
+ @meal_scraps = CopyablePetFood.create!(name: 'meal scraps')
136
+
137
+ # sitters
138
+ @marty = CopyablePetSitter.create!(name: 'Martin Luther King, Jr.')
139
+ @bob = CopyablePetSitter.create!(name: 'Robert Frost')
140
+ @bubba = CopyablePetSitter.create!(name: 'William Jefferson Clinton')
141
+
142
+ # Rover
143
+ @rover = CopyablePet.create!(
144
+ name: 'Rover',
145
+ kind: 'canine',
146
+ birth_year: 2009)
147
+ @plastic_bone = CopyableToy.create!(
148
+ name: 'favorite bone',
149
+ kind: 'plastic squeaky toy',
150
+ copyable_pet: @rover)
151
+ @rovers_tag = CopyablePetTag.create!(
152
+ registered_name: 'Rover McSmith #3872D',
153
+ copyable_pet: @rover)
154
+ @rovers_profile = CopyablePetProfile.create!(
155
+ description: 'part Dachshund, part Great Dane, totally badass',
156
+ nickname: 'Wubby Wubby Roverkins',
157
+ copyable_pet: @rover)
158
+ @rovers_address = CopyableAddress.create!(
159
+ address1: '5 Maple St',
160
+ city: 'Mapleton',
161
+ state: 'MA',
162
+ copyable_pet_profile: @rovers_profile)
163
+ @rover_bubba_patronage = CopyablePetSittingPatronage.create!(
164
+ copyable_pet: @rover,
165
+ copyable_pet_sitter: @bubba)
166
+ @rover.copyable_pet_foods << @chow_mix
167
+ @rover.copyable_pet_foods << @meal_scraps
168
+
169
+ # Toonces
170
+ @toonces = CopyablePet.create!(
171
+ name: 'toonces',
172
+ kind: 'feline',
173
+ birth_year: 2005)
174
+ @yarn = CopyableToy.create!(
175
+ name: 'red yarn',
176
+ kind: 'just yarn',
177
+ copyable_pet: @toonces)
178
+ @toonces_tag = CopyablePetTag.create!(
179
+ registered_name: 'Toonces McSmith #3879F',
180
+ copyable_pet: @toonces)
181
+ @toonces_profile = CopyablePetProfile.create!(
182
+ description: 'really bad driver',
183
+ nickname: '*$#!',
184
+ copyable_pet: @toonces)
185
+ @tooncess_address = CopyableAddress.create!(
186
+ address1: '5 Maple St #2',
187
+ city: 'Mapleton',
188
+ state: 'MA',
189
+ copyable_pet_profile: @toonces_profile)
190
+ @toonces_bubba_patronage = CopyablePetSittingPatronage.create!(
191
+ copyable_pet: @toonces,
192
+ copyable_pet_sitter: @bubba)
193
+ @toonces_marty_patronage = CopyablePetSittingPatronage.create!(
194
+ copyable_pet: @toonces,
195
+ copyable_pet_sitter: @marty)
196
+ @toonces.copyable_pet_foods << @mushy_food
197
+ end
198
+
199
+ it 'should copy without error' do
200
+ expect {
201
+ @rover.create_copy!
202
+ @toonces.create_copy!
203
+ }.to_not raise_error
204
+ end
205
+
206
+ it 'should not copy toys' do
207
+ expect(CopyableToy.count).to eq(2)
208
+ @rover.create_copy!
209
+ @toonces.create_copy!
210
+ expect(CopyableToy.count).to eq(2)
211
+ end
212
+
213
+ it 'should not copy pet profile nickname' do
214
+ copied_rover = @rover.create_copy!
215
+ expect(copied_rover.copyable_pet_profile.nickname).to be_nil
216
+ end
217
+
218
+ it 'should not copy pet sitters when copying pets' do
219
+ expect(CopyablePetSitter.count).to eq(3)
220
+ @rover.create_copy!
221
+ @toonces.create_copy!
222
+ expect(CopyablePetSitter.count).to eq(3)
223
+ end
224
+
225
+ it 'should add a * to the pet sitter name after copying' do
226
+ expect(@marty.create_copy!.name).to eq('Martin Luther King, Jr.*')
227
+ expect(@bob.create_copy!.name).to eq('Robert Frost*')
228
+ end
229
+
230
+ it 'should allow columns to be overriden' do
231
+ copied_rover = @rover.create_copy!(
232
+ override: {
233
+ name: 'overridden name',
234
+ kind: 'overridden kind',
235
+ birth_year: 1900 })
236
+ expect(copied_rover.name).to eq('overridden name')
237
+ expect(copied_rover.kind).to eq('overridden kind')
238
+ expect(copied_rover.birth_year).to eq(1900)
239
+ end
240
+
241
+ it 'should allow multiple copying' do
242
+ expect {
243
+ 3.times { @rover.create_copy! }
244
+ }.to_not raise_error
245
+ end
246
+
247
+ it 'should have exactly the expected records after copying' do
248
+ @rover.create_copy!
249
+ expect(CopyablePet.count).to eq(3)
250
+ expect(CopyablePet.where(name: 'Rover').count).to eq(2)
251
+ expect(CopyableToy.count).to eq(2)
252
+ expect(CopyablePetTag.count).to eq(3)
253
+ expect(CopyablePetTag.where(registered_name: 'Rover McSmith #3872D').count).to eq(2)
254
+ expect(CopyablePetFood.count).to eq(3)
255
+ expect(CopyablePetSitter.count).to eq(3)
256
+ expect(CopyablePetSittingPatronage.count).to eq(4)
257
+ expect(CopyablePetProfile.count).to eq(3)
258
+ expect(CopyableAddress.count).to eq(3)
259
+ expect(CopyableAddress.where(address1: '5 Maple St').count).to eq(2)
260
+ end
261
+ end