Skip to content

Commit 4d2693a

Browse files
authored
Merge pull request #81 from ndlib/favor-instances-over-classes
Favoring instance over classes
2 parents 45d8051 + c992671 commit 4d2693a

File tree

3 files changed

+187
-7
lines changed

3 files changed

+187
-7
lines changed

lib/oai/provider.rb

Lines changed: 129 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,35 @@
139139
#
140140
# Special thanks to Jose Hales-Garcia for this solution.
141141
#
142+
# ### Leverage the Provider instance
143+
#
144+
# The traditional implementation of the OAI::Provider would pass the OAI::Provider
145+
# class to the different resposnes. This made it hard to inject context into a
146+
# common provider. Consider that we might have different request headers that
147+
# change the scope of the OAI::Provider queries.
148+
#
149+
# ```ruby
150+
# class InstanceProvider
151+
# def initialize(options = {})
152+
# super({ :provider_context => :instance_based })
153+
# @controller = options.fetch(:controller)
154+
# end
155+
# attr_reader :controller
156+
# end
157+
#
158+
# class OaiController < ApplicationController
159+
# def index
160+
# provider = InstanceProvider.new({ :controller => self })
161+
# request_body = provider.process_request(oai_params.to_h)
162+
# render :body => request_body, :content_type => 'text/xml'
163+
# end
164+
# ```
165+
#
166+
# In the above example, the underlying response object will now receive an
167+
# instance of the InstanceProvider. Without the `super({ :provider_context => :instance_based })`
168+
# the response objects would have received the class InstanceProvider as the
169+
# given provider.
170+
#
142171
# ## Supporting custom metadata formats
143172
#
144173
# See {OAI::MetadataFormat} for details.
@@ -292,39 +321,132 @@ def inherited(klass)
292321

293322
Base.register_format(OAI::Provider::Metadata::DublinCore.instance)
294323

324+
PROVIDER_CONTEXTS = {
325+
:class_based => :class_based,
326+
:instance_based => :instance_based
327+
}
328+
329+
def initialize(options = {})
330+
provider_context = options.fetch(:provider_context) { :class_based }
331+
@provider_context = PROVIDER_CONTEXTS.fetch(provider_context)
332+
end
333+
334+
# @note These are the accessor methods on the class. If you need to overwrite
335+
# them on the instance level you can do that. However, an instance of this
336+
# class won't be used unless you initialize with:
337+
# { :provider_context => :instance_based }
338+
attr_writer :name, :url, :prefix, :email, :delete_support, :granularity, :model, :identifier, :description
339+
340+
# The traditional interaction of a Provider has been to:
341+
#
342+
# 1) Assign attributes to the Provider class
343+
# 2) Instantiate the Provider class
344+
# 3) Call response instance methods for theProvider which pass
345+
# the Provider class and not the instance.
346+
#
347+
# The above behavior continues unless you initialize the Provider with
348+
# { :provider_context => :instance_based }. If you do that, then the
349+
# Provider behavior will be:
350+
#
351+
# 1) Assign attributes to Provider class
352+
# 2) Instantiate the Provider class
353+
# 3) Call response instance methods for theProvider which pass an
354+
# instance of the Provider to those response objects.
355+
# a) The instance will mirror all of the assigned Provider class
356+
# attributes, but allows for overriding and extending on a
357+
# case by case basis.
358+
# (Dear reader, please note the second behavior is something most
359+
# of us would've assumed to be the case, but for historic now lost
360+
# reasons is not the case.)
361+
def provider_context
362+
if @provider_context == :instance_based
363+
self
364+
else
365+
self.class
366+
end
367+
end
368+
369+
def format_supported?(*args)
370+
self.class.format_supported?(*args)
371+
end
372+
373+
def format(*args)
374+
self.class.format(*args)
375+
end
376+
377+
def formats
378+
self.class.formats
379+
end
380+
381+
def name
382+
@name || self.class.name
383+
end
384+
385+
def url
386+
@url || self.class.url
387+
end
388+
389+
def prefix
390+
@prefix || self.class.prefix
391+
end
392+
393+
def email
394+
@email || self.class.email
395+
end
396+
397+
def delete_support
398+
@delete_support || self.class.delete_support
399+
end
400+
401+
def granularity
402+
@granularity || self.class.granularity
403+
end
404+
405+
def model
406+
@model || self.class.model
407+
end
408+
409+
def identifier
410+
@identifier || self.class.identifier
411+
end
412+
413+
def description
414+
@description || self.class.description
415+
end
416+
295417
# Equivalent to '&verb=Identify', returns information about the repository
296418
def identify(options = {})
297-
Response::Identify.new(self.class, options).to_xml
419+
Response::Identify.new(provider_context, options).to_xml
298420
end
299421

300422
# Equivalent to '&verb=ListSets', returns a list of sets that are supported
301423
# by the repository or an error if sets are not supported.
302424
def list_sets(options = {})
303-
Response::ListSets.new(self.class, options).to_xml
425+
Response::ListSets.new(provider_context, options).to_xml
304426
end
305427

306428
# Equivalent to '&verb=ListMetadataFormats', returns a list of metadata formats
307429
# supported by the repository.
308430
def list_metadata_formats(options = {})
309-
Response::ListMetadataFormats.new(self.class, options).to_xml
431+
Response::ListMetadataFormats.new(provider_context, options).to_xml
310432
end
311433

312434
# Equivalent to '&verb=ListIdentifiers', returns a list of record headers that
313435
# meet the supplied criteria.
314436
def list_identifiers(options = {})
315-
Response::ListIdentifiers.new(self.class, options).to_xml
437+
Response::ListIdentifiers.new(provider_context, options).to_xml
316438
end
317439

318440
# Equivalent to '&verb=ListRecords', returns a list of records that meet the
319441
# supplied criteria.
320442
def list_records(options = {})
321-
Response::ListRecords.new(self.class, options).to_xml
443+
Response::ListRecords.new(provider_context, options).to_xml
322444
end
323445

324446
# Equivalent to '&verb=GetRecord', returns a record matching the required
325447
# :identifier option
326448
def get_record(options = {})
327-
Response::GetRecord.new(self.class, options).to_xml
449+
Response::GetRecord.new(provider_context, options).to_xml
328450
end
329451

330452
# xml_response = process_verb('ListRecords', :from => 'October 1, 2005',
@@ -336,7 +458,7 @@ def process_request(params = {})
336458
begin
337459

338460
# Allow the request to pass in a url
339-
self.class.url = params['url'] ? params.delete('url') : self.class.url
461+
provider_context.url = params['url'] ? params.delete('url') : self.url
340462

341463
verb = params.delete('verb') || params.delete(:verb)
342464

test/provider/tc_instance_provider.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
require 'test_helper_provider'
2+
3+
class TestInstanceProvider < Test::Unit::TestCase
4+
5+
# Prior to the commit introducing this code, the InstanceProvider#identify
6+
# method would instantiate a Response::Identify object, passing the
7+
# InstanceProvider class as the provider for the Response::Identify
8+
# instance. With the commit introducing this test, the
9+
# InstanceProvider#identify now passes the instance of InstanceProvider
10+
# to the instantiation of Response::Identify.
11+
#
12+
# Thus we can override, on an instance by instance basis, the behavior of a
13+
# response object.
14+
def test_instance_used_in_responses
15+
@url_path = "/stringy-mc-string-face"
16+
@instance_provider = InstanceProvider.new({ :provider_context => :instance_based, :url_path => @url_path })
17+
18+
xml = @instance_provider.identify
19+
doc = REXML::Document.new(xml)
20+
assert_equal "http://localhost#{@url_path}", doc.elements["OAI-PMH/Identify/baseURL"].text
21+
end
22+
23+
def test_class_used_in_responses
24+
@url_path = "/stringy-mc-string-face"
25+
@instance_provider = InstanceProvider.new({ :provider_context => :class_based, :url_path => @url_path })
26+
27+
xml = @instance_provider.identify
28+
doc = REXML::Document.new(xml)
29+
assert_equal "http://localhost", doc.elements["OAI-PMH/Identify/baseURL"].text
30+
end
31+
32+
def test_by_default_class_used_in_responses
33+
@url_path = "/stringy-mc-string-face"
34+
@instance_provider = InstanceProvider.new({ :url_path => @url_path })
35+
36+
xml = @instance_provider.identify
37+
doc = REXML::Document.new(xml)
38+
assert_equal "http://localhost", doc.elements["OAI-PMH/Identify/baseURL"].text
39+
end
40+
41+
end

test/provider/test_helper_provider.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,20 @@ class DescribedProvider < Provider::Base
4343
sample_id '13900'
4444
extra_description "<my_custom_xml />"
4545
end
46+
47+
class InstanceProvider < Provider::Base
48+
repository_name 'Instance Provider'
49+
record_prefix 'oai:test'
50+
repository_url 'http://localhost'
51+
source_model SimpleModel.new
52+
53+
def initialize(options = {})
54+
super
55+
@url_path = options.fetch(:url_path)
56+
end
57+
attr_reader :url_path
58+
59+
def url
60+
File.join(super, url_path)
61+
end
62+
end

0 commit comments

Comments
 (0)