Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ tmp
coverage
test/log
test_db
test_db-journal
test_db-journal
.idea
114 changes: 114 additions & 0 deletions lib/jsonapi/associated_resources.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
module JSONAPI
class AssociatedResources
class << self
def associated_resources_for(resource, relationship_name, options = {})
relationship = resource._relationships[relationship_name]
associated_records_method_name = case relationship
when JSONAPI::Relationship::ToOne then "record_for_#{relationship_name}"
when JSONAPI::Relationship::ToMany then "records_for_#{relationship_name}"
end
if relationship.is_a?(JSONAPI::Relationship::ToOne)
if relationship.belongs_to?
_associated_resource_for_belongs_to(resource, relationship, associated_records_method_name, options)
else
_associated_resource_for_has_one(resource, relationship, associated_records_method_name, options)
end

elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
_associated_resources_for_has_many(resource, relationship, associated_records_method_name, options)
end

end

def associated_foreign_keys_for(resource, relationship_name)
relationship = resource._relationships[relationship_name]
foreign_key = relationship.foreign_key
associated_records_method_name = case relationship
when JSONAPI::Relationship::ToOne then "record_for_#{relationship_name}"
when JSONAPI::Relationship::ToMany then "records_for_#{relationship_name}"
end
if relationship.is_a?(JSONAPI::Relationship::ToOne)
if relationship.belongs_to?
_associated_foreign_key_for_belongs_to(resource, relationship, associated_records_method_name)
else
_associated_foreign_key_for_has_one(resource, relationship, associated_records_method_name)
end
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
_associated_foreign_keys_for_has_many(resource, relationship, associated_records_method_name)
end
end

private

def _associated_foreign_key_for_belongs_to(resource, relationship, associated_records_method_name)
resource._model.respond_to?(relationship.foreign_key) ? resource._model.method(relationship.foreign_key).call : resource.associated_foreign_keys_for(relationship.name.to_sym)
end

def _associated_foreign_key_for_has_one(resource, relationship, associated_records_method_name)
record = resource.respond_to?(associated_records_method_name) ? resource.public_send(associated_records_method_name) : resource.associated_records_for(relationship.name.to_sym)
return nil if record.nil?
record.public_send(relationship.resource_klass._primary_key)
end

def _associated_foreign_keys_for_has_many(resource, relationship, associated_records_method_name)
records = resource.respond_to?(associated_records_method_name) ? resource.public_send(associated_records_method_name) : resource.associated_records_for(relationship.name.to_sym)
return records.collect do |record|
record.public_send(relationship.resource_klass._primary_key)
end
end

def _associated_resource_for_belongs_to(resource, relationship, associated_records_method_name = nil, options = {})
if relationship.polymorphic?
associated_model = associated_records_method_name.nil? ? resource.associated_records_for(relationship_name) : resource.public_send(associated_records_method_name)
resource_klass = resource.class.resource_for_model(associated_model) if associated_model
return resource_klass.new(associated_model, @context) if resource_klass
else
resource_klass = relationship.resource_klass
if resource_klass
associated_model = resource.respond_to?(associated_records_method_name) ? resource.public_send(associated_records_method_name) : resource.associated_records_for(relationship_name)
return associated_model ? resource_klass.new(associated_model, @context) : nil
end
end

end

def _associated_resource_for_has_one(resource, relationship, associated_records_method_name = nil, options = {})
resource_klass = relationship.resource_klass
if resource_klass
associated_model = resource.respond_to?(associated_records_method_name) ? resource.public_send(associated_records_method_name) : resource.associated_records_for(relationship_name)
return associated_model ? resource_klass.new(associated_model, @context) : nil
end
end

def _associated_resources_for_has_many(resource, relationship, associated_records_method_name = nil, options = {})
resource_klass = relationship.resource_klass
records = resource.respond_to?(associated_records_method_name) ? resource.public_send(associated_records_method_name) : resource.associated_records_for(relationship.name.to_sym)

filters = options.fetch(:filters, {})
unless filters.nil? || filters.empty?
records = resource_klass.apply_filters(records, filters, options)
end

sort_criteria = options.fetch(:sort_criteria, {})
unless sort_criteria.nil? || sort_criteria.empty?
order_options = relationship.resource_klass.construct_order_options(sort_criteria)
records = resource_klass.apply_sort(records, order_options, @context)
end

paginator = options[:paginator]
if paginator
records = resource_klass.apply_pagination(records, paginator, order_options)
end

return records.collect do |record|
if relationship.polymorphic?
resource_klass = resource.class.resource_for_model(record)
end
resource_klass.new(record, @context)
end
end

end

end
end
8 changes: 6 additions & 2 deletions lib/jsonapi/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ class Configuration
:exception_class_whitelist,
:always_include_to_one_linkage_data,
:always_include_to_many_linkage_data,
:cache_formatters
:cache_formatters,
:validate_includes,
:validate_fields

def initialize
#:underscored_key, :camelized_key, :dasherized_key, or custom
Expand All @@ -40,6 +42,8 @@ def initialize
self.allow_include = true
self.allow_sort = true
self.allow_filter = true
self.validate_includes = true
self.validate_fields = true

self.raise_if_parameters_not_allowed = true

Expand Down Expand Up @@ -152,7 +156,7 @@ def default_processor_klass=(default_processor_klass)
@default_processor_klass = default_processor_klass
end

attr_writer :allow_include, :allow_sort, :allow_filter
attr_writer :allow_include, :allow_sort, :allow_filter, :validate_includes, :validate_fields

attr_writer :default_paginator

Expand Down
2 changes: 1 addition & 1 deletion lib/jsonapi/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ class ValidationErrors < Error
def initialize(resource)
@error_messages = resource.model_error_messages
@error_metadata = resource.validation_error_metadata
@resource_relationships = resource.class._relationships.keys
@resource_relationships = resource._relationships.keys
@key_formatter = JSONAPI.configuration.key_formatter
end

Expand Down
7 changes: 4 additions & 3 deletions lib/jsonapi/request_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def parse_pagination(page)

def parse_fields(fields)
return if fields.nil?
validate_fields = JSONAPI.configuration.validate_fields

extracted_fields = {}

Expand Down Expand Up @@ -180,7 +181,7 @@ def parse_fields(fields)
unless values.nil?
valid_fields = type_resource.fields.collect { |key| format_key(key) }
values.each do |field|
if valid_fields.include?(field)
if !validate_fields || valid_fields.include?(field)
extracted_fields[type].push unformat_key(field)
else
@errors.concat(JSONAPI::Exceptions::InvalidField.new(type, field).errors)
Expand Down Expand Up @@ -211,7 +212,7 @@ def check_include(resource_klass, include_parts)

def parse_include_directives(include)
return if include.nil?

validate_includes = JSONAPI.configuration.validate_includes
unless JSONAPI.configuration.allow_include
fail JSONAPI::Exceptions::ParametersNotAllowed.new([:include])
end
Expand All @@ -221,7 +222,7 @@ def parse_include_directives(include)

include = []
included_resources.each do |included_resource|
check_include(@resource_klass, included_resource.partition('.'))
check_include(@resource_klass, included_resource.partition('.')) if validate_includes
include.push(unformat_key(included_resource).to_s)
end

Expand Down
Loading