diff --git a/app/controllers/admin/object_store/items_controller.rb b/app/controllers/admin/object_store/items_controller.rb
new file mode 100644
index 00000000000..56d8128d5d9
--- /dev/null
+++ b/app/controllers/admin/object_store/items_controller.rb
@@ -0,0 +1,27 @@
+module Admin::ObjectStore
+ class ItemsController < Admin::EditionsController
+ private
+
+ def edition_class
+ ObjectStore::Item
+ end
+
+ def permitted_edition_attributes
+ (super << ObjectStore.field_names_for_item_type(params[:item_type])).flatten
+ end
+
+ def show_or_edit_path
+ if params[:save].present?
+ edit_admin_object_store_item_path(@edition.item_type, @edition)
+ else
+ admin_object_store_item_path @edition
+ end
+ end
+
+ def new_edition
+ edition = edition_class.new(item_type: params[:item_type])
+ edition.assign_attributes(new_edition_params)
+ edition
+ end
+ end
+end
diff --git a/app/helpers/admin/object_store/items_helper.rb b/app/helpers/admin/object_store/items_helper.rb
new file mode 100644
index 00000000000..3a65e24e6f0
--- /dev/null
+++ b/app/helpers/admin/object_store/items_helper.rb
@@ -0,0 +1,39 @@
+module Admin
+ module ObjectStore
+ module ItemsHelper
+ def edition_name(edition)
+ edition.item_type.titleize
+ end
+
+ def render_partial_path(edition, partial_name)
+ render partial: partial_path(edition, partial_name), object: edition, as: edition.item_type
+ end
+
+ def partial_path(edition, partial_name)
+ dirname = edition.item_type.pluralize
+ File.join("admin", "object_store", dirname, partial_name)
+ end
+
+ def object_store_item_form(edition)
+ form_for edition, url: form_url_for_object_store_item(edition), as: edition.item_type do |form|
+ yield(form)
+ concat render("govuk_publishing_components/components/button", {
+ text: "Save",
+ value: "save",
+ name: "save",
+ data_attributes: {
+ module: "gem-track-click",
+ "track-category": "form-button",
+ "track-action": "object-store-item-button",
+ "track-label": "Save",
+ },
+ })
+ end
+ end
+
+ def form_url_for_object_store_item(edition)
+ admin_object_store_items_path(item_type: edition.item_type)
+ end
+ end
+ end
+end
diff --git a/app/views/admin/object_store/email_addresses/_form.html.erb b/app/views/admin/object_store/email_addresses/_form.html.erb
new file mode 100644
index 00000000000..e656d7dda0a
--- /dev/null
+++ b/app/views/admin/object_store/email_addresses/_form.html.erb
@@ -0,0 +1,21 @@
+<%= object_store_item_form(email_address) do |form| %>
+ <%= render "govuk_publishing_components/components/input", {
+ label: {
+ text: "Title",
+ },
+ heading_size: "m",
+ value: form.object.title,
+ name: "edition[title]",
+ id: "edition_title",
+ } %>
+
+ <%= render "govuk_publishing_components/components/input", {
+ label: {
+ text: "Email address",
+ },
+ heading_size: "m",
+ value: form.object.email_address,
+ name: "edition[email_address]",
+ id: "edition_email_address",
+ } %>
+<% end %>
diff --git a/app/views/admin/object_store/items/edit.html.erb b/app/views/admin/object_store/items/edit.html.erb
new file mode 100644
index 00000000000..f4e82ca0fde
--- /dev/null
+++ b/app/views/admin/object_store/items/edit.html.erb
@@ -0,0 +1,10 @@
+<% content_for :page_title, "Object Store - Edit #{edition_name(@edition)}" %>
+<% content_for :context, "Object Store" %>
+<% content_for :title, "Edit #{edition_name(@edition)}" %>
+<% content_for :error_summary, render(Admin::ErrorSummaryComponent.new(object: @edition, parent_class: "edition")) %>
+
+
+
+ <%= render_partial_path(@edition, "form") %>
+
+
diff --git a/app/views/admin/object_store/items/new.html.erb b/app/views/admin/object_store/items/new.html.erb
new file mode 100644
index 00000000000..f459ffe250f
--- /dev/null
+++ b/app/views/admin/object_store/items/new.html.erb
@@ -0,0 +1,10 @@
+<% content_for :page_title, "Object Store - New #{edition_name(@edition)}" %>
+<% content_for :context, "Object Store" %>
+<% content_for :title, "New #{edition_name(@edition)}" %>
+<% content_for :error_summary, render(Admin::ErrorSummaryComponent.new(object: @edition, parent_class: "edition")) %>
+
+
+
+ <%= render_partial_path(@edition, "form") %>
+
+
diff --git a/config/routes.rb b/config/routes.rb
index b477a1ce910..761a0386dac 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -424,6 +424,12 @@ def redirect(path, options = { prefix: Whitehall.router_prefix })
get :confirm_destroy
end
post "/link-checker-api-callback" => "link_checker_api#callback"
+
+ namespace :object_store do
+ scope "/:item_type", constraints: { item_type: Regexp.compile(ObjectStore.item_types.join("|")) } do
+ resources :items, path: "/"
+ end
+ end
end
end
diff --git a/test/integration/object_store/create_email_test.rb b/test/integration/object_store/create_email_test.rb
new file mode 100644
index 00000000000..0501ba26f37
--- /dev/null
+++ b/test/integration/object_store/create_email_test.rb
@@ -0,0 +1,32 @@
+require "test_helper"
+require "capybara/rails"
+
+class CreateEmailTest < ActionDispatch::IntegrationTest
+ include Capybara::DSL
+
+ setup do
+ login_as_admin
+ end
+
+ test "allows an email object to be created" do
+ visit new_admin_object_store_item_path(item_type: "email_address")
+
+ fill_in "Title", with: "Some Title"
+ fill_in "Email address", with: "foo@example.com"
+ click_on "Save"
+
+ created_object = ObjectStore::Item.find_by(title: "Some Title")
+
+ assert_not_nil created_object
+ assert created_object.email_address, "foo@example.com"
+ end
+
+ test "shows errors when missing fields blank" do
+ visit new_admin_object_store_item_path(item_type: "email_address")
+
+ click_on "Save"
+
+ assert_text "Title can't be blank"
+ assert_text "Email address can't be blank"
+ end
+end
diff --git a/test/integration/object_store/edit_email_test.rb b/test/integration/object_store/edit_email_test.rb
new file mode 100644
index 00000000000..4b0c5c68142
--- /dev/null
+++ b/test/integration/object_store/edit_email_test.rb
@@ -0,0 +1,22 @@
+require "test_helper"
+require "capybara/rails"
+
+class EditEmailTest < ActionDispatch::IntegrationTest
+ include Capybara::DSL
+
+ setup do
+ login_as_admin
+ end
+
+ test "allows an email object to be updated" do
+ item = create(:object_store_item, item_type: "email_address", email_address: "foo@example.com")
+ visit edit_admin_object_store_item_path(item_type: "email_address", id: item.id)
+
+ fill_in "Email address", with: "bar@example.com"
+ click_on "Save"
+
+ item.reload
+
+ assert item.email_address, "bar@example.com"
+ end
+end
diff --git a/test/unit/app/helpers/admin/object_store/items_helper_test.rb b/test/unit/app/helpers/admin/object_store/items_helper_test.rb
new file mode 100644
index 00000000000..55fe89d562b
--- /dev/null
+++ b/test/unit/app/helpers/admin/object_store/items_helper_test.rb
@@ -0,0 +1,47 @@
+require "test_helper"
+
+class Admin::ObjectStore::ItemsHelperTest < ActionView::TestCase
+ include Admin::ObjectStore::ItemsHelper
+
+ test "#edition_name returns the title for an edition" do
+ edition = build(:object_store_item, item_type: "email_address")
+ assert_equal edition_name(edition), "Email Address"
+ end
+
+ test "#partial_path should return a path for an edition" do
+ edition = build(:object_store_item, item_type: "email_address")
+ path = partial_path(edition, "form")
+ assert_equal path, "admin/object_store/email_addresses/form"
+ end
+
+ test "#render_partial_path should render the correct path with the edition" do
+ edition = build(:object_store_item, item_type: "email_address")
+ partial_name = "form"
+
+ expects(:render).with(partial: partial_path(edition, partial_name), object: edition, as: edition.item_type)
+
+ render_partial_path(edition, partial_name)
+ end
+
+ test "#object_store_item_form renders a form" do
+ edition = build(:object_store_item, item_type: "email_address")
+
+ html = object_store_item_form(edition) do |_form|
+ concat("Some text here")
+ end
+
+ assert_includes html, "Some text here"
+ assert_includes html, form_url_for_object_store_item(edition)
+ assert_includes html, render("govuk_publishing_components/components/button", {
+ text: "Save",
+ value: "save",
+ name: "save",
+ data_attributes: {
+ module: "gem-track-click",
+ "track-category": "form-button",
+ "track-action": "object-store-item-button",
+ "track-label": "Save",
+ },
+ })
+ end
+end