diff --git a/app/presenters/content_embed_presenter.rb b/app/presenters/content_embed_presenter.rb index 7774bb609..e31693769 100644 --- a/app/presenters/content_embed_presenter.rb +++ b/app/presenters/content_embed_presenter.rb @@ -29,7 +29,12 @@ def embedded_editions state_fallback_order: %w[published], ) - Edition.where(id: embedded_edition_ids).index_by(&:content_id) + Edition + .joins("LEFT JOIN documents on documents.id = editions.document_id") + .joins("LEFT JOIN content_id_aliases on content_id_aliases.content_id = documents.content_id") + .where(id: embedded_edition_ids) + .select("editions.title, editions.details, editions.document_type, content_id_aliases.name") + .index_by(&:name) end end @@ -54,7 +59,7 @@ def render_embedded_editions(content) embedded_content_references.each do |content_reference| embed_code = content_reference.embed_code - embedded_edition = embedded_editions[content_reference.content_id] + embedded_edition = embedded_editions[content_reference.alias] content = content.gsub( embed_code, get_content_for_edition(embedded_edition), diff --git a/app/services/embedded_content_finder_service.rb b/app/services/embedded_content_finder_service.rb index 18f6c8fd5..d56c0a98a 100644 --- a/app/services/embedded_content_finder_service.rb +++ b/app/services/embedded_content_finder_service.rb @@ -1,17 +1,15 @@ class EmbeddedContentFinderService - ContentReference = Data.define(:document_type, :content_id, :embed_code) + ContentReference = Data.define(:document_type, :alias, :embed_code) SUPPORTED_DOCUMENT_TYPES = %w[contact content_block_email_address].freeze - UUID_REGEX = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/ - EMBED_REGEX = /({{embed:(#{SUPPORTED_DOCUMENT_TYPES.join('|')}):#{UUID_REGEX}}})/ + EMBED_REGEX = /({{embed:(#{SUPPORTED_DOCUMENT_TYPES.join('|')}):(.*?)}})/ def fetch_linked_content_ids(details, locale) content_references = details.values.map { |value| find_content_references(value) }.flatten.compact.uniq - check_all_references_exist(content_references, locale) - content_references.map(&:content_id) + get_content_ids_from_content_references(content_references, locale) end def find_content_references(value) @@ -21,7 +19,7 @@ def find_content_references(value) when Hash value.map { |_, v| find_content_references(v) }.flatten when String - value.scan(EMBED_REGEX).map { |match| ContentReference.new(document_type: match[1], content_id: match[2], embed_code: match[0]) }.uniq + value.scan(EMBED_REGEX).map { |match| ContentReference.new(document_type: match[1], alias: match[2], embed_code: match[0]) }.uniq else [] end @@ -29,23 +27,40 @@ def find_content_references(value) private - def check_all_references_exist(content_references, locale) - found_editions = live_editions(content_references, locale) - if found_editions.count != content_references.count - not_found_content_ids = content_references.map(&:content_id) - found_editions.map(&:content_id) + def get_content_ids_from_content_references(content_references, locale) + embedded_aliases = content_references.map(&:alias) + content_id_aliases = ContentIdAlias.where(name: embedded_aliases) + + if content_id_aliases.count != content_references.count + not_found_aliases = embedded_aliases - content_id_aliases.map(&:name) raise CommandError.new( code: 422, - message: "Could not find any live editions in locale #{locale} for: #{not_found_content_ids.join(', ')}, ", + message: "Could not find any live editions in locale #{locale} for: #{not_found_aliases.join(', ')}", ) end + + embedded_content_ids = content_id_aliases.map(&:content_id) + document_types = content_references.map(&:document_type) + + found_editions = live_editions(embedded_content_ids, document_types, locale) + + if found_editions.count != content_id_aliases.count + not_found_aliases = content_id_aliases.reject { |ecr| found_editions.map(&:content_id).include?(ecr) }.map(&:name) + raise CommandError.new( + code: 422, + message: "Could not find any live editions in locale #{locale} for: #{not_found_aliases.join(', ')}", + ) + end + + embedded_content_ids end - def live_editions(content_references, locale) + def live_editions(content_ids, document_types, locale) Edition.with_document.where( state: "published", content_store: "live", - document_type: content_references.map(&:document_type), - documents: { content_id: content_references.map(&:content_id), locale: }, + document_type: document_types, + documents: { content_id: content_ids, locale: }, ) end end diff --git a/spec/integration/put_content/content_with_embedded_content_spec.rb b/spec/integration/put_content/content_with_embedded_content_spec.rb index 0a77cf0d6..65232096c 100644 --- a/spec/integration/put_content/content_with_embedded_content_spec.rb +++ b/spec/integration/put_content/content_with_embedded_content_spec.rb @@ -5,9 +5,21 @@ let(:first_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } let(:second_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } let(:document) { create(:document, content_id:) } + let(:first_contact_content_id_alias) do + create(:content_id_alias, name: "alias-1", content_id: first_contact.document.content_id) + end + let(:second_contact_content_id_alias) do + create(:content_id_alias, name: "alias-2", content_id: second_contact.document.content_id) + end before do - payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:contact:#{first_contact.document.content_id}}} {{embed:contact:#{second_contact.document.content_id}}}" }) + payload.merge!( + document_type: "press_release", + schema_name: "news_article", + details: { + body: "{{embed:contact:#{first_contact_content_id_alias.name}}} {{embed:contact:#{second_contact_content_id_alias.name}}}", + }, + ) end it "should create links" do @@ -27,16 +39,18 @@ end context "when embedded content is in a details field other than body" do - let(:first_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } - let(:second_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } + let(:contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } let(:document) { create(:document, content_id:) } + let(:content_id_alias) do + create(:content_id_alias, name: "alias-1", content_id: contact.document.content_id) + end let(:payload_for_multiple_field_embeds) do payload.merge!( document_type: "transaction", schema_name: "transaction", details: { - downtime_message: "{{embed:contact:#{first_contact.document.content_id}}}", + downtime_message: "{{embed:contact:#{content_id_alias.name}}}", }, ) end @@ -46,13 +60,13 @@ put "/v2/content/#{content_id}", params: payload_for_multiple_field_embeds.to_json }.to change(Link, :count).by(1) - expect(Link.find_by(target_content_id: first_contact.content_id)).not_to be_nil + expect(Link.find_by(target_content_id: contact.content_id)).not_to be_nil end it "should send transformed content to the content store" do put "/v2/content/#{content_id}", params: payload_for_multiple_field_embeds.to_json - expect_content_store_to_have_received_details_including({ "downtime_message" => first_contact.title.to_s }) + expect_content_store_to_have_received_details_including({ "downtime_message" => contact.title.to_s }) end end @@ -60,6 +74,13 @@ let(:first_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } let(:second_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } let(:document) { create(:document, content_id:) } + let(:first_contact_content_id_alias) do + create(:content_id_alias, name: "alias-1", content_id: first_contact.document.content_id) + end + let(:second_contact_content_id_alias) do + create(:content_id_alias, name: "alias-2", content_id: second_contact.document.content_id) + end + let(:details) do { country: { @@ -78,11 +99,11 @@ body: [ { "content_type": "text/govspeak", - "content": "{{embed:contact:#{first_contact.document.content_id}}}", + "content": "{{embed:contact:#{first_contact_content_id_alias.name}}}", }, { "content_type": "text/html", - "content": "
{{embed:contact:#{first_contact.document.content_id}}}
", + "content": "{{embed:contact:#{first_contact_content_id_alias.name}}}
", }, ], }, @@ -92,11 +113,11 @@ body: [ { "content_type": "text/govspeak", - "content": "{{embed:contact:#{second_contact.document.content_id}}}", + "content": "{{embed:contact:#{second_contact_content_id_alias.name}}}", }, { "content_type": "text/html", - "content": "{{embed:contact:#{second_contact.document.content_id}}}
", + "content": "{{embed:contact:#{second_contact_content_id_alias.name}}}
", }, ], }, @@ -159,9 +180,24 @@ let(:first_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } let(:second_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } let(:document) { create(:document, content_id:) } + let(:first_contact_content_id_alias) do + create(:content_id_alias, name: "alias-1", content_id: first_contact.document.content_id) + end + let(:second_contact_content_id_alias) do + create(:content_id_alias, name: "alias-2", content_id: second_contact.document.content_id) + end before do - payload.merge!(document_type: "person", schema_name: "person", details: { body: [{ content_type: "text/govspeak", content: "{{embed:contact:#{first_contact.document.content_id}}} {{embed:contact:#{second_contact.document.content_id}}}" }] }) + payload.merge!( + document_type: "person", + schema_name: "person", + details: { + body: [{ + content_type: "text/govspeak", + content: "{{embed:contact:#{first_contact_content_id_alias.name}}} {{embed:contact:#{second_contact_content_id_alias.name}}}", + }], + }, + ) end it "should create links" do @@ -184,9 +220,21 @@ let(:first_email_address) { create(:edition, state: "published", content_store: "live", document_type: "content_block_email_address", details: { email_address: "foo@example.com" }) } let(:second_email_address) { create(:edition, state: "published", content_store: "live", document_type: "content_block_email_address", details: { email_address: "bar@example.com" }) } let(:document) { create(:document, content_id:) } + let(:first_email_address_content_id_alias) do + create(:content_id_alias, name: "alias-1", content_id: first_email_address.document.content_id) + end + let(:second_email_address_content_id_alias) do + create(:content_id_alias, name: "alias-2", content_id: second_email_address.document.content_id) + end before do - payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:content_block_email_address:#{first_email_address.document.content_id}}} {{embed:content_block_email_address:#{second_email_address.document.content_id}}}" }) + payload.merge!( + document_type: "press_release", + schema_name: "news_article", + details: { + body: "{{embed:content_block_email_address:#{first_email_address_content_id_alias.name}}} {{embed:content_block_email_address:#{second_email_address_content_id_alias.name}}}", + }, + ) end it "should create links" do @@ -209,9 +257,21 @@ let(:email_address) { create(:edition, state: "published", content_store: "live", document_type: "content_block_email_address", details: { email_address: "foo@example.com" }) } let(:contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } let(:document) { create(:document, content_id:) } + let(:email_address_content_id_alias) do + create(:content_id_alias, name: "alias-1", content_id: email_address.document.content_id) + end + let(:contact_content_id_alias) do + create(:content_id_alias, name: "alias-2", content_id: contact.document.content_id) + end before do - payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:content_block_email_address:#{email_address.document.content_id}}} {{embed:contact:#{contact.document.content_id}}}" }) + payload.merge!( + document_type: "press_release", + schema_name: "news_article", + details: { + body: "{{embed:content_block_email_address:#{email_address_content_id_alias.name}}} {{embed:contact:#{contact_content_id_alias.name}}}", + }, + ) end it "should create links" do @@ -258,6 +318,12 @@ let(:first_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } let(:second_contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } let(:document) { create(:document, content_id:) } + let(:first_contact_content_id_alias) do + create(:content_id_alias, name: "alias-1", content_id: first_contact.document.content_id) + end + let(:second_contact_content_id_alias) do + create(:content_id_alias, name: "alias-2", content_id: second_contact.document.content_id) + end let(:edition) { create(:edition, document:) } before do @@ -267,7 +333,7 @@ target_content_id: first_contact.content_id, position: 0, }) - payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:contact:#{second_contact.document.content_id}}}" }) + payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:contact:#{second_contact_content_id_alias.name}}}" }) end it "should replace the embed link" do @@ -282,35 +348,44 @@ context "with embedded content that does not exist" do let(:document) { create(:document, content_id:) } - let(:fake_content_id) { SecureRandom.uuid } + let(:fake_friendly_id) { "non-existent" } before do - payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:contact:#{fake_content_id}}}" }) + payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:contact:#{fake_friendly_id}}}" }) end it "should return a 422 error" do put "/v2/content/#{content_id}", params: payload.to_json expect(response).to be_unprocessable - expect(response.body).to match(/Could not find any live editions in locale en for: #{fake_content_id}/) + expect(response.body).to match(/Could not find any live editions in locale en for: #{fake_friendly_id}/) end end context "with a mixture of embedded content that does and does not exist" do let(:contact) { create(:edition, state: "published", content_store: "live", document_type: "contact") } let(:document) { create(:document, content_id:) } - let(:first_fake_content_id) { SecureRandom.uuid } - let(:second_fake_content_id) { SecureRandom.uuid } + let(:contact_content_id_alias) do + create(:content_id_alias, name: "alias-1", content_id: contact.document.content_id) + end + let(:first_fake_friendly_id) { "non-existent-1" } + let(:second_fake_friendly_id) { "non-existent-2" } before do - payload.merge!(document_type: "press_release", schema_name: "news_article", details: { body: "{{embed:contact:#{contact.document.content_id}}} {{embed:contact:#{first_fake_content_id}}} {{embed:contact:#{second_fake_content_id}}}" }) + payload.merge!( + document_type: "press_release", + schema_name: "news_article", + details: { + body: "{{embed:contact:#{contact_content_id_alias.name}}} {{embed:contact:#{first_fake_friendly_id}}} {{embed:contact:#{second_fake_friendly_id}}}", + }, + ) end it "should return a 422 error" do put "/v2/content/#{content_id}", params: payload.to_json expect(response).to be_unprocessable - expect(response.body).to match(/Could not find any live editions in locale en for: #{first_fake_content_id}, #{second_fake_content_id}/) + expect(response.body).to match(/Could not find any live editions in locale en for: #{first_fake_friendly_id}, #{second_fake_friendly_id}/) end end diff --git a/spec/presenters/content_embed_presenter_spec.rb b/spec/presenters/content_embed_presenter_spec.rb index 84e2160e3..af165032a 100644 --- a/spec/presenters/content_embed_presenter_spec.rb +++ b/spec/presenters/content_embed_presenter_spec.rb @@ -9,6 +9,9 @@ links_hash:, ) end + let(:content_id_alias) do + create(:content_id_alias, name: "some-friendly-name", content_id: embedded_content_id) + end let(:links_hash) do { embed: [embedded_content_id], @@ -30,7 +33,7 @@ describe "#render_embedded_content" do context "when body is a string" do - let(:details) { { body: "some string with a reference: {{embed:contact:#{embedded_content_id}}}" } } + let(:details) { { body: "some string with a reference: {{embed:contact:#{content_id_alias.name}}}" } } it "returns embedded content references with values from their editions" do expect(described_class.new(edition).render_embedded_content(details)).to eq({ @@ -42,8 +45,8 @@ context "when body is an array" do let(:details) do { body: [ - { content_type: "text/govspeak", content: "some string with a reference: {{embed:contact:#{embedded_content_id}}}" }, - { content_type: "text/html", content: "some string with a reference: {{embed:contact:#{embedded_content_id}}}" }, + { content_type: "text/govspeak", content: "some string with a reference: {{embed:contact:#{content_id_alias.name}}}" }, + { content_type: "text/html", content: "some string with a reference: {{embed:contact:#{content_id_alias.name}}}" }, ] } end @@ -59,7 +62,7 @@ context "when body is a hash" do let(:details) do - { body: { title: "some string with a reference: {{embed:contact:#{embedded_content_id}}}" } } + { body: { title: "some string with a reference: {{embed:contact:#{content_id_alias.name}}}" } } end it "returns embedded content references with values from their editions" do @@ -75,7 +78,7 @@ parts: [ body: [ { - content: "some string with a reference: {{embed:contact:#{embedded_content_id}}}", + content: "some string with a reference: {{embed:contact:#{content_id_alias.name}}}", content_type: "text/govspeak", }, ], @@ -104,7 +107,7 @@ end context "when the embedded content is available in multiple locales" do - let(:details) { { body: "some string with a reference: {{embed:contact:#{embedded_content_id}}}" } } + let(:details) { { body: "some string with a reference: {{embed:contact:#{content_id_alias.name}}}" } } before do embedded_document = create(:document, content_id: embedded_content_id, locale: "cy") @@ -149,6 +152,8 @@ context "when the document is an email address" do let(:embedded_document) { create(:document) } + let(:name) { "abc" } + let(:links_hash) do { embed: [embedded_document.content_id], @@ -166,9 +171,10 @@ email_address: "foo@example.com", }, ) + create(:content_id_alias, name:, content_id: embedded_document.content_id) end - let(:details) { { body: "some string with a reference: {{embed:content_block_email_address:#{embedded_document.content_id}}}" } } + let(:details) { { body: "some string with a reference: {{embed:content_block_email_address:#{name}}}" } } it "returns an email address" do expect(described_class.new(edition).render_embedded_content(details)).to eq({ @@ -179,6 +185,7 @@ context "when multiple documents are embedded in different parts of the document" do let(:other_embedded_content_id) { SecureRandom.uuid } + let(:other_name) { "another-name" } before do embedded_document = create(:document, content_id: other_embedded_content_id) @@ -190,6 +197,7 @@ document_type: "contact", title: "VALUE2", ) + create(:content_id_alias, name: other_name, content_id: other_embedded_content_id) end let(:links_hash) do @@ -200,8 +208,8 @@ let(:details) do { - title: "title string with reference: {{embed:contact:#{other_embedded_content_id}}}", - body: "some string with a reference: {{embed:contact:#{embedded_content_id}}}", + title: "title string with reference: {{embed:contact:#{other_name}}}", + body: "some string with a reference: {{embed:contact:#{content_id_alias.name}}}", } end diff --git a/spec/presenters/details_presenter_spec.rb b/spec/presenters/details_presenter_spec.rb index 1a4adfe3f..416b705af 100644 --- a/spec/presenters/details_presenter_spec.rb +++ b/spec/presenters/details_presenter_spec.rb @@ -190,7 +190,10 @@ create(:edition, state: "published", content_store: "live", document_type: "contact", title: "Some contact") end let(:content_embed_presenter) { Presenters::ContentEmbedPresenter.new(edition) } - let(:field_value) { "{{embed:contact:#{embeddable_content.document.content_id}}}" } + let(:content_id_alias) do + create(:content_id_alias, name: "a-friendly-name", content_id: embeddable_content.document.content_id) + end + let(:field_value) { "{{embed:contact:#{content_id_alias.name}}}" } context "when the #{field_name} is not enumerable" do let(:edition) { create(:edition, details: { field_name => field_value }, links_hash: { embed: [embeddable_content.document.content_id] }) } diff --git a/spec/presenters/queries/expanded_link_set_spec.rb b/spec/presenters/queries/expanded_link_set_spec.rb index 577d5274f..ecc9a81d2 100644 --- a/spec/presenters/queries/expanded_link_set_spec.rb +++ b/spec/presenters/queries/expanded_link_set_spec.rb @@ -81,6 +81,9 @@ let(:contact) do create(:edition, state: "published", content_store: "live", document_type: "contact", title: "Some contact") end + let(:content_id_alias) do + create(:content_id_alias, content_id: contact.document.content_id) + end before do create_edition(a, "/a", document_type: "person") @@ -92,7 +95,7 @@ body: [ { content_type: "text/govspeak", - content: "{{embed:contact:#{contact.document.content_id}}}", + content: "{{embed:contact:#{content_id_alias.name}}}", }, ], }, diff --git a/spec/services/embedded_content_finder_service_spec.rb b/spec/services/embedded_content_finder_service_spec.rb index 05d624880..34c85e2cd 100644 --- a/spec/services/embedded_content_finder_service_spec.rb +++ b/spec/services/embedded_content_finder_service_spec.rb @@ -23,9 +23,19 @@ details: { title: "Some Title" }) end + let(:content_id_alias_1) do + create(:content_id_alias, name: "some-friendly-name", content_id: editions[0].content_id) + end + let(:content_id_alias_2) do + create(:content_id_alias, name: "some-other-friendly-name", content_id: editions[1].content_id) + end + let(:draft_content_id_alias) do + create(:content_id_alias, name: "draft-friendly-name", content_id: draft_edition.content_id) + end + %w[body downtime_message more_information].each do |field_name| it "finds content references" do - details = { field_name => "{{embed:#{document_type}:#{editions[0].content_id}}} {{embed:#{document_type}:#{editions[1].content_id}}}" } + details = { field_name => "{{embed:#{document_type}:#{content_id_alias_1.name}}} {{embed:#{document_type}:#{content_id_alias_2.name}}}" } links = EmbeddedContentFinderService.new.fetch_linked_content_ids(details, Edition::DEFAULT_LOCALE) @@ -33,7 +43,7 @@ end it "finds content references when #{field_name} is an array of hashes" do - details = { field_name => [{ "content" => "{{embed:#{document_type}:#{editions[0].content_id}}} {{embed:#{document_type}:#{editions[1].content_id}}}" }] } + details = { field_name => [{ "content" => "{{embed:#{document_type}:#{content_id_alias_1.name}}} {{embed:#{document_type}:#{content_id_alias_2.name}}}" }] } links = EmbeddedContentFinderService.new.fetch_linked_content_ids(details, Edition::DEFAULT_LOCALE) @@ -46,7 +56,7 @@ { body: [ { - content: "some string with a reference: {{embed:#{document_type}:#{editions[0].content_id}}}", + content: "some string with a reference: {{embed:#{document_type}:#{content_id_alias_1.name}}}", content_type: "text/govspeak", }, ], @@ -56,7 +66,7 @@ { body: [ { - content: "some string with another reference: {{embed:#{document_type}:#{editions[1].content_id}}}", + content: "some string with another reference: {{embed:#{document_type}:#{content_id_alias_2.name}}}", content_type: "text/govspeak", }, ], @@ -71,23 +81,33 @@ end it "finds content references when the field is a hash" do - details = { field_name => { title: "{{embed:#{document_type}:#{editions[0].content_id}}}", slug: "{{embed:#{document_type}:#{editions[1].content_id}}}", current: true } } + details = { field_name => { title: "{{embed:#{document_type}:#{content_id_alias_1.name}}}", slug: "{{embed:#{document_type}:#{content_id_alias_2.name}}}", current: true } } links = EmbeddedContentFinderService.new.fetch_linked_content_ids(details, Edition::DEFAULT_LOCALE) expect(links).to eq([editions[0].content_id, editions[1].content_id]) end - it "errors when given a content ID that is still draft" do - details = { field_name => "{{embed:#{document_type}:#{draft_edition.content_id}}}" } + it "errors when given an alias for an Edition that is still draft" do + details = { field_name => "{{embed:#{document_type}:#{draft_content_id_alias.name}}}" } - expect { EmbeddedContentFinderService.new.fetch_linked_content_ids(details, Edition::DEFAULT_LOCALE) }.to raise_error(CommandError) + expect { + EmbeddedContentFinderService.new.fetch_linked_content_ids(details, Edition::DEFAULT_LOCALE) + }.to raise_error( + CommandError, + "Could not find any live editions in locale #{Edition::DEFAULT_LOCALE} for: #{draft_content_id_alias.name}", + ) end - it "errors when given a live content ID that is not available in the current locale" do - details = { field_name => "{{embed:#{document_type}:#{editions[0].content_id}}}" } + it "errors when given an alias for a live Edition that is not available in the current locale" do + details = { field_name => "{{embed:#{document_type}:#{content_id_alias_1.name}}}" } - expect { EmbeddedContentFinderService.new.fetch_linked_content_ids(details, "foo") }.to raise_error(CommandError) + expect { + EmbeddedContentFinderService.new.fetch_linked_content_ids(details, "foo") + }.to raise_error( + CommandError, + "Could not find any live editions in locale foo for: #{content_id_alias_1.name}", + ) end end end