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