amoeba 3.3.0 → 3.4.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/activerecord_head.yml +26 -0
- data/.github/workflows/jruby.yml +4 -1
- data/.github/workflows/rspec.yml +49 -0
- data/.github/workflows/rubocop.yml +20 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +2 -2
- data/.rubocop_todo.yml +64 -35
- data/Appraisals +25 -15
- data/CHANGELOG.md +54 -0
- data/Gemfile +1 -0
- data/README.md +26 -1
- data/amoeba.gemspec +10 -12
- data/docs/contributing.md +19 -0
- data/docs/test_refactor.md +20 -0
- data/gemfiles/activerecord_6.1.gemfile +8 -0
- data/gemfiles/activerecord_7.0.gemfile +3 -0
- data/gemfiles/{activerecord_6.0.gemfile → activerecord_7.1.gemfile} +3 -1
- data/gemfiles/{activerecord_5.2.gemfile → activerecord_8.0.gemfile} +4 -2
- data/gemfiles/activerecord_8.1.gemfile +21 -0
- data/gemfiles/activerecord_head.gemfile +3 -1
- data/gemfiles/jruby_activerecord_7.0.gemfile +2 -2
- data/gemfiles/jruby_activerecord_head.gemfile +2 -0
- data/lib/amoeba/cloner.rb +4 -2
- data/lib/amoeba/config.rb +8 -3
- data/lib/amoeba/version.rb +1 -1
- data/spec/lib/amoeba_spec.rb +405 -225
- data/spec/spec_helper.rb +3 -1
- data/spec/support/data.rb +2 -0
- data/spec/support/models.rb +11 -2
- data/spec/support/schema.rb +2 -0
- metadata +26 -43
- data/.github/workflows/ruby_25.yml +0 -30
- data/.github/workflows/ruby_26.yml +0 -30
- data/.github/workflows/ruby_27.yml +0 -30
- data/.github/workflows/ruby_30.yml +0 -34
- data/.github/workflows/ruby_31.yml +0 -34
- data/.github/workflows/ruby_32.yml +0 -34
- data/.github/workflows/ruby_head.yml +0 -34
data/spec/lib/amoeba_spec.rb
CHANGED
|
@@ -1,185 +1,238 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
describe 'amoeba' do
|
|
4
|
-
context '
|
|
5
|
-
before
|
|
6
|
-
|
|
6
|
+
context 'amoeba_dup' do
|
|
7
|
+
before { require ::File.dirname(__FILE__) + '/../support/data.rb' }
|
|
8
|
+
|
|
9
|
+
context 'with posts' do
|
|
10
|
+
subject(:save_post) { new_post.save! }
|
|
11
|
+
|
|
12
|
+
let(:old_post) { ::Post.find(1) }
|
|
13
|
+
let(:new_post) { old_post.amoeba_dup }
|
|
14
|
+
|
|
15
|
+
before do
|
|
16
|
+
old_post.class.amoeba do
|
|
17
|
+
prepend contents: "Here's a copy: "
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Unecessary. Included in refactor for completeness.
|
|
22
|
+
it { expect(old_post.comments.map(&:contents).include?('I love it!')).to be_truthy }
|
|
23
|
+
|
|
24
|
+
it { is_expected.to be_truthy }
|
|
25
|
+
|
|
26
|
+
it do
|
|
27
|
+
save_post
|
|
28
|
+
expect(new_post.title).to eq("Copy of #{old_post.title}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it { expect { save_post }.not_to change(Tag.all, :count) }
|
|
32
|
+
it { expect { save_post }.not_to change(Category, :count) }
|
|
33
|
+
|
|
34
|
+
# The original versions of these tests counted checked that the counts of
|
|
35
|
+
# various models were multipled when the new instance is saved. This
|
|
36
|
+
# depends on there being no other data in the database, which is not
|
|
37
|
+
# reliable. The new versions of these tests check for an increase in the
|
|
38
|
+
# count by a certain amount. This depends on how the test data is created.
|
|
39
|
+
|
|
40
|
+
# Testing a 'has_one' relation with default amoeba configuration.
|
|
41
|
+
it { expect { save_post }.to change(Account, :count).by(1) }
|
|
42
|
+
|
|
43
|
+
# Testing a 'has_one ... through' relation with default amoeba
|
|
44
|
+
# configuration.
|
|
45
|
+
it { expect { save_post }.to change(History.all, :count).by(1) }
|
|
46
|
+
|
|
47
|
+
# Testing a 'has_many' relation with default amoeba configuration.
|
|
48
|
+
it { expect { save_post }.to change(Supercat.all, :count).by(3) }
|
|
49
|
+
|
|
50
|
+
# Testing that a new instance is created when a duplicate is saved.
|
|
51
|
+
it { expect { save_post }.to change(Post.all, :count).by(1) }
|
|
52
|
+
|
|
53
|
+
# Testing a 'has_one' relation with default amoeba configuration.
|
|
54
|
+
it { expect { save_post }.to change(PostConfig.all, :count).by(1) }
|
|
55
|
+
|
|
56
|
+
# The dup adds extra comments based on 'customize'. Three comments are
|
|
57
|
+
# added and two new ones are created.
|
|
58
|
+
it { expect { save_post }.to change(Comment.all, :count).by(5) }
|
|
59
|
+
|
|
60
|
+
# Ratings are linked to comments. The dup will duplicate the ratings for
|
|
61
|
+
# the existing comments (6 each for the test data) and the new comments
|
|
62
|
+
# will have no ratings.
|
|
63
|
+
it { expect { save_post }.to change(Rating.all, :count).by(18) }
|
|
64
|
+
|
|
65
|
+
# Testing a 'has_and_belongs_to_many' relationship with the default amoeba
|
|
66
|
+
# configuration.
|
|
67
|
+
# The `tag_count` method fetches the number of records in the join table
|
|
68
|
+
# and this tests that the three tags on the post are applied to the new
|
|
69
|
+
# post.
|
|
70
|
+
it { expect { save_post }.to change(Post, :tag_count).by(3) }
|
|
71
|
+
|
|
72
|
+
# Testing a 'has_one ... through' relationship with the 'clone' amoeba
|
|
73
|
+
# configuration.
|
|
74
|
+
# The three widgets that are linked, via a join table, should be duplicated
|
|
75
|
+
# in the Widget model and add corresponding records in the join table,
|
|
76
|
+
# PostWidget.
|
|
77
|
+
it { expect { save_post }.to change(Widget.all, :count).by(3) }
|
|
78
|
+
it { expect { save_post }.to change(PostWidget, :count).by(3) }
|
|
79
|
+
|
|
80
|
+
# Testing a 'has_and_belongs_to_many' relationship with the 'clone' amoeba
|
|
81
|
+
# configuration.
|
|
82
|
+
# There are three notes attached to the post so three new notes should be
|
|
83
|
+
# created in the Note model as well as three records in the join table. The
|
|
84
|
+
# `note_count` method fetches the number of records in the join table.
|
|
85
|
+
it { expect { save_post }.to change(Note.all, :count).by(3) }
|
|
86
|
+
it { expect { save_post }.to change(Post, :note_count).by(3) }
|
|
87
|
+
|
|
88
|
+
# Testing the 'include_association' amoeba configuration.
|
|
89
|
+
# Each Post can have many Supercats and each Supercat can have many
|
|
90
|
+
# Supperkitens. In the test data the post has three supercats and each
|
|
91
|
+
# supercat has three superkittens (how many were there going to St Ives?).
|
|
92
|
+
# As a result 9 extra Superkittens should be generated.
|
|
93
|
+
it { expect { save_post }.to change(Superkitten.all, :count).by(9) }
|
|
94
|
+
|
|
95
|
+
# Testing 'prepend' configuration on attribute.
|
|
96
|
+
# The attribute `ramblings` is configured to prepend 'Copy of '.
|
|
97
|
+
it do
|
|
98
|
+
expect(new_post.supercats.map(&:ramblings))
|
|
99
|
+
.to contain_exactly('Copy of zomg', 'Copy of why', 'Copy of ohnoes')
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Testing 'set' configuration on attribute.
|
|
103
|
+
# The attribute 'other_ramblings' is configured to set to 'La la la'.
|
|
104
|
+
# The original tests check that (a) the values are unique and (b) the value
|
|
105
|
+
# is set correctly. This new test will check that there are three values
|
|
106
|
+
# that are all set the same.
|
|
107
|
+
it { expect(new_post.supercats.map(&:other_ramblings)).to contain_exactly('La la la', 'La la la', 'La la la') }
|
|
108
|
+
|
|
109
|
+
# Testing various string modifications.
|
|
110
|
+
# * 'prepend' to add "Here's a copy: ", defined in the 'before' above
|
|
111
|
+
# * 'append' to add " (copied version)"
|
|
112
|
+
# * 'regext' to replace "dog" with "cat"
|
|
113
|
+
it do
|
|
114
|
+
expect(new_post.contents)
|
|
115
|
+
.to eq("Here's a copy: Lorum ipsum dolor rainbow bright. I like cats, cats are awesome. (copied version)")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Testing 'customize' configuration.
|
|
119
|
+
# Two of the comments in the old post have 'nerf' values that cause lambdas
|
|
120
|
+
# to generate additional when the post is duplicated.
|
|
121
|
+
# The old post has three comments so the new post will have five comments.
|
|
122
|
+
it { expect(new_post.comments.length).to eq(5) }
|
|
123
|
+
# When the comment's 'nerf' value is 'ratatat' the new post will have the
|
|
124
|
+
# comment duplicated.
|
|
125
|
+
# The 'where' will only work once new_post has been saved.
|
|
126
|
+
it { expect(new_post.tap(&:save).comments.where(nerf: 'ratatat').length).to eq(2) }
|
|
127
|
+
# The duplicate will have a nil 'contents' while the other version is not.
|
|
128
|
+
it { expect(new_post.tap(&:save).comments.where(nerf: 'ratatat', contents: nil).length).to eq(1) }
|
|
129
|
+
# When the comment's 'nerf' value is 'bonk' the new post will have the
|
|
130
|
+
# comment duplicated. The new comment will have a modified 'nerf' value.
|
|
131
|
+
# The 'where' will only work once new_post has been saved.
|
|
132
|
+
# There will only be a single comment with the original 'nerf' value.
|
|
133
|
+
it { expect(new_post.tap(&:save).comments.where(nerf: 'bonk').length).to eq(1) }
|
|
134
|
+
# The duplicate will have a modified 'nerf' value.
|
|
135
|
+
it { expect(new_post.tap(&:save).comments.where(nerf: 'bonkers', contents: nil).length).to eq(1) }
|
|
136
|
+
|
|
137
|
+
# Testing duplicates with a 'has_many ... through' reference and a 'clone'
|
|
138
|
+
# amoeba configuration results in new records (with new ids).
|
|
139
|
+
it { expect(old_post.widgets.map(&:id) & new_post.tap(&:save).widgets.map(&:id)).to be_empty }
|
|
140
|
+
|
|
141
|
+
# Testing with a value that is serialized with a custom serializer.
|
|
142
|
+
# The 'value' of a CustomThing is a string that is serialized as an array.
|
|
143
|
+
# The test data has three instances of CustomThing; [], [1, 2] and [78]
|
|
144
|
+
it { expect(new_post.tap(&:save).custom_things.pluck(:value)).to contain_exactly([], [1, 2], [78]) }
|
|
7
145
|
end
|
|
8
146
|
|
|
9
|
-
|
|
147
|
+
context 'with authors' do
|
|
148
|
+
let(:old_author) { Author.find(1) }
|
|
149
|
+
let(:new_author) { old_author.amoeba_dup.tap(&:save!) }
|
|
10
150
|
|
|
11
|
-
|
|
12
|
-
# Posts {{{
|
|
13
|
-
old_post = ::Post.find(1)
|
|
14
|
-
expect(old_post.comments.map(&:contents).include?('I love it!')).to be_truthy
|
|
151
|
+
it { expect(new_author.errors.messages).to be_empty }
|
|
15
152
|
|
|
16
|
-
|
|
17
|
-
|
|
153
|
+
# An author has multiple posts (default amoeba configuration) and posts
|
|
154
|
+
# in turn have multiple custom things (default amoeba configuration). The
|
|
155
|
+
# custom things have a string value field that is serialized via a custom
|
|
156
|
+
# serializer into an array.
|
|
157
|
+
# The test author has three posts, each of which have three custom
|
|
158
|
+
# things. These should be duplicated.
|
|
159
|
+
it { expect(new_author.posts.first.custom_things.map(&:value)).to contain_exactly([], [1, 2], [78]) }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context 'with inherited classes' do
|
|
163
|
+
# Testing the effect of single-table inheritance on amoeba duplication.
|
|
164
|
+
# The Product class has two subclasses, Shirt and Necklace. Each class
|
|
165
|
+
# has the same amoeba configuration that is defined with the 'raised'
|
|
166
|
+
# option.
|
|
167
|
+
# These tests are copied from the original and I do not fully understand
|
|
168
|
+
# the purpose of some of them as the behavior appears to be the same for
|
|
169
|
+
# all three classes.
|
|
170
|
+
|
|
171
|
+
subject(:new_product) { old_product.amoeba_dup.tap(&:save) }
|
|
172
|
+
|
|
173
|
+
context 'with the base class' do
|
|
174
|
+
let(:old_product) { Product.find(1) }
|
|
175
|
+
# The base product class has two relations;
|
|
176
|
+
# * image; has_many with default amoeba configuration
|
|
177
|
+
# * section; has_and_belongs_to_many with default amoeba configuration
|
|
178
|
+
# The amoeba configuration is defined with the propogate option.
|
|
179
|
+
|
|
180
|
+
# Duplicating product does not result in errors.
|
|
181
|
+
it { expect(new_product.errors.messages).to be_empty }
|
|
182
|
+
|
|
183
|
+
# New product copies all images from old product. The old product has
|
|
184
|
+
# two images so two new images are created for the new product.
|
|
185
|
+
it { expect { new_product }.to change(Image, :count).by(2) }
|
|
186
|
+
it { expect { new_product }.not_to change(old_product.images, :count) }
|
|
187
|
+
it { expect(new_product.images.count).to eq(old_product.images.count) }
|
|
188
|
+
|
|
189
|
+
# New product copies references to all sections from old product. No
|
|
190
|
+
# new sections are created and the new product is linked to the same
|
|
191
|
+
# number of sections as the old product.
|
|
192
|
+
it { expect { new_product }.not_to change(Section.all, :count) }
|
|
193
|
+
it { expect { new_product }.not_to change(old_product, :section_count) }
|
|
194
|
+
it { expect(new_product.section_count).to eq(old_product.section_count) }
|
|
18
195
|
end
|
|
19
196
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
expect(new_post.save!).to be_truthy
|
|
39
|
-
expect(new_post.title).to eq("Copy of #{old_post.title}")
|
|
40
|
-
|
|
41
|
-
end_account_count = Account.count
|
|
42
|
-
end_history_count = History.count
|
|
43
|
-
end_cat_count = Category.count
|
|
44
|
-
end_supercat_count = Supercat.count
|
|
45
|
-
end_tag_count = Tag.all.count
|
|
46
|
-
end_note_count = Note.all.count
|
|
47
|
-
end_widget_count = Widget.all.count
|
|
48
|
-
end_post_count = Post.all.count
|
|
49
|
-
end_comment_count = Comment.all.count
|
|
50
|
-
end_rating_count = Rating.all.count
|
|
51
|
-
end_postconfig_count = PostConfig.all.count
|
|
52
|
-
end_postwidget_count = PostWidget.all.count
|
|
53
|
-
end_superkitten_count = Superkitten.all.count
|
|
54
|
-
end_posttag_count = Post.tag_count
|
|
55
|
-
end_postnote_count = Post.note_count
|
|
56
|
-
|
|
57
|
-
expect(end_tag_count).to eq(start_tag_count)
|
|
58
|
-
expect(end_cat_count).to eq(start_cat_count)
|
|
59
|
-
expect(end_account_count).to eq(start_account_count * 2)
|
|
60
|
-
expect(end_history_count).to eq(start_history_count * 2)
|
|
61
|
-
expect(end_supercat_count).to eq(start_supercat_count * 2)
|
|
62
|
-
expect(end_post_count).to eq(start_post_count * 2)
|
|
63
|
-
expect(end_comment_count).to eq((start_comment_count * 2) + 2)
|
|
64
|
-
expect(end_rating_count).to eq(start_rating_count * 2)
|
|
65
|
-
expect(end_postconfig_count).to eq(start_postconfig_count * 2)
|
|
66
|
-
expect(end_posttag_count).to eq(start_posttag_count * 2)
|
|
67
|
-
expect(end_widget_count).to eq(start_widget_count * 2)
|
|
68
|
-
expect(end_postwidget_count).to eq(start_postwidget_count * 2)
|
|
69
|
-
expect(end_note_count).to eq(start_note_count * 2)
|
|
70
|
-
expect(end_postnote_count).to eq(start_postnote_count * 2)
|
|
71
|
-
expect(end_superkitten_count).to eq(start_superkitten_count * 2)
|
|
72
|
-
|
|
73
|
-
expect(new_post.supercats.map(&:ramblings)).to include('Copy of zomg')
|
|
74
|
-
expect(new_post.supercats.map(&:other_ramblings).uniq.length).to eq(1)
|
|
75
|
-
expect(new_post.supercats.map(&:other_ramblings).uniq).to include('La la la')
|
|
76
|
-
expect(new_post.contents).to eq("Here's a copy: #{old_post.contents.gsub(/dog/,
|
|
77
|
-
'cat')} (copied version)")
|
|
78
|
-
expect(new_post.comments.length).to eq(5)
|
|
79
|
-
expect(new_post.comments.select do |c|
|
|
80
|
-
c.nerf == 'ratatat' && c.contents.nil?
|
|
81
|
-
end.length).to eq(1)
|
|
82
|
-
expect(new_post.comments.select { |c| c.nerf == 'ratatat' }.length).to eq(2)
|
|
83
|
-
expect(new_post.comments.select { |c| c.nerf == 'bonk' }.length).to eq(1)
|
|
84
|
-
expect(new_post.comments.select do |c|
|
|
85
|
-
c.nerf == 'bonkers' && c.contents.nil?
|
|
86
|
-
end.length).to eq(1)
|
|
87
|
-
|
|
88
|
-
new_post.widgets.map(&:id).each do |id|
|
|
89
|
-
expect(old_post.widgets.map(&:id)).not_to include(id)
|
|
197
|
+
context "with a class inheriting with the 'raised :submissive' configuration" do
|
|
198
|
+
let(:old_product) { Shirt.find(2) }
|
|
199
|
+
|
|
200
|
+
# Duplicating product does not result in errors.
|
|
201
|
+
it { expect(new_product.errors.messages).to be_empty }
|
|
202
|
+
|
|
203
|
+
# New product copies all images from old product. The old product has
|
|
204
|
+
# two images so two new images are created for the new product.
|
|
205
|
+
it { expect { new_product }.to change(Image, :count).by(2) }
|
|
206
|
+
it { expect { new_product }.not_to change(old_product.images, :count) }
|
|
207
|
+
it { expect(new_product.images.count).to eq(old_product.images.count) }
|
|
208
|
+
|
|
209
|
+
# New product copies references to all sections from old product. No
|
|
210
|
+
# new sections are created and the new product is linked to the same
|
|
211
|
+
# number of sections as the old product.
|
|
212
|
+
it { expect { new_product }.not_to change(Section.all, :count) }
|
|
213
|
+
it { expect { new_product }.not_to change(old_product, :section_count) }
|
|
214
|
+
it { expect(new_product.section_count).to eq(old_product.section_count) }
|
|
90
215
|
end
|
|
91
216
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
start_image_count = first_product.images.count
|
|
113
|
-
start_section_count = Section.all.length
|
|
114
|
-
start_prodsection_count = first_product.section_count
|
|
115
|
-
|
|
116
|
-
new_product = first_product.amoeba_dup
|
|
117
|
-
new_product.save
|
|
118
|
-
expect(new_product.errors.messages).to be_empty
|
|
119
|
-
|
|
120
|
-
end_image_count = first_product.images.count
|
|
121
|
-
end_newimage_count = new_product.images.count
|
|
122
|
-
end_section_count = Section.all.length
|
|
123
|
-
end_prodsection_count = first_product.section_count
|
|
124
|
-
end_newprodsection_count = new_product.section_count
|
|
125
|
-
|
|
126
|
-
expect(end_image_count).to eq(start_image_count)
|
|
127
|
-
expect(end_newimage_count).to eq(start_image_count)
|
|
128
|
-
expect(end_section_count).to eq(start_section_count)
|
|
129
|
-
expect(end_prodsection_count).to eq(start_prodsection_count)
|
|
130
|
-
expect(end_newprodsection_count).to eq(start_prodsection_count)
|
|
131
|
-
# }}}
|
|
132
|
-
|
|
133
|
-
# Inherited Class {{{
|
|
134
|
-
# Shirt {{{
|
|
135
|
-
old_product = Shirt.find(2)
|
|
136
|
-
|
|
137
|
-
start_image_count = old_product.images.count
|
|
138
|
-
start_section_count = Section.count
|
|
139
|
-
start_prodsection_count = old_product.section_count
|
|
140
|
-
|
|
141
|
-
new_product = old_product.amoeba_dup
|
|
142
|
-
new_product.save
|
|
143
|
-
expect(new_product.errors.messages).to be_empty
|
|
144
|
-
|
|
145
|
-
end_image_count = old_product.images.count
|
|
146
|
-
end_newimage_count = new_product.images.count
|
|
147
|
-
end_section_count = Section.count
|
|
148
|
-
end_prodsection_count = first_product.section_count
|
|
149
|
-
end_newprodsection_count = new_product.section_count
|
|
150
|
-
|
|
151
|
-
expect(end_image_count).to eq(start_image_count)
|
|
152
|
-
expect(end_newimage_count).to eq(start_image_count)
|
|
153
|
-
expect(end_section_count).to eq(start_section_count)
|
|
154
|
-
expect(end_prodsection_count).to eq(start_prodsection_count)
|
|
155
|
-
expect(end_newprodsection_count).to eq(start_prodsection_count)
|
|
156
|
-
# }}}
|
|
157
|
-
|
|
158
|
-
# Necklace {{{
|
|
159
|
-
old_product = Necklace.find(3)
|
|
160
|
-
|
|
161
|
-
start_image_count = old_product.images.count
|
|
162
|
-
start_section_count = Section.count
|
|
163
|
-
start_prodsection_count = old_product.section_count
|
|
164
|
-
|
|
165
|
-
new_product = old_product.amoeba_dup
|
|
166
|
-
new_product.save
|
|
167
|
-
expect(new_product.errors.messages).to be_empty
|
|
168
|
-
|
|
169
|
-
end_image_count = old_product.images.count
|
|
170
|
-
end_newimage_count = new_product.images.count
|
|
171
|
-
end_section_count = Section.count
|
|
172
|
-
end_prodsection_count = first_product.section_count
|
|
173
|
-
end_newprodsection_count = new_product.section_count
|
|
174
|
-
|
|
175
|
-
expect(end_image_count).to eq(start_image_count)
|
|
176
|
-
expect(end_newimage_count).to eq(start_image_count)
|
|
177
|
-
expect(end_section_count).to eq(start_section_count)
|
|
178
|
-
expect(end_prodsection_count).to eq(start_prodsection_count)
|
|
179
|
-
expect(end_newprodsection_count).to eq(start_prodsection_count)
|
|
180
|
-
# }}}
|
|
181
|
-
# }}}
|
|
182
|
-
# }}}
|
|
217
|
+
context "with a class inheriting with the 'raised :relaxed' configuration" do
|
|
218
|
+
let(:old_product) { Necklace.find(3) }
|
|
219
|
+
|
|
220
|
+
# Duplicating product does not result in errors.
|
|
221
|
+
it { expect(new_product.errors.messages).to be_empty }
|
|
222
|
+
|
|
223
|
+
# New product copies all images from old product. The old product has
|
|
224
|
+
# two images so two new images are created for the new product.
|
|
225
|
+
it { expect { new_product }.to change(Image, :count).by(2) }
|
|
226
|
+
it { expect { new_product }.not_to change(old_product.images, :count) }
|
|
227
|
+
it { expect(new_product.images.count).to eq(old_product.images.count) }
|
|
228
|
+
|
|
229
|
+
# New product copies references to all sections from old product. No
|
|
230
|
+
# new sections are created and the new product is linked to the same
|
|
231
|
+
# number of sections as the old product.
|
|
232
|
+
it { expect { new_product }.not_to change(Section.all, :count) }
|
|
233
|
+
it { expect { new_product }.not_to change(old_product, :section_count) }
|
|
234
|
+
it { expect(new_product.section_count).to eq(old_product.section_count) }
|
|
235
|
+
end
|
|
183
236
|
end
|
|
184
237
|
end
|
|
185
238
|
|
|
@@ -251,50 +304,131 @@ describe 'amoeba' do
|
|
|
251
304
|
end
|
|
252
305
|
end
|
|
253
306
|
|
|
254
|
-
context 'override' do
|
|
307
|
+
context 'when there is an override configured' do
|
|
308
|
+
subject(:image_dup) { image.amoeba_dup }
|
|
309
|
+
|
|
310
|
+
let(:image) { Image.create(filename: 'test.jpg', product_id: 12) }
|
|
311
|
+
|
|
255
312
|
before do
|
|
256
|
-
|
|
257
|
-
|
|
313
|
+
Image.fresh_amoeba
|
|
314
|
+
Image.amoeba do
|
|
258
315
|
override ->(old, new) { new.product_id = 13 if old.filename == 'test.jpg' }
|
|
259
316
|
end
|
|
260
317
|
end
|
|
261
318
|
|
|
262
|
-
it
|
|
263
|
-
|
|
264
|
-
image_dup = image.amoeba_dup
|
|
265
|
-
expect(image_dup.save).to be_truthy
|
|
266
|
-
expect(image_dup.product_id).to eq(13)
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
it 'does not override fields' do
|
|
270
|
-
image = ::Image.create(filename: 'test2.jpg', product_id: 12)
|
|
271
|
-
image_dup = image.amoeba_dup
|
|
272
|
-
expect(image_dup.save).to be_truthy
|
|
273
|
-
expect(image_dup.product_id).to eq(12)
|
|
274
|
-
end
|
|
319
|
+
it { is_expected.to be_truthy }
|
|
320
|
+
it { expect(image_dup.product_id).to eq(13) }
|
|
275
321
|
end
|
|
276
322
|
|
|
277
|
-
context '
|
|
323
|
+
context 'when there is an override configured that does not apply' do
|
|
324
|
+
subject(:image_dup) { image.amoeba_dup }
|
|
325
|
+
|
|
326
|
+
let(:image) { Image.create(filename: 'test2.jpg', product_id: 12) }
|
|
327
|
+
|
|
278
328
|
before do
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
329
|
+
Image.fresh_amoeba
|
|
330
|
+
Image.amoeba do
|
|
331
|
+
override ->(old, new) { new.product_id = 13 if old.filename == 'test.jpg' }
|
|
282
332
|
end
|
|
283
333
|
end
|
|
284
334
|
|
|
335
|
+
it { is_expected.to be_truthy }
|
|
336
|
+
it { expect(image_dup.product_id).to eq(12) }
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
context 'with a field configured with nullify' do
|
|
340
|
+
subject(:image_dup) { image.amoeba_dup }
|
|
341
|
+
|
|
285
342
|
let(:image) { ::Image.create(filename: 'test.jpg', product_id: 12) }
|
|
286
|
-
let(:image_dup) { image.amoeba_dup }
|
|
287
343
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
344
|
+
before { ::Image.fresh_amoeba }
|
|
345
|
+
|
|
346
|
+
context 'without a condition' do
|
|
347
|
+
before do
|
|
348
|
+
::Image.amoeba do
|
|
349
|
+
nullify :product_id
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
it { is_expected.to be_truthy }
|
|
354
|
+
it { expect(image_dup.product_id).to be_nil }
|
|
355
|
+
|
|
356
|
+
it 'stores the field with no options' do
|
|
357
|
+
expect(::Image.amoeba.null_fields).to eq(product_id: {})
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
context 'with a truthy if condition' do
|
|
362
|
+
before do
|
|
363
|
+
::Image.amoeba do
|
|
364
|
+
nullify :product_id, if: :truthy?
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
it { expect(image_dup.product_id).to be_nil }
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
context 'with a falsey if condition' do
|
|
372
|
+
before do
|
|
373
|
+
::Image.amoeba do
|
|
374
|
+
nullify :product_id, if: :falsey?
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
it { expect(image_dup.product_id).to eq(12) }
|
|
379
|
+
|
|
380
|
+
it 'stores the condition alongside the field' do
|
|
381
|
+
expect(::Image.amoeba.null_fields).to eq(product_id: { if: :falsey? })
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
context 'with an array of fields and a falsey if condition' do
|
|
386
|
+
before do
|
|
387
|
+
::Image.amoeba do
|
|
388
|
+
nullify %i[filename product_id], if: :falsey?
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
it { expect(image_dup.filename).to eq('test.jpg') }
|
|
393
|
+
it { expect(image_dup.product_id).to eq(12) }
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
context 'when an array of fields replaces the previously configured ones' do
|
|
397
|
+
before do
|
|
398
|
+
::Image.amoeba do
|
|
399
|
+
nullify :filename
|
|
400
|
+
nullify [:product_id]
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
it { expect(image_dup.filename).to eq('test.jpg') }
|
|
405
|
+
it { expect(image_dup.product_id).to be_nil }
|
|
406
|
+
|
|
407
|
+
it 'drops the replaced field from null_fields' do
|
|
408
|
+
expect(::Image.amoeba.null_fields).to eq(product_id: {})
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
context 'when a field is redeclared without a condition' do
|
|
413
|
+
before do
|
|
414
|
+
::Image.amoeba do
|
|
415
|
+
nullify :product_id, if: :falsey?
|
|
416
|
+
nullify :product_id
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
it { expect(image_dup.product_id).to be_nil }
|
|
421
|
+
|
|
422
|
+
it 'clears the previous condition' do
|
|
423
|
+
expect(::Image.amoeba.null_fields).to eq(product_id: {})
|
|
424
|
+
end
|
|
291
425
|
end
|
|
292
426
|
end
|
|
293
427
|
|
|
294
428
|
context 'strict propagate' do
|
|
295
429
|
it 'calls #reset_amoeba' do
|
|
296
|
-
|
|
297
|
-
box =
|
|
430
|
+
allow(SuperBlackBox).to receive(:reset_amoeba).and_call_original
|
|
431
|
+
box = SuperBlackBox.create(title: 'Super Black Box', price: 9.99, length: 1, metal: '1')
|
|
298
432
|
new_box = box.amoeba_dup
|
|
299
433
|
expect(new_box.save).to be_truthy
|
|
300
434
|
end
|
|
@@ -335,46 +469,92 @@ describe 'amoeba' do
|
|
|
335
469
|
end
|
|
336
470
|
end
|
|
337
471
|
|
|
338
|
-
context 'inheritance' do
|
|
472
|
+
context 'with inheritance' do
|
|
473
|
+
# Box
|
|
474
|
+
# has_many :products, class_name: 'BoxProduct'
|
|
475
|
+
# has_many :sub_products, class_name: 'BoxSubProduct'
|
|
476
|
+
#
|
|
477
|
+
# BoxProduct
|
|
478
|
+
# belongs_to :box, class_name: 'Box'
|
|
479
|
+
# amoeba do
|
|
480
|
+
# enable
|
|
481
|
+
# propagate
|
|
482
|
+
# end
|
|
483
|
+
#
|
|
484
|
+
# BoxSubProduct < BoxProduct
|
|
485
|
+
# has_one :another_product, class_name: 'BoxAnotherProduct'
|
|
486
|
+
#
|
|
487
|
+
# BoxSubSubProduct < BoxSubProduct
|
|
488
|
+
#
|
|
489
|
+
# BoxAnotherProduct < BoxProduct
|
|
490
|
+
# belongs_to :sub_product, class_name: 'BoxSubProduct'
|
|
491
|
+
#
|
|
492
|
+
# This test, from the original suite, appears to be testing
|
|
493
|
+
# * BoxProduct has amoeba configured with 'propagate'
|
|
494
|
+
# * BoxSubProduct inherits (STI) from BoxProduct
|
|
495
|
+
# * BoxSubSubProduct inherits (STI) from BoxSubProduct
|
|
496
|
+
#
|
|
497
|
+
# When the instance of BoxSubProduct is duplicated as a result of an
|
|
498
|
+
# an instance of Box being duplicated then amoeba also acts on it to make
|
|
499
|
+
# a duplicate of the instance of `another_product`.
|
|
500
|
+
#
|
|
501
|
+
# This test could be improved to also test the behaviour when `propagate`
|
|
502
|
+
# is not used.
|
|
503
|
+
|
|
339
504
|
let(:box) { Box.create }
|
|
505
|
+
let(:sub_sub_product) { BoxSubSubProduct.create(title: 'Awesome shoes') }
|
|
506
|
+
let(:another_product) { BoxAnotherProduct.create(title: 'Cleaning product') }
|
|
340
507
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
sub_sub_product.update(box: box, another_product: another_product)
|
|
345
|
-
expect(box.sub_products.first.another_product.title).to eq('Cleaning product')
|
|
346
|
-
expect(box.amoeba_dup.sub_products.first.another_product.title).to eq('Cleaning product')
|
|
347
|
-
end
|
|
508
|
+
before { sub_sub_product.update(box: box, another_product: another_product) }
|
|
509
|
+
|
|
510
|
+
it { expect(box.amoeba_dup.sub_products.first.another_product.title).to eq('Cleaning product') }
|
|
348
511
|
end
|
|
349
512
|
|
|
350
|
-
context 'inheritance extended' do
|
|
351
|
-
subject { stage.amoeba_dup }
|
|
513
|
+
context 'with inheritance extended' do
|
|
514
|
+
subject(:stage_dup) { stage.amoeba_dup }
|
|
352
515
|
|
|
353
|
-
|
|
354
|
-
|
|
516
|
+
# CustomStage inherits from Stage
|
|
517
|
+
# Stage has amoeba configured with `propagate`
|
|
518
|
+
# These tests are not exactly equivalent to the previous verison. Formerly
|
|
519
|
+
# all the expectations were in a single block and `save!` was executed on
|
|
520
|
+
# the subject. This does not need to be done in some cases.
|
|
521
|
+
|
|
522
|
+
let(:stage) { CustomStage.new(title: 'My Stage', external_id: 213) }
|
|
523
|
+
|
|
524
|
+
before do
|
|
355
525
|
stage.listeners.build(name: 'John')
|
|
356
526
|
stage.listeners.build(name: 'Helen')
|
|
357
527
|
stage.specialists.build(name: 'Jack')
|
|
358
528
|
stage.custom_rules.build(description: 'Kill all humans')
|
|
359
529
|
stage.save!
|
|
360
|
-
stage
|
|
361
530
|
end
|
|
362
531
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
532
|
+
# Stage has the listeners and specialist has_many attributes configured
|
|
533
|
+
# with amoeba with `include_association`. This is inherited by CustomStage.
|
|
534
|
+
it { expect { stage_dup.save! }.to change(Listener, :count).by(2) }
|
|
535
|
+
it { expect(stage_dup.tap(&:save!).listeners.find_by(name: 'John')).not_to be_nil }
|
|
536
|
+
it { expect(stage_dup.tap(&:save!).listeners.find_by(name: 'Helen')).not_to be_nil }
|
|
537
|
+
it { expect { stage_dup.save! }.to change(Specialist, :count).by(1) }
|
|
538
|
+
it { expect(stage_dup.tap(&:save!).specialists.find_by(name: 'Jack')).not_to be_nil }
|
|
539
|
+
|
|
540
|
+
# CustomStage has the custom_rules has_many attribute configured with
|
|
541
|
+
# amoeba with `include_association`. This attribute does not exist for
|
|
542
|
+
# Stage.
|
|
543
|
+
it { expect { stage_dup.save! }.to change(CustomRule, :count).by(1) }
|
|
544
|
+
# save! is not required here as it is testing the unsaved instance of
|
|
545
|
+
# CustomRule. This differs from the tests above for listeners and
|
|
546
|
+
# specialists which uses find_by. This is testing the same thing but needs
|
|
547
|
+
# the instances to be in the database.
|
|
548
|
+
it { expect(stage_dup.custom_rules.first.description).to eq 'Kill all humans' }
|
|
549
|
+
|
|
550
|
+
# Stage has an attribute title that is not explicitly configured for amoeba
|
|
551
|
+
# and so has the default configuration. CustomStage inherits this
|
|
552
|
+
# configuration.
|
|
553
|
+
it { expect(stage_dup.title).to eq 'My Stage' }
|
|
554
|
+
|
|
555
|
+
# Stage has an attribute external_id that is configured for amoeba with
|
|
556
|
+
# nullify. CustomStage inherits this configuration.
|
|
557
|
+
it { expect(stage_dup.external_id).to be_nil }
|
|
378
558
|
end
|
|
379
559
|
|
|
380
560
|
context 'polymorphic' do
|