Skip to content

Commit caef0e9

Browse files
thatbudakguyclaude
andcommitted
Expose harvested documents via each_document
Refactors #index onto a private #each_page enumerator, so the documents harvested from a site can be used for something other than indexing them into Solr (e.g. writing them to disk for your own OpenGeoMetadata repository). Mirrors the docs_to_index seam in GeoCombine::Harvester. Also fixes documents the document transformer omits being sent to Solr as nulls: #index mutated the page with map! and discarded the compacted copy. Closes #207 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 7999858 commit caef0e9

3 files changed

Lines changed: 73 additions & 13 deletions

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,17 @@ Crawl delays can be configured (in seconds) either globally for all sites or on
197197

198198
Solr's commitWithin option can be configured (in milliseconds) by passing a value under the commit_within key.
199199

200+
#### Harvesting Documents Without Indexing Them
201+
202+
The harvester exposes the documents it harvests as an enumerable, so you can do something with them other than index them into Solr -- writing them to disk to contribute them to your own OpenGeoMetadata repository, for example. The documents yielded have already been through the configured document transformer.
203+
204+
```ruby
205+
harvester = GeoCombine::GeoBlacklightHarvester.new(:SITE1)
206+
harvester.each_document do |document|
207+
File.write("#{document['id']}.json", JSON.pretty_generate(document))
208+
end
209+
```
210+
200211
#### Transforming Documents
201212

202213
You may need to transform documents that are harvested for various purposes (removing fields, adding fields, omitting a document all together, etc). You can configure some ruby code (a proc) that will take the document in, transform it, and return the transformed document. By default the indexer will remove the `score`, `timestamp`, and `_version_` fields from the documents harvested. If you provide your own transformer, you'll likely want to remove these fields in addition to the other transformations you provide.

lib/geo_combine/geo_blacklight_harvester.rb

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,25 +55,25 @@ def initialize(site_key, logger: GeoCombine::Logger.logger)
5555
raise ArgumentError, "Site key #{@site_key.inspect} is not configured for #{self.class.name}" unless @site
5656
end
5757

58+
# Index the documents harvested from the site into Solr
5859
def index
59-
@logger.debug "fetching page 1 @ #{base_url}&page=1"
60-
response = JSON.parse(Net::HTTP.get(URI("#{base_url}&page=1")))
61-
response_class = BlacklightResponseVersionFactory.call(response)
62-
63-
response_class.new(response:, base_url:, logger: @logger).documents.each do |docs|
64-
docs.map! do |document|
65-
self.class.document_transformer&.call(document)
66-
end.compact
67-
68-
@logger.debug "adding #{docs.count} documents to solr"
60+
each_page do |documents|
61+
@logger.debug "adding #{documents.count} documents to solr"
6962
solr_connection.update params: { commitWithin: commit_within, overwrite: true },
70-
data: docs.to_json,
63+
data: documents.to_json,
7164
headers: { 'Content-Type' => 'application/json' }
72-
73-
sleep(crawl_delay.to_i) if crawl_delay
7465
end
7566
end
7667

68+
# Enumerable of the documents harvested from the site, for passing to an
69+
# indexer or doing something else with them (e.g. writing them to disk).
70+
# Documents have already been through the configured document transformer.
71+
def each_document(&block)
72+
return to_enum(:each_document) unless block_given?
73+
74+
each_page { |documents| documents.each(&block) }
75+
end
76+
7777
##
7878
# A "factory" class to determine the blacklight response version to use
7979
class BlacklightResponseVersionFactory
@@ -188,6 +188,21 @@ def documents_from_urls(urls)
188188

189189
private
190190

191+
# Enumerable of pages of transformed documents harvested from the site
192+
def each_page
193+
return to_enum(:each_page) unless block_given?
194+
195+
@logger.debug "fetching page 1 @ #{base_url}&page=1"
196+
response = JSON.parse(Net::HTTP.get(URI("#{base_url}&page=1")))
197+
response_class = BlacklightResponseVersionFactory.call(response)
198+
199+
response_class.new(response:, base_url:, logger: @logger).documents.each do |documents|
200+
yield documents.map { |document| self.class.document_transformer&.call(document) }.compact
201+
202+
sleep(crawl_delay.to_i) if crawl_delay
203+
end
204+
end
205+
191206
def base_url
192207
"#{site[:host]}?#{default_params.to_query}"
193208
end

spec/lib/geo_combine/geo_blacklight_harvester_spec.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,40 @@
120120
end
121121
end
122122

123+
describe '#each_document' do
124+
before do
125+
expect(Net::HTTP).to receive(:get).with(
126+
URI('https://example.com?f%5Bdct_provenance_s%5D%5B%5D=INSTITUTION&format=json&per_page=100&page=1')
127+
).and_return(stub_json_response)
128+
end
129+
130+
let(:docs) { [{ 'layer_slug_s' => 'abc-123', 'score' => 0.1 }, { 'layer_slug_s' => 'abc-321' }] }
131+
let(:transformed_docs) { [{ 'layer_slug_s' => 'abc-123' }, { 'layer_slug_s' => 'abc-321' }] }
132+
let(:stub_json_response) do
133+
{ response: { docs:, pages: { current_page: 1, total_pages: 1 } } }.to_json
134+
end
135+
136+
it 'yields each transformed document' do
137+
expect { |block| harvester.each_document(&block) }.to yield_successive_args(*transformed_docs)
138+
end
139+
140+
it 'returns an enumerator when no block is given' do
141+
expect(harvester.each_document.to_a).to eq(transformed_docs)
142+
end
143+
144+
context 'when the document transformer omits a document' do
145+
before do
146+
allow(described_class).to receive(:document_transformer).and_return(
147+
->(document) { document unless document['layer_slug_s'] == 'abc-123' }
148+
)
149+
end
150+
151+
it 'does not yield the omitted document' do
152+
expect { |block| harvester.each_document(&block) }.to yield_successive_args({ 'layer_slug_s' => 'abc-321' })
153+
end
154+
end
155+
end
156+
123157
describe 'BlacklightResponseVersionFactory' do
124158
let(:version_class) { described_class::BlacklightResponseVersionFactory.call(json) }
125159

0 commit comments

Comments
 (0)