lib/jsonapi/link_builder.rb in jsonapi-resources-0.9.8 vs lib/jsonapi/link_builder.rb in jsonapi-resources-0.9.9

- old
+ new

@@ -1,77 +1,88 @@ module JSONAPI class LinkBuilder attr_reader :base_url, :primary_resource_klass, + :route_formatter, :engine, - :routes + :engine_mount_point, + :url_helpers + @@url_helper_methods = {} + def initialize(config = {}) - @base_url = config[:base_url] + @base_url = config[:base_url] @primary_resource_klass = config[:primary_resource_klass] - @engine = build_engine + @route_formatter = config[:route_formatter] + @engine = build_engine + @engine_mount_point = @engine ? @engine.routes.find_script_name({}) : "" - if engine? - @routes = @engine.routes - else - @routes = Rails.application.routes - end - - # ToDo: Use NaiveCache for values. For this we need to not return nils and create composite keys which work - # as efficient cache lookups. This could be an array of the [source.identifier, relationship] since the - # ResourceIdentity will compare equality correctly + # 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 - @primary_resources_url_cached ||= "#{ base_url }#{ primary_resources_path }" - rescue NoMethodError - warn "primary_resources_url for #{@primary_resource_klass} could not be generated" if JSONAPI.configuration.warn_on_missing_routes + 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) - "#{ primary_resources_url }?#{ query_params.to_query }" + 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.parent_resource.singleton? - url_helper_name = singleton_related_url_helper_name(relationship) - url = call_url_helper(url_helper_name) + if relationship._routed + url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }" + url = "#{ url }?#{ query_params.to_query }" if query_params.present? + url else - url_helper_name = related_url_helper_name(relationship) - url = call_url_helper(url_helper_name, source.id) + 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 - - url = "#{ base_url }#{ url }" - url = "#{ url }?#{ query_params.to_query }" if query_params.present? - url - rescue NoMethodError - warn "related_link for #{relationship} could not be generated" if JSONAPI.configuration.warn_on_missing_routes end def relationships_self_link(source, relationship) - if relationship.parent_resource.singleton? - url_helper_name = singleton_relationship_self_url_helper_name(relationship) - url = call_url_helper(url_helper_name) + if relationship._routed + "#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }" else - url_helper_name = relationship_self_url_helper_name(relationship) - url = call_url_helper(url_helper_name, source.id) + 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 - - url = "#{ base_url }#{ url }" - url - rescue NoMethodError - warn "self_link for #{relationship} could not be generated" if JSONAPI.configuration.warn_on_missing_routes end def self_link(source) - "#{ base_url }#{ resource_path(source) }" - rescue NoMethodError - warn "self_link for #{source.class} could not be generated" if JSONAPI.configuration.warn_on_missing_routes + 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 @@ -79,107 +90,57 @@ begin unless scopes.empty? "#{ scopes.first.to_s.camelize }::Engine".safe_constantize end + # :nocov: rescue LoadError => _e nil # :nocov: end end - def call_url_helper(method, *args) - routes.url_helpers.public_send(method, args) - rescue NoMethodError => e - raise e + def format_route(route) + route_formatter.format(route) end - def path_from_resource_class(klass) - url_helper_name = resources_url_helper_name_from_class(klass) - call_url_helper(url_helper_name) - 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 - def resource_path(source) - url_helper_name = resource_url_helper_name_from_source(source) - if source.class.singleton? - call_url_helper(url_helper_name) + unless scopes.empty? + "/#{ scopes.map {|scope| format_route(scope.to_s.underscore)}.compact.join('/') }/" else - call_url_helper(url_helper_name, source.id) + "/" end end - def primary_resources_path - path_from_resource_class(primary_resource_klass) + def module_scopes_from_class(klass) + klass.name.to_s.split("::")[0...-1] end - def url_helper_name_from_parts(parts) - (parts << "path").reject(&:blank?).join("_") + def resources_path(source_klass) + formatted_module_path_from_class(source_klass) + format_route(source_klass._type.to_s) end - def resources_path_parts_from_class(klass) - if engine? - scopes = module_scopes_from_class(klass)[1..-1] - else - scopes = module_scopes_from_class(klass) - end + def resource_path(source) + url = "#{resources_path(source.class)}" - base_path_name = scopes.map { |scope| scope.underscore }.join("_") - end_path_name = klass._type.to_s - [base_path_name, end_path_name] - end - - def resources_url_helper_name_from_class(klass) - url_helper_name_from_parts(resources_path_parts_from_class(klass)) - end - - def resource_path_parts_from_class(klass) - if engine? - scopes = module_scopes_from_class(klass)[1..-1] - else - scopes = module_scopes_from_class(klass) + unless source.class.singleton? + url = "#{url}/#{source.id}" end - - base_path_name = scopes.map { |scope| scope.underscore }.join("_") - end_path_name = klass._type.to_s.singularize - [base_path_name, end_path_name] + url end - def resource_url_helper_name_from_source(source) - url_helper_name_from_parts(resource_path_parts_from_class(source.class)) + def resource_url(source) + "#{ base_url }#{ engine_mount_point }#{ resource_path(source) }" end - def related_url_helper_name(relationship) - relationship_parts = resource_path_parts_from_class(relationship.parent_resource) - relationship_parts << "related" - relationship_parts << relationship.name - url_helper_name_from_parts(relationship_parts) - end - - def singleton_related_url_helper_name(relationship) - relationship_parts = [] - relationship_parts << "related" - relationship_parts << relationship.name - relationship_parts += resource_path_parts_from_class(relationship.parent_resource) - url_helper_name_from_parts(relationship_parts) - end - - def relationship_self_url_helper_name(relationship) - relationship_parts = resource_path_parts_from_class(relationship.parent_resource) - relationship_parts << "relationships" - relationship_parts << relationship.name - url_helper_name_from_parts(relationship_parts) - end - - def singleton_relationship_self_url_helper_name(relationship) - relationship_parts = [] - relationship_parts << "relationships" - relationship_parts << relationship.name - relationship_parts += resource_path_parts_from_class(relationship.parent_resource) - url_helper_name_from_parts(relationship_parts) - end - - def module_scopes_from_class(klass) - klass.name.to_s.split("::")[0...-1] + def route_for_relationship(relationship) + format_route(relationship.name) end end end