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,116 @@
1
+ require_relative 'helper/copyable_spec_helper'
2
+
3
+ describe 'copyable:columns' do
4
+ context 'when asked to do a simple column copy' do
5
+ include ActiveSupport::Testing::TimeHelpers
6
+
7
+ before(:each) do
8
+ undefine_copyable_in CopyableCoin
9
+ class CopyableCoin < ActiveRecord::Base
10
+ copyable do
11
+ disable_all_callbacks_and_observers_except_validate
12
+ columns({
13
+ kind: :copy,
14
+ year: :copy,
15
+ })
16
+ associations({
17
+ })
18
+ end
19
+ end
20
+ end
21
+
22
+ it 'should create a new record with matching columns' do
23
+ expect(CopyableCoin.count).to eq(0)
24
+ coin = CopyableCoin.create!(kind: 'cent', year: 1943)
25
+ coin.create_copy!
26
+ expect(CopyableCoin.where(kind: 'cent').count).to eq(2)
27
+ expect(CopyableCoin.where(year: 1943).count).to eq(2)
28
+ end
29
+
30
+ it 'should update timestamp columns' do
31
+ expect(CopyableCoin.count).to eq(0)
32
+ coin = CopyableCoin.create!(kind: 'cent', year: 1943)
33
+ travel_to 2.seconds.from_now do
34
+ coin_copy = coin.create_copy!
35
+ expect(coin_copy.updated_at).to be_present
36
+ expect(coin_copy.created_at).to be_present
37
+ expect(coin_copy.updated_at).not_to eq(coin.updated_at)
38
+ expect(coin_copy.created_at).not_to eq(coin.created_at)
39
+ end
40
+ end
41
+ end
42
+
43
+ context 'when asked to do a column copy with customization' do
44
+ before(:each) do
45
+ undefine_copyable_in CopyableCoin
46
+ class CopyableCoin < ActiveRecord::Base
47
+ copyable do
48
+ disable_all_callbacks_and_observers_except_validate
49
+ columns({
50
+ kind: lambda { |orig| "Copy of #{orig.kind}" },
51
+ year: :copy,
52
+ })
53
+ associations({
54
+ })
55
+ end
56
+ end
57
+ end
58
+ describe '#create_copy!' do
59
+ it 'should create a new record with the customized column' do
60
+ expect(CopyableCoin.count).to eq(0)
61
+ coin = CopyableCoin.create!(kind: 'quarter', year: 1964)
62
+ coin.create_copy!
63
+ expect(CopyableCoin.where(kind: 'quarter').count).to eq(1)
64
+ expect(CopyableCoin.where(kind: 'Copy of quarter').count).to eq(1)
65
+ expect(CopyableCoin.where(year: 1964).count).to eq(2)
66
+ end
67
+ end
68
+ end
69
+
70
+ context 'when asked to do a copy that produces invalid data' do
71
+ before(:each) do
72
+ undefine_copyable_in CopyableCoin
73
+ class CopyableCoin < ActiveRecord::Base
74
+ copyable do
75
+ disable_all_callbacks_and_observers_except_validate
76
+ columns({
77
+ kind: :copy,
78
+ year: lambda { |orig| -444 },
79
+ })
80
+ associations({
81
+ })
82
+ end
83
+ end
84
+ end
85
+ it 'should raise an error if data is not valid' do
86
+ expect(CopyableCoin.count).to eq(0)
87
+ coin = CopyableCoin.create!(kind: 'cent', year: 1982)
88
+ expect {
89
+ coin.create_copy!
90
+ }.to raise_error(ActiveRecord::RecordInvalid)
91
+ end
92
+ end
93
+
94
+ context 'with :do_not_copy advice' do
95
+ before(:each) do
96
+ undefine_copyable_in CopyableCoin
97
+ class CopyableCoin < ActiveRecord::Base
98
+ copyable do
99
+ disable_all_callbacks_and_observers_except_validate
100
+ columns({
101
+ kind: :do_not_copy,
102
+ year: :copy,
103
+ })
104
+ associations({
105
+ })
106
+ end
107
+ end
108
+ end
109
+ it 'should result in a nil value for that column' do
110
+ expect(CopyableCoin.count).to eq(0)
111
+ coin = CopyableCoin.create!(kind: 'cent', year: 1982)
112
+ copied_coin = coin.create_copy!
113
+ expect(copied_coin.kind).to be_nil
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Copyable do
4
+ it 'has a version number' do
5
+ expect(Copyable::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,136 @@
1
+ require_relative 'helper/copyable_spec_helper'
2
+
3
+ describe 'create_copy!' do
4
+ before(:each) do
5
+ undefine_copyable_in CopyableCoin
6
+ class CopyableCoin < ActiveRecord::Base
7
+ copyable do
8
+ disable_all_callbacks_and_observers_except_validate
9
+ columns({
10
+ kind: :copy,
11
+ year: :copy,
12
+ })
13
+ associations({
14
+ })
15
+ end
16
+ end
17
+ end
18
+
19
+ it 'should exist' do
20
+ coin = CopyableCoin.create!(kind: 'cent', year: 1909)
21
+ expect(coin).to respond_to(:create_copy!)
22
+ end
23
+
24
+ it 'should create a new record' do
25
+ expect(CopyableCoin.count).to eq(0)
26
+ coin = CopyableCoin.create!(kind: 'cent', year: 1909)
27
+ coin.create_copy!
28
+ expect(CopyableCoin.count).to eq(2)
29
+ end
30
+
31
+ it 'should return the new record' do
32
+ expect(CopyableCoin.count).to eq(0)
33
+ coin = CopyableCoin.create!(kind: 'cent', year: 1909)
34
+ coin2 = coin.create_copy!
35
+ expect(coin2).to be_a CopyableCoin
36
+ end
37
+
38
+ it 'should return the new record having an id' do
39
+ expect(CopyableCoin.count).to eq(0)
40
+ coin = CopyableCoin.create!(kind: 'cent', year: 1909)
41
+ coin2 = coin.create_copy!
42
+ expect(coin2.id).not_to be_nil
43
+ end
44
+
45
+ it 'should allow columns to be overridden' do
46
+ expect(CopyableCoin.count).to eq(0)
47
+ coin = CopyableCoin.create!(kind: 'cent', year: 1909)
48
+ coin2 = coin.create_copy!(override: { year: 1959 })
49
+ expect(coin2.kind).to eq('cent')
50
+ expect(coin2.year).to eq(1959)
51
+ end
52
+
53
+ it 'should allow columns to be overridden using strings as column names' do
54
+ expect(CopyableCoin.count).to eq(0)
55
+ coin = CopyableCoin.create!('kind' => 'cent', 'year' => 1909)
56
+ coin2 = coin.create_copy!(override: { 'year' => 1959 })
57
+ expect(coin2.kind).to eq('cent')
58
+ expect(coin2.year).to eq(1959)
59
+ end
60
+
61
+ it 'should allow validations to be skipped' do
62
+ coin = CopyableCoin.create!('kind' => 'cent', 'year' => 1909)
63
+ expect {
64
+ coin.create_copy!(override: { 'year' => 2 })
65
+ }.to raise_error(ActiveRecord::RecordInvalid)
66
+ expect {
67
+ coin.create_copy!(override: { 'year' => 2 }, skip_validations: true)
68
+ }.to_not raise_error
69
+ end
70
+
71
+ it 'should skip validations even in associated objects when skip_validations is true' do
72
+ # set up classes
73
+ undefine_copyable_in CopyablePet
74
+ class CopyablePet < ActiveRecord::Base
75
+ copyable do
76
+ disable_all_callbacks_and_observers_except_validate
77
+ columns({
78
+ name: :copy,
79
+ kind: :copy,
80
+ birth_year: :copy,
81
+ })
82
+ associations({
83
+ copyable_toys: :copy,
84
+ copyable_pet_tag: :do_not_copy,
85
+ copyable_pet_profile: :do_not_copy,
86
+ copyable_pet_foods: :do_not_copy,
87
+ copyable_pet_sitting_patronages: :do_not_copy,
88
+ })
89
+ end
90
+ end
91
+
92
+ undefine_copyable_in CopyableToy
93
+ class CopyableToy < ActiveRecord::Base
94
+ copyable do
95
+ disable_all_callbacks_and_observers_except_validate
96
+ columns({
97
+ name: :copy,
98
+ kind: :copy,
99
+ copyable_pet_id: :copy,
100
+ })
101
+ associations({
102
+ })
103
+ end
104
+ end
105
+
106
+ # set up objects
107
+ fido = CopyablePet.create!(
108
+ name: 'Fido',
109
+ kind: 'canine',
110
+ birth_year: 2005)
111
+
112
+ pillow = CopyableToy.create!(
113
+ name: 'favorite pillow',
114
+ kind: 'soft',
115
+ copyable_pet: fido)
116
+
117
+ # everything should be wonderful
118
+ expect {
119
+ fido.create_copy!
120
+ }.to_not raise_error
121
+
122
+ # now inject some invalid data in an associated object
123
+ pillow.update_column(:name, 'o')
124
+ fido.reload
125
+
126
+ # fail because invalid!
127
+ expect {
128
+ fido.create_copy!
129
+ }.to raise_error(ActiveRecord::RecordInvalid)
130
+
131
+ # oh quit complaning and do it anyway!
132
+ expect {
133
+ fido.create_copy!(skip_validations: true)
134
+ }.to_not raise_error
135
+ end
136
+ end
@@ -0,0 +1,169 @@
1
+ require_relative 'helper/copyable_spec_helper'
2
+
3
+ describe 'complex model hierarchies:' do
4
+ before(:each) do
5
+ undefine_copyable_in CopyableOwner
6
+ class CopyableOwner < ActiveRecord::Base
7
+ copyable do
8
+ disable_all_callbacks_and_observers_except_validate
9
+ columns({
10
+ name: :copy,
11
+ })
12
+ associations({
13
+ copyable_vehicles: :copy,
14
+ copyable_amenities: :copy,
15
+ })
16
+ end
17
+ end
18
+ undefine_copyable_in CopyableVehicle
19
+ class CopyableVehicle < ActiveRecord::Base
20
+ copyable do
21
+ disable_all_callbacks_and_observers_except_validate
22
+ columns({
23
+ name: :copy,
24
+ copyable_owner_id: :copy,
25
+ })
26
+ associations({
27
+ copyable_amenities: :copy,
28
+ })
29
+ end
30
+ end
31
+ undefine_copyable_in CopyableAmenity
32
+ class CopyableAmenity < ActiveRecord::Base
33
+ copyable do
34
+ disable_all_callbacks_and_observers_except_validate
35
+ columns({
36
+ name: :copy,
37
+ copyable_vehicle_id: :copy,
38
+ copyable_owner_id: :copy,
39
+ })
40
+ associations({
41
+ copyable_warranty: :copy,
42
+ })
43
+ end
44
+ end
45
+ undefine_copyable_in CopyableWarranty
46
+ class CopyableWarranty < ActiveRecord::Base
47
+ copyable do
48
+ disable_all_callbacks_and_observers_except_validate
49
+ columns({
50
+ name: :copy,
51
+ copyable_amenity_id: :copy,
52
+ })
53
+ associations({
54
+ })
55
+ end
56
+ end
57
+ end
58
+
59
+ describe 'a tree structure' do
60
+ before(:each) do
61
+ # We are avoiding using owners to avoid the denormalized aspect
62
+ # of the data model and keep this a simple tree.
63
+ @vehicle1 = CopyableVehicle.create!(name: 'Corvette')
64
+
65
+ @amenity1 = CopyableAmenity.create!(name: 'moon roof', copyable_vehicle: @vehicle1)
66
+ @warranty1 = CopyableWarranty.create!(name: 'moon roof warranty', copyable_amenity: @amenity1)
67
+ @amenity2 = CopyableAmenity.create!(name: 'twitter', copyable_vehicle: @vehicle1)
68
+ @warranty2 = CopyableWarranty.create!(name: 'twitter warranty', copyable_amenity: @amenity2)
69
+ @amenity3 = CopyableAmenity.create!(name: 'jazz', copyable_vehicle: @vehicle1)
70
+ @warranty3 = CopyableWarranty.create!(name: 'jazz warranty', copyable_amenity: @amenity3)
71
+ end
72
+
73
+ it 'should copy the entire tree as directed' do
74
+ @vehicle2 = @vehicle1.create_copy!
75
+ expect(CopyableVehicle.count).to eq(2)
76
+ expect(CopyableAmenity.count).to eq(6)
77
+ expect(CopyableWarranty.count).to eq(6)
78
+ expect(@vehicle2.name).to eq('Corvette')
79
+ expect(@vehicle2.copyable_amenities.map(&:name)).to match_array(
80
+ ['moon roof', 'twitter', 'jazz'])
81
+ expect(@vehicle2.copyable_amenities.map(&:copyable_warranty).map(&:name)).to match_array(
82
+ ['moon roof warranty', 'twitter warranty', 'jazz warranty'])
83
+ end
84
+
85
+ it 'should create the expected records if copied multiple times' do
86
+ # this test makes sure the SingleCopyEnforcer isn't too eager
87
+ @vehicle1.create_copy!
88
+ expect(CopyableVehicle.count).to eq(2)
89
+ expect(CopyableAmenity.count).to eq(6)
90
+ expect(CopyableWarranty.count).to eq(6)
91
+ @vehicle1.create_copy!
92
+ expect(CopyableVehicle.count).to eq(3)
93
+ expect(CopyableAmenity.count).to eq(9)
94
+ expect(CopyableWarranty.count).to eq(9)
95
+ @vehicle1.create_copy!
96
+ expect(CopyableVehicle.count).to eq(4)
97
+ expect(CopyableAmenity.count).to eq(12)
98
+ expect(CopyableWarranty.count).to eq(12)
99
+ end
100
+ end
101
+
102
+ describe 'a denormalized structure' do
103
+ context 'having one redundant association' do
104
+ before(:each) do
105
+ @joe = CopyableOwner.create!(name: 'Joe')
106
+ @porsche = CopyableVehicle.create!(name: 'Porsche', copyable_owner: @joe)
107
+ @moon_roof = CopyableAmenity.create!(name: 'moon roof',
108
+ copyable_vehicle: @porsche,
109
+ copyable_owner: @joe)
110
+ end
111
+
112
+ it 'should copy the records without copying any given record more than once' do
113
+ @copy_of_joe = @joe.create_copy!
114
+ expect(CopyableOwner.count).to eq(2)
115
+ expect(CopyableVehicle.count).to eq(2)
116
+ expect(CopyableAmenity.count).to eq(2)
117
+ end
118
+ end
119
+
120
+ context 'with many models, some having redundant associations' do
121
+ before(:each) do
122
+ @joe = CopyableOwner.create!(name: 'Joe')
123
+
124
+ @porsche = CopyableVehicle.create!(name: 'Porsche', copyable_owner: @joe)
125
+ @moon_roof = CopyableAmenity.create!(
126
+ name: 'moon roof',
127
+ copyable_vehicle: @porsche,
128
+ copyable_owner: @joe)
129
+
130
+ @mustang = CopyableVehicle.create!(name: 'Mustang', copyable_owner: @joe)
131
+ @air_conditioning = CopyableAmenity.create!(
132
+ name: 'air conditioning',
133
+ copyable_vehicle: @mustang,
134
+ copyable_owner: @joe)
135
+ @air_conditioning_warranty = CopyableWarranty.create!(
136
+ name: 'air conditioning warranty',
137
+ copyable_amenity: @air_conditioning)
138
+
139
+ @corvette = CopyableVehicle.create!(name: 'Corvette', copyable_owner: @joe)
140
+ @pillows = CopyableAmenity.create!(
141
+ name: 'pillows',
142
+ copyable_vehicle: @corvette,
143
+ copyable_owner: @joe)
144
+ @pillow_warranty = CopyableWarranty.create!(name: 'pillow warranty', copyable_amenity: @pillows)
145
+ @airbags = CopyableAmenity.create!(
146
+ name: 'airbags',
147
+ copyable_vehicle: @corvette,
148
+ copyable_owner: @joe)
149
+ @airbag_warranty = CopyableWarranty.create!(name: 'airbag warranty', copyable_amenity: @airbags)
150
+ @radio = CopyableAmenity.create!(
151
+ name: 'satellite radio',
152
+ copyable_vehicle: @corvette,
153
+ copyable_owner: @joe)
154
+ end
155
+
156
+ it 'should copy the records without copying any given record more than once' do
157
+ @copy_of_joe = @joe.create_copy!
158
+ expect(CopyableOwner.count).to eq(2)
159
+ expect(CopyableVehicle.count).to eq(6)
160
+ expect(CopyableAmenity.count).to eq(10)
161
+ expect(CopyableWarranty.count).to eq(6)
162
+ expect(@copy_of_joe.copyable_vehicles.map(&:name)).to match_array([
163
+ 'Porsche', 'Mustang', 'Corvette'])
164
+ expect(@copy_of_joe.copyable_amenities.map(&:name)).to match_array([
165
+ 'moon roof', 'air conditioning', 'pillows', 'airbags', 'satellite radio'])
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ # copyable needs its own set of tables and ActiveRecord models to test with.
4
+ require_relative 'test_tables'
5
+ Copyable::TestTables.create!
6
+ require_relative 'test_models'
7
+
8
+ # useful for starting off specs with a clean slate
9
+ def undefine_copyable_in(klass)
10
+ klass.instance_eval do
11
+ define_method(:create_copy!) do |*args|
12
+ raise "the create_copy! method has been wiped clean for testing purposes"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,136 @@
1
+ # We need various test models in various configurations to properly test the
2
+ # copying functionality.
3
+
4
+
5
+ #*******************************************************************************
6
+ # For testing basic copying behavior and validation.
7
+ #
8
+
9
+ class CopyableCoin < ActiveRecord::Base
10
+ validates :year, numericality: { greater_than: 1700 }
11
+ end
12
+
13
+
14
+
15
+ #*******************************************************************************
16
+ # For testing observers.
17
+ #
18
+
19
+ class CopyableCar < ActiveRecord::Base
20
+ validates :make, presence: true
21
+ validates :model, presence: true
22
+ validates :year, presence: true
23
+ end
24
+
25
+
26
+
27
+ #*******************************************************************************
28
+ # For testing callbacks.
29
+ #
30
+
31
+ class CopyableTree < ActiveRecord::Base
32
+ after_create :callback1
33
+ before_save { |tree| raise "callback2 called" }
34
+ def callback1
35
+ raise "callback1 called"
36
+ end
37
+ end
38
+
39
+
40
+
41
+ #*******************************************************************************
42
+ # For testing basic associations.
43
+ #
44
+
45
+ class CopyablePet < ActiveRecord::Base
46
+ has_many :copyable_toys
47
+ has_one :copyable_pet_tag
48
+ has_one :copyable_pet_profile
49
+ has_one :copyable_address, through: :copyable_pet_profile
50
+ # Rails 3 expects the join table to be called copyable_pet_foods_copyable_pets
51
+ # Rails 4 expects the join table to be called copyable_pet_foods_pets
52
+ # We explicitly name the join table so that this code works with both Rails 3 and Rails 4.
53
+ has_and_belongs_to_many :copyable_pet_foods, join_table: 'copyable_pet_foods_pets'
54
+ has_many :copyable_pet_sitting_patronages
55
+ has_many :copyable_pet_sitters, through: :copyable_pet_sitting_patronages
56
+ end
57
+
58
+ class CopyableToy < ActiveRecord::Base
59
+ validates :name, length: { minimum: 3 }
60
+ belongs_to :copyable_pet
61
+ end
62
+
63
+ class CopyablePetTag < ActiveRecord::Base
64
+ belongs_to :copyable_pet
65
+ end
66
+
67
+ class CopyablePetFood < ActiveRecord::Base
68
+ # Rails 3 expects the join table to be called copyable_pet_foods_copyable_pets
69
+ # Rails 4 expects the join table to be called copyable_pet_foods_pets
70
+ # We explicitly name the join table so that this code works with both Rails 3 and Rails 4.
71
+ has_and_belongs_to_many :copyable_pets, join_table: 'copyable_pet_foods_pets'
72
+ end
73
+
74
+ class CopyablePetSitter < ActiveRecord::Base
75
+ has_many :copyable_pet_sitting_patronages
76
+ has_many :copyable_pets, through: :copyable_pet_sitting_patronages
77
+ end
78
+
79
+ class CopyablePetSittingPatronage < ActiveRecord::Base
80
+ belongs_to :copyable_pet
81
+ belongs_to :copyable_pet_sitter
82
+ end
83
+
84
+ class CopyablePetProfile < ActiveRecord::Base
85
+ belongs_to :copyable_pet
86
+ has_one :copyable_address
87
+ end
88
+
89
+ class CopyableAddress < ActiveRecord::Base
90
+ belongs_to :copyable_pet_profile
91
+ end
92
+
93
+
94
+
95
+ #*******************************************************************************
96
+ # For testing polymorphic associations and custom-named foreign keys.
97
+ #
98
+
99
+ class CopyablePicture < ActiveRecord::Base
100
+ belongs_to :imageable, polymorphic: true
101
+ belongs_to :copyable_album, foreign_key: 'picture_album_id'
102
+ end
103
+
104
+ class CopyableProduct < ActiveRecord::Base
105
+ has_many :copyable_pictures, as: :imageable
106
+ end
107
+
108
+ class CopyableAlbum < ActiveRecord::Base
109
+ has_many :copyable_pictures, foreign_key: 'picture_album_id'
110
+ end
111
+
112
+
113
+
114
+ #*******************************************************************************
115
+ # For testing denormalized data structures.
116
+ #
117
+
118
+ class CopyableOwner < ActiveRecord::Base
119
+ has_many :copyable_vehicles
120
+ has_many :copyable_amenities # this is not normalized, you wouldn't normally do this
121
+ end
122
+
123
+ class CopyableVehicle < ActiveRecord::Base
124
+ has_many :copyable_amenities
125
+ belongs_to :copyable_owner
126
+ end
127
+
128
+ class CopyableAmenity < ActiveRecord::Base
129
+ belongs_to :copyable_vehicle
130
+ belongs_to :copyable_owner # this is not normalized, you wouldn't normally do this
131
+ has_one :copyable_warranty
132
+ end
133
+
134
+ class CopyableWarranty < ActiveRecord::Base
135
+ belongs_to :copyable_amenity
136
+ end