amoeba 3.2.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 +30 -0
- data/.github/workflows/rspec.yml +49 -0
- data/.github/workflows/rubocop.yml +20 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +12 -8
- data/.rubocop_todo.yml +149 -0
- data/Appraisals +31 -38
- data/CHANGELOG.md +68 -0
- data/Gemfile +6 -2
- data/LICENSE.md +11 -0
- data/README.md +28 -17
- data/Rakefile +2 -0
- data/amoeba.gemspec +18 -17
- data/docs/contributing.md +19 -0
- data/docs/test_refactor.md +20 -0
- data/gemfiles/activerecord_6.1.gemfile +17 -8
- data/gemfiles/activerecord_7.0.gemfile +22 -0
- data/gemfiles/activerecord_7.1.gemfile +21 -0
- data/gemfiles/activerecord_8.0.gemfile +21 -0
- data/gemfiles/activerecord_8.1.gemfile +21 -0
- data/gemfiles/activerecord_head.gemfile +12 -13
- data/gemfiles/jruby_activerecord_7.0.gemfile +20 -0
- data/gemfiles/jruby_activerecord_head.gemfile +15 -15
- data/lib/amoeba/class_methods.rb +2 -0
- data/lib/amoeba/cloner.rb +15 -6
- data/lib/amoeba/config.rb +30 -53
- data/lib/amoeba/instance_methods.rb +3 -0
- data/lib/amoeba/macros/base.rb +4 -1
- data/lib/amoeba/macros/has_and_belongs_to_many.rb +2 -0
- data/lib/amoeba/macros/has_many.rb +2 -0
- data/lib/amoeba/macros/has_one.rb +4 -0
- data/lib/amoeba/macros.rb +2 -0
- data/lib/amoeba/version.rb +3 -1
- data/lib/amoeba.rb +6 -2
- data/spec/lib/amoeba_spec.rb +425 -231
- data/spec/spec_helper.rb +16 -6
- data/spec/support/data.rb +23 -14
- data/spec/support/models.rb +50 -40
- data/spec/support/schema.rb +2 -0
- metadata +53 -39
- data/.travis.yml +0 -110
- data/gemfiles/activerecord_4.2.gemfile +0 -18
- data/gemfiles/activerecord_5.0.gemfile +0 -18
- data/gemfiles/activerecord_5.1.gemfile +0 -18
- data/gemfiles/activerecord_5.2.gemfile +0 -18
- data/gemfiles/activerecord_6.0.gemfile +0 -18
- data/gemfiles/jruby_activerecord_6.1.gemfile +0 -19
data/spec/lib/amoeba_spec.rb
CHANGED
|
@@ -1,291 +1,434 @@
|
|
|
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/, 'cat')} (copied version)")
|
|
77
|
-
expect(new_post.comments.length).to eq(5)
|
|
78
|
-
expect(new_post.comments.select { |c| c.nerf == 'ratatat' && c.contents.nil? }.length).to eq(1)
|
|
79
|
-
expect(new_post.comments.select { |c| c.nerf == 'ratatat' }.length).to eq(2)
|
|
80
|
-
expect(new_post.comments.select { |c| c.nerf == 'bonk' }.length).to eq(1)
|
|
81
|
-
expect(new_post.comments.select { |c| c.nerf == 'bonkers' && c.contents.nil? }.length).to eq(1)
|
|
82
|
-
|
|
83
|
-
new_post.widgets.map(&:id).each do |id|
|
|
84
|
-
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) }
|
|
85
215
|
end
|
|
86
216
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
start_section_count = Section.all.length
|
|
107
|
-
start_prodsection_count = first_product.section_count
|
|
108
|
-
|
|
109
|
-
new_product = first_product.amoeba_dup
|
|
110
|
-
new_product.save
|
|
111
|
-
expect(new_product.errors.messages).to be_empty
|
|
112
|
-
|
|
113
|
-
end_image_count = first_product.images.count
|
|
114
|
-
end_newimage_count = new_product.images.count
|
|
115
|
-
end_section_count = Section.all.length
|
|
116
|
-
end_prodsection_count = first_product.section_count
|
|
117
|
-
end_newprodsection_count = new_product.section_count
|
|
118
|
-
|
|
119
|
-
expect(end_image_count).to eq(start_image_count)
|
|
120
|
-
expect(end_newimage_count).to eq(start_image_count)
|
|
121
|
-
expect(end_section_count).to eq(start_section_count)
|
|
122
|
-
expect(end_prodsection_count).to eq(start_prodsection_count)
|
|
123
|
-
expect(end_newprodsection_count).to eq(start_prodsection_count)
|
|
124
|
-
# }}}
|
|
125
|
-
|
|
126
|
-
# Inherited Class {{{
|
|
127
|
-
# Shirt {{{
|
|
128
|
-
old_product = Shirt.find(2)
|
|
129
|
-
|
|
130
|
-
start_image_count = old_product.images.count
|
|
131
|
-
start_section_count = Section.count
|
|
132
|
-
start_prodsection_count = old_product.section_count
|
|
133
|
-
|
|
134
|
-
new_product = old_product.amoeba_dup
|
|
135
|
-
new_product.save
|
|
136
|
-
expect(new_product.errors.messages).to be_empty
|
|
137
|
-
|
|
138
|
-
end_image_count = old_product.images.count
|
|
139
|
-
end_newimage_count = new_product.images.count
|
|
140
|
-
end_section_count = Section.count
|
|
141
|
-
end_prodsection_count = first_product.section_count
|
|
142
|
-
end_newprodsection_count = new_product.section_count
|
|
143
|
-
|
|
144
|
-
expect(end_image_count).to eq(start_image_count)
|
|
145
|
-
expect(end_newimage_count).to eq(start_image_count)
|
|
146
|
-
expect(end_section_count).to eq(start_section_count)
|
|
147
|
-
expect(end_prodsection_count).to eq(start_prodsection_count)
|
|
148
|
-
expect(end_newprodsection_count).to eq(start_prodsection_count)
|
|
149
|
-
# }}}
|
|
150
|
-
|
|
151
|
-
# Necklace {{{
|
|
152
|
-
old_product = Necklace.find(3)
|
|
153
|
-
|
|
154
|
-
start_image_count = old_product.images.count
|
|
155
|
-
start_section_count = Section.count
|
|
156
|
-
start_prodsection_count = old_product.section_count
|
|
157
|
-
|
|
158
|
-
new_product = old_product.amoeba_dup
|
|
159
|
-
new_product.save
|
|
160
|
-
expect(new_product.errors.messages).to be_empty
|
|
161
|
-
|
|
162
|
-
end_image_count = old_product.images.count
|
|
163
|
-
end_newimage_count = new_product.images.count
|
|
164
|
-
end_section_count = Section.count
|
|
165
|
-
end_prodsection_count = first_product.section_count
|
|
166
|
-
end_newprodsection_count = new_product.section_count
|
|
167
|
-
|
|
168
|
-
expect(end_image_count).to eq(start_image_count)
|
|
169
|
-
expect(end_newimage_count).to eq(start_image_count)
|
|
170
|
-
expect(end_section_count).to eq(start_section_count)
|
|
171
|
-
expect(end_prodsection_count).to eq(start_prodsection_count)
|
|
172
|
-
expect(end_newprodsection_count).to eq(start_prodsection_count)
|
|
173
|
-
# }}}
|
|
174
|
-
# }}}
|
|
175
|
-
# }}}
|
|
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
|
|
176
236
|
end
|
|
177
237
|
end
|
|
178
238
|
|
|
179
239
|
context 'Using a if condition' do
|
|
240
|
+
subject { post.amoeba_dup.save! }
|
|
241
|
+
|
|
180
242
|
before(:all) do
|
|
181
243
|
require ::File.dirname(__FILE__) + '/../support/data.rb'
|
|
182
244
|
end
|
|
245
|
+
|
|
183
246
|
before { ::Post.fresh_amoeba }
|
|
184
247
|
|
|
185
|
-
subject { post.amoeba_dup.save! }
|
|
186
248
|
let(:post) { Post.first }
|
|
187
249
|
|
|
188
250
|
it 'includes an association with truthy condition' do
|
|
189
251
|
::Post.amoeba do
|
|
190
252
|
include_association :comments, if: :truthy?
|
|
191
253
|
end
|
|
192
|
-
expect { subject }.to change
|
|
254
|
+
expect { subject }.to change(Comment, :count).by(3)
|
|
193
255
|
end
|
|
194
256
|
|
|
195
257
|
it 'does not include an association with a falsey condition' do
|
|
196
258
|
::Post.amoeba do
|
|
197
259
|
include_association :comments, if: :falsey?
|
|
198
260
|
end
|
|
199
|
-
expect { subject }.not_to change
|
|
261
|
+
expect { subject }.not_to change(Comment, :count)
|
|
200
262
|
end
|
|
201
263
|
|
|
202
264
|
it 'excludes an association with a truthy condition' do
|
|
203
265
|
::Post.amoeba do
|
|
204
266
|
exclude_association :comments, if: :truthy?
|
|
205
267
|
end
|
|
206
|
-
expect { subject }.not_to change
|
|
268
|
+
expect { subject }.not_to change(Comment, :count)
|
|
207
269
|
end
|
|
208
270
|
|
|
209
271
|
it 'does not exclude an association with a falsey condition' do
|
|
210
272
|
::Post.amoeba do
|
|
211
273
|
exclude_association :comments, if: :falsey?
|
|
212
274
|
end
|
|
213
|
-
expect { subject }.to change
|
|
275
|
+
expect { subject }.to change(Comment, :count).by(3)
|
|
214
276
|
end
|
|
215
277
|
|
|
216
278
|
it 'includes associations from a given array with a truthy condition' do
|
|
217
279
|
::Post.amoeba do
|
|
218
280
|
include_association [:comments], if: :truthy?
|
|
219
281
|
end
|
|
220
|
-
expect { subject }.to change
|
|
282
|
+
expect { subject }.to change(Comment, :count).by(3)
|
|
221
283
|
end
|
|
222
284
|
|
|
223
285
|
it 'does not include associations from a given array with a falsey condition' do
|
|
224
286
|
::Post.amoeba do
|
|
225
287
|
include_association [:comments], if: :falsey?
|
|
226
288
|
end
|
|
227
|
-
expect { subject }.not_to change
|
|
289
|
+
expect { subject }.not_to change(Comment, :count)
|
|
228
290
|
end
|
|
229
291
|
|
|
230
292
|
it 'does exclude associations from a given array with a truthy condition' do
|
|
231
293
|
::Post.amoeba do
|
|
232
294
|
exclude_association [:comments], if: :truthy?
|
|
233
295
|
end
|
|
234
|
-
expect { subject }.not_to change
|
|
296
|
+
expect { subject }.not_to change(Comment, :count)
|
|
235
297
|
end
|
|
236
298
|
|
|
237
299
|
it 'does not exclude associations from a given array with a falsey condition' do
|
|
238
300
|
::Post.amoeba do
|
|
239
301
|
exclude_association [:comments], if: :falsey?
|
|
240
302
|
end
|
|
241
|
-
expect { subject }.to change
|
|
303
|
+
expect { subject }.to change(Comment, :count).by(3)
|
|
242
304
|
end
|
|
243
305
|
end
|
|
244
306
|
|
|
245
|
-
context 'override' do
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
+
|
|
312
|
+
before do
|
|
313
|
+
Image.fresh_amoeba
|
|
314
|
+
Image.amoeba do
|
|
249
315
|
override ->(old, new) { new.product_id = 13 if old.filename == 'test.jpg' }
|
|
250
316
|
end
|
|
251
317
|
end
|
|
252
318
|
|
|
253
|
-
it
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
319
|
+
it { is_expected.to be_truthy }
|
|
320
|
+
it { expect(image_dup.product_id).to eq(13) }
|
|
321
|
+
end
|
|
322
|
+
|
|
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) }
|
|
259
327
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
328
|
+
before do
|
|
329
|
+
Image.fresh_amoeba
|
|
330
|
+
Image.amoeba do
|
|
331
|
+
override ->(old, new) { new.product_id = 13 if old.filename == 'test.jpg' }
|
|
332
|
+
end
|
|
265
333
|
end
|
|
334
|
+
|
|
335
|
+
it { is_expected.to be_truthy }
|
|
336
|
+
it { expect(image_dup.product_id).to eq(12) }
|
|
266
337
|
end
|
|
267
338
|
|
|
268
|
-
context 'nullify' do
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
339
|
+
context 'with a field configured with nullify' do
|
|
340
|
+
subject(:image_dup) { image.amoeba_dup }
|
|
341
|
+
|
|
342
|
+
let(:image) { ::Image.create(filename: 'test.jpg', product_id: 12) }
|
|
343
|
+
|
|
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: {})
|
|
273
358
|
end
|
|
274
359
|
end
|
|
275
360
|
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
278
367
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
|
282
425
|
end
|
|
283
426
|
end
|
|
284
427
|
|
|
285
428
|
context 'strict propagate' do
|
|
286
|
-
it '
|
|
287
|
-
|
|
288
|
-
box =
|
|
429
|
+
it 'calls #reset_amoeba' do
|
|
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')
|
|
289
432
|
new_box = box.amoeba_dup
|
|
290
433
|
expect(new_box.save).to be_truthy
|
|
291
434
|
end
|
|
@@ -312,55 +455,106 @@ describe 'amoeba' do
|
|
|
312
455
|
|
|
313
456
|
context 'preprocessing fields' do
|
|
314
457
|
subject { super_admin.amoeba_dup }
|
|
315
|
-
let(:super_admin) { ::SuperAdmin.create!(email: 'user@example.com', active: true, password: 'password') }
|
|
316
458
|
|
|
317
|
-
|
|
459
|
+
let(:super_admin) do
|
|
460
|
+
::SuperAdmin.create!(email: 'user@example.com', active: true, password: 'password')
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
it 'accepts "set" to set false to attribute' do
|
|
318
464
|
expect(subject.active).to be false
|
|
319
465
|
end
|
|
320
466
|
|
|
321
|
-
it '
|
|
467
|
+
it 'skips "prepend" if it equal to false' do
|
|
322
468
|
expect(subject.password).to eq('password')
|
|
323
469
|
end
|
|
324
470
|
end
|
|
325
471
|
|
|
326
|
-
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
|
+
|
|
327
504
|
let(:box) { Box.create }
|
|
505
|
+
let(:sub_sub_product) { BoxSubSubProduct.create(title: 'Awesome shoes') }
|
|
506
|
+
let(:another_product) { BoxAnotherProduct.create(title: 'Cleaning product') }
|
|
328
507
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
sub_sub_product.update(box: box, another_product: another_product)
|
|
333
|
-
expect(box.sub_products.first.another_product.title).to eq('Cleaning product')
|
|
334
|
-
expect(box.amoeba_dup.sub_products.first.another_product.title).to eq('Cleaning product')
|
|
335
|
-
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') }
|
|
336
511
|
end
|
|
337
512
|
|
|
338
|
-
context 'inheritance extended' do
|
|
339
|
-
|
|
340
|
-
|
|
513
|
+
context 'with inheritance extended' do
|
|
514
|
+
subject(:stage_dup) { stage.amoeba_dup }
|
|
515
|
+
|
|
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
|
|
341
525
|
stage.listeners.build(name: 'John')
|
|
342
526
|
stage.listeners.build(name: 'Helen')
|
|
343
527
|
stage.specialists.build(name: 'Jack')
|
|
344
528
|
stage.custom_rules.build(description: 'Kill all humans')
|
|
345
529
|
stage.save!
|
|
346
|
-
stage
|
|
347
530
|
end
|
|
348
531
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
it
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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 }
|
|
364
558
|
end
|
|
365
559
|
|
|
366
560
|
context 'polymorphic' do
|