diff --git a/db/migrate/20240716141505_create_content_block_edition_authors.rb b/db/migrate/20240716141505_create_content_block_edition_authors.rb new file mode 100644 index 00000000000..cf89d5869e7 --- /dev/null +++ b/db/migrate/20240716141505_create_content_block_edition_authors.rb @@ -0,0 +1,10 @@ +class CreateContentBlockEditionAuthors < ActiveRecord::Migration[7.1] + def change + create_table :content_block_edition_authors do |t| + t.references :user, index: true, null: false + t.references :content_block_edition, index: true, foreign_key: true, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 8ca9f248e51..ea1dce6b1bf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_07_01_080000) do +ActiveRecord::Schema[7.1].define(version: 2024_07_16_141505) do create_table "assets", charset: "utf8mb3", force: :cascade do |t| t.string "asset_manager_id", null: false t.string "variant", null: false @@ -199,6 +199,15 @@ t.datetime "updated_at", precision: nil end + create_table "content_block_edition_authors", charset: "utf8mb3", force: :cascade do |t| + t.bigint "user_id", null: false + t.bigint "content_block_edition_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["content_block_edition_id"], name: "idx_on_content_block_edition_id_9e1066caab" + t.index ["user_id"], name: "index_content_block_edition_authors_on_user_id" + end + create_table "content_block_editions", charset: "utf8mb3", force: :cascade do |t| t.json "details", null: false t.bigint "content_block_document_id", null: false @@ -1309,6 +1318,7 @@ t.datetime "updated_at", precision: nil end + add_foreign_key "content_block_edition_authors", "content_block_editions" add_foreign_key "content_block_editions", "content_block_documents" add_foreign_key "documents", "editions", column: "latest_edition_id", on_update: :cascade, on_delete: :nullify add_foreign_key "documents", "editions", column: "live_edition_id", on_update: :cascade, on_delete: :nullify diff --git a/docs/diagrams/object_store_models.png b/docs/diagrams/object_store_models.png index ff6361b8084..55d79f362b2 100644 Binary files a/docs/diagrams/object_store_models.png and b/docs/diagrams/object_store_models.png differ diff --git a/docs/diagrams/object_store_models.puml b/docs/diagrams/object_store_models.puml index a2c6603dbe0..b7f13949c68 100644 --- a/docs/diagrams/object_store_models.puml +++ b/docs/diagrams/object_store_models.puml @@ -20,6 +20,12 @@ together { ContentBlockEdition .> content_block_editions } +class ContentBlockEditionAuthor { + user_id + content_block_edition_id +} + ContentBlockDocument *-r- ContentBlockEdition : "has_many" +ContentBlockEdition *-r- ContentBlockEditionAuthor : "has_many" @enduml diff --git a/lib/engines/content_object_store/app/components/content_object_store/content_block_edition/show/summary_list_component.rb b/lib/engines/content_object_store/app/components/content_object_store/content_block_edition/show/summary_list_component.rb index 9178e4706b3..aaf81ee1b6b 100644 --- a/lib/engines/content_object_store/app/components/content_object_store/content_block_edition/show/summary_list_component.rb +++ b/lib/engines/content_object_store/app/components/content_object_store/content_block_edition/show/summary_list_component.rb @@ -8,7 +8,7 @@ def initialize(content_block_edition:) attr_reader :content_block_edition def items - [title_item].concat(details_items) + [title_item].concat(details_items).concat([creator_item]) end def title_item @@ -23,4 +23,11 @@ def details_items { field: key.humanize, value: } end end + + def creator_item + { + field: "Creator", + value: content_block_edition.creator.name, + } + end end diff --git a/lib/engines/content_object_store/app/controllers/content_object_store/content_block_editions_controller.rb b/lib/engines/content_object_store/app/controllers/content_object_store/content_block_editions_controller.rb index 110c3edc3c6..73ccac162d7 100644 --- a/lib/engines/content_object_store/app/controllers/content_object_store/content_block_editions_controller.rb +++ b/lib/engines/content_object_store/app/controllers/content_object_store/content_block_editions_controller.rb @@ -34,6 +34,6 @@ def root_params end def edition_params - root_params.permit(:title, :block_type, details: @schema.fields) + root_params.permit(:title, :block_type, details: @schema.fields).merge(creator: current_user) end end diff --git a/lib/engines/content_object_store/app/models/concerns/content_object_store/has_authors.rb b/lib/engines/content_object_store/app/models/concerns/content_object_store/has_authors.rb new file mode 100644 index 00000000000..c6c8f199ffb --- /dev/null +++ b/lib/engines/content_object_store/app/models/concerns/content_object_store/has_authors.rb @@ -0,0 +1,10 @@ +module ContentObjectStore + module HasAuthors + extend ActiveSupport::Concern + include ContentObjectStore::HasCreator + + included do + has_many :content_block_edition_authors, dependent: :destroy + end + end +end diff --git a/lib/engines/content_object_store/app/models/concerns/content_object_store/has_creator.rb b/lib/engines/content_object_store/app/models/concerns/content_object_store/has_creator.rb new file mode 100644 index 00000000000..c83c92407a5 --- /dev/null +++ b/lib/engines/content_object_store/app/models/concerns/content_object_store/has_creator.rb @@ -0,0 +1,22 @@ +module ContentObjectStore + module HasCreator + extend ActiveSupport::Concern + + included do + validates :creator, presence: true + end + + def creator + content_block_edition_authors.first&.user + end + + def creator=(user) + if new_record? + content_block_edition_author = content_block_edition_authors.first || content_block_edition_authors.build + content_block_edition_author.user = user + else + raise "author can only be set on new records" + end + end + end +end diff --git a/lib/engines/content_object_store/app/models/content_object_store/content_block_edition.rb b/lib/engines/content_object_store/app/models/content_object_store/content_block_edition.rb index 73bf677cdbc..28bbeb85de9 100644 --- a/lib/engines/content_object_store/app/models/content_object_store/content_block_edition.rb +++ b/lib/engines/content_object_store/app/models/content_object_store/content_block_edition.rb @@ -1,4 +1,5 @@ class ContentObjectStore::ContentBlockEdition < ApplicationRecord include ContentObjectStore::Identifiable include ContentObjectStore::ValidatesDetails + include ContentObjectStore::HasAuthors end diff --git a/lib/engines/content_object_store/app/models/content_object_store/content_block_edition_author.rb b/lib/engines/content_object_store/app/models/content_object_store/content_block_edition_author.rb new file mode 100644 index 00000000000..d7bfb5ba054 --- /dev/null +++ b/lib/engines/content_object_store/app/models/content_object_store/content_block_edition_author.rb @@ -0,0 +1,4 @@ +class ContentObjectStore::ContentBlockEditionAuthor < ApplicationRecord + belongs_to :content_block_edition + belongs_to :user +end diff --git a/lib/engines/content_object_store/features/step_definitions/content_object_store_steps.rb b/lib/engines/content_object_store/features/step_definitions/content_object_store_steps.rb index 386895e5e05..e5edab31109 100644 --- a/lib/engines/content_object_store/features/step_definitions/content_object_store_steps.rb +++ b/lib/engines/content_object_store/features/step_definitions/content_object_store_steps.rb @@ -78,7 +78,12 @@ Given("an email address content block has been created") do @content_blocks ||= [] @email_address = "foo@example.com" - @content_block = create(:content_block_edition, :email_address, details: { email_address: @email_address }) + @content_block = create( + :content_block_edition, + :email_address, + details: { email_address: @email_address }, + creator: @user, + ) @content_blocks.push(@content_block) end @@ -113,6 +118,8 @@ def should_show_summary_details_for_email_address_content_block(content_block, e expect(page).to have_selector(".govuk-summary-list__value", text: content_block.document.title) expect(page).to have_selector(".govuk-summary-list__key", text: "Email address") expect(page).to have_selector(".govuk-summary-list__value", text: email_address) + expect(page).to have_selector(".govuk-summary-list__key", text: "Creator") + expect(page).to have_selector(".govuk-summary-list__value", text: @user.name) end Then("I should see errors for the required fields") do diff --git a/lib/engines/content_object_store/test/components/content_block_edition/index/summary_card_component_test.rb b/lib/engines/content_object_store/test/components/content_block_edition/index/summary_card_component_test.rb index 26b0db9fddd..e67b4d1616e 100644 --- a/lib/engines/content_object_store/test/components/content_block_edition/index/summary_card_component_test.rb +++ b/lib/engines/content_object_store/test/components/content_block_edition/index/summary_card_component_test.rb @@ -5,7 +5,13 @@ class ContentObjectStore::ContentBlockEdition::Index::SummaryCardComponentTest < test "it renders a content block as a summary card" do content_block_document = build(:content_block_document, :email_address) - content_block_edition = build(:content_block_edition, :email_address, id: 123, details: { foo: "bar", something: "else" }, content_block_document:) + content_block_edition = build( + :content_block_edition, + :email_address, + id: 123, + details: { foo: "bar", something: "else" }, + content_block_document:, creator: build(:user) + ) render_inline(ContentObjectStore::ContentBlockEdition::Index::SummaryCardComponent.new(content_block_edition:)) @@ -13,12 +19,14 @@ class ContentObjectStore::ContentBlockEdition::Index::SummaryCardComponentTest < assert_selector ".govuk-summary-card__action", count: 1 assert_selector ".govuk-summary-card__action .govuk-link[href='#{content_object_store_content_block_edition_path(content_block_edition)}']" - assert_selector ".govuk-summary-list__row", count: 3 + assert_selector ".govuk-summary-list__row", count: 4 assert_selector ".govuk-summary-list__key", text: "Title" assert_selector ".govuk-summary-list__value", text: content_block_edition.title assert_selector ".govuk-summary-list__key", text: "Foo" assert_selector ".govuk-summary-list__value", text: "bar" assert_selector ".govuk-summary-list__key", text: "Something" assert_selector ".govuk-summary-list__value", text: "else" + assert_selector ".govuk-summary-list__key", text: "Creator" + assert_selector ".govuk-summary-list__value", text: content_block_edition.creator.name end end diff --git a/lib/engines/content_object_store/test/components/content_block_edition/show/summary_list_component_test.rb b/lib/engines/content_object_store/test/components/content_block_edition/show/summary_list_component_test.rb index 7509de21526..b7993c57569 100644 --- a/lib/engines/content_object_store/test/components/content_block_edition/show/summary_list_component_test.rb +++ b/lib/engines/content_object_store/test/components/content_block_edition/show/summary_list_component_test.rb @@ -2,15 +2,22 @@ class ContentObjectStore::ContentBlockEdition::Show::SummaryListComponentTest < ViewComponent::TestCase test "renders a content block correctly" do - content_block_edition = build(:content_block_edition, :email_address, details: { foo: "bar", something: "else" }) + content_block_edition = build( + :content_block_edition, + :email_address, + details: { foo: "bar", something: "else" }, + creator: build(:user), + ) render_inline(ContentObjectStore::ContentBlockEdition::Show::SummaryListComponent.new(content_block_edition:)) - assert_selector ".govuk-summary-list__row", count: 3 + assert_selector ".govuk-summary-list__row", count: 4 assert_selector ".govuk-summary-list__key", text: "Title" assert_selector ".govuk-summary-list__value", text: content_block_edition.title assert_selector ".govuk-summary-list__key", text: "Foo" assert_selector ".govuk-summary-list__value", text: "bar" assert_selector ".govuk-summary-list__key", text: "Something" assert_selector ".govuk-summary-list__value", text: "else" + assert_selector ".govuk-summary-list__key", text: "Creator" + assert_selector ".govuk-summary-list__value", text: content_block_edition.creator.name end end diff --git a/lib/engines/content_object_store/test/factories/content_block_edition.rb b/lib/engines/content_object_store/test/factories/content_block_edition.rb index d8046b2d232..ec48d026d71 100644 --- a/lib/engines/content_object_store/test/factories/content_block_edition.rb +++ b/lib/engines/content_object_store/test/factories/content_block_edition.rb @@ -5,6 +5,7 @@ updated_at { Time.zone.now.utc } block_type { "block_type" } schema { build(:content_block_schema) } + creator ContentObjectStore::ContentBlockSchema.valid_schemas.each do |type| trait type.to_sym do diff --git a/lib/engines/content_object_store/test/integration/content_block_editions_test.rb b/lib/engines/content_object_store/test/integration/content_block_editions_test.rb index 08512bb183d..07bf857dd40 100644 --- a/lib/engines/content_object_store/test/integration/content_block_editions_test.rb +++ b/lib/engines/content_object_store/test/integration/content_block_editions_test.rb @@ -38,23 +38,27 @@ class ContentBlockEditionsTest < ActionDispatch::IntegrationTest assert_changes -> { ContentObjectStore::ContentBlockDocument.count }, from: 0, to: 1 do assert_changes -> { ContentObjectStore::ContentBlockEdition.count }, from: 0, to: 1 do - post content_object_store.content_object_store_content_block_editions_path, params: { - something: "else", - content_object_store_content_block_edition: { - title:, - block_type:, - details:, - }, - } + assert_changes -> { ContentObjectStore::ContentBlockEditionAuthor.count }, from: 0, to: 1 do + post content_object_store.content_object_store_content_block_editions_path, params: { + something: "else", + content_object_store_content_block_edition: { + title:, + block_type:, + details:, + }, + } + end end end new_document = ContentObjectStore::ContentBlockDocument.find_by!(content_id: @content_id) new_edition = new_document.content_block_editions.first + new_author = ContentObjectStore::ContentBlockEditionAuthor.first assert_equal title, new_document.title assert_equal block_type, new_document.block_type assert_equal details, new_edition.details assert_equal new_edition.content_block_document_id, new_document.id + assert_equal new_edition.creator, new_author.user end test "#create posts the new edition to the Publishing API" do diff --git a/lib/engines/content_object_store/test/unit/app/models/content_block_edition_test.rb b/lib/engines/content_object_store/test/unit/app/models/content_block_edition_test.rb index 1f6751fa9f3..2b2c2d0cca9 100644 --- a/lib/engines/content_object_store/test/unit/app/models/content_block_edition_test.rb +++ b/lib/engines/content_object_store/test/unit/app/models/content_block_edition_test.rb @@ -9,6 +9,7 @@ class ContentObjectStore::ContentBlockEditionTest < ActiveSupport::TestCase @updated_at = Time.zone.local(2000, 12, 31, 23, 59, 59).utc @details = { "some_field" => "some_content" } @title = "Document title" + @creator = create(:user) @content_block_edition = build( :content_block_edition, @@ -18,6 +19,7 @@ class ContentObjectStore::ContentBlockEditionTest < ActiveSupport::TestCase details: @details, title: @title, content_block_document: nil, + creator: @creator, ) end @@ -88,4 +90,17 @@ class ContentObjectStore::ContentBlockEditionTest < ActiveSupport::TestCase assert_invalid content_block_edition assert content_block_edition.errors.full_messages.include?("Title can't be blank") end + + test "it adds a creator and first edition author for new records" do + @content_block_edition.save! + @content_block_edition.reload + assert_equal @content_block_edition.creator, @content_block_edition.content_block_edition_authors.first.user + end + + test "#creator= raises an exception if called for a persisted record" do + @content_block_edition.save! + assert_raise RuntimeError do + @content_block_edition.creator = create(:user) + end + end end diff --git a/lib/engines/content_object_store/test/unit/app/services/create_edition_service_test.rb b/lib/engines/content_object_store/test/unit/app/services/create_edition_service_test.rb index 19ad561a831..3d62fd8d5b3 100644 --- a/lib/engines/content_object_store/test/unit/app/services/create_edition_service_test.rb +++ b/lib/engines/content_object_store/test/unit/app/services/create_edition_service_test.rb @@ -14,6 +14,7 @@ class ContentObjectStore::CreateEditionServiceTest < ActiveSupport::TestCase "foo" => "Foo text", "bar" => "Bar text", }, + creator: build(:user), } end