lib/hyperclient/link.rb
require 'addressable'
module Hyperclient
# Internal: The Link is used to let a Resource interact with the API.
#
class Link
include Enumerable
# Public: Initializes a new Link.
#
# key - The key or name of the link.
# link - The String with the URI of the link.
# entry_point - The EntryPoint object to inject the configuration.
# uri_variables - The optional Hash with the variables to expand the link
# if it is templated.
def initialize(key, link, entry_point, uri_variables = nil)
@key = key
@link = link
@entry_point = entry_point
@uri_variables = uri_variables
@resource = nil
end
# Public: Each implementation to allow the class to use the Enumerable
# benefits for paginated, embedded items.
#
# Returns an Enumerator.
def each(&block)
if block_given?
current = self
while current
coll = current.respond_to?(@key) ? current.send(@key) : _resource
coll.each(&block)
break unless current._links[:next]
current = current._links.next
end
else
to_enum(:each)
end
end
# Public: Indicates if the link is an URITemplate or a regular URI.
#
# Returns true if it is templated.
# Returns false if it not templated.
def _templated?
!!@link['templated']
end
# Public: Expands the Link when is templated with the given variables.
#
# uri_variables - The Hash with the variables to expand the URITemplate.
#
# Returns a new Link with the expanded variables.
def _expand(uri_variables = {})
self.class.new(@key, @link, @entry_point, uri_variables)
end
# Public: Returns the url of the Link.
def _url
return @link['href'] unless _templated?
@url ||= _uri_template.expand(@uri_variables || {}).to_s
end
# Public: Returns an array of variables from the URITemplate.
#
# Returns an empty array for regular URIs.
def _variables
_uri_template.variables
end
# Public: Returns the type property of the Link
def _type
@link['type']
end
# Public: Returns the name property of the Link
def _name
@link['name']
end
# Public: Returns the deprecation property of the Link
def _deprecation
@link['deprecation']
end
# Public: Returns the profile property of the Link
def _profile
@link['profile']
end
# Public: Returns the title property of the Link
def _title
@link['title']
end
# Public: Returns the hreflang property of the Link
def _hreflang
@link['hreflang']
end
def _resource
@resource || _get
end
# Public: Returns the Resource which the Link is pointing to.
def _get
http_method(:get)
end
def _options
http_method(:options)
end
def _head
http_method(:head)
end
def _delete
http_method(:delete)
end
def _post(params = {})
http_method(:post, params)
end
def _put(params = {})
http_method(:put, params)
end
def _patch(params = {})
http_method(:patch, params)
end
def inspect
"#<#{self.class.name}(#{@key}) #{@link}>"
end
def to_s
_url
end
private
# Internal: Delegate the method further down the API if the resource cannot serve it.
def method_missing(method, *args, &block)
if _resource.respond_to?(method.to_s)
result = _resource.send(method, *args, &block)
result.nil? ? delegate_method(method, *args, &block) : result
else
super
end
end
# Internal: Delegate the method to the API if the resource cannot serve it.
#
# This allows `api.posts` instead of `api._links.posts.embedded.posts`
def delegate_method(method, *args, &block)
return unless @key && _resource.respond_to?(@key)
@delegate ||= _resource.send(@key)
return unless @delegate&.respond_to?(method.to_s)
@delegate.send(method, *args, &block)
end
# Internal: Accessory method to allow the link respond to the
# methods that will hit method_missing.
def respond_to_missing?(method, _include_private = false)
if @key && _resource.respond_to?(@key) && (delegate = _resource.send(@key)) && delegate.respond_to?(method.to_s)
true
else
_resource.respond_to?(method.to_s)
end
end
# Internal: avoid delegating to resource
#
# #to_ary is called for implicit array coercion (such as parallel assignment
# or Array#flatten). Returning nil tells Ruby that this record is not Array-like.
def to_ary
nil
end
# Internal: Memoization for a URITemplate instance
def _uri_template
@uri_template ||= Addressable::Template.new(@link['href'])
end
def http_method(method, body = nil)
@resource = begin
response = @entry_point.connection.run_request(method, _url, body, nil)
Resource.new(response.body, @entry_point, response)
end
end
end
end