lib/jsonapi/processor.rb in jsonapi-resources-0.9.12 vs lib/jsonapi/processor.rb in jsonapi-resources-0.10.0.beta1

- old
+ new

@@ -15,34 +15,10 @@ :replace_to_many_relationships, :remove_to_many_relationships, :remove_to_one_relationship, :operation - class << self - def processor_instance_for(resource_klass, operation_type, params) - _processor_from_resource_type(resource_klass).new(resource_klass, operation_type, params) - end - - def _processor_from_resource_type(resource_klass) - processor = resource_klass.name.gsub(/Resource$/,'Processor').safe_constantize - if processor.nil? - processor = JSONAPI.configuration.default_processor_klass - end - - return processor - end - - def transactional_operation_type?(operation_type) - case operation_type - when :find, :show, :show_related_resource, :show_related_resources - return false - else - return true - end - end - end - attr_reader :resource_klass, :operation_type, :params, :context, :result, :result_options def initialize(resource_klass, operation_type, params) @resource_klass = resource_klass @operation_type = operation_type @@ -67,204 +43,255 @@ filters = params[:filters] include_directives = params[:include_directives] sort_criteria = params.fetch(:sort_criteria, []) paginator = params[:paginator] fields = params[:fields] + serializer = params[:serializer] verified_filters = resource_klass.verify_filters(filters, context) + find_options = { context: context, - include_directives: include_directives, sort_criteria: sort_criteria, paginator: paginator, - fields: fields + fields: fields, + filters: verified_filters, + include_directives: include_directives } - resource_records = if params[:cache_serializer] - resource_klass.find_serialized_with_caching(verified_filters, - params[:cache_serializer], - find_options) - else - resource_klass.find(verified_filters, find_options) - end + resource_set = find_resource_set(resource_klass, + include_directives, + find_options) - page_options = {} - if (JSONAPI.configuration.top_level_meta_include_record_count || - (paginator && paginator.class.requires_record_count)) - page_options[:record_count] = resource_klass.find_count(verified_filters, - context: context, - include_directives: include_directives) + resource_set.populate!(serializer, context, find_options) + + page_options = result_options + if (JSONAPI.configuration.top_level_meta_include_record_count || (paginator && paginator.class.requires_record_count)) + page_options[:record_count] = resource_klass.count(verified_filters, + context: context, + include_directives: include_directives) end - if (JSONAPI.configuration.top_level_meta_include_page_count && page_options[:record_count]) + if (JSONAPI.configuration.top_level_meta_include_page_count && paginator && page_options[:record_count]) page_options[:page_count] = paginator ? paginator.calculate_page_count(page_options[:record_count]) : 1 end if JSONAPI.configuration.top_level_links_include_pagination && paginator - page_options[:pagination_params] = paginator.links_page_params(page_options.merge(fetched_resources: resource_records)) + page_options[:pagination_params] = paginator.links_page_params(page_options.merge(fetched_resources: resource_set)) end - return JSONAPI::ResourcesOperationResult.new(:ok, resource_records, page_options) + return JSONAPI::ResourcesSetOperationResult.new(:ok, resource_set, page_options) end def show include_directives = params[:include_directives] fields = params[:fields] id = params[:id] + serializer = params[:serializer] key = resource_klass.verify_key(id, context) find_options = { context: context, - include_directives: include_directives, - fields: fields + fields: fields, + filters: { resource_klass._primary_key => key } } - resource_record = if params[:cache_serializer] - resource_klass.find_by_key_serialized_with_caching(key, - params[:cache_serializer], - find_options) - else - resource_klass.find_by_key(key, find_options) - end + resource_set = find_resource_set(resource_klass, + include_directives, + find_options) - return JSONAPI::ResourceOperationResult.new(:ok, resource_record) + resource_set.populate!(serializer, context, find_options) + + return JSONAPI::ResourceSetOperationResult.new(:ok, resource_set, result_options) end def show_relationship parent_key = params[:parent_key] relationship_type = params[:relationship_type].to_sym + paginator = params[:paginator] + sort_criteria = params.fetch(:sort_criteria, []) + include_directives = params[:include_directives] + fields = params[:fields] parent_resource = resource_klass.find_by_key(parent_key, context: context) - return JSONAPI::RelationshipOperationResult.new(:ok, - parent_resource, - resource_klass._relationship(relationship_type)) + find_options = { + context: context, + sort_criteria: sort_criteria, + paginator: paginator, + fields: fields + } + + resource_id_tree = find_related_resource_id_tree(resource_klass, + JSONAPI::ResourceIdentity.new(resource_klass, parent_key), + relationship_type, + find_options, + nil) + + return JSONAPI::LinksObjectOperationResult.new(:ok, + parent_resource, + resource_klass._relationship(relationship_type), + resource_id_tree.fragments.keys, + result_options) end def show_related_resource + include_directives = params[:include_directives] source_klass = params[:source_klass] source_id = params[:source_id] - relationship_type = params[:relationship_type].to_sym + relationship_type = params[:relationship_type] + serializer = params[:serializer] fields = params[:fields] - # TODO Should fetch related_resource from cache if caching enabled + find_options = { + context: context, + fields: fields, + filters: {} + } + source_resource = source_klass.find_by_key(source_id, context: context, fields: fields) - related_resource = source_resource.public_send(relationship_type) + resource_set = find_related_resource_set(source_resource, + relationship_type, + include_directives, + find_options) - return JSONAPI::ResourceOperationResult.new(:ok, related_resource) + resource_set.populate!(serializer, context, find_options) + + return JSONAPI::ResourceSetOperationResult.new(:ok, resource_set, result_options) end def show_related_resources source_klass = params[:source_klass] source_id = params[:source_id] relationship_type = params[:relationship_type] filters = params[:filters] - sort_criteria = params[:sort_criteria] + sort_criteria = params.fetch(:sort_criteria, resource_klass.default_sort) paginator = params[:paginator] fields = params[:fields] include_directives = params[:include_directives] + serializer = params[:serializer] - source_resource ||= source_klass.find_by_key(source_id, context: context, fields: fields) verified_filters = resource_klass.verify_filters(filters, context) - rel_opts = { + find_options = { filters: verified_filters, sort_criteria: sort_criteria, paginator: paginator, fields: fields, - context: context, - include_directives: include_directives + context: context } - related_resources = nil - if params[:cache_serializer] - # TODO Could also avoid instantiating source_resource as actual Resource by - # allowing LinkBuilder to accept CachedResourceFragment as source in - # relationships_related_link - scope = source_resource.public_send(:"records_for_#{relationship_type}", rel_opts) - relationship = source_klass._relationship(relationship_type) - related_resources = relationship.resource_klass.find_serialized_with_caching( - scope, - params[:cache_serializer], - rel_opts - ) - else - related_resources = source_resource.public_send(relationship_type, rel_opts) - end + source_resource = source_klass.find_by_key(source_id, context: context, fields: fields) + resource_set = find_related_resource_set(source_resource, + relationship_type, + include_directives, + find_options) + + resource_set.populate!(serializer, context, find_options) + + opts = result_options if ((JSONAPI.configuration.top_level_meta_include_record_count) || (paginator && paginator.class.requires_record_count) || (JSONAPI.configuration.top_level_meta_include_page_count)) - related_resource_records = source_resource.public_send("records_for_" + relationship_type) - records = resource_klass.filter_records(verified_filters, rel_opts, - related_resource_records) - record_count = resource_klass.count_records(records) + opts[:record_count] = source_resource.class.count_related( + source_resource.identity, + relationship_type, + find_options) end - if (JSONAPI.configuration.top_level_meta_include_page_count && record_count) - page_count = paginator.calculate_page_count(record_count) + if (JSONAPI.configuration.top_level_meta_include_page_count && opts[:record_count]) + opts[:page_count] = paginator.calculate_page_count(opts[:record_count]) end - pagination_params = if paginator && JSONAPI.configuration.top_level_links_include_pagination - page_options = {} - page_options[:record_count] = record_count if paginator.class.requires_record_count - paginator.links_page_params(page_options.merge(fetched_resources: related_resources)) - else - {} - end + opts[:pagination_params] = if paginator && JSONAPI.configuration.top_level_links_include_pagination + page_options = {} + page_options[:record_count] = opts[:record_count] if paginator.class.requires_record_count + paginator.links_page_params(page_options.merge(fetched_resources: resource_set)) + else + {} + end - opts = {} - opts.merge!(pagination_params: pagination_params) if JSONAPI.configuration.top_level_links_include_pagination - opts.merge!(record_count: record_count) if JSONAPI.configuration.top_level_meta_include_record_count - opts.merge!(page_count: page_count) if JSONAPI.configuration.top_level_meta_include_page_count - - return JSONAPI::RelatedResourcesOperationResult.new(:ok, - source_resource, - relationship_type, - related_resources, - opts) + return JSONAPI::RelatedResourcesSetOperationResult.new(:ok, + source_resource, + relationship_type, + resource_set, + opts) end def create_resource + include_directives = params[:include_directives] + fields = params[:fields] + serializer = params[:serializer] + data = params[:data] resource = resource_klass.create(context) result = resource.replace_fields(data) - return JSONAPI::ResourceOperationResult.new((result == :completed ? :created : :accepted), resource) + find_options = { + context: context, + fields: fields, + filters: { resource_klass._primary_key => resource.id } + } + + resource_set = find_resource_set(resource_klass, + include_directives, + find_options) + + resource_set.populate!(serializer, context, find_options) + + return JSONAPI::ResourceSetOperationResult.new((result == :completed ? :created : :accepted), resource_set, result_options) end def remove_resource resource_id = params[:resource_id] resource = resource_klass.find_by_key(resource_id, context: context) result = resource.remove - return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted) + return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options) end def replace_fields resource_id = params[:resource_id] + include_directives = params[:include_directives] + fields = params[:fields] + serializer = params[:serializer] + data = params[:data] resource = resource_klass.find_by_key(resource_id, context: context) + result = resource.replace_fields(data) - return JSONAPI::ResourceOperationResult.new(result == :completed ? :ok : :accepted, resource) + find_options = { + context: context, + fields: fields, + filters: { resource_klass._primary_key => resource.id } + } + + resource_set = find_resource_set(resource_klass, + include_directives, + find_options) + + resource_set.populate!(serializer, context, find_options) + + return JSONAPI::ResourceSetOperationResult.new((result == :completed ? :ok : :accepted), resource_set, result_options) end def replace_to_one_relationship resource_id = params[:resource_id] relationship_type = params[:relationship_type].to_sym key_value = params[:key_value] resource = resource_klass.find_by_key(resource_id, context: context) result = resource.replace_to_one_link(relationship_type, key_value) - return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted) + return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options) end def replace_polymorphic_to_one_relationship resource_id = params[:resource_id] relationship_type = params[:relationship_type].to_sym @@ -272,33 +299,33 @@ key_type = params[:key_type] resource = resource_klass.find_by_key(resource_id, context: context) result = resource.replace_polymorphic_to_one_link(relationship_type, key_value, key_type) - return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted) + return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options) end def create_to_many_relationships resource_id = params[:resource_id] relationship_type = params[:relationship_type].to_sym data = params[:data] resource = resource_klass.find_by_key(resource_id, context: context) result = resource.create_to_many_links(relationship_type, data) - return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted) + return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options) end def replace_to_many_relationships resource_id = params[:resource_id] relationship_type = params[:relationship_type].to_sym data = params.fetch(:data) resource = resource_klass.find_by_key(resource_id, context: context) result = resource.replace_to_many_links(relationship_type, data) - return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted) + return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options) end def remove_to_many_relationships resource_id = params[:resource_id] relationship_type = params[:relationship_type].to_sym @@ -311,19 +338,113 @@ result = resource.remove_to_many_link(relationship_type, key) if complete && result != :completed complete = false end end - return JSONAPI::OperationResult.new(complete ? :no_content : :accepted) + return JSONAPI::OperationResult.new(complete ? :no_content : :accepted, result_options) end def remove_to_one_relationship resource_id = params[:resource_id] relationship_type = params[:relationship_type].to_sym resource = resource_klass.find_by_key(resource_id, context: context) result = resource.remove_to_one_link(relationship_type) - return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted) + return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options) + end + + def result_options + options = {} + options[:warnings] = params[:warnings] if params[:warnings] + options + end + + def find_resource_set(resource_klass, include_directives, options) + include_related = include_directives.include_directives[:include_related] if include_directives + + resource_id_tree = find_resource_id_tree(resource_klass, options, include_related) + + JSONAPI::ResourceSet.new(resource_id_tree) + end + + def find_related_resource_set(resource, relationship_name, include_directives, options) + include_related = include_directives.include_directives[:include_related] if include_directives + + resource_id_tree = find_resource_id_tree_from_resource_relationship(resource, relationship_name, options, include_related) + + JSONAPI::ResourceSet.new(resource_id_tree) + end + + private + def find_related_resource_id_tree(resource_klass, source_id, relationship_name, find_options, include_related) + options = find_options.except(:include_directives) + options[:cache] = resource_klass.caching? + + fragments = resource_klass.find_included_fragments([source_id], relationship_name, options) + + primary_resource_id_tree = PrimaryResourceIdTree.new + primary_resource_id_tree.add_resource_fragments(fragments, include_related) + + load_included(resource_klass, primary_resource_id_tree, include_related, options.except(:filters, :sort_criteria)) + + primary_resource_id_tree + end + + def find_resource_id_tree(resource_klass, find_options, include_related) + options = find_options + options[:cache] = resource_klass.caching? + + fragments = resource_klass.find_fragments(find_options[:filters], options) + + primary_resource_id_tree = PrimaryResourceIdTree.new + primary_resource_id_tree.add_resource_fragments(fragments, include_related) + + load_included(resource_klass, primary_resource_id_tree, include_related, options.except(:filters, :sort_criteria)) + + primary_resource_id_tree + end + + def find_resource_id_tree_from_resource_relationship(resource, relationship_name, find_options, include_related) + relationship = resource.class._relationship(relationship_name) + + options = find_options.except(:include_directives) + options[:cache] = relationship.resource_klass.caching? + + fragments = resource.class.find_related_fragments([resource.identity], relationship_name, options) + + primary_resource_id_tree = PrimaryResourceIdTree.new + primary_resource_id_tree.add_resource_fragments(fragments, include_related) + + load_included(resource_klass, primary_resource_id_tree, include_related, options.except(:filters, :sort_criteria)) + + primary_resource_id_tree + end + + def load_included(resource_klass, source_resource_id_tree, include_related, options) + source_rids = source_resource_id_tree.fragments.keys + + include_related.try(:each_pair) do |key, value| + next unless value[:include] + relationship = resource_klass._relationship(key) + relationship_name = relationship.name.to_sym + + find_related_resource_options = options.dup + find_related_resource_options[:sort_criteria] = relationship.resource_klass.default_sort + find_related_resource_options[:cache] = resource_klass.caching? + + related_fragments = resource_klass.find_included_fragments( + source_rids, relationship_name, find_related_resource_options + ) + + related_resource_id_tree = source_resource_id_tree.fetch_related_resource_id_tree(relationship) + related_resource_id_tree.add_resource_fragments(related_fragments, include_related[key][include_related]) + + # Now recursively get the related resources for the currently found resources + load_included(relationship.resource_klass, + related_resource_id_tree, + include_related[relationship_name][:include_related], + options) + end end end end