lib/jsonapi/link_builder.rb
# frozen_string_literal: true
module JSONAPI
class LinkBuilder
attr_reader :base_url,
:primary_resource_klass,
:route_formatter,
:engine,
:engine_mount_point,
:url_helpers
@@url_helper_methods = {}
def initialize(config = {})
@base_url = config[:base_url]
@primary_resource_klass = config[:primary_resource_klass]
@route_formatter = config[:route_formatter]
@engine = build_engine
@engine_mount_point = @engine ? @engine.routes.find_script_name({}) : ""
# url_helpers may be either a controller which has the route helper methods, or the application router's
# url helpers module, `Rails.application.routes.url_helpers`. Because the method no longer behaves as a
# singleton, and it's expensive to generate the module, the controller is preferred.
@url_helpers = config[:url_helpers]
end
def engine?
!!@engine
end
def primary_resources_url
if @primary_resource_klass._routed
primary_resources_path = resources_path(primary_resource_klass)
@primary_resources_url_cached ||= "#{ base_url }#{ engine_mount_point }#{ primary_resources_path }"
else
if JSONAPI.configuration.warn_on_missing_routes && !@primary_resource_klass._warned_missing_route
warn "primary_resources_url for #{@primary_resource_klass} could not be generated"
@primary_resource_klass._warned_missing_route = true
end
nil
end
end
def query_link(query_params)
url = primary_resources_url
return url if url.nil?
"#{ url }?#{ query_params.to_query }"
end
def relationships_related_link(source, relationship, query_params = {})
if relationship._routed
url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }"
url = "#{ url }?#{ query_params.to_query }" if query_params.present?
url
else
if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
warn "related_link for #{relationship} could not be generated"
relationship._warned_missing_route = true
end
nil
end
end
def relationships_self_link(source, relationship)
if relationship._routed
"#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
else
if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
warn "self_link for #{relationship} could not be generated"
relationship._warned_missing_route = true
end
nil
end
end
def self_link(source)
if source.class._routed
resource_url(source)
else
if JSONAPI.configuration.warn_on_missing_routes && !source.class._warned_missing_route
warn "self_link for #{source.class} could not be generated"
source.class._warned_missing_route = true
end
nil
end
end
private
def build_engine
scopes = module_scopes_from_class(primary_resource_klass)
begin
unless scopes.empty?
"#{ scopes.first.to_s.camelize }::Engine".safe_constantize
end
# :nocov:
rescue LoadError => _e
nil
# :nocov:
end
end
def format_route(route)
route_formatter.format(route)
end
def formatted_module_path_from_class(klass)
scopes = if @engine
module_scopes_from_class(klass)[1..-1]
else
module_scopes_from_class(klass)
end
unless scopes.empty?
"/#{ scopes.map {|scope| format_route(scope.to_s.underscore)}.compact.join('/') }/"
else
"/"
end
end
def module_scopes_from_class(klass)
klass.name.to_s.split("::")[0...-1]
end
def resources_path(source_klass)
@_resources_path ||= {}
@_resources_path[source_klass] ||= formatted_module_path_from_class(source_klass) + format_route(source_klass._type.to_s)
end
def resource_path(source)
if source.class.singleton?
resources_path(source.class)
else
"#{resources_path(source.class)}/#{source.id}"
end
end
def resource_url(source)
"#{ base_url }#{ engine_mount_point }#{ resource_path(source) }"
end
def route_for_relationship(relationship)
format_route(relationship.name)
end
end
end