lib/jsonapi/routing_ext.rb
# frozen_string_literal: true
module ActionDispatch
module Routing
class Mapper
Resource.class_eval do
def unformat_route(route)
JSONAPI.configuration.route_formatter.unformat(route.to_s)
end
def nested_param
:"#{unformat_route(singular)}_#{param}"
end
end
Resources.class_eval do
def format_route(route)
JSONAPI.configuration.route_formatter.format(route.to_s)
end
def jsonapi_resource(*resources, &_block)
@resource_type = resources.first
res = JSONAPI::Resource.resource_klass_for(resource_type_with_module_prefix(@resource_type))
res._routed = true
unless res.singleton?
warn "Singleton routes created for non singleton resource #{res}. Links may not be generated correctly."
end
options = resources.extract_options!.dup
options[:controller] ||= @resource_type
options.merge!(res.routing_resource_options)
options[:path] = format_route(@resource_type)
if options[:except]
options[:except] << :new unless options[:except].include?(:new) || options[:except].include?('new')
options[:except] << :edit unless options[:except].include?(:edit) || options[:except].include?('edit')
else
options[:except] = [:new, :edit]
end
if res._immutable
options[:except] << :create unless options[:except].include?(:create) || options[:except].include?('create')
options[:except] << :update unless options[:except].include?(:update) || options[:except].include?('update')
options[:except] << :destroy unless options[:except].include?(:destroy) || options[:except].include?('destroy')
end
resource @resource_type, options do
# :nocov:
if @scope.respond_to? :[]=
# Rails 4
@scope[:jsonapi_resource] = @resource_type
if block_given?
yield
else
jsonapi_relationships
end
else
# Rails 5
jsonapi_resource_scope(SingletonResource.new(@resource_type, api_only?, @scope[:shallow], options), @resource_type) do
if block_given?
yield
else
jsonapi_relationships
end
end
end
# :nocov:
end
end
def jsonapi_relationships(options = {})
res = JSONAPI::Resource.resource_klass_for(resource_type_with_module_prefix(@resource_type))
res._relationships.each do |relationship_name, relationship|
if relationship.is_a?(JSONAPI::Relationship::ToMany)
jsonapi_links(relationship_name, options)
jsonapi_related_resources(relationship_name, options)
else
jsonapi_link(relationship_name, options)
jsonapi_related_resource(relationship_name, options)
end
end
end
def jsonapi_resources(*resources, &_block)
@resource_type = resources.first
res = JSONAPI::Resource.resource_klass_for(resource_type_with_module_prefix(@resource_type))
res._routed = true
if res.singleton?
warn "Singleton resource #{res} should use `jsonapi_resource` instead."
end
options = resources.extract_options!.dup
options[:controller] ||= @resource_type
options.merge!(res.routing_resource_options)
options[:param] = :id
options[:path] = format_route(@resource_type)
if res.resource_key_type == :uuid
options[:constraints] ||= {}
options[:constraints][:id] ||= /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/
end
if options[:except]
options[:except] = Array(options[:except])
options[:except] << :new unless options[:except].include?(:new) || options[:except].include?('new')
options[:except] << :edit unless options[:except].include?(:edit) || options[:except].include?('edit')
else
options[:except] = [:new, :edit]
end
if res._immutable
options[:except] << :create unless options[:except].include?(:create) || options[:except].include?('create')
options[:except] << :update unless options[:except].include?(:update) || options[:except].include?('update')
options[:except] << :destroy unless options[:except].include?(:destroy) || options[:except].include?('destroy')
end
resources @resource_type, options do
# :nocov:
if @scope.respond_to? :[]=
# Rails 4
@scope[:jsonapi_resource] = @resource_type
if block_given?
yield
else
jsonapi_relationships
end
else
# Rails 5
jsonapi_resource_scope(Resource.new(@resource_type, api_only?, @scope[:shallow], options), @resource_type) do
if block_given?
yield
else
jsonapi_relationships
end
end
end
# :nocov:
end
end
def links_methods(options)
default_methods = [:show, :create, :destroy, :update]
if only = options[:only]
Array(only).map(&:to_sym)
elsif except = options[:except]
default_methods - Array(except)
else
default_methods
end
end
def jsonapi_link(*links)
link_type = links.first
formatted_relationship_name = format_route(link_type)
options = links.extract_options!.dup
res = JSONAPI::Resource.resource_klass_for(resource_type_with_module_prefix)
options[:controller] ||= res._type.to_s
methods = links_methods(options)
if methods.include?(:show)
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
action: 'show_relationship', relationship: link_type.to_s, via: [:get],
as: "relationships/#{link_type}"
end
if res.mutable?
if methods.include?(:update)
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
end
if methods.include?(:destroy)
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
end
end
end
def jsonapi_links(*links)
link_type = links.first
formatted_relationship_name = format_route(link_type)
options = links.extract_options!.dup
res = JSONAPI::Resource.resource_klass_for(resource_type_with_module_prefix)
options[:controller] ||= res._type.to_s
methods = links_methods(options)
if methods.include?(:show)
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
action: 'show_relationship', relationship: link_type.to_s, via: [:get],
as: "relationships/#{link_type}"
end
if res.mutable?
if methods.include?(:create)
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
action: 'create_relationship', relationship: link_type.to_s, via: [:post]
end
if methods.include?(:update)
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
end
if methods.include?(:destroy)
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
end
end
end
def jsonapi_related_resource(*relationship)
source = JSONAPI::Resource.resource_klass_for(resource_type_with_module_prefix)
options = relationship.extract_options!.dup
relationship_name = relationship.first
relationship = source._relationships[relationship_name]
relationship._routed = true
formatted_relationship_name = format_route(relationship.name)
if relationship.polymorphic?
options[:controller] ||= relationship.class_name.underscore.pluralize
else
related_resource = JSONAPI::Resource.resource_klass_for(resource_type_with_module_prefix(relationship.class_name.underscore.pluralize))
options[:controller] ||= related_resource._type.to_s
end
match formatted_relationship_name, controller: options[:controller],
relationship: relationship.name, source: resource_type_with_module_prefix(source._type),
action: 'show_related_resource', via: [:get],
as: "related/#{relationship_name}"
end
def jsonapi_related_resources(*relationship)
source = JSONAPI::Resource.resource_klass_for(resource_type_with_module_prefix)
options = relationship.extract_options!.dup
relationship_name = relationship.first
relationship = source._relationships[relationship_name]
relationship._routed = true
formatted_relationship_name = format_route(relationship.name)
related_resource = JSONAPI::Resource.resource_klass_for(resource_type_with_module_prefix(relationship.class_name.underscore))
options[:controller] ||= related_resource._type.to_s
match formatted_relationship_name,
controller: options[:controller],
relationship: relationship.name, source: resource_type_with_module_prefix(source._type),
action: 'index_related_resources', via: [:get],
as: "related/#{relationship_name}"
end
protected
# :nocov:
def jsonapi_resource_scope(resource, resource_type) #:nodoc:
@scope = @scope.new(scope_level_resource: resource, jsonapi_resource: resource_type)
controller(resource.resource_scope) { yield }
ensure
@scope = @scope.parent
end
# :nocov:
private
def resource_type_with_module_prefix(resource = nil)
resource_name = resource || @scope[:jsonapi_resource]
[@scope[:module], resource_name].compact.collect(&:to_s).join('/')
end
end
end
end
end