jsonapi-renderer-0.1.3/0000755000175100017510000000000013154552261013504 5ustar srudsrudjsonapi-renderer-0.1.3/README.md0000644000175100017510000000570413154552261014771 0ustar srudsrud# jsonapi-renderer Ruby gem for rendering [JSON API](http://jsonapi.org) documents. ## Status [![Gem Version](https://badge.fury.io/rb/jsonapi-renderer.svg)](https://badge.fury.io/rb/jsonapi-renderer) [![Build Status](https://secure.travis-ci.org/jsonapi-rb/jsonapi-renderer.svg?branch=master)](http://travis-ci.org/jsonapi-rb/renderer?branch=master) [![codecov](https://codecov.io/gh/jsonapi-rb/jsonapi-renderer/branch/master/graph/badge.svg)](https://codecov.io/gh/jsonapi-rb/renderer) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/jsonapi-rb/Lobby) ## Resources * Chat: [gitter](http://gitter.im/jsonapi-rb) * Twitter: [@jsonapirb](http://twitter.com/jsonapirb) * Docs: [jsonapi-rb.org](http://jsonapi-rb.org) ## Installation ```ruby # In Gemfile gem 'jsonapi-renderer' ``` then ``` $ bundle ``` or manually via ``` $ gem install jsonapi-renderer ``` ## Usage First, require the gem: ```ruby require 'jsonapi/renderer' ``` ### Rendering resources A resource here is any class that implements the following interface: ```ruby class ResourceInterface # Returns the type of the resource. # @return [String] def jsonapi_type; end # Returns the id of the resource. # @return [String] def jsonapi_id; end # Returns a hash containing, for each included relationship, an array of the # resources to be included from that one. # @param included_relationships [Array] The keys of the relationships # to be included. # @return [Hash{Symbol => Array<#ResourceInterface>}] def jsonapi_related(included_relationships); end # Returns a JSON API-compliant representation of the resource as a hash. # @param options [Hash] # @option fields [Array, Nil] The requested fields, or nil. # @option include [Array] The requested relationships to # include (defaults to []). # @return [Hash] def as_jsonapi(options = {}); end end ``` #### Rendering a single resource ```ruby JSONAPI.render(data: resource, include: include_string, fields: fields_hash, meta: meta_hash, links: links_hash) ``` This returns a JSON API compliant hash representing the described document. #### Rendering a collection of resources ```ruby JSONAPI.render(data: resources, include: include_string, fields: fields_hash, meta: meta_hash, links: links_hash) ``` This returns a JSON API compliant hash representing the described document. ### Rendering errors ```ruby JSONAPI.render_errors(errors: errors, meta: meta_hash, links: links_hash) ``` where `errors` is an array of objects implementing the `as_jsonapi` method, that returns a JSON API-compliant representation of the error. This returns a JSON API compliant hash representing the described document. ## License jsonapi-renderer is released under the [MIT License](http://www.opensource.org/licenses/MIT). jsonapi-renderer-0.1.3/lib/0000755000175100017510000000000013154552261014252 5ustar srudsrudjsonapi-renderer-0.1.3/lib/jsonapi/0000755000175100017510000000000013154552261015715 5ustar srudsrudjsonapi-renderer-0.1.3/lib/jsonapi/include_directive.rb0000644000175100017510000000406213154552261021725 0ustar srudsrudrequire 'jsonapi/include_directive/parser' module JSONAPI # Represent a recursive set of include directives # (c.f. http://jsonapi.org/format/#fetching-includes) # # Addition to the spec: two wildcards, namely '*' and '**'. # The former stands for any one level of relationship, and the latter stands # for any number of levels of relationships. # @example 'posts.*' # => Include related posts, and all the included posts' # related resources. # @example 'posts.**' # => Include related posts, and all the included # posts' related resources, and their related resources, recursively. class IncludeDirective # @param include_args (see Parser.parse_include_args) def initialize(include_args, options = {}) include_hash = Parser.parse_include_args(include_args) @hash = include_hash.each_with_object({}) do |(key, value), hash| hash[key] = self.class.new(value, options) end @options = options end # @param key [Symbol, String] def key?(key) @hash.key?(key.to_sym) || (@options[:allow_wildcard] && (@hash.key?(:*) || @hash.key?(:**))) end # @return [Array] def keys @hash.keys end # @param key [Symbol, String] # @return [IncludeDirective, nil] def [](key) case when @hash.key?(key.to_sym) @hash[key.to_sym] when @options[:allow_wildcard] && @hash.key?(:**) self.class.new({ :** => {} }, @options) when @options[:allow_wildcard] && @hash.key?(:*) @hash[:*] end end # @return [Hash{Symbol => Hash}] def to_hash @hash.each_with_object({}) do |(key, value), hash| hash[key] = value.to_hash end end # @return [String] def to_string string_array = @hash.map do |(key, value)| string_value = value.to_string if string_value == '' key.to_s else string_value .split(',') .map { |x| key.to_s + '.' + x } .join(',') end end string_array.join(',') end end end jsonapi-renderer-0.1.3/lib/jsonapi/include_directive/0000755000175100017510000000000013154552261021376 5ustar srudsrudjsonapi-renderer-0.1.3/lib/jsonapi/include_directive/parser.rb0000644000175100017510000000303513154552261023220 0ustar srudsrudmodule JSONAPI class IncludeDirective # Utilities to create an IncludeDirective hash from various types of # inputs. module Parser module_function # @api private def parse_include_args(include_args) case include_args when Symbol { include_args => {} } when Hash parse_hash(include_args) when Array parse_array(include_args) when String parse_string(include_args) else {} end end # @api private def parse_string(include_string) include_string.split(',') .each_with_object({}) do |path, hash| deep_merge!(hash, parse_path_string(path)) end end # @api private def parse_path_string(include_path) include_path.split('.') .reverse .reduce({}) { |a, e| { e.to_sym => a } } end # @api private def parse_hash(include_hash) include_hash.each_with_object({}) do |(key, value), hash| hash[key.to_sym] = parse_include_args(value) end end # @api private def parse_array(include_array) include_array.each_with_object({}) do |x, hash| deep_merge!(hash, parse_include_args(x)) end end # @api private def deep_merge!(src, ext) ext.each do |k, v| if src[k].is_a?(Hash) && v.is_a?(Hash) deep_merge!(src[k], v) else src[k] = v end end end end end end jsonapi-renderer-0.1.3/lib/jsonapi/renderer.rb0000644000175100017510000000210513154552261020046 0ustar srudsrudrequire 'jsonapi/renderer/document' module JSONAPI class Renderer # Render a JSON API document. # # @param params [Hash] # @option data [(#jsonapi_id, #jsonapi_type, #jsonapi_related, #as_jsonapi), # Array<(#jsonapi_id, #jsonapi_type, #jsonapi_related, # #as_jsonapi)>, # nil] Primary resource(s) to be rendered. # @option errors [Array<#jsonapi_id>] Errors to be rendered. # @option include Relationships to be included. See # JSONAPI::IncludeDirective. # @option fields [Hash{Symbol, Array}, Hash{String, Array}] # List of requested fields for some or all of the resource types. # @option meta [Hash] Non-standard top-level meta information to be # included. # @option links [Hash] Top-level links to be included. # @option jsonapi_object [Hash] JSON API object. def render(params) Document.new(params).to_hash end end module_function # @see JSONAPI::Renderer#render def render(params) Renderer.new.render(params) end end jsonapi-renderer-0.1.3/lib/jsonapi/renderer/0000755000175100017510000000000013154552261017523 5ustar srudsrudjsonapi-renderer-0.1.3/lib/jsonapi/renderer/resources_processor.rb0000644000175100017510000000463013154552261024164 0ustar srudsrudrequire 'set' module JSONAPI class Renderer class ResourcesProcessor def initialize(resources, include, fields) @resources = resources @include = include @fields = fields end def process traverse_resources process_resources [@primary, @included] end private def traverse_resources @traversed = Set.new # [type, id, prefix] @include_rels = {} # [type, id => Set] @queue = [] @primary = [] @included = [] initialize_queue traverse_queue end def initialize_queue @resources.each do |res| @traversed.add([res.jsonapi_type, res.jsonapi_id, '']) traverse_resource(res, @include.keys, true) enqueue_related_resources(res, '', @include) end end def traverse_queue until @queue.empty? res, prefix, include_dir = @queue.shift traverse_resource(res, include_dir.keys, false) enqueue_related_resources(res, prefix, include_dir) end end def traverse_resource(res, include_keys, primary) ri = [res.jsonapi_type, res.jsonapi_id] if @include_rels.include?(ri) @include_rels[ri].merge(include_keys) else @include_rels[ri] = Set.new(include_keys) (primary ? @primary : @included) << res end end def enqueue_related_resources(res, prefix, include_dir) res.jsonapi_related(include_dir.keys).each do |key, data| data.each do |child_res| next if child_res.nil? child_prefix = "#{prefix}.#{key}" enqueue_resource(child_res, child_prefix, include_dir[key]) end end end def enqueue_resource(res, prefix, include_dir) return unless @traversed.add?([res.jsonapi_type, res.jsonapi_id, prefix]) @queue << [res, prefix, include_dir] end def process_resources [@primary, @included].each do |resources| resources.map! do |res| ri = [res.jsonapi_type, res.jsonapi_id] include_dir = @include_rels[ri] fields = @fields[res.jsonapi_type.to_sym] res.as_jsonapi(include: include_dir, fields: fields) end end end end end end jsonapi-renderer-0.1.3/lib/jsonapi/renderer/document.rb0000644000175100017510000000307313154552261021671 0ustar srudsrudrequire 'jsonapi/include_directive' require 'jsonapi/renderer/resources_processor' module JSONAPI class Renderer class Document def initialize(params = {}) @data = params.fetch(:data, :no_data) @errors = params.fetch(:errors, []) @meta = params[:meta] @links = params[:links] || {} @fields = _symbolize_fields(params[:fields] || {}) @jsonapi = params[:jsonapi] @include = JSONAPI::IncludeDirective.new(params[:include] || {}) end def to_hash @hash ||= document_hash end alias to_h to_hash private def document_hash {}.tap do |hash| if @data != :no_data hash.merge!(data_hash) elsif @errors.any? hash.merge!(errors_hash) end hash[:links] = @links if @links.any? hash[:meta] = @meta unless @meta.nil? hash[:jsonapi] = @jsonapi unless @jsonapi.nil? end end def data_hash primary, included = ResourcesProcessor.new(Array(@data), @include, @fields).process {}.tap do |hash| hash[:data] = @data.respond_to?(:to_ary) ? primary : primary[0] hash[:included] = included if included.any? end end def errors_hash {}.tap do |hash| hash[:errors] = @errors.map(&:as_jsonapi) end end def _symbolize_fields(fields) fields.each_with_object({}) do |(k, v), h| h[k.to_sym] = v.map(&:to_sym) end end end end end jsonapi-renderer-0.1.3/jsonapi-renderer.gemspec0000644000175100017510000000341013154552261020316 0ustar srudsrud######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: jsonapi-renderer 0.1.3 ruby lib Gem::Specification.new do |s| s.name = "jsonapi-renderer".freeze s.version = "0.1.3" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Lucas Hosseini".freeze] s.date = "2017-07-12" s.description = "Efficiently render JSON API documents.".freeze s.email = "lucas.hosseini@gmail.com".freeze s.files = ["README.md".freeze, "lib/jsonapi/include_directive.rb".freeze, "lib/jsonapi/include_directive/parser.rb".freeze, "lib/jsonapi/renderer.rb".freeze, "lib/jsonapi/renderer/document.rb".freeze, "lib/jsonapi/renderer/resources_processor.rb".freeze] s.homepage = "https://github.com/jsonapi-rb/jsonapi-renderer".freeze s.licenses = ["MIT".freeze] s.rubygems_version = "2.5.2".freeze s.summary = "Render JSONAPI documents.".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q.freeze, ["~> 0.1"]) s.add_development_dependency(%q.freeze, ["~> 11.3"]) s.add_development_dependency(%q.freeze, ["~> 3.5"]) else s.add_dependency(%q.freeze, ["~> 0.1"]) s.add_dependency(%q.freeze, ["~> 11.3"]) s.add_dependency(%q.freeze, ["~> 3.5"]) end else s.add_dependency(%q.freeze, ["~> 0.1"]) s.add_dependency(%q.freeze, ["~> 11.3"]) s.add_dependency(%q.freeze, ["~> 3.5"]) end end