Browse Source

Soft delete

pull/48/head
baarkerlounger 4 years ago
parent
commit
1390893549
  1. 4
      Gemfile
  2. 20
      Gemfile.lock
  3. 18
      app/controllers/case_logs_controller.rb
  4. 6
      app/helpers/tasklist_helper.rb
  5. 42
      app/models/case_log.rb
  6. 6
      app/views/case_logs/index.html.erb
  7. 6
      db/migrate/20211014154616_add_discarded_at_to_case_logs.rb
  8. 4
      db/schema.rb
  9. 6
      spec/factories/case_log.rb
  10. 6
      spec/helpers/tasklist_helper_spec.rb
  11. 46
      spec/requests/case_log_controller_spec.rb
  12. 16
      spec/views/case_log_index_view_spec.rb

4
Gemfile

@ -19,8 +19,12 @@ gem "jbuilder", "~> 2.7"
gem "bootsnap", ">= 1.4.4", require: false gem "bootsnap", ">= 1.4.4", require: false
# Gov.UK frontend components # Gov.UK frontend components
gem "govuk-components" gem "govuk-components"
# Gov.UK component form builder DSL
gem "govuk_design_system_formbuilder" gem "govuk_design_system_formbuilder"
# Turbo & Stimulus
gem "hotwire-rails" gem "hotwire-rails"
# Soft delete ActiveRecords objects
gem "discard"
group :development, :test do group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console # Call 'byebug' anywhere in the code to stop execution and get a debugger console

20
Gemfile.lock

@ -123,12 +123,14 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
childprocess (3.0.0) childprocess (4.1.0)
coderay (1.1.3) coderay (1.1.3)
concurrent-ruby (1.1.9) concurrent-ruby (1.1.9)
crass (1.0.6) crass (1.0.6)
deep_merge (1.2.1) deep_merge (1.2.1)
diff-lcs (1.4.4) diff-lcs (1.4.4)
discard (1.2.0)
activerecord (>= 4.2, < 7)
docile (1.4.0) docile (1.4.0)
dotenv (2.7.6) dotenv (2.7.6)
dotenv-rails (2.7.6) dotenv-rails (2.7.6)
@ -143,7 +145,7 @@ GEM
ffi (1.15.4) ffi (1.15.4)
globalid (0.5.2) globalid (0.5.2)
activesupport (>= 5.0) activesupport (>= 5.0)
govuk-components (2.1.2) govuk-components (2.1.3)
activemodel (>= 6.0) activemodel (>= 6.0)
railties (>= 6.0) railties (>= 6.0)
view_component (~> 2.39.0) view_component (~> 2.39.0)
@ -171,7 +173,7 @@ GEM
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
marcel (1.0.2) marcel (1.0.2)
method_source (1.0.0) method_source (1.0.0)
mini_mime (1.1.1) mini_mime (1.1.2)
minitest (5.14.4) minitest (5.14.4)
msgpack (1.4.2) msgpack (1.4.2)
nio4r (2.5.8) nio4r (2.5.8)
@ -194,7 +196,7 @@ GEM
byebug (~> 11.0) byebug (~> 11.0)
pry (~> 0.13.0) pry (~> 0.13.0)
public_suffix (4.0.6) public_suffix (4.0.6)
puma (5.5.0) puma (5.5.2)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.5.2) racc (1.5.2)
rack (2.2.3) rack (2.2.3)
@ -277,8 +279,9 @@ GEM
sass (~> 3.5, >= 3.5.5) sass (~> 3.5, >= 3.5.5)
scss_lint-govuk (0.2.0) scss_lint-govuk (0.2.0)
scss_lint scss_lint
selenium-webdriver (3.142.7) selenium-webdriver (4.0.0)
childprocess (>= 0.5, < 4.0) childprocess (>= 0.5, < 5.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2) rubyzip (>= 1.2.2)
semantic_range (3.0.0) semantic_range (3.0.0)
simplecov (0.21.2) simplecov (0.21.2)
@ -294,10 +297,10 @@ GEM
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
stimulus-rails (0.7.0) stimulus-rails (0.7.1)
rails (>= 6.0.0) rails (>= 6.0.0)
thor (1.1.0) thor (1.1.0)
turbo-rails (0.8.1) turbo-rails (7.1.0)
rails (>= 6.0.0) rails (>= 6.0.0)
tzinfo (2.0.4) tzinfo (2.0.4)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
@ -331,6 +334,7 @@ DEPENDENCIES
bootsnap (>= 1.4.4) bootsnap (>= 1.4.4)
byebug byebug
capybara capybara
discard
dotenv-rails dotenv-rails
factory_bot_rails factory_bot_rails
govuk-components govuk-components

18
app/controllers/case_logs_controller.rb

@ -6,8 +6,8 @@ class CaseLogsController < ApplicationController
# rubocop:enable Style/ClassVars # rubocop:enable Style/ClassVars
def index def index
@submitted_case_logs = CaseLog.where(status: 1) @completed_case_logs = CaseLog.where(status: 2)
@in_progress_case_logs = CaseLog.where(status: 0) @in_progress_case_logs = CaseLog.where(status: 1)
end end
def create def create
@ -63,6 +63,18 @@ class CaseLogsController < ApplicationController
end end
end end
def destroy
if case_log = CaseLog.find_by(id: params[:id])
if case_log.discard
head :no_content
else
render json: { errors: case_log.errors.full_messages }, status: :unprocessable_entity
end
else
render json: { error: "Case Log #{params[:id]} not found" }, status: :not_found
end
end
def check_answers def check_answers
form = @@form_handler.get_form("2021_2022") form = @@form_handler.get_form("2021_2022")
@case_log = CaseLog.find(params[:case_log_id]) @case_log = CaseLog.find(params[:case_log_id])
@ -81,7 +93,7 @@ class CaseLogsController < ApplicationController
private private
API_ACTIONS = %w[create update].freeze API_ACTIONS = %w[create update destroy].freeze
def question_responses(questions_for_page) def question_responses(questions_for_page)
questions_for_page.each_with_object({}) do |(question_key, question_info), result| questions_for_page.each_with_object({}) do |(question_key, question_info), result|

6
app/helpers/tasklist_helper.rb

@ -15,7 +15,7 @@ module TasklistHelper
def get_subsection_status(subsection_name, case_log, questions) def get_subsection_status(subsection_name, case_log, questions)
if subsection_name == "declaration" if subsection_name == "declaration"
return all_questions_completed(case_log) ? :not_started : :cannot_start_yet return case_log.completed? ? :not_started : :cannot_start_yet
end end
return :not_started if questions.all? { |question| case_log[question].blank? } return :not_started if questions.all? { |question| case_log[question].blank? }
@ -47,10 +47,6 @@ module TasklistHelper
private private
def all_questions_completed(case_log)
case_log.attributes.all? { |_question, answer| answer.present? }
end
def is_incomplete?(subsection, case_log, questions) def is_incomplete?(subsection, case_log, questions)
status = get_subsection_status(subsection, case_log, questions) status = get_subsection_status(subsection, case_log, questions)
%i[not_started in_progress].include?(status) %i[not_started in_progress].include?(status)

42
app/models/case_log.rb

@ -26,29 +26,59 @@ class CaseLogValidator < ActiveModel::Validator
end end
class CaseLog < ApplicationRecord class CaseLog < ApplicationRecord
include Discard::Model
default_scope -> { kept }
validate :instance_validations validate :instance_validations
before_save :update_status! before_save :update_status!
attr_writer :previous_page attr_writer :previous_page
enum status: { "in progress" => 0, "submitted" => 1 } enum status: { "not_started" => 0, "in_progress" => 1, "completed" => 2 }
AUTOGENERATED_FIELDS = %w[status created_at updated_at id].freeze AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze
def instance_validations def instance_validations
validates_with CaseLogValidator, ({ previous_page: @previous_page } || {}) validates_with CaseLogValidator, ({ previous_page: @previous_page } || {})
end end
def self.editable_fields
attribute_names - AUTOGENERATED_FIELDS
end
def completed?
status == "completed"
end
def not_started?
status == "not started"
end
def in_progress?
status == "in progress"
end
private
def update_status! def update_status!
self.status = (all_fields_completed? && errors.empty? ? "submitted" : "in progress") self.status = if all_fields_completed? && errors.empty?
"completed"
elsif all_fields_nil?
"not_started"
else
"in_progress"
end
end end
def all_fields_completed? def all_fields_completed?
mandatory_fields = attributes.except(*AUTOGENERATED_FIELDS)
mandatory_fields.none? { |_key, val| val.nil? } mandatory_fields.none? { |_key, val| val.nil? }
end end
def self.editable_fields def all_fields_nil?
attribute_names - AUTOGENERATED_FIELDS mandatory_fields.all? { |_key, val| val.nil? }
end
def mandatory_fields
attributes.except(*AUTOGENERATED_FIELDS)
end end
end end

6
app/views/case_logs/index.html.erb

@ -10,10 +10,10 @@
<%= render partial: "log_list", locals: { case_logs: @in_progress_case_logs, title: "Logs you need to complete", date_title: "Last Changed" } %> <%= render partial: "log_list", locals: { case_logs: @in_progress_case_logs, title: "Logs you need to complete", date_title: "Last Changed" } %>
<% end %> <% end %>
<% if @submitted_case_logs.present? %> <% if @completed_case_logs.present? %>
<%= render partial: "log_list", locals: { case_logs: @submitted_case_logs, title: "Logs you've submitted", date_title: "Date Submitted" } %> <%= render partial: "log_list", locals: { case_logs: @completed_case_logs, title: "Logs you've submitted", date_title: "Date Submitted" } %>
<% end %> <% end %>
<p><a href="#" class="govuk-link">See all completed logs (<%= @submitted_case_logs.count %>)</a></p> <p><a href="#" class="govuk-link">See all completed logs (<%= @completed_case_logs.count %>)</a></p>
</div> </div>
</div> </div>

6
db/migrate/20211014154616_add_discarded_at_to_case_logs.rb

@ -0,0 +1,6 @@
class AddDiscardedAtToCaseLogs < ActiveRecord::Migration[6.1]
def change
add_column :case_logs, :discarded_at, :datetime
add_index :case_logs, :discarded_at
end
end

4
db/schema.rb

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_10_13_113607) do ActiveRecord::Schema.define(version: 2021_10_14_154616) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -131,6 +131,8 @@ ActiveRecord::Schema.define(version: 2021_10_13_113607) do
t.boolean "reasonable_preference_reason_medical_grounds" t.boolean "reasonable_preference_reason_medical_grounds"
t.boolean "reasonable_preference_reason_avoid_hardship" t.boolean "reasonable_preference_reason_avoid_hardship"
t.boolean "reasonable_preference_reason_do_not_know" t.boolean "reasonable_preference_reason_do_not_know"
t.datetime "discarded_at"
t.index ["discarded_at"], name: "index_case_logs_on_discarded_at"
end end
end end

6
spec/factories/case_log.rb

@ -2,14 +2,14 @@ FactoryBot.define do
factory :case_log do factory :case_log do
sequence(:id) { |i| i } sequence(:id) { |i| i }
trait :in_progress do trait :in_progress do
status { 0 } status { 1 }
tenant_code { "TH356" } tenant_code { "TH356" }
property_postcode { "SW2 6HI" } property_postcode { "SW2 6HI" }
previous_postcode { "P0 5ST" } previous_postcode { "P0 5ST" }
tenant_age { "12" } tenant_age { "12" }
end end
trait :submitted do trait :completed do
status { 1 } status { 2 }
tenant_code { "BZ737" } tenant_code { "BZ737" }
property_postcode { "NW1 7TY" } property_postcode { "NW1 7TY" }
end end

6
spec/helpers/tasklist_helper_spec.rb

@ -1,8 +1,9 @@
require "rails_helper" require "rails_helper"
RSpec.describe TasklistHelper do RSpec.describe TasklistHelper do
let!(:empty_case_log) { FactoryBot.create(:case_log) } let(:empty_case_log) { FactoryBot.build(:case_log) }
let!(:case_log) { FactoryBot.create(:case_log, :in_progress) } let(:case_log) { FactoryBot.build(:case_log, :in_progress) }
let(:completed_case_log) { FactoryBot.build(:case_log, :completed) }
form_handler = FormHandler.instance form_handler = FormHandler.instance
let(:form) { form_handler.get_form("test_form") } let(:form) { form_handler.get_form("test_form") }
@ -35,7 +36,6 @@ RSpec.describe TasklistHelper do
end end
it "returns not started if the subsection is declaration and all the questions are completed" do it "returns not started if the subsection is declaration and all the questions are completed" do
completed_case_log = CaseLog.new(case_log.attributes.map { |key, value| Hash[key, value || "value"] }.reduce(:merge))
status = get_subsection_status("declaration", completed_case_log, declaration_questions) status = get_subsection_status("declaration", completed_case_log, declaration_questions)
expect(status).to eq(:not_started) expect(status).to eq(:not_started)
end end

46
spec/requests/case_log_controller_spec.rb

@ -26,8 +26,8 @@ RSpec.describe CaseLogsController, type: :request do
let(:tenant_code) { "T365" } let(:tenant_code) { "T365" }
let(:tenant_age) { 35 } let(:tenant_age) { 35 }
let(:property_postcode) { "SE11 6TY" } let(:property_postcode) { "SE11 6TY" }
let(:in_progress) { "in progress" } let(:in_progress) { "in_progress" }
let(:submitted) { "submitted" } let(:completed) { "completed" }
let(:params) do let(:params) do
{ {
@ -79,9 +79,9 @@ RSpec.describe CaseLogsController, type: :request do
JSON.parse(File.open("spec/fixtures/complete_case_log.json").read) JSON.parse(File.open("spec/fixtures/complete_case_log.json").read)
end end
it "marks the record as submitted" do it "marks the record as completed" do
json_response = JSON.parse(response.body) json_response = JSON.parse(response.body)
expect(json_response["status"]).to eq(submitted) expect(json_response["status"]).to eq(completed)
end end
end end
@ -182,4 +182,42 @@ RSpec.describe CaseLogsController, type: :request do
end end
end end
end end
describe "DELETE" do
let!(:case_log) do
FactoryBot.create(:case_log, :in_progress)
end
let(:id) { case_log.id }
before do
delete "/case_logs/#{id}", headers: headers
end
it "returns http success" do
expect(response).to have_http_status(:success)
end
it "soft deletes the case log" do
expect { CaseLog.find(id) }.to raise_error(ActiveRecord::RecordNotFound)
expect(CaseLog.with_discarded.find(id)).to be_a(CaseLog)
end
context "invalid case log id" do
let(:id) { (CaseLog.order(:id).last&.id || 0) + 1 }
it "returns 404" do
expect(response).to have_http_status(:not_found)
end
end
context "request with invalid credentials" do
let(:basic_credentials) do
ActionController::HttpAuthentication::Basic.encode_credentials(api_username, "Oops")
end
it "returns 401" do
expect(response).to have_http_status(:unauthorized)
end
end
end
end end

16
spec/views/case_log_index_view_spec.rb

@ -1,12 +1,12 @@
require "rails_helper" require "rails_helper"
RSpec.describe "case_logs/index" do RSpec.describe "case_logs/index" do
let(:in_progress_log) { FactoryBot.build(:case_log, :in_progress) } let(:in_progress_log) { FactoryBot.build(:case_log, :in_progress) }
let(:submitted_log) { FactoryBot.build(:case_log, :submitted) } let(:completed_log) { FactoryBot.build(:case_log, :completed) }
context "given an in progress log list" do context "given an in progress log list" do
it "renders a table for in progress logs only" do it "renders a table for in progress logs only" do
assign(:in_progress_case_logs, [in_progress_log]) assign(:in_progress_case_logs, [in_progress_log])
assign(:submitted_case_logs, []) assign(:completed_case_logs, [])
render render
expect(rendered).to match(/<table class="govuk-table">/) expect(rendered).to match(/<table class="govuk-table">/)
expect(rendered).to match(/Logs you need to complete/) expect(rendered).to match(/Logs you need to complete/)
@ -16,23 +16,23 @@ RSpec.describe "case_logs/index" do
end end
end end
context "given a submitted log list" do context "given a completed log list" do
it "renders a table for in progress logs only" do it "renders a table for in progress logs only" do
assign(:in_progress_case_logs, []) assign(:in_progress_case_logs, [])
assign(:submitted_case_logs, [submitted_log]) assign(:completed_case_logs, [completed_log])
render render
expect(rendered).to match(/<table class="govuk-table">/) expect(rendered).to match(/<table class="govuk-table">/)
expect(rendered).to match(/Logs you&#39;ve submitted/) expect(rendered).to match(/Logs you&#39;ve submitted/)
expect(rendered).not_to match(/Logs you need to complete/) expect(rendered).not_to match(/Logs you need to complete/)
expect(rendered).to match(submitted_log.tenant_code) expect(rendered).to match(completed_log.tenant_code)
expect(rendered).to match(submitted_log.property_postcode) expect(rendered).to match(completed_log.property_postcode)
end end
end end
context "given a submitted log list and an in_progress log list" do context "given a completed log list and an in_progress log list" do
it "renders two tables, one for each status" do it "renders two tables, one for each status" do
assign(:in_progress_case_logs, [in_progress_log]) assign(:in_progress_case_logs, [in_progress_log])
assign(:submitted_case_logs, [submitted_log]) assign(:completed_case_logs, [completed_log])
render render
expect(rendered).to match(/<table class="govuk-table">/) expect(rendered).to match(/<table class="govuk-table">/)
expect(rendered).to match(/Logs you&#39;ve submitted/) expect(rendered).to match(/Logs you&#39;ve submitted/)

Loading…
Cancel
Save