From 5e5d7a3ad110a7df418add4198e0c1aeae0be3d2 Mon Sep 17 00:00:00 2001 From: zfletch Date: Fri, 18 Oct 2019 13:32:20 -0400 Subject: [PATCH] add up to document link header Add `rel="up"` to the document `Link` header. If the fragment is a single `ref`, then return the `parent` if it exists. If it's a `start...end` fragment, then return a link with a `start` being the start fragment's parent and `end` the end fragment's parent. --- app/models/fragment.rb | 6 + app/presenters/fragment_presenter.rb | 1 + .../start_stop_fragment_presenter.rb | 17 +- spec/models/fragment_spec.rb | 10 + spec/requests/documents_spec.rb | 175 ++++++++++++++++-- 5 files changed, 192 insertions(+), 17 deletions(-) diff --git a/app/models/fragment.rb b/app/models/fragment.rb index 35785c6..58f7d61 100644 --- a/app/models/fragment.rb +++ b/app/models/fragment.rb @@ -50,6 +50,12 @@ def last_range(stop_fragment) [range.first, range.last] end + def parent_range(stop_fragment) + return [] unless parent && stop_fragment.parent + + [parent, stop_fragment.parent] + end + private def steps(stop_fragment) diff --git a/app/presenters/fragment_presenter.rb b/app/presenters/fragment_presenter.rb index 74a3f80..651092f 100644 --- a/app/presenters/fragment_presenter.rb +++ b/app/presenters/fragment_presenter.rb @@ -12,6 +12,7 @@ def links { prev: fragment.previous_fragment, next: fragment.next_fragment, + up: fragment.parent, first: fragment.first_fragment, last: fragment.last_fragment, }.map { |name, fragment| link(name, fragment) }.compact diff --git a/app/presenters/start_stop_fragment_presenter.rb b/app/presenters/start_stop_fragment_presenter.rb index aa63871..1b339bb 100644 --- a/app/presenters/start_stop_fragment_presenter.rb +++ b/app/presenters/start_stop_fragment_presenter.rb @@ -20,12 +20,7 @@ def xml end def links - { - prev: start.previous_range(stop), - next: start.next_range(stop), - first: start.first_range(stop), - last: start.last_range(stop), - }.map { |name, fragments| link(name, fragments) }.compact + link_headers.map { |name, fragments| link(name, fragments) }.compact end private @@ -40,6 +35,16 @@ def combine_xml_documents(original_xml, xmls) end.to_xml end + def link_headers + { + prev: start.previous_range(stop), + next: start.next_range(stop), + up: start.parent_range(stop), + first: start.first_range(stop), + last: start.last_range(stop), + } + end + def link(name, fragments) return nil if fragments.empty? diff --git a/spec/models/fragment_spec.rb b/spec/models/fragment_spec.rb index 8a611cb..6170a8f 100644 --- a/spec/models/fragment_spec.rb +++ b/spec/models/fragment_spec.rb @@ -239,5 +239,15 @@ expect(child11.last_range(child13)).to eq([child21, child23]) end end + + describe '#parent_range' do + it 'is empty for a parent fragment' do + expect(parent1.parent_range(parent2)).to be_empty + end + + it 'gets the parent range for a child fragment' do + expect(child13.parent_range(child21)).to eq([parent1, parent2]) + end + end end end diff --git a/spec/requests/documents_spec.rb b/spec/requests/documents_spec.rb index c653650..749f8fd 100644 --- a/spec/requests/documents_spec.rb +++ b/spec/requests/documents_spec.rb @@ -2,7 +2,7 @@ RSpec.describe '/documents', type: :request do let!(:collection) do - Collection.create(urn: 'urn', title: 'title', display_type: 'resource', cite_structure: ['book']) + Collection.create(urn: 'urn', title: 'title', display_type: 'resource', cite_structure: %w[book chapter]) end let!(:document) { Document.create(urn: 'urn', xml: '', collection: collection) } let!(:xml1) do @@ -11,6 +11,20 @@
+
+ abc +
+
+
+
+ ) + end + let!(:xml11) do + %( + + + +
abc
@@ -23,6 +37,20 @@
+
+ def +
+
+
+
+ ) + end + let!(:xml21) do + %( + + + +
def
@@ -35,6 +63,20 @@
+
+ ghi +
+
+
+
+ ) + end + let!(:xml31) do + %( + + + +
ghi
@@ -47,24 +89,72 @@
+
+ jkl +
+
+ mno +
+
+
+
+ ) + end + let!(:xml41) do + %( + + + +
jkl
) end - let!(:book1) { Fragment.create(document: document, ref: '1', level: 1, rank: 0, descendent_rank: 0, xml: xml1) } - let!(:book2) { Fragment.create(document: document, ref: '2', level: 1, rank: 1, descendent_rank: 1, xml: xml2) } - let!(:book3) { Fragment.create(document: document, ref: '3', level: 1, rank: 2, descendent_rank: 2, xml: xml3) } - let!(:book4) { Fragment.create(document: document, ref: '4', level: 1, rank: 3, descendent_rank: 3, xml: xml4) } + let!(:xml42) do + %( + + + +
+ mno +
+
+
+ ) + end + let!(:book1) { Fragment.create(document: document, ref: '1', level: 1, rank: 0, descendent_rank: 1, xml: xml1) } + let!(:chapter11) do + Fragment.create(document: document, parent: book1, ref: '1.1', level: 2, rank: 1, descendent_rank: 1, xml: xml11) + end + + let!(:book2) { Fragment.create(document: document, ref: '2', level: 1, rank: 2, descendent_rank: 3, xml: xml2) } + let!(:chapter21) do + Fragment.create(document: document, parent: book2, ref: '2.1', level: 2, rank: 3, descendent_rank: 3, xml: xml21) + end + + let!(:book3) { Fragment.create(document: document, ref: '3', level: 1, rank: 4, descendent_rank: 5, xml: xml3) } + let!(:chapter31) do + Fragment.create(document: document, parent: book3, ref: '3.1', level: 2, rank: 5, descendent_rank: 5, xml: xml31) + end + + let!(:book4) { Fragment.create(document: document, ref: '4', level: 1, rank: 6, descendent_rank: 8, xml: xml4) } + let!(:chapter41) do + Fragment.create(document: document, parent: book4, ref: '4.1', level: 2, rank: 7, descendent_rank: 7, xml: xml41) + end + let!(:chapter42) do + Fragment.create(document: document, parent: book4, ref: '4.2', level: 2, rank: 8, descendent_rank: 8, xml: xml42) + end specify 'Retrieve a passage using ref' do - get '/documents/?id=urn&ref=1' + get '/documents/?id=urn&ref=2' expect(response.content_type).to eq('application/tei+xml; charset=utf-8') expect(response).to have_http_status(:ok) expect(response.headers['Link']).to eq(%( - ; rel="next", + ; rel="prev", + ; rel="next", ; rel="first", ; rel="last", ; rel="contents", @@ -74,8 +164,36 @@ -
- abc +
+
+ def +
+
+ + + )) + end + + specify 'Retrieve a passage with a parent using ref' do + get '/documents/?id=urn&ref=3.1' + + expect(response.content_type).to eq('application/tei+xml; charset=utf-8') + expect(response).to have_http_status(:ok) + expect(response.headers['Link']).to eq(%( + ; rel="prev", + ; rel="next", + ; rel="up", + ; rel="first", + ; rel="last", + ; rel="contents", + ; rel="collection" + ).squish) + expect(response.body).to be_equivalent_to(%( + + + +
+ ghi
@@ -99,10 +217,45 @@
- ghi +
+ ghi +
- jkl +
+ jkl +
+
+ mno +
+
+
+
+ )) + end + + specify 'Retrieve a passage using start and end across books' do + get '/documents/?id=urn&start=1.1&end=2.1' + + expect(response.content_type).to eq('application/tei+xml; charset=utf-8') + expect(response).to have_http_status(:ok) + expect(response.headers['Link']).to eq(%( + ; rel="next", + ; rel="up", + ; rel="first", + ; rel="last", + ; rel="contents", + ; rel="collection" + ).squish) + expect(response.body).to be_equivalent_to(%( + + + +
+ abc +
+
+ def