releaf-content 1.0.8 → 1.0.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2d45911222a0289b8828b3ebc98199a018e8735c
4
- data.tar.gz: 0ec32f976aa909a836e5d1a31d7bf20d4591e4ae
3
+ metadata.gz: 7a3bb38f60b14152aac0b00614ff0d3ba1558b3c
4
+ data.tar.gz: cad1145449af7968692665a2804302f5b66384d5
5
5
  SHA512:
6
- metadata.gz: ee44f44bf754da129aadb0982666ee8a99b74f575bfd621a9027388f8a84d0490e034b2176e4fba8abaad08beea888fa1a94f25dab83fec36b0be6424c9a75d4
7
- data.tar.gz: 809a8f614e26754534feb4d31d67df03426b9f1229b22cf9b0d5477ab10507a977c9e7775749cba43e76f7c13f730a02cd02fa795a06620b418dfe7a95fbc713
6
+ metadata.gz: 7053c97279c704c480cf8eb46f66dbf94611dd34d50934cf78a1cc92aba8b2b69918c422115ecdcbdc8d6a6ba161254b4567ebf7b477010b9bbdedeb3019c795
7
+ data.tar.gz: d448e9e2920b641d3dc12ad41354e5f5469c088b5c0f3eda854d3710bf4e03e2b0b60aa909cce6ac1bfdc6a764fbc85aa03552bd314f977f1ed234499bef3246
@@ -11,6 +11,7 @@ module Releaf
11
11
  node.class.transaction do
12
12
  new_node = make_copy
13
13
  end
14
+
14
15
  new_node
15
16
  rescue ActiveRecord::RecordInvalid
16
17
  add_error_and_raise 'descendant invalid'
@@ -24,6 +25,16 @@ module Releaf
24
25
  add_error_and_raise("source or descendant node can't be parent of new node")
25
26
  end
26
27
 
28
+ def make_copy
29
+ new_node = duplicate_under
30
+
31
+ node.children.each do |child|
32
+ self.class.new(node: child, parent_id: new_node.id).make_copy
33
+ end
34
+
35
+ new_node
36
+ end
37
+
27
38
  def duplicate_under
28
39
  new_node = nil
29
40
  node.class.transaction do
@@ -34,57 +45,64 @@ module Releaf
34
45
  Releaf::Content::Node::SaveUnderParent.call(node: new_node, parent_id: parent_id)
35
46
  end
36
47
  end
48
+
37
49
  new_node
38
50
  end
39
51
 
40
52
  def duplicate_content
41
- return if node.content_id.blank?
53
+ if node.content.present?
54
+ new_content = duplicate_object(node.content)
55
+ new_content.save!
56
+ new_content
57
+ else
58
+ nil
59
+ end
60
+ end
42
61
 
43
- new_content = node.content.class.new(cloned_content_attributes)
44
- duplicate_content_dragonfly_attributes(new_content)
62
+ def duplicate_object object
63
+ object.deep_clone include: duplicatable_associations(object.class) do |original, copy|
64
+ supplement_object_duplication(original, copy)
65
+ end
66
+ end
45
67
 
46
- new_content.save!
47
- new_content
68
+ def supplement_object_duplication(original, copy)
69
+ duplicate_dragonfly_attachments(original, copy)
48
70
  end
49
71
 
50
- def cloned_content_attributes
51
- skippable_attribute_names = ["id"] + content_dragonfly_attributes
52
- node.content.attributes.except(*skippable_attribute_names)
72
+ def duplicatable_associations(owner_class)
73
+ Releaf::ResourceBase.new(owner_class).associations.collect do |association|
74
+ { association.name => duplicatable_associations(association.klass) }
75
+ end
53
76
  end
54
77
 
55
- def duplicate_content_dragonfly_attributes(new_content)
56
- content_dragonfly_attributes.each do |attribute_name|
57
- accessor_name = attribute_name.gsub("_uid", "")
58
- dragonfly_attachment = node.content.send(accessor_name)
78
+ def duplicate_dragonfly_attachments(original, copy)
79
+ attachment_keys = original.dragonfly_attachments.keys
80
+ return unless attachment_keys.present?
81
+
82
+ # during the dup() call the copy object has its dragonfly_attachments property duplicated from the original.
83
+ # here it gets set to nil to force its reinitialization on next access
84
+ copy.instance_variable_set(:@dragonfly_attachments, nil)
85
+
86
+ # all uids must be cleared in a separate loop before accessing any of the attachments
87
+ # (accessing any of the accessors regenerates the dragonfly_attachments collection and loads all uids)
88
+ attachment_keys.each { |accessor| copy.send("#{accessor}_uid=", nil) }
59
89
 
60
- if dragonfly_attachment.present?
90
+ # once all uids have been reset, each attachment may be reassigned from the original.
91
+ # reassignment forces dragonfly to internally treat the new attachment as a copy
92
+ attachment_keys.each do |accessor|
93
+ attachment = original.send(accessor)
94
+
95
+ if attachment.present?
61
96
  begin
62
- dragonfly_attachment.path # verify that the file exists
97
+ attachment.path # verify that the file exists
63
98
  rescue Dragonfly::Job::Fetch::NotFound
64
- dragonfly_attachment = nil
99
+ attachment = nil
65
100
  end
66
101
  end
67
102
 
68
- new_content.send("#{attribute_name}=", nil)
69
- new_content.send("#{accessor_name}=", dragonfly_attachment)
103
+ copy.send("#{accessor}=", attachment)
70
104
  end
71
105
  end
72
-
73
- def content_dragonfly_attributes
74
- node.content.class.attribute_names.select do |attribute_name|
75
- Releaf::Builders::Utilities::ResolveAttributeFieldMethodName.new(object: node.content, attribute_name: attribute_name).file?
76
- end
77
- end
78
-
79
- def make_copy
80
- new_node = duplicate_under
81
-
82
- node.children.each do |child|
83
- self.class.new(node: child, parent_id: new_node.id).make_copy
84
- end
85
-
86
- new_node
87
- end
88
106
  end
89
107
  end
90
108
  end
@@ -1,5 +1,6 @@
1
1
  require 'awesome_nested_set'
2
2
  require 'stringex'
3
+ require 'deep_cloneable'
3
4
 
4
5
  module Releaf::Content
5
6
  require 'releaf/content/engine'
@@ -64,7 +64,7 @@ describe "Nodes services (copy, move)" do
64
64
 
65
65
  end
66
66
 
67
- describe "Copying", create_nodes: true do
67
+ describe "Basic node copying", create_nodes: true do
68
68
  before create_nodes: true do
69
69
  @home_page_node = create(:home_page_node, locale: "lv")
70
70
  @home_page_node_2 = create(:home_page_node, locale: "en")
@@ -204,4 +204,187 @@ describe "Nodes services (copy, move)" do
204
204
  end
205
205
  end
206
206
  end
207
+
208
+
209
+ feature "Deep copying of content nodes", js: true do
210
+
211
+ let(:node_class) { Node }
212
+ let(:locale) { "en" }
213
+
214
+ background do
215
+ Rails.cache.clear
216
+ # preload ActsAsNode classes
217
+ Rails.application.eager_load!
218
+
219
+ create(:home_page_node, name: "Root page", locale: "en", slug: "en")
220
+ auth_as_user
221
+ end
222
+
223
+ def expect_different_values original, copy
224
+ expect(original).to be_present
225
+ expect(copy).to be_present
226
+ expect(original).to_not eq copy
227
+ end
228
+
229
+ scenario "Deep copying of content nodes with nested associations and files" do
230
+ visit "/admin/nodes/"
231
+ open_toolbox("Add child", node_class.first, ".view-index .collection li")
232
+
233
+ dummy_file_path = File.expand_path('../fixtures/dummy.png', __dir__)
234
+
235
+ within('.dialog.content-type.initialized') do
236
+ click_link "Banner page"
237
+ end
238
+
239
+ expect(page).to have_css('input[type="text"][name="resource[content_type]"][value="Banner page"]')
240
+
241
+ fill_in 'Name', with: "Banner page"
242
+ fill_in 'Slug', with: "banner-page"
243
+
244
+ attach_file('Top banner', dummy_file_path)
245
+ attach_file('Bottom banner', dummy_file_path)
246
+
247
+ within('section.nested[data-name="banner_groups"]') do
248
+ add_nested_item "banner_groups", 0 do
249
+ fill_in "Title", with: "Banner group 1 title"
250
+
251
+ within('section.nested[data-name="banners"]') do
252
+ add_nested_item "banners", 0 do
253
+ attach_file('Image', dummy_file_path)
254
+ fill_in "Url", with: "Banner-1-url"
255
+ end
256
+ add_nested_item "banners", 1 do
257
+ attach_file('Image', dummy_file_path)
258
+ fill_in "Url", with: "Banner-2-url"
259
+ end
260
+ end
261
+ end
262
+
263
+ add_nested_item "banner_groups", 1 do
264
+ fill_in "Title", with: "Banner group 2 title"
265
+ end
266
+ end
267
+
268
+ wait_for_all_richtexts
269
+ save_and_check_response "Create succeeded"
270
+
271
+ open_toolbox("Copy")
272
+
273
+ within('.dialog.copy.initialized') do
274
+ find('label', text: "Root page").click
275
+ click_button "Copy"
276
+ end
277
+
278
+ expect(page).to have_css('body > .notifications .notification[data-id="resource_status"][data-type="success"]', text: "Copy succeeded")
279
+
280
+ # verify record count in db
281
+ expect(node_class.where(content_type: 'BannerPage').count).to eq 2
282
+ expect(BannerGroup.count).to eq 4
283
+ expect(Banner.count).to eq 4
284
+
285
+
286
+ # verify that direct and nested files are duplicated correctly
287
+ [
288
+ { original: BannerPage.first, copy: BannerPage.last, accessor: :top_banner },
289
+ { original: BannerPage.first, copy: BannerPage.last, accessor: :bottom_banner },
290
+ {
291
+ original: BannerPage.first.banner_groups.first.banners.first,
292
+ copy: BannerPage.last.banner_groups.first.banners.first,
293
+ accessor: :image
294
+ }
295
+ ].each do |entry|
296
+ original = entry[:original]
297
+ copy = entry[:copy]
298
+ accessor = entry[:accessor]
299
+ expect(original.id).to_not eq copy.id
300
+
301
+ # verify that uids exist and are different
302
+ expect_different_values(original.send("#{accessor}_uid"), copy.send("#{accessor}_uid"))
303
+
304
+ # verify files exist and are different
305
+ original_file = original.send(accessor)
306
+ copied_file = copy.send(accessor)
307
+
308
+ expect_different_values(original_file.path, copied_file.path)
309
+ expect(original_file.path).to start_with Dragonfly.app.datastore.root_path
310
+ expect(copied_file.path).to start_with Dragonfly.app.datastore.root_path
311
+ expect(File.exist?(original_file.path)).to be true
312
+ expect(File.exist?(copied_file.path)).to be true
313
+ end
314
+
315
+
316
+ # change values in the copied node to make sure associations are not linked
317
+ original_id = node_class.where(content_type: "BannerPage").order(:item_position).first.id
318
+ copy_id = node_class.where(content_type: "BannerPage").order(:item_position).last.id
319
+ expect(copy_id).to_not eq original_id
320
+
321
+ visit "/admin/nodes/#{copy_id}/edit/"
322
+
323
+ within('section.nested[data-name="banner_groups"]') do
324
+ within('.item[data-name="banner_groups"][data-index="0"]') do
325
+ expect(page).to have_field('Title', with: 'Banner group 1 title')
326
+ fill_in "Title", with: "Copied banner group 1 title"
327
+
328
+ within('.item[data-name="banners"][data-index="0"]') do
329
+ expect(page).to have_field('Url', with: 'Banner-1-url')
330
+ fill_in "Url", with: "Copied-banner-1-url"
331
+ end
332
+ within('.item[data-name="banners"][data-index="1"]') do
333
+ expect(page).to have_field('Url', with: 'Banner-2-url')
334
+ fill_in "Url", with: "Copied-banner-2-url"
335
+ end
336
+ end
337
+ within('.item[data-name="banner_groups"][data-index="1"]') do
338
+ expect(page).to have_field('Title', with: 'Banner group 2 title')
339
+ fill_in "Title", with: "Copied banner group 2 title"
340
+ end
341
+ end
342
+
343
+ copied_file_urls = {
344
+ top_banner: find('.field[data-name="top_banner"]').find_link[:href],
345
+ bottom_banner: find('.field[data-name="bottom_banner"]').find_link[:href],
346
+ nested_banner: find('.item[data-name="banner_groups"][data-index="0"] .item[data-name="banners"][data-index="0"] .field[data-name="image"]').find_link[:href]
347
+ }
348
+
349
+ wait_for_all_richtexts
350
+ save_and_check_response "Update succeeded"
351
+
352
+ # verify that the original banner page still has the old values
353
+ visit "/admin/nodes/#{original_id}/edit/"
354
+
355
+ within('section.nested[data-name="banner_groups"]') do
356
+ within('.item[data-name="banner_groups"][data-index="0"]') do
357
+ expect(page).to have_field('Title', with: 'Banner group 1 title')
358
+
359
+ within('.item[data-name="banners"][data-index="0"]') do
360
+ expect(page).to have_field('Url', with: 'Banner-1-url')
361
+ end
362
+ within('.item[data-name="banners"][data-index="1"]') do
363
+ expect(page).to have_field('Url', with: 'Banner-2-url')
364
+ end
365
+ end
366
+ within('.item[data-name="banner_groups"][data-index="1"]') do
367
+ expect(page).to have_field('Title', with: 'Banner group 2 title')
368
+ end
369
+ end
370
+
371
+ original_file_urls = {
372
+ top_banner: find('.field[data-name="top_banner"]').find_link[:href],
373
+ bottom_banner: find('.field[data-name="bottom_banner"]').find_link[:href],
374
+ nested_banner: find('.item[data-name="banner_groups"][data-index="0"] .item[data-name="banners"][data-index="0"] .field[data-name="image"]').find_link[:href]
375
+ }
376
+
377
+ # make sure that original and copied file urls are different and working
378
+ original_file_urls.each do |key, original_url|
379
+ expect_different_values(original_url, copied_file_urls[key])
380
+
381
+ [original_url, copied_file_urls[key]].each do |file_url|
382
+ visit file_url
383
+ expect(page.status_code).to eq 200
384
+ end
385
+ end
386
+
387
+ end
388
+
389
+ end
207
390
  end
Binary file
@@ -1,90 +1,59 @@
1
1
  require "rails_helper"
2
2
 
3
3
  describe Releaf::Content::Node::Copy do
4
- class DummyNodeServiceIncluder
5
- include Releaf::Content::Node::Service
6
- end
7
4
 
8
- let(:node){ Node.new }
9
- subject{ described_class.new(node: node, parent_id: 12) }
5
+ let(:root_node) { create(:home_page_node) }
10
6
 
11
- describe "#duplicate_content" do
12
- let(:node) { create(:node) }
7
+ let(:node) { create(node_factory, parent: root_node) }
8
+ let(:node_factory) { :text_page_node }
13
9
 
14
- context "when content_id is blank" do
15
- it "returns nil" do
16
- expect( subject.duplicate_content ).to be nil
17
- end
18
- end
10
+ let(:content_factory) { :text_page }
11
+ let(:content_attributes) { { } }
19
12
 
20
- context "when content_id is not blank" do
21
- let(:node) { create(:home_page_node) }
13
+ let(:complex_node_factory) { :banner_page_node }
14
+ let(:complex_content_factory) { :banner_page }
15
+ let(:complex_content_duplicatable_associations) {
16
+ [
17
+ {
18
+ banner_groups: [
19
+ { banners: [] }
20
+ ]
21
+ }
22
+ ] }
22
23
 
23
- it "returns saved duplicated content" do
24
- content = HomePage.new
25
- expect( content ).to receive(:save!)
26
- expect( node.content.class ).to receive(:new).and_return(content)
27
- expect( subject.duplicate_content ).to eq content
28
- end
24
+ let(:file) { File.new(File.expand_path('../../../../fixtures/dummy.png', __dir__)) }
29
25
 
30
- it "reassigns dragonfly accessors" do
31
- content = HomePage.new
32
- expect( content ).to receive(:save!)
33
- allow( node.content.class ).to receive(:new).and_return(content)
34
- expect( subject ).to receive(:duplicate_content_dragonfly_attributes).with(content)
35
- expect( subject.duplicate_content ).to eq content
36
- end
26
+ let(:original) { create(content_factory, content_attributes) }
37
27
 
38
- it "doesn't return same as original content" do
39
- result = subject.duplicate_content
40
- expect( result ).to be_an_instance_of HomePage
41
- expect( result.id ).to_not eq node.content_id
42
- end
43
- end
44
- end
45
28
 
46
- describe "#cloned_content_attributes" do
47
- it "returns attributes without id and dragonfly attributes" do
48
- node.content = HomePage.new(id: 42, banner_uid: "re", intro_text_html: "some text")
29
+ describe "#make_copy" do
30
+ # this is a double check to verify file duplication by doing the full copying process.
31
+ # #duplicate_dragonfly_attachments is tested separately in more detail below
32
+ subject { described_class.new(node: node, parent_id: root_node.id ) }
49
33
 
50
- expect(subject.cloned_content_attributes).to have_key("intro_text_html")
51
- expect(subject.cloned_content_attributes).not_to include(:id, :banner_uid)
52
- end
53
- end
34
+ context "when node content has dragonfly file fields" do
35
+ let(:node_factory) { complex_node_factory }
54
36
 
55
- describe "#duplicate_content_dragonfly_attributes" do
56
- context "when dragonfly file is present" do
57
- it "reassigns dragonfly accessors to given content instance from node content" do
58
- old_content = HomePage.create(banner: File.new("releaf-core/spec/fixtures/cs.png"))
59
- new_content = HomePage.new()
60
- node.content = old_content
61
-
62
- expect(new_content).to receive(:banner=).with(old_content.banner)
63
- expect(new_content).to receive(:banner_uid=).with(nil)
64
- subject.duplicate_content_dragonfly_attributes(new_content)
65
- end
66
- end
37
+ it "duplicates dragonfly attachments, storing them as separate files that are no longer linked" do
38
+ node.content.top_banner = file
39
+ node.save!
40
+ copy = subject.make_copy
67
41
 
68
- context "when dragonfly file is not present" do
69
- it "doesn't reassigns dragonfly accessors to given content instance from node content" do
70
- old_content = HomePage.new(banner_uid: "yy")
71
- new_content = HomePage.new()
72
- node.content = old_content
42
+ # make sure the uids are non-empty and different
43
+ expect(node.content.top_banner_uid).to be_present
44
+ expect(copy.content.top_banner_uid).to be_present
45
+ expect(copy.content.top_banner_uid).to_not eq node.content.top_banner_uid
73
46
 
74
- expect(new_content).to receive(:banner=).with(nil)
75
- expect(new_content).to receive(:banner_uid=).with(nil)
76
- subject.duplicate_content_dragonfly_attributes(new_content)
47
+ # make sure the files are stored and exist
48
+ expect(copy.content.top_banner.path).to start_with Dragonfly.app.datastore.root_path
49
+ expect(node.content.top_banner.path).to start_with Dragonfly.app.datastore.root_path
50
+ expect(copy.content.top_banner.path).to_not eq node.content.top_banner.path
77
51
  end
78
52
  end
79
- end
80
53
 
81
- describe "#content_dragonfly_attributes" do
82
- it "returns array of node content object dragonfly attributes" do
83
- node.content = HomePage.new
84
- expect(subject.content_dragonfly_attributes).to eq(["banner_uid"])
85
- end
86
54
  end
87
55
 
56
+
88
57
  describe "#duplicate_under" do
89
58
  let!(:source_node) { create(:node, locale: "lv") }
90
59
  let!(:target_node) { create(:node, locale: "en") }
@@ -112,4 +81,251 @@ describe Releaf::Content::Node::Copy do
112
81
  subject.duplicate_under
113
82
  end
114
83
  end
84
+
85
+
86
+ describe "#duplicate_content" do
87
+
88
+ let(:content) { node.content }
89
+
90
+ before do
91
+ allow(subject).to receive(:node).and_return(node)
92
+ end
93
+
94
+ context "when node has a content object" do
95
+
96
+ it "calls #duplicate_object with current content" do
97
+ expect(subject).to receive(:duplicate_object).with(content).and_return(content)
98
+ subject.duplicate_content
99
+ end
100
+
101
+ it "saves the duplicated result" do
102
+ new_content = subject.duplicate_content
103
+ expect(new_content.new_record?).to be false
104
+ end
105
+
106
+ it "returns the newly saved content" do
107
+ new_content = subject.duplicate_content
108
+ expect(new_content.id).to_not eq content.id
109
+ end
110
+ end
111
+
112
+ context "when node does not have a content object" do
113
+ before do
114
+ node.content = nil
115
+ end
116
+
117
+ it "does nothing" do
118
+ expect(subject).to_not receive(:duplicate_object)
119
+ expect(node).to_not receive(:save!)
120
+ subject.duplicate_content
121
+ end
122
+ it "returns nil" do
123
+ expect(subject.duplicate_content).to be_nil
124
+ end
125
+ end
126
+
127
+ end
128
+
129
+
130
+ describe "#duplicate_object" do
131
+
132
+ context "when object has no duplicatable associations" do
133
+ let(:content_factory) { :text_page }
134
+ let(:content_attributes) { { text_html: "html" } }
135
+
136
+ it "builds an unsaved copy of the object" do
137
+ copy = subject.duplicate_object(original)
138
+ expect(copy.class).to be original.class
139
+ expect(copy.new_record?).to be true
140
+
141
+ expect(copy.text_html).to eq original.text_html
142
+ end
143
+
144
+ it "uses #deep_clone for copying" do
145
+ expect(subject).to receive(:duplicatable_associations).with(original.class).and_call_original
146
+ expect(original).to receive(:deep_clone).with( include: [] ).and_call_original
147
+ subject.duplicate_object(original)
148
+ end
149
+
150
+ it "calls #supplement_object_duplication with the original object and its new copy" do
151
+ copy = original.dup
152
+
153
+ # stub #dup (which is called by deep_clone internally) so that it returns a known instance
154
+ allow(original).to receive(:dup).and_return(copy)
155
+
156
+ expect(subject).to receive(:supplement_object_duplication).with(original, copy)
157
+ subject.duplicate_object(original)
158
+ end
159
+
160
+ end
161
+
162
+ context "when object has duplicatable associations" do
163
+ let(:content_factory) { complex_content_factory }
164
+
165
+ let(:content_attributes) do
166
+ {
167
+ intro_text_html: "Intro html",
168
+ banner_groups_attributes: [
169
+ {
170
+ title: "Group title",
171
+ banners_attributes: [
172
+ url: "Banner url"
173
+ ]
174
+ },
175
+ ]
176
+ }
177
+ end
178
+
179
+ it "passes duplicatable associations to #deep_clone" do
180
+ expect(original).to receive(:deep_clone).with( include: complex_content_duplicatable_associations ).and_call_original
181
+ subject.duplicate_object(original)
182
+ end
183
+
184
+ it "builds an unsaved copy with nested copies of associated objects" do
185
+ copy = subject.duplicate_object(original)
186
+ expect(copy.class).to be original.class
187
+ expect(copy.new_record?).to be true
188
+ expect(copy.intro_text_html).to eq original.intro_text_html
189
+
190
+ original_item = original.banner_groups.first
191
+ copied_item = copy.banner_groups.first
192
+ expect(copied_item.class).to be original_item.class
193
+ expect(copied_item.new_record?).to be true
194
+ expect(copied_item.title).to eq original_item.title
195
+
196
+ original_nested_item = original.banner_groups.first.banners.first
197
+ copied_nested_item = copy.banner_groups.first.banners.first
198
+ expect(copied_nested_item.class).to be original_nested_item.class
199
+ expect(copied_nested_item.new_record?).to be true
200
+ expect(copied_nested_item.url).to eq original_nested_item.url
201
+ end
202
+
203
+ it "calls #supplement_object_duplication on all cloned objects" do
204
+ original_item = original.banner_groups.first
205
+ original_nested_item = original_item.banners.first
206
+
207
+ expect(subject).to receive(:supplement_object_duplication).with(original, kind_of(original.class))
208
+ expect(subject).to receive(:supplement_object_duplication).with(original_item, kind_of(original_item.class))
209
+ expect(subject).to receive(:supplement_object_duplication).with(original_nested_item, kind_of(original_nested_item.class))
210
+ subject.duplicate_object(original)
211
+ end
212
+
213
+ end
214
+
215
+ end
216
+
217
+ describe "#supplement_object_duplication" do
218
+ it "calls #duplicate_dragonfly_attachments with given original and copy" do
219
+ expect(subject).to receive(:duplicate_dragonfly_attachments).with(:foo, :bar)
220
+ subject.supplement_object_duplication(:foo, :bar)
221
+ end
222
+ end
223
+
224
+ describe "#duplicatable_associations" do
225
+ let(:content_class) { build(content_factory).class }
226
+
227
+ it "uses Releaf::ResourceBase to detect duplicatable associations" do
228
+ expect(Releaf::ResourceBase).to receive(:new).with(content_class).and_call_original
229
+ subject.duplicatable_associations(content_class)
230
+ end
231
+
232
+ context "when given class has duplicatable associations" do
233
+ it "returns duplicatable association names as nested arrays of hashes" do
234
+ expect(subject.duplicatable_associations(content_class)).to eq []
235
+ end
236
+ end
237
+
238
+ context "when given class has no duplicatable associations" do
239
+ let(:content_factory) { complex_content_factory }
240
+ it "returns an empty array" do
241
+ expect(subject.duplicatable_associations(content_class)).to eq complex_content_duplicatable_associations
242
+ end
243
+ end
244
+ end
245
+
246
+
247
+ describe "#duplicate_dragonfly_attachments" do
248
+ let(:content_factory) { complex_content_factory }
249
+ let(:copy) { original.dup }
250
+
251
+ context "when the original object's dragonfly attributes" do
252
+
253
+ context "are blank" do
254
+ it "sets the file attributes to nil on the given copy" do
255
+ expect(copy).to receive(:top_banner_uid=).with(nil).ordered
256
+ expect(copy).to receive(:top_banner=).with(nil).ordered
257
+ subject.duplicate_dragonfly_attachments(original, copy)
258
+ end
259
+ end
260
+
261
+ context "contain files" do
262
+
263
+ it "separates the attachments so that dragonfly treats them as separate files" do
264
+ # it is important that the owner class used in this test has at least two dragonfly attributes
265
+ # and the test is not using the first one.
266
+ # (an earlier implementation worked only with the first file
267
+ # and would have passed the test if the test used the first file)
268
+ expect(original.dragonfly_attachments.keys.find_index(:bottom_banner)).to be > 0
269
+
270
+ original.bottom_banner = file
271
+ original.save!
272
+
273
+ subject.duplicate_dragonfly_attachments(original, copy)
274
+ copy.save!
275
+
276
+ # refetch the objects. calling #reload on them is not enough
277
+ original_id = original.id
278
+ copy_id = copy.id
279
+ klass = original.class
280
+
281
+ original = klass.find(original_id)
282
+ copy = klass.find(copy_id)
283
+
284
+ # make sure the uids are non-empty and different
285
+ expect(original.bottom_banner_uid).to be_present
286
+ expect(copy.bottom_banner_uid).to be_present
287
+ expect(copy.bottom_banner_uid).to_not eq original.bottom_banner_uid
288
+
289
+ # make sure the files are stored and exist
290
+ expect(copy.bottom_banner.path).to start_with Dragonfly.app.datastore.root_path
291
+ expect(original.bottom_banner.path).to start_with Dragonfly.app.datastore.root_path
292
+ expect(copy.bottom_banner.path).to_not eq original.bottom_banner.path
293
+
294
+ # make sure the metadata is readable as well
295
+ expect(copy.bottom_banner.format).to eq original.bottom_banner.format
296
+ end
297
+
298
+ end
299
+
300
+ context "contain files that are missing" do
301
+
302
+ it "sets the file attributes to nil on the given copy" do
303
+ original.top_banner = file
304
+ original.save!
305
+
306
+ original_id = original.id
307
+
308
+ # simulate deletion from filesystem by setting the uid to an invalid path
309
+ original.top_banner_uid = "xxx" + original.top_banner_uid
310
+ original.save!
311
+
312
+ klass = original.class
313
+ original = klass.find(original_id)
314
+
315
+ subject.duplicate_dragonfly_attachments(original, copy)
316
+ copy.save!
317
+ copy_id = copy.id
318
+ copy = klass.find(copy_id)
319
+
320
+ expect(copy.top_banner_uid).to be_nil
321
+ expect(copy.top_banner).to be_nil
322
+ end
323
+
324
+ end
325
+
326
+ end
327
+ end
328
+
329
+
330
+
115
331
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: releaf-content
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.8
4
+ version: 1.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - CubeSystems
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-31 00:00:00.000000000 Z
11
+ date: 2017-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: releaf-core
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 1.0.8
19
+ version: 1.0.9
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 1.0.8
26
+ version: 1.0.9
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: stringex
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: deep_cloneable
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.2.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.2.2
55
69
  description: Content subsystem for releaf
56
70
  email: info@cubesystems.lv
57
71
  executables: []
@@ -100,6 +114,7 @@ files:
100
114
  - spec/controllers/releaf/content/nodes_controller_spec.rb
101
115
  - spec/features/nodes_services_spec.rb
102
116
  - spec/features/nodes_spec.rb
117
+ - spec/fixtures/dummy.png
103
118
  - spec/lib/releaf/content/acts_as_node_spec.rb
104
119
  - spec/lib/releaf/content/configuration_spec.rb
105
120
  - spec/lib/releaf/content/engine_spec.rb
@@ -134,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
149
  version: '0'
135
150
  requirements: []
136
151
  rubyforge_project:
137
- rubygems_version: 2.5.1
152
+ rubygems_version: 2.6.10
138
153
  signing_key:
139
154
  specification_version: 4
140
155
  summary: Node and content routes support for releaf
@@ -146,6 +161,7 @@ test_files:
146
161
  - spec/controllers/releaf/content/nodes_controller_spec.rb
147
162
  - spec/features/nodes_services_spec.rb
148
163
  - spec/features/nodes_spec.rb
164
+ - spec/fixtures/dummy.png
149
165
  - spec/lib/releaf/content/acts_as_node_spec.rb
150
166
  - spec/lib/releaf/content/configuration_spec.rb
151
167
  - spec/lib/releaf/content/engine_spec.rb