summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/related_assets_controller.rb18
-rw-r--r--app/models/node.rb12
-rw-r--r--test/controllers/related_assets_controller_test.rb44
3 files changed, 55 insertions, 19 deletions
diff --git a/app/controllers/related_assets_controller.rb b/app/controllers/related_assets_controller.rb
index ca894f2f..3aa6d3db 100644
--- a/app/controllers/related_assets_controller.rb
+++ b/app/controllers/related_assets_controller.rb
@@ -2,6 +2,10 @@ class RelatedAssetsController < ApplicationController
2 before_action :login_required 2 before_action :login_required
3 before_action :find_node 3 before_action :find_node
4 4
5 rescue_from LockedByAnotherUser do
6 head :locked
7 end
8
5 def search 9 def search
6 term = params[:search_term].to_s.strip 10 term = params[:search_term].to_s.strip
7 attached_ids = @node.editable_page.related_assets.pluck(:asset_id) 11 attached_ids = @node.editable_page.related_assets.pluck(:asset_id)
@@ -19,8 +23,9 @@ class RelatedAssetsController < ApplicationController
19 end 23 end
20 24
21 def create 25 def create
26 page = @node.ensure_autosave!(current_user)
22 asset = Asset.find(params[:asset_id]) 27 asset = Asset.find(params[:asset_id])
23 related = @node.editable_page.related_assets.find_or_create_by!(asset: asset) 28 related = page.related_assets.find_or_create_by!(asset: asset)
24 29
25 render json: { 30 render json: {
26 id: related.id, 31 id: related.id,
@@ -35,22 +40,25 @@ class RelatedAssetsController < ApplicationController
35 end 40 end
36 41
37 def destroy 42 def destroy
38 @node.editable_page.related_assets.find(params[:id]).destroy 43 page = @node.ensure_autosave!(current_user)
44 source = RelatedAsset.find(params[:id])
45 page.related_assets.find_by!(:asset_id => source.asset_id).destroy
39 head :ok 46 head :ok
40 end 47 end
41 48
42 def update 49 def update
43 related = @node.editable_page.related_assets.find(params[:id]) 50 page = @node.ensure_autosave!(current_user)
51 source = RelatedAsset.find(params[:id])
52 related = page.related_assets.find_by!(:asset_id => source.asset_id)
44 53
45 if params.key?(:headline) 54 if params.key?(:headline)
46 RelatedAsset.transaction do 55 RelatedAsset.transaction do
47 @node.editable_page.related_assets.update_all(headline: false) 56 page.related_assets.update_all(headline: false)
48 related.update!(headline: true) if params[:headline] == "true" 57 related.update!(headline: true) if params[:headline] == "true"
49 end 58 end
50 else 59 else
51 related.insert_at(params[:position].to_i) 60 related.insert_at(params[:position].to_i)
52 end 61 end
53
54 head :ok 62 head :ok
55 end 63 end
56 64
diff --git a/app/models/node.rb b/app/models/node.rb
index 274b2f94..02502f6c 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -112,18 +112,18 @@ class Node < ApplicationRecord
112 end 112 end
113 end 113 end
114 114
115 # Creates or updates the autosave buffer from the given attributes. 115 def ensure_autosave! current_user
116 # Autosave rows are never associated to the node via node_id -- they
117 # must never appear in self.pages / the revisions list, which is the
118 # whole reason autosave exists as a separate, unversioned layer.
119 def autosave! attributes, current_user
120 assert_locked_by! current_user 116 assert_locked_by! current_user
121
122 unless self.autosave 117 unless self.autosave
123 self.autosave = Page.create!(:editor => current_user) 118 self.autosave = Page.create!(:editor => current_user)
124 self.autosave.clone_attributes_from(self.draft || self.head) if self.draft || self.head 119 self.autosave.clone_attributes_from(self.draft || self.head) if self.draft || self.head
125 self.save! 120 self.save!
126 end 121 end
122 self.autosave
123 end
124
125 def autosave! attributes, current_user
126 ensure_autosave!(current_user)
127 self.autosave.assign_attributes(attributes) 127 self.autosave.assign_attributes(attributes)
128 self.autosave.save! 128 self.autosave.save!
129 self.autosave 129 self.autosave
diff --git a/test/controllers/related_assets_controller_test.rb b/test/controllers/related_assets_controller_test.rb
index ced4b74d..fd30dddd 100644
--- a/test/controllers/related_assets_controller_test.rb
+++ b/test/controllers/related_assets_controller_test.rb
@@ -38,19 +38,24 @@ class RelatedAssetsControllerTest < ActionController::TestCase
38 test "create attaches an asset to the node's editable page" do 38 test "create attaches an asset to the node's editable page" do
39 login_as :quentin 39 login_as :quentin
40 node = Node.root.children.create!(:slug => "related_assets_create_test") 40 node = Node.root.children.create!(:slug => "related_assets_create_test")
41 node.lock_for_editing!(users(:quentin))
41 asset = Asset.create!(:name => "erfa-photo", :upload_content_type => "image/png") 42 asset = Asset.create!(:name => "erfa-photo", :upload_content_type => "image/png")
42 43
43 post :create, params: { :node_id => node.id, :asset_id => asset.id } 44 post :create, params: { :node_id => node.id, :asset_id => asset.id }
44 45
45 assert_response :success 46 assert_response :success
46 assert_includes node.draft.reload.related_assets.map(&:asset_id), asset.id 47 current = node.reload.editable_page
48 assert_equal node.autosave, current, "mutation should have created an autosave layer"
49 assert_includes current.related_assets.map(&:asset_id), asset.id
47 json = JSON.parse(response.body) 50 json = JSON.parse(response.body)
48 assert json["url"].present? 51 assert json["url"].present?
52 assert_empty node.draft.reload.related_assets, "the draft must stay untouched"
49 end 53 end
50 54
51 test "create does not duplicate an already-attached asset" do 55 test "create does not duplicate an already-attached asset" do
52 login_as :quentin 56 login_as :quentin
53 node = Node.root.children.create!(:slug => "related_assets_dup_test") 57 node = Node.root.children.create!(:slug => "related_assets_dup_test")
58 node.lock_for_editing!(users(:quentin))
54 asset = Asset.create!(:name => "erfa-photo-2", :upload_content_type => "image/png") 59 asset = Asset.create!(:name => "erfa-photo-2", :upload_content_type => "image/png")
55 node.draft.assets << asset 60 node.draft.assets << asset
56 61
@@ -63,19 +68,22 @@ class RelatedAssetsControllerTest < ActionController::TestCase
63 test "destroy removes the attached asset" do 68 test "destroy removes the attached asset" do
64 login_as :quentin 69 login_as :quentin
65 node = Node.root.children.create!(:slug => "related_assets_destroy_test") 70 node = Node.root.children.create!(:slug => "related_assets_destroy_test")
71 node.lock_for_editing!(users(:quentin))
66 asset = Asset.create!(:name => "old-photo", :upload_content_type => "image/png") 72 asset = Asset.create!(:name => "old-photo", :upload_content_type => "image/png")
67 node.draft.assets << asset 73 node.draft.assets << asset
68 related = node.draft.related_assets.first
69 74
75 related = node.draft.related_assets.first
70 delete :destroy, params: { :node_id => node.id, :id => related.id } 76 delete :destroy, params: { :node_id => node.id, :id => related.id }
71 77
72 assert_response :success 78 assert_response :success
73 assert_equal 0, node.draft.reload.related_assets.count 79 assert_equal 0, node.reload.editable_page.related_assets.count
80 assert_equal 1, node.draft.reload.related_assets.count, "the draft must stay untouched"
74 end 81 end
75 82
76 test "update reorders the attached assets" do 83 test "update reorders the attached assets" do
77 login_as :quentin 84 login_as :quentin
78 node = Node.root.children.create!(:slug => "related_assets_reorder_test") 85 node = Node.root.children.create!(:slug => "related_assets_reorder_test")
86 node.lock_for_editing!(users(:quentin))
79 first = Asset.create!(:name => "first-photo", :upload_content_type => "image/png") 87 first = Asset.create!(:name => "first-photo", :upload_content_type => "image/png")
80 second = Asset.create!(:name => "second-photo", :upload_content_type => "image/png") 88 second = Asset.create!(:name => "second-photo", :upload_content_type => "image/png")
81 node.draft.assets << first 89 node.draft.assets << first
@@ -85,13 +93,15 @@ class RelatedAssetsControllerTest < ActionController::TestCase
85 patch :update, params: { :node_id => node.id, :id => second_related.id, :position => 1 } 93 patch :update, params: { :node_id => node.id, :id => second_related.id, :position => 1 }
86 94
87 assert_response :success 95 assert_response :success
88 ordered_asset_ids = node.draft.reload.related_assets.map(&:asset_id) 96 ordered_asset_ids = node.reload.editable_page.related_assets.order(:position).map(&:asset_id)
97 # XXXX ordered_asset_ids = node.draft.reload.related_assets.map(&:asset_id)
89 assert_equal [second.id, first.id], ordered_asset_ids 98 assert_equal [second.id, first.id], ordered_asset_ids
90 end 99 end
91 100
92 test "update sets the headline flag" do 101 test "update sets the headline flag" do
93 login_as :quentin 102 login_as :quentin
94 node = Node.root.children.create!(:slug => "related_assets_headline_test") 103 node = Node.root.children.create!(:slug => "related_assets_headline_test")
104 node.lock_for_editing!(users(:quentin))
95 asset = Asset.create!(:name => "headline-photo", :upload_content_type => "image/png") 105 asset = Asset.create!(:name => "headline-photo", :upload_content_type => "image/png")
96 node.draft.assets << asset 106 node.draft.assets << asset
97 related = node.draft.related_assets.find_by(:asset_id => asset.id) 107 related = node.draft.related_assets.find_by(:asset_id => asset.id)
@@ -99,12 +109,14 @@ class RelatedAssetsControllerTest < ActionController::TestCase
99 patch :update, params: { :node_id => node.id, :id => related.id, :headline => "true" } 109 patch :update, params: { :node_id => node.id, :id => related.id, :headline => "true" }
100 110
101 assert_response :success 111 assert_response :success
102 assert related.reload.headline? 112 assert node.reload.editable_page.related_assets.find_by!(:asset_id => asset.id).headline?
113 assert_not related.reload.headline?, "the draft must stay untouched"
103 end 114 end
104 115
105 test "update with headline=true clears any previous headline on the same page" do 116 test "update with headline=true clears any previous headline on the same page" do
106 login_as :quentin 117 login_as :quentin
107 node = Node.root.children.create!(:slug => "related_assets_headline_swap_test") 118 node = Node.root.children.create!(:slug => "related_assets_headline_swap_test")
119 node.lock_for_editing!(users(:quentin))
108 first = Asset.create!(:name => "first-headline", :upload_content_type => "image/png") 120 first = Asset.create!(:name => "first-headline", :upload_content_type => "image/png")
109 second = Asset.create!(:name => "second-headline", :upload_content_type => "image/png") 121 second = Asset.create!(:name => "second-headline", :upload_content_type => "image/png")
110 node.draft.assets << first 122 node.draft.assets << first
@@ -117,13 +129,16 @@ class RelatedAssetsControllerTest < ActionController::TestCase
117 patch :update, params: { :node_id => node.id, :id => second_related.id, :headline => "true" } 129 patch :update, params: { :node_id => node.id, :id => second_related.id, :headline => "true" }
118 130
119 assert_response :success 131 assert_response :success
120 assert_not first_related.reload.headline? 132 current = node.reload.editable_page
121 assert second_related.reload.headline? 133 assert_not current.related_assets.find_by!(:asset_id => first.id).headline?
134 assert current.related_assets.find_by!(:asset_id => second.id).headline?
135 assert first_related.reload.headline?, "the draft must stay untouched"
122 end 136 end
123 137
124 test "update with headline=false clears the headline" do 138 test "update with headline=false clears the headline" do
125 login_as :quentin 139 login_as :quentin
126 node = Node.root.children.create!(:slug => "related_assets_headline_unset_test") 140 node = Node.root.children.create!(:slug => "related_assets_headline_unset_test")
141 node.lock_for_editing!(users(:quentin))
127 asset = Asset.create!(:name => "unset-headline", :upload_content_type => "image/png") 142 asset = Asset.create!(:name => "unset-headline", :upload_content_type => "image/png")
128 node.draft.assets << asset 143 node.draft.assets << asset
129 related = node.draft.related_assets.find_by(:asset_id => asset.id) 144 related = node.draft.related_assets.find_by(:asset_id => asset.id)
@@ -132,7 +147,8 @@ class RelatedAssetsControllerTest < ActionController::TestCase
132 patch :update, params: { :node_id => node.id, :id => related.id, :headline => "false" } 147 patch :update, params: { :node_id => node.id, :id => related.id, :headline => "false" }
133 148
134 assert_response :success 149 assert_response :success
135 assert_not related.reload.headline? 150 assert_not node.reload.editable_page.related_assets.find_by!(:asset_id => asset.id).headline?
151 assert related.reload.headline?, "the draft must stay untouched"
136 end 152 end
137 153
138 test "search includes PDF assets as headline-eligible candidates" do 154 test "search includes PDF assets as headline-eligible candidates" do
@@ -159,4 +175,16 @@ class RelatedAssetsControllerTest < ActionController::TestCase
159 ids = JSON.parse(response.body).map { |r| r["id"] } 175 ids = JSON.parse(response.body).map { |r| r["id"] }
160 assert_includes ids, asset.id 176 assert_includes ids, asset.id
161 end 177 end
178
179 test "curation without holding the lock is refused with 423" do
180 login_as :quentin
181 node = Node.root.children.create!(:slug => "curation_lock_test")
182 asset = Asset.create!(:name => "Untouchable", :upload_content_type => "image/png")
183 node.lock_for_editing!(users(:aaron))
184
185 post :create, params: { :node_id => node.id, :asset_id => asset.id }
186
187 assert_response :locked
188 assert_empty node.draft.assets.reload
189 end
162end 190end