diff options
| -rw-r--r-- | app/controllers/related_assets_controller.rb | 18 | ||||
| -rw-r--r-- | app/models/node.rb | 12 | ||||
| -rw-r--r-- | test/controllers/related_assets_controller_test.rb | 44 |
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 | ||
| 162 | end | 190 | end |
