lib/pmp/collection_document.rb
require 'ostruct'
require 'uri'
module PMP
# Using OpenStruct for now - perhaps use ActiveModel? hmm...
class CollectionDocument
include Configuration
include Connection
include Parser
# the href/url string to retrieve info for this resource
attr_accessor :href
# keep a ref to response obj if this resulted from one
# should this be private?
attr_accessor :response
# keep a ref to original doc from which this obj was created
# should this be private?
attr_accessor :original
# all collection docs have a version
# default is '1.0'
attr_accessor :version
# has this resource actually been loaded from remote url or json document?
attr_accessor :loaded
# private var to save attributes obj, to handle object attributes
attr_accessor :attributes
# private var to save links obj, to handle link additions
attr_accessor :links
# this is a tricky, read-only list of items, same as if loaded from link->item links
# do not put into json form of this doc
attr_accessor :items
# document is the original json derived doc used to create this resource
# assumption is that doc is a parsed json doc confirming to collection.doc+json
# TODO: check if this is a json string or hash, for now assume it has been mashified
def initialize(options={}, &block)
@mutex = Mutex.new
apply_configuration(options)
self.root = current_options.delete(:root)
self.href = current_options.delete(:href)
self.version = current_options.delete(:version) || '1.0'
self.attributes = OpenStruct.new
# if there is a doc to be had, pull it out
self.response = current_options.delete(:response)
self.original = current_options.delete(:document)
yield(self) if block_given?
end
def items
get
@items ||= []
end
def attributes
get
attributes_object
end
def attributes_object
@attributes || OpenStruct.new
end
def response=(resp)
unless (!resp || loaded?)
@response = resp
self.original = resp.body
end
end
def original=(doc)
unless (!doc || loaded?)
@original = doc
parse(@original)
self.loaded = true
end
end
def get
@mutex.synchronize {
return self if loaded? || !href
self.response = request(:get, href)
self.loaded = true
}
self
end
def links
get
links_object
end
def links_object
@links ||= PMP::Links.new(self)
end
def save
set_guid_if_blank
url = save_link.where(guid: self.guid).url
resp = request(:put, url, self)
self.response = resp
self.href = resp.body['url']
end
def delete
raise 'No guid specified to delete' if self.guid.blank?
url = delete_link.where(guid: self.guid).url
request(:delete, url)
end
def save_link
link = root.edit['urn:collectiondoc:form:documentsave']
raise 'Save link not found' unless link
link
end
def delete_link
link = root.edit['urn:collectiondoc:form:documentdelete']
raise 'Delete link not found' unless link
link
end
def root=(r)
@root = r
end
def root
@root ||= new_root
end
def new_root
root_options = current_options.merge(href: endpoint)
PMP::CollectionDocument.new(root_options).tap{|r| r.root = r }
end
def loaded?
!!self.loaded
end
def setup_oauth_token
if !oauth_token && current_options[:client_id] && current_options[:client_secret]
token = PMP::Token.new(current_options.merge(root: root)).get_token
self.oauth_token = token.token
self.token_type = token.params['token_type']
if @root
@root.oauth_token = token.token
@root.token_type = token.params['token_type']
end
end
end
# url includes any params - full url
def request(method, url, body=nil) # :nodoc:
unless ['/', ''].include?(URI::parse(url).path)
setup_oauth_token
end
begin
raw = connection(current_options.merge({url: url})).send(method) do |req|
if [:post, :put].include?(method.to_sym) && !body.blank?
req.body = PMP::CollectionDocument.to_persist_json(body)
end
end
rescue Faraday::Error::ResourceNotFound => not_found_ex
if (method.to_sym == :get)
raw = OpenStruct.new(body: nil, status: 404)
else
raise not_found_ex
end
end
# may not need this, but remember how we made this response
PMP::Response.new(raw, {method: method, url: url, body: body})
end
# static method to filter out static parts of c.doc+j hash before PUT/POST to PMP server
# in the future this should be fixed in PMP API to no longer be necessary
def self.to_persist_json(body)
return body.to_s if body.is_a?(String) || !body.respond_to?(:as_json)
result = body.as_json.select { |k,v| %w(version attributes links).include?(k) }
result['attributes'].reject! { |k,v| %w(created modified).include?(k) }
result['links'].reject! { |k,v| %w(creator query edit auth).include?(k) }
result.to_json
end
def attributes_map
HashWithIndifferentAccess.new(attributes.marshal_dump)
end
def set_guid_if_blank
self.guid = SecureRandom.uuid if guid.blank?
end
def method_missing(method, *args)
get if (method.to_s.last != '=')
respond_to?(method) ? send(method, *args) : attributes_object.send(method, *args)
end
end
end