Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

--wip-- [skip ci] #1220

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ coverage
test/log
test_db
test_db-journal
*.iml
.idea
21 changes: 20 additions & 1 deletion lib/jsonapi/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ class Configuration
:default_caching,
:default_resource_cache_field,
:resource_cache_digest_function,
:resource_cache_usage_report_function
:resource_cache_usage_report_function,
:version_roots

def initialize
#:underscored_key, :camelized_key, :dasherized_key, or custom
Expand Down Expand Up @@ -155,6 +156,14 @@ def initialize
# Optionally provide a callable which JSONAPI will call with information about cache
# performance. Should accept three arguments: resource name, hits count, misses count.
self.resource_cache_usage_report_function = nil

# Version Roots
# Define roots of resource versions, this allows for the usage of namespacing within a
# version (useful for preventing a kitchen sink situation)
#
# Defined by specifying the root module under which namespaces will not be treated as
# versions but instead as namespaces
self.roots = []
end

def cache_formatters=(bool)
Expand Down Expand Up @@ -235,6 +244,14 @@ def allow_include=(allow_include)
@default_allow_include_to_many = allow_include
end

def root_names
root.map(&:to_s)
end

def root_paths
root.map(&:to_s)
end

attr_writer :allow_sort, :allow_filter, :default_allow_include_to_one, :default_allow_include_to_many

attr_writer :default_paginator
Expand Down Expand Up @@ -284,6 +301,8 @@ def allow_include=(allow_include)
attr_writer :resource_cache_digest_function

attr_writer :resource_cache_usage_report_function

attr_writer :roots
end

class << self
Expand Down
88 changes: 60 additions & 28 deletions lib/jsonapi/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def _replace_to_many_links(relationship_type, relationship_key_values, options)
if reflect
existing_rids = self.class.find_related_fragments([identity], relationship_type, options)

existing = existing_rids.keys.collect { |rid| rid.id }
existing = existing_rids.keys.collect {|rid| rid.id}

to_delete = existing - (relationship_key_values & existing)
to_delete.each do |key|
Expand Down Expand Up @@ -414,9 +414,6 @@ def inherited(subclass)

subclass._allowed_sort = _allowed_sort.dup

type = subclass.name.demodulize.sub(/Resource$/, '').underscore
subclass._type = type.pluralize.to_sym

unless subclass._attributes[:id]
subclass.attribute :id, format: :id, readonly: true
end
Expand Down Expand Up @@ -505,12 +502,47 @@ def rebuild_relationships(relationships)
end
end

def root_path_for(klass)
root_for(klass).to_s.underscore
end

def root_path_for_path(path)
JSONAPI.configuration.root_paths.detect {|p| path.start_with? p}
end

# @param [Class] klass
# @return [String]
def root_for(klass)
s = klass.to_s
JSONAPI.configuration.root_names.detect {|root| s.start_with? root.to_s}
end

# @return [String]
def root
self.class.root_for(self)
end

# @return [String]
def root_path
self.class.root_path_for(self)
end

# This is the type with its root namespace removed API::V1::Foo::Bar -> foo/bars
def _type
@_type ||= name.sub("#{self.root}::", '').sub(/Resource$/, '').underscore.pluralize.to_sym
end

# Called in two contexts
#
# 1) From within a namespace for resource resolution
#
# 2) Globally from the router context, in which case we have the fully qualified path
def resource_klass_for(type)
type = type.underscore
type_with_module = type.start_with?(module_path) ? type : module_path + type

resource_name = _resource_name_from_type(type_with_module)
resource_name = _resource_name_from_type(type)
resource = resource_name.safe_constantize if resource_name

if resource.nil?
fail NameError, "JSONAPI: Could not find resource '#{type}'. (Class #{resource_name} not found)"
end
Expand All @@ -530,7 +562,7 @@ def resource_type_for(model)
if _model_hints[model_name]
_model_hints[model_name]
else
model_name.rpartition('/').last
model_name.gsub("#{self.root_path}/", '')
end
end

Expand Down Expand Up @@ -602,7 +634,7 @@ def attribute_to_model_field(attribute)
else
attribute_type = _model_class.column_types[field_name.to_s]
end
{ name: field_name, type: attribute_type}
{name: field_name, type: attribute_type}
end

def cast_to_attribute_type(value, type)
Expand All @@ -614,20 +646,20 @@ def cast_to_attribute_type(value, type)
end

def default_attribute_options
{ format: :default }
{format: :default}
end

def relationship(*attrs)
options = attrs.extract_options!
klass = case options[:to]
when :one
Relationship::ToOne
when :many
Relationship::ToMany
else
#:nocov:#
fail ArgumentError.new('to: must be either :one or :many')
#:nocov:#
when :one
Relationship::ToOne
when :many
Relationship::ToMany
else
#:nocov:#
fail ArgumentError.new('to: must be either :one or :many')
#:nocov:#
end
_add_relationship(klass, *attrs, options.except(:to))
end
Expand Down Expand Up @@ -664,7 +696,7 @@ def model_hint(model: _model_name, resource: _type)
end

def filters(*attrs)
@_allowed_filters.merge!(attrs.inject({}) { |h, attr| h[attr] = {}; h })
@_allowed_filters.merge!(attrs.inject({}) {|h, attr| h[attr] = {}; h})
end

def filter(attr, *args)
Expand All @@ -677,7 +709,7 @@ def sort(sorting, options = {})

def sorts(*args)
options = args.extract_options!
_allowed_sort.merge!(args.inject({}) { |h, sorting| h[sorting.to_sym] = options.dup; h })
_allowed_sort.merge!(args.inject({}) {|h, sorting| h[sorting.to_sym] = options.dup; h})
end

def primary_key(key)
Expand Down Expand Up @@ -835,11 +867,11 @@ def _has_attribute?(attr)
end

def _updatable_attributes
_attributes.map { |key, options| key unless options[:readonly] }.compact
_attributes.map {|key, options| key unless options[:readonly]}.compact
end

def _updatable_relationships
@_relationships.map { |key, relationship| key unless relationship.readonly? }.compact
@_relationships.map {|key, relationship| key unless relationship.readonly?}.compact
end

def _relationship(type)
Expand All @@ -850,12 +882,12 @@ def _relationship(type)

def _model_name
if _abstract
''
''
else
return @_model_name.to_s if defined?(@_model_name)
class_name = self.name
return '' if class_name.nil?
@_model_name = class_name.demodulize.sub(/Resource$/, '')
@_model_name = JSONAPI.configuration.root_names.inject(class_name) {|name, rn| name.sub(rn, '')}.sub(/Resource$/, '')
@_model_name.to_s
end
end
Expand All @@ -873,7 +905,7 @@ def _primary_key
end

def _default_primary_key
@_default_primary_key ||=_model_class.respond_to?(:primary_key) ? _model_class.primary_key : :id
@_default_primary_key ||= _model_class.respond_to?(:primary_key) ? _model_class.primary_key : :id
end

def _cache_field
Expand All @@ -889,7 +921,7 @@ def _as_parent_key
end

def _allowed_filters
defined?(@_allowed_filters) ? @_allowed_filters : { id: {} }
defined?(@_allowed_filters) ? @_allowed_filters : {id: {}}
end

def _allowed_sort
Expand Down Expand Up @@ -917,7 +949,7 @@ def _polymorphic_types
ObjectSpace.each_object do |klass|
next unless Module === klass
if klass < ActiveRecord::Base
klass.reflect_on_all_associations(:has_many).select{|r| r.options[:as] }.each do |reflection|
klass.reflect_on_all_associations(:has_many).select {|r| r.options[:as]}.each do |reflection|
(hash[reflection.options[:as]] ||= []) << klass.name.downcase
end
end
Expand Down Expand Up @@ -1040,8 +1072,8 @@ def _add_relationship(klass, *attrs)
# ResourceBuilder methods
def define_relationship_methods(relationship_name, relationship_klass, options)
relationship = register_relationship(
relationship_name,
relationship_klass.new(relationship_name, options)
relationship_name,
relationship_klass.new(relationship_name, options)
)

define_foreign_key_setter(relationship)
Expand Down
9 changes: 7 additions & 2 deletions lib/jsonapi/routing_ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def jsonapi_related_resource(*relationship)
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))
related_resource = JSONAPI::Resource.resource_klass_for(related_resource_type_with_module_prefix(relationship.class_name.underscore.pluralize))
options[:controller] ||= related_resource._type.to_s
end

Expand All @@ -232,7 +232,7 @@ def jsonapi_related_resources(*relationship)
relationship = source._relationships[relationship_name]

formatted_relationship_name = format_route(relationship.name)
related_resource = JSONAPI::Resource.resource_klass_for(resource_type_with_module_prefix(relationship.class_name.underscore))
related_resource = JSONAPI::Resource.resource_klass_for(related_resource_type_with_module_prefix(relationship.class_name.underscore))
options[:controller] ||= related_resource._type.to_s

match formatted_relationship_name,
Expand All @@ -258,6 +258,11 @@ def resource_type_with_module_prefix(resource = nil)
resource_name = resource || @scope[:jsonapi_resource]
[@scope[:module], resource_name].compact.collect(&:to_s).join('/')
end

def related_resource_type_with_module_prefix(resource = nil)
resource_name = resource || @scope[:jsonapi_resource]
[JSONAPI::Resource.root_path_for_path(@scope[:module]), resource_name].compact.collect(&:to_s).join('/')
end
end
end
end
Expand Down