Skip to content

Commit a214453

Browse files
Add document delete (#227)
* basic doc delete * show deleted --------- Co-authored-by: Vijay Swamidass <[email protected]>
1 parent 8e71e45 commit a214453

File tree

11 files changed

+192
-15
lines changed

11 files changed

+192
-15
lines changed

app/controllers/base_documents_controller.rb

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@
22

33
class BaseDocumentsController < ApplicationController
44
helper_method :can_manage_documents?
5-
before_action :set_document, only: %i[show edit update]
5+
before_action :set_document, only: %i[show edit update destroy restore]
66

77
include Hashable
88
include GptConcern
99
include NeighborConcern
1010
# GET /documents or /documents.json
1111
def index
12-
@documents = Document.includes(:library, :user)
12+
# Handle show_deleted parameter to include deleted documents
13+
@documents = if params[:show_deleted] == 'true'
14+
Document.unscoped.includes(:library, :user)
15+
elsif params[:show_deleted] == 'only'
16+
Document.unscoped.includes(:library, :user).where.not(deleted_date: nil)
17+
else
18+
Document.includes(:library, :user).not_deleted
19+
end
1320

1421
# Apply cheap filters first for better performance
1522
library_id = params[:library_id]
@@ -136,6 +143,30 @@ def create
136143
end
137144
end
138145

146+
# DELETE /documents/1 or /documents/1.json
147+
def destroy
148+
authorize @document
149+
150+
@document.soft_delete!
151+
152+
respond_to do |format|
153+
format.html { redirect_to @document, notice: 'Document was successfully deleted.' }
154+
format.json { head :no_content }
155+
end
156+
end
157+
158+
# PATCH /documents/1/restore or /documents/1/restore.json
159+
def restore
160+
authorize @document, :destroy? # Use same authorization as destroy
161+
162+
@document.restore!
163+
164+
respond_to do |format|
165+
format.html { redirect_to @document, notice: 'Document was successfully restored.' }
166+
format.json { render :show, status: :ok, location: @document }
167+
end
168+
end
169+
139170
private
140171

141172
def can_manage_documents?
@@ -151,7 +182,7 @@ def can_manage_documents?
151182

152183
# Use callbacks to share common setup or constraints between actions.
153184
def set_document
154-
@document = Document.find(params[:id])
185+
@document = Document.unscoped.find(params[:id])
155186
end
156187

157188
# Only allow a list of trusted parameters through.

app/models/document.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ class Document < ApplicationRecord
6262
scope = nearest_neighbors(:embedding, embedding, distance: 'euclidean')
6363
scope.order(updated_at: :desc).limit(limit)
6464
}
65+
66+
# Default scope to only show non-deleted documents
67+
default_scope { where(deleted_date: nil) }
68+
69+
# Soft delete scopes
70+
scope :not_deleted, -> { where(deleted_date: nil) }
71+
scope :deleted, -> { where.not(deleted_date: nil) }
72+
scope :with_deleted, -> { unscoped }
6573
belongs_to :library, counter_cache: true
6674
belongs_to :user
6775

@@ -174,4 +182,21 @@ def update_search_vector
174182
"SELECT #{sanitized_sql} AS tsvector"
175183
).first['tsvector']
176184
end
185+
186+
# Soft delete methods
187+
def soft_delete!
188+
update!(deleted_date: Time.current)
189+
end
190+
191+
def restore!
192+
update!(deleted_date: nil)
193+
end
194+
195+
def deleted?
196+
deleted_date.present?
197+
end
198+
199+
def active?
200+
!deleted?
201+
end
177202
end

app/policies/document_policy.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ def update?
1616
user.admin? || document.library.user_id == user.id || user_is_editor?
1717
end
1818

19+
def destroy?
20+
user.admin? || document.library.user_id == user.id || user_is_editor?
21+
end
22+
1923
def flag?
2024
!user.nil? # Any logged-in user can flag documents
2125
end

app/views/documents/_document.html.erb

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,34 @@
11
<div class="flex justify-between items-center">
2-
<h1 class="text-3xl font-semibold <%= @document.enabled ? 'text-stone-900' : 'text-gray-500' %>"><%= document.title %></h1>
2+
<h1 class="text-3xl font-semibold <%= @document.enabled ? 'text-stone-900' : 'text-gray-500' %>">
3+
<%= document.title %>
4+
<% if document.deleted? %>
5+
<span class="text-lg text-red-600 font-normal ml-2">(Deleted)</span>
6+
<% end %>
7+
</h1>
38
<div class="flex space-x-2">
49
<% if policy(@document).edit? %>
510
<div class="text-gray-700">
6-
<div class="flex items-center">
7-
<%= render partial: 'shared/button_group', locals: { buttons: { "Edit" => edit_document_path } } %>
11+
<div class="inline-flex rounded-md shadow-sm" role="group">
12+
<% unless document.deleted? %>
13+
<%= link_to "Edit", edit_document_path, class: "border text-sky-700 border-sky-500 py-3 px-5 bg-white text-sm #{'border-r-0' if policy(@document).destroy?} rounded-l-lg #{'rounded-r-lg' unless policy(@document).destroy?} hover:bg-sky-500 hover:text-sky-100" %>
14+
<% end %>
15+
<% if policy(@document).destroy? %>
16+
<% if document.deleted? %>
17+
<%= link_to "Undelete", restore_document_path(@document),
18+
data: {
19+
"turbo-method": "patch",
20+
"turbo-confirm": "Are you sure you want to restore this document?"
21+
},
22+
class: "border text-green-700 border-green-300 py-3 px-5 bg-white text-sm rounded-lg hover:bg-green-50 hover:text-green-800" %>
23+
<% else %>
24+
<%= link_to "Delete", document_path(@document),
25+
data: {
26+
"turbo-method": "delete",
27+
"turbo-confirm": "Are you sure you want to delete this document?"
28+
},
29+
class: "border text-red-700 border-red-300 py-3 px-5 bg-white text-sm #{'rounded-l-lg' if document.deleted?} rounded-r-lg hover:bg-red-50 hover:text-red-800" %>
30+
<% end %>
31+
<% end %>
832
</div>
933
</div>
1034
<% end %>

app/views/documents/show.html.erb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,23 @@
22

33
<%= render partial: 'shared/breadcrumb', locals: { breadcrumbs: [['Home', root_path], [@document.library.name, library_path(@document.library)], ["Document", document_path(@document)]] } %>
44

5-
<% unless @document.enabled %>
5+
<% if @document.deleted? %>
6+
<div class="bg-red-50 border-l-4 border-red-400 p-4 mb-4">
7+
<div class="flex">
8+
<div class="flex-shrink-0">
9+
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
10+
<path fill-rule="evenodd" d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z" clip-rule="evenodd" />
11+
<path fill-rule="evenodd" d="M4 5a2 2 0 012-2h8a2 2 0 012 2v6a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 2a1 1 0 000 2h6a1 1 0 100-2H7zm0 4a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd" />
12+
</svg>
13+
</div>
14+
<div class="ml-3">
15+
<p class="text-sm text-red-700">
16+
<strong>Document is deleted.</strong> This document was deleted on <%= @document.deleted_date.strftime("%B %d, %Y at %I:%M %p") %> and will not appear in searches or question results.
17+
</p>
18+
</div>
19+
</div>
20+
</div>
21+
<% elsif !@document.enabled %>
622
<div class="bg-amber-50 border-l-4 border-amber-400 p-4 mb-4">
723
<div class="flex">
824
<div class="flex-shrink-0">

app/views/shared/_doc_list.html.erb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@
3131
</svg>
3232
<%= document.questions_count %> references
3333
</div>
34-
<% unless document.enabled %>
34+
<% if document.deleted? %>
35+
<div class="flex items-center text-red-600">
36+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-3 h-3 mr-1">
37+
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
38+
</svg>
39+
Deleted
40+
</div>
41+
<% elsif !document.enabled %>
3542
<div class="flex items-center text-amber-600">
3643
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-3 h-3 mr-1">
3744
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />

config/routes.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,20 @@
3434
resources :questions
3535
resources :documents do
3636
resources :comments, only: %i[create update destroy]
37+
member do
38+
patch :restore
39+
end
3740
end
3841
resources :api_tokens
3942

4043
# Nested Resources
4144
resources :libraries do
4245
resources :library_users
43-
resources :documents
46+
resources :documents do
47+
member do
48+
patch :restore
49+
end
50+
end
4451
member do
4552
get 'users'
4653
get 'download'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class AddDeletedDateToDocuments < ActiveRecord::Migration[7.2]
2+
def change
3+
add_column :documents, :deleted_date, :datetime
4+
end
5+
end

db/schema.rb

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/controllers/documents_controller_spec.rb

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535

3636
context 'when the document saves successfully' do
3737
it 'enqueues an EmbedDocumentJob' do
38-
expect {
38+
expect do
3939
post :create, params: { document: valid_attributes }
40-
}.to have_enqueued_job(EmbedDocumentJob).on_queue('default')
40+
end.to have_enqueued_job(EmbedDocumentJob).on_queue('default')
4141
end
4242
end
4343

@@ -64,6 +64,33 @@
6464
get :index, params: { contains: 'including keyword' }
6565
expect(assigns(:documents)).to match_array([Document.find_by(title: 'Second Document')])
6666
end
67+
68+
context 'with show_deleted parameter' do
69+
let!(:active_document) { Document.create!(valid_attributes.merge(title: 'Active Document', document: 'Active document content')) }
70+
let!(:deleted_document) { Document.create!(valid_attributes.merge(title: 'Deleted Document', document: 'Deleted document content')) }
71+
72+
before do
73+
deleted_document.soft_delete!
74+
end
75+
76+
it 'shows only active documents by default' do
77+
get :index
78+
expect(assigns(:documents)).to include(active_document)
79+
expect(assigns(:documents)).not_to include(deleted_document)
80+
end
81+
82+
it 'shows both active and deleted documents when show_deleted=true' do
83+
get :index, params: { show_deleted: 'true' }
84+
expect(assigns(:documents)).to include(active_document)
85+
expect(assigns(:documents)).to include(deleted_document)
86+
end
87+
88+
it 'shows only deleted documents when show_deleted=only' do
89+
get :index, params: { show_deleted: 'only' }
90+
expect(assigns(:documents)).not_to include(active_document)
91+
expect(assigns(:documents)).to include(deleted_document)
92+
end
93+
end
6794
end
6895

6996
describe 'POST #create' do

0 commit comments

Comments
 (0)