talis 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +24 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +28 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/README.md +76 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/talis.rb +25 -0
- data/lib/talis/analytics.rb +31 -0
- data/lib/talis/analytics/event.rb +67 -0
- data/lib/talis/authentication.rb +14 -0
- data/lib/talis/authentication/client.rb +82 -0
- data/lib/talis/authentication/login.rb +169 -0
- data/lib/talis/authentication/public_key.rb +53 -0
- data/lib/talis/authentication/token.rb +172 -0
- data/lib/talis/bibliography.rb +52 -0
- data/lib/talis/bibliography/ebook.rb +50 -0
- data/lib/talis/bibliography/manifestation.rb +141 -0
- data/lib/talis/bibliography/result_set.rb +34 -0
- data/lib/talis/bibliography/work.rb +164 -0
- data/lib/talis/constants.rb +9 -0
- data/lib/talis/errors.rb +10 -0
- data/lib/talis/errors/authentication_failed_error.rb +4 -0
- data/lib/talis/errors/client_errors.rb +19 -0
- data/lib/talis/errors/server_communication_error.rb +4 -0
- data/lib/talis/errors/server_error.rb +4 -0
- data/lib/talis/extensions/object.rb +11 -0
- data/lib/talis/feeds.rb +8 -0
- data/lib/talis/feeds/annotation.rb +129 -0
- data/lib/talis/feeds/feed.rb +58 -0
- data/lib/talis/hierarchy.rb +9 -0
- data/lib/talis/hierarchy/asset.rb +265 -0
- data/lib/talis/hierarchy/node.rb +200 -0
- data/lib/talis/hierarchy/resource.rb +159 -0
- data/lib/talis/oauth_service.rb +18 -0
- data/lib/talis/resource.rb +68 -0
- data/lib/talis/user.rb +112 -0
- data/lib/talis/version.rb +3 -0
- data/talis.gemspec +39 -0
- metadata +327 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'talis/authentication/token'
|
3
|
+
|
4
|
+
module Talis
|
5
|
+
module Authentication
|
6
|
+
# Provides the ability to fetch a public key to verify tokens.
|
7
|
+
# There is no need to use this class directly as it is used by the Token
|
8
|
+
# class to verify tokens.
|
9
|
+
class PublicKey < Talis::Resource
|
10
|
+
base_uri Token.base_uri
|
11
|
+
|
12
|
+
# Construct an empty public key object.
|
13
|
+
# @param cache_store [ActiveSupport::Cache::MemoryStore] A cache
|
14
|
+
# store to use to fetch locally cached keys before trying remotely.
|
15
|
+
def initialize(cache_store)
|
16
|
+
@cache_store = cache_store
|
17
|
+
end
|
18
|
+
|
19
|
+
# Fetch a public key for use with token verification, either from
|
20
|
+
# the provided cache or remotely.
|
21
|
+
# @param request_id [String] (uuid) unique ID for the remote request.
|
22
|
+
# @return [String] the public key.
|
23
|
+
def fetch(request_id: self.class.new_req_id)
|
24
|
+
# Token base URI may have changed after the class was loaded.
|
25
|
+
self.class.base_uri(Token.base_uri)
|
26
|
+
public_key = @cache_store.fetch(cache_key, cache_options) do
|
27
|
+
opts = { format: :plain, headers: { 'X-Request-Id' => request_id } }
|
28
|
+
response = self.class.get('/oauth/keys', opts)
|
29
|
+
self.class.handle_response(response)
|
30
|
+
end
|
31
|
+
OpenSSL::PKey.read(public_key)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def digest_data(data)
|
37
|
+
md4 = OpenSSL::Digest::MD4.new
|
38
|
+
Base64.encode64(md4.digest(data))
|
39
|
+
end
|
40
|
+
|
41
|
+
def cache_key
|
42
|
+
"public_key:#{digest_data(self.class.base_uri)}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def cache_options
|
46
|
+
{
|
47
|
+
expires_in: ENV.fetch('PUBLIC_KEY_EXPIRY_SECONDS', 7.minutes),
|
48
|
+
race_condition_ttl: 10.seconds
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'jwt'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module Talis
|
6
|
+
module Authentication
|
7
|
+
# Represents a JWT-based OAuth access token.
|
8
|
+
#
|
9
|
+
# Optionally configure an ActiveSupport-based cache store for caching the
|
10
|
+
# public key and tokens. The default cache used is an in-memory one.
|
11
|
+
# See http://api.rubyonrails.org/classes/ActiveSupport/Cache.html for
|
12
|
+
# supported cache types.
|
13
|
+
# @example Using an in-memory cache.
|
14
|
+
# store = ActiveSupport::Cache::MemoryStore.new
|
15
|
+
# Talis::Authentication::Token.cache_store = store
|
16
|
+
class Token < Talis::Resource
|
17
|
+
base_uri Talis::PERSONA_HOST
|
18
|
+
cattr_accessor :cache_store
|
19
|
+
Token.cache_store = ActiveSupport::Cache::MemoryStore.new
|
20
|
+
|
21
|
+
# Create a new token object from an existing JWT.
|
22
|
+
# @param jwt [String] the encoded JWT.
|
23
|
+
# @param public_key [PublicKey] (nil) Only used in unit tests.
|
24
|
+
def initialize(jwt:, public_key: nil)
|
25
|
+
@jwt = jwt
|
26
|
+
@public_key = public_key || PublicKey.new(Token.cache_store)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Validate the token, optionally against one or more required scopes.
|
30
|
+
#
|
31
|
+
# Scope validation is performed locally unless there are too many tokens
|
32
|
+
# to list inside the token payload. When this is the case, a remote
|
33
|
+
# request is performed to validate the token against the scopes.
|
34
|
+
#
|
35
|
+
# The validation error returned can be one of the following:
|
36
|
+
# - `:expired_token` if the token has expired.
|
37
|
+
# - `:insufficient_scope` if the provided scopes are not in the token.
|
38
|
+
# - `:invalid_token` if the token could not be verified by the public
|
39
|
+
# key.
|
40
|
+
# - `:invalid_token` if the token could not be decoded.
|
41
|
+
# - `:invalid_key` if the public key is corrupt.
|
42
|
+
# @param request_id [String] (uuid) unique ID for the remote request.
|
43
|
+
# @param scopes [Array] Scope(s) that the token needs in order to be
|
44
|
+
# valid.
|
45
|
+
# @param all [Boolean] (true) Whether or not all scopes must be present
|
46
|
+
# within the token for validation to pass. If false, only one matching
|
47
|
+
# scope is required.
|
48
|
+
# @return [Symbol, Nil] nil iff the token is valid else a symbol error.
|
49
|
+
# @raise [Talis::ServerError] if the sever cannot validate the
|
50
|
+
# scope.
|
51
|
+
# @raise [Talis::ServerCommunicationError] for network issues.
|
52
|
+
def validate(request_id: self.class.new_req_id, scopes: [], all: true)
|
53
|
+
decoded = JWT.decode(@jwt, p_key(request_id), true, algorithm: 'RS256')
|
54
|
+
validate_scopes(request_id, scopes, decoded[0], all)
|
55
|
+
rescue JWT::ExpiredSignature
|
56
|
+
return :expired_token
|
57
|
+
rescue JWT::VerificationError, JWT::DecodeError
|
58
|
+
return :invalid_token
|
59
|
+
rescue NoMethodError
|
60
|
+
return :invalid_key
|
61
|
+
rescue Talis::ClientError
|
62
|
+
:insufficient_scope
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [String] the encoded version of the token - a JWT string.
|
66
|
+
def to_s
|
67
|
+
@jwt
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def p_key(req_id)
|
73
|
+
@public_key.fetch(request_id: req_id)
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_scopes(request_id, wanted_scopes, token, all_must_match)
|
77
|
+
# The existence of this key means there are too many scopes to fit
|
78
|
+
# into an encoded token, it must be fetched from the server
|
79
|
+
token = fetch_token(request_id) if token.key? 'scopeCount'
|
80
|
+
|
81
|
+
return :invalid_token unless token.key? 'scopes'
|
82
|
+
provided_scopes = token['scopes']
|
83
|
+
return nil if wanted_scopes.empty?
|
84
|
+
return nil if provided_scopes.include? 'su'
|
85
|
+
compare_scope_intersect(wanted_scopes, provided_scopes, all_must_match)
|
86
|
+
end
|
87
|
+
|
88
|
+
def compare_scope_intersect(wanted_scope, provided_scope, all_must_match)
|
89
|
+
intersect_scope = (wanted_scope & provided_scope)
|
90
|
+
if (all_must_match && intersect_scope != wanted_scope) ||
|
91
|
+
intersect_scope.empty?
|
92
|
+
:insufficient_scope
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def fetch_token(request_id)
|
97
|
+
token_url = "/oauth/tokens/#{@jwt}"
|
98
|
+
headers = { headers: { 'X-Request-Id' => request_id } }
|
99
|
+
self.class.handle_response(self.class.get(token_url, headers))
|
100
|
+
end
|
101
|
+
|
102
|
+
class << self
|
103
|
+
# Generate a new token for the given client.
|
104
|
+
# If a previous token has been generated for the client and has not
|
105
|
+
# expired then this will be returned from the cache.
|
106
|
+
# @param request_id [String] ('uuid') unique ID for the remote request.
|
107
|
+
# @param client_id [String] the client for whom this token is for.
|
108
|
+
# @param client_secret [String] secret belonging to the client.
|
109
|
+
# @param host [String] Optional persona host override for service
|
110
|
+
# @return [Talis::Authentication::Token] the generated or cached token.
|
111
|
+
# @raise [Talis::ClientError] if the client ID/secret are
|
112
|
+
# invalid.
|
113
|
+
# @raise [Talis::ServerError] if the generation failed on the
|
114
|
+
# server.
|
115
|
+
# @raise [Talis::ServerCommunicationError] for network issues.
|
116
|
+
def generate(request_id: new_req_id, client_id:, client_secret:,
|
117
|
+
host: base_uri)
|
118
|
+
token = cached_token(client_id, host)
|
119
|
+
if token
|
120
|
+
new(jwt: token)
|
121
|
+
else
|
122
|
+
generate_remote_token(request_id, client_id, client_secret, host)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def generate_remote_token(request_id, client_id, client_secret, host)
|
129
|
+
response = create_token(request_id, client_id, client_secret, host)
|
130
|
+
parsed_response = handle_response(response)
|
131
|
+
cache_token(parsed_response, client_id, host)
|
132
|
+
new(jwt: parsed_response['access_token'])
|
133
|
+
end
|
134
|
+
|
135
|
+
def digest_data(data)
|
136
|
+
md4 = OpenSSL::Digest::MD4.new
|
137
|
+
Base64.encode64(md4.digest(data))
|
138
|
+
end
|
139
|
+
|
140
|
+
def cache_key(client_id, host)
|
141
|
+
"token:#{digest_data(client_id)}_#{digest_data(host)}"
|
142
|
+
end
|
143
|
+
|
144
|
+
def cache_token(data, client_id, host)
|
145
|
+
access_token = data['access_token']
|
146
|
+
# Expire the cache slightly before the token expires to cater for
|
147
|
+
# communication delay between server issuing and client receiving.
|
148
|
+
expiry_time = data['expires_in'].to_i - 5.seconds
|
149
|
+
Token.cache_store.write(cache_key(client_id, host), access_token,
|
150
|
+
expires_in: expiry_time)
|
151
|
+
end
|
152
|
+
|
153
|
+
def cached_token(client_id, host)
|
154
|
+
key = cache_key(client_id, host)
|
155
|
+
Token.cache_store.fetch(key) if Token.cache_store.exist?(key)
|
156
|
+
end
|
157
|
+
|
158
|
+
def create_token(request_id, client_id, client_secret, host)
|
159
|
+
post(host + '/oauth/tokens',
|
160
|
+
headers: { 'X-Request-Id' => request_id },
|
161
|
+
body: {
|
162
|
+
client_id: client_id,
|
163
|
+
client_secret: client_secret,
|
164
|
+
grant_type: 'client_credentials'
|
165
|
+
})
|
166
|
+
rescue
|
167
|
+
raise Talis::ServerCommunicationError
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative 'bibliography/result_set'
|
2
|
+
require_relative 'bibliography/work'
|
3
|
+
require_relative 'bibliography/manifestation'
|
4
|
+
require_relative 'bibliography/ebook'
|
5
|
+
|
6
|
+
module Talis
|
7
|
+
# Encompasses all classes associated with bibliographic resources
|
8
|
+
module Bibliography
|
9
|
+
# Exposes the underlying Metatron API client.
|
10
|
+
# @param request_id [String] ('uuid') unique ID for remote requests.
|
11
|
+
# @return MetatronClient::DefaultApi
|
12
|
+
def api_client(request_id = new_req_id)
|
13
|
+
configure_metatron
|
14
|
+
|
15
|
+
api_client = MetatronClient::ApiClient.new
|
16
|
+
api_client.default_headers = {
|
17
|
+
'X-Request-Id' => request_id,
|
18
|
+
'User-Agent' => "talis-ruby-client/#{Talis::VERSION} "\
|
19
|
+
"ruby/#{RUBY_VERSION}"
|
20
|
+
}
|
21
|
+
|
22
|
+
MetatronClient::DefaultApi.new(api_client)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def configure_metatron
|
28
|
+
MetatronClient.configure do |config|
|
29
|
+
config.scheme = base_uri[/https?/]
|
30
|
+
config.host = base_uri
|
31
|
+
# Non-production environments have a base path
|
32
|
+
if ENV['METATRON_BASE_PATH']
|
33
|
+
config.base_path = ENV['METATRON_BASE_PATH']
|
34
|
+
end
|
35
|
+
config.api_key_prefix['Authorization'] = 'Bearer'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def empty_result_set(klass, meta_properties)
|
40
|
+
meta = OpenStruct.new(meta_properties)
|
41
|
+
klass.new(data: [], meta: meta).extend(ResultSet)
|
42
|
+
end
|
43
|
+
|
44
|
+
def escape_query(query_string)
|
45
|
+
# TODO: are all of these necessary?
|
46
|
+
pattern = %r{
|
47
|
+
(\+|\-|\=|\&\&|\|\||\>|\<|\!|\(|\)|\{|\}|\[|\]|\^|\"|\~|\*|\?|\:|\\|\/)
|
48
|
+
}x
|
49
|
+
query_string.gsub(pattern) { |match| "\\#{match}" }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'metatron_ruby_client'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Talis
|
5
|
+
module Bibliography
|
6
|
+
# Represents an eBook which is a type of asset associated with
|
7
|
+
# works and their manifestations.
|
8
|
+
#
|
9
|
+
# In order to perform remote operations, the client must be configured
|
10
|
+
# with a valid OAuth client that is allowed to query nodes:
|
11
|
+
#
|
12
|
+
# Talis::Authentication.client_id = 'client_id'
|
13
|
+
# Talis::Authentication.client_secret = 'client_secret'
|
14
|
+
#
|
15
|
+
class EBook < Talis::Resource
|
16
|
+
extend Forwardable, Talis::OAuthService, Talis::Bibliography
|
17
|
+
base_uri Talis::METATRON_HOST
|
18
|
+
attr_accessor :id, :vbid, :title, :author, :format, :digital_list_price,
|
19
|
+
:publisher_list_price, :store_price
|
20
|
+
private_class_method :new
|
21
|
+
|
22
|
+
def initialize(asset_data)
|
23
|
+
attrs = asset_data.try(:attributes) || {}
|
24
|
+
@id = asset_data.id
|
25
|
+
@vbid = attrs[:vbid]
|
26
|
+
@title = attrs[:title]
|
27
|
+
@author = attrs[:author]
|
28
|
+
@format = attrs[:'book-format']
|
29
|
+
price_list = attrs.fetch(:pricelist, {})
|
30
|
+
@digital_list_price = price_list[:'digital-list-price']
|
31
|
+
@publisher_list_price = price_list[:'publisher-list-price']
|
32
|
+
@store_price = price_list.fetch(:'store-price', {})[:value]
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
def find_by_manifestation_id(manifestation_id, request_id = new_req_id)
|
37
|
+
id = manifestation_id.gsub('nbd:', '')
|
38
|
+
begin
|
39
|
+
set = api_client(request_id).get_manifestation_assets(token, id)
|
40
|
+
rescue MetatronClient::ApiError => error
|
41
|
+
return [] if error.code == 404
|
42
|
+
handle_response(error)
|
43
|
+
end
|
44
|
+
set.data.select { |asset| asset.type == Talis::EBOOK_TYPE }
|
45
|
+
.map { |asset| new(asset) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'metatron_ruby_client'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Talis
|
5
|
+
module Bibliography
|
6
|
+
# Represents bibliographic manifestations API operations provided by the
|
7
|
+
# Metatron gem
|
8
|
+
# {https://github.com/talis/metatron_rb}
|
9
|
+
#
|
10
|
+
# In order to perform remote operations, the client must be configured
|
11
|
+
# with a valid OAuth client that is allowed to query nodes:
|
12
|
+
#
|
13
|
+
# Talis::Authentication.client_id = 'client_id'
|
14
|
+
# Talis::Authentication.client_secret = 'client_secret'
|
15
|
+
#
|
16
|
+
class Manifestation < Talis::Resource
|
17
|
+
extend Forwardable, Talis::OAuthService, Talis::Bibliography
|
18
|
+
base_uri Talis::METATRON_HOST
|
19
|
+
attr_reader :contributors, :assets, :manifestation_data, :work
|
20
|
+
attr_accessor :id, :type, :title
|
21
|
+
def_delegators :@manifestation_data, :id, :type
|
22
|
+
|
23
|
+
# rubocop:disable Metrics/LineLength
|
24
|
+
class << self
|
25
|
+
# Search for bibliographic manifestations
|
26
|
+
# @param request_id [String] ('uuid') unique ID for the remote request.
|
27
|
+
# @param opts [Hash] the query parameters: currently supported: work_id and isbn
|
28
|
+
# see {https://github.com/talis/metatron_rb/blob/metatron-swagger-updates/docs/DefaultApi.md#manifestation}
|
29
|
+
# @return [MetatronClient::ManifestationResultSet] containing data and meta attributes.
|
30
|
+
# The structure is as follows:
|
31
|
+
# {
|
32
|
+
# data: [manifestation1, manifestation2, manifestation3],
|
33
|
+
# meta: { count: 3 }
|
34
|
+
# included: [contributor1]
|
35
|
+
# }
|
36
|
+
# where manifestations are of type Talis::Bibliography::Manifestation, which are also available
|
37
|
+
# directly via the Enumerable methods: each, find, find_all, first, last
|
38
|
+
# @raise [Talis::ClientError] if the request was invalid.
|
39
|
+
# @raise [Talis::ServerError] if the search failed on the
|
40
|
+
# server.
|
41
|
+
# @raise [Talis::ServerCommunicationError] for network issues.
|
42
|
+
def find(request_id: new_req_id, opts: {})
|
43
|
+
api_client(request_id).manifestation(token, opts)
|
44
|
+
.extend(ResultSet).hydrate
|
45
|
+
rescue MetatronClient::ApiError => error
|
46
|
+
begin
|
47
|
+
handle_response(error)
|
48
|
+
rescue Talis::NotFoundError
|
49
|
+
empty_result_set(MetatronClient::ManifestationResultSet, count: 0)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Fetch a single work by id
|
54
|
+
# @param request_id [String] ('uuid') unique ID for the remote request.
|
55
|
+
# @param id [String] the ID of the work to fetch.
|
56
|
+
# @return Talis::Bibliography::Work or nil if the work cannot be found.
|
57
|
+
# @raise [Talis::ClientError] if the request was invalid.
|
58
|
+
# @raise [Talis::ServerError] if the fetch failed on the
|
59
|
+
# server.
|
60
|
+
# @raise [Talis::ServerCommunicationError] for network issues.
|
61
|
+
def get(request_id: new_req_id, id:)
|
62
|
+
new api_client(request_id).get_manifestation(token, id).data
|
63
|
+
rescue MetatronClient::ApiError => error
|
64
|
+
begin
|
65
|
+
handle_response(error)
|
66
|
+
rescue Talis::NotFoundError
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(manifestation_data = nil)
|
73
|
+
if manifestation_data.is_a? MetatronClient::ManifestationData
|
74
|
+
parse_manifestation_data manifestation_data
|
75
|
+
else
|
76
|
+
@manifestation_data = MetatronClient::ManifestationData.new
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def contributors
|
81
|
+
@contributors ||= []
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO: call assets route if not set
|
85
|
+
def assets
|
86
|
+
@assets ||= []
|
87
|
+
end
|
88
|
+
|
89
|
+
# By default, the metatron client returns generic ResourceLink objects
|
90
|
+
# as the related resources. When passed an array of Metatron::ResourceData
|
91
|
+
# objects, it will replace the ResourceLink objects with more appropriately
|
92
|
+
# typed objects
|
93
|
+
# @param resources [Array] an array of Metatron::ResourceData objects
|
94
|
+
def hydrate_relationships(included_resources)
|
95
|
+
contributors.map! do |contributor|
|
96
|
+
find_relationship_in_included(contributor,
|
97
|
+
included_resources)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def find_relationship_in_included(resource_data, included)
|
104
|
+
included.find do |resource|
|
105
|
+
resource.id == resource_data.id && resource.type == resource_data.type
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def parse_manifestation_data(manifestation_data)
|
110
|
+
@manifestation_data = manifestation_data
|
111
|
+
@title = manifestation_data.try(:attributes).try(:title)
|
112
|
+
|
113
|
+
unless manifestation_data.relationships.nil?
|
114
|
+
add_relationships(manifestation_data)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_relationships(manifestation_data)
|
119
|
+
[:contributors, :work].each do |rel|
|
120
|
+
next unless manifestation_data.relationships.try(rel).try(:data)
|
121
|
+
if rel == :contributors
|
122
|
+
add_related_contributors(manifestation_data)
|
123
|
+
else
|
124
|
+
add_related_work(manifestation_data)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def add_related_contributors(manifestation_data)
|
130
|
+
@contributors ||= []
|
131
|
+
@contributors += manifestation_data.relationships.contributors.data
|
132
|
+
end
|
133
|
+
|
134
|
+
def add_related_work(manifestation_data)
|
135
|
+
@work = MetatronClient::WorkData.new(
|
136
|
+
manifestation_data.relationships.work.data.to_hash
|
137
|
+
)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|