New Upstream Release - ruby-jsonapi-renderer

Ready changes

Summary

Merged new upstream version: 0.2.2 (was: 0.1.3).

Resulting package

Built on 2022-06-01T10:13 (took 3m13s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases ruby-jsonapi-renderer

Lintian Result

Diff

diff --git a/README.md b/README.md
index 7d8bf4d..2b6d443 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,7 @@ class ResourceInterface
   # @return [String]
   def jsonapi_id; end
 
-  # Returns a hash containing, for each included relationship, an array of the 
+  # Returns a hash containing, for each included relationship, an array of the
   # resources to be included from that one.
   # @param included_relationships [Array<Symbol>] The keys of the relationships
   #   to be included.
@@ -57,8 +57,8 @@ class ResourceInterface
 
   # Returns a JSON API-compliant representation of the resource as a hash.
   # @param options [Hash]
-  #   @option fields [Array<Symbol>, Nil] The requested fields, or nil.
-  #   @option include [Array<Symbol>] The requested relationships to 
+  #   @option fields [Set<Symbol>, Nil] The requested fields, or nil.
+  #   @option include [Set<Symbol>] The requested relationships to
   #     include (defaults to []).
   # @return [Hash]
   def as_jsonapi(options = {}); end
@@ -87,6 +87,18 @@ JSONAPI.render(data: resources,
 
 This returns a JSON API compliant hash representing the described document.
 
+#### Rendering a relationship
+```ruby
+JSONAPI.render(data: resource,
+               relationship: :posts,
+               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
@@ -100,6 +112,32 @@ returns a JSON API-compliant representation of the error.
 
 This returns a JSON API compliant hash representing the described document.
 
+### Caching
+
+The generated JSON fragments can be cached in any cache implementation
+supporting the `fetch_multi` method.
+
+When using caching, the serializable resources must implement an
+additional `jsonapi_cache_key` method:
+```ruby
+  # Returns a cache key for the resource, parameterized by the `include` and
+  #   `fields` options.
+  # @param options [Hash]
+  #   @option fields [Set<Symbol>, Nil] The requested fields, or nil.
+  #   @option include [Set<Symbol>] The requested relationships to
+  #     include (defaults to []).
+  # @return [String]
+  def jsonapi_cache_key(options = {}); end
+```
+
+The cache instance must be passed to the renderer as follows:
+```ruby
+JSONAPI.render(data: resources,
+               include: include_string,
+               fields: fields_hash,
+               cache: cache_instance)
+```
+
 ## License
 
 jsonapi-renderer is released under the [MIT License](http://www.opensource.org/licenses/MIT).
diff --git a/debian/changelog b/debian/changelog
index 9005761..c02ed5d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-ruby-jsonapi-renderer (0.1.3-2) UNRELEASED; urgency=medium
+ruby-jsonapi-renderer (0.2.2-1) UNRELEASED; urgency=medium
 
   [ Utkarsh Gupta ]
   * Add salsa-ci.yml
@@ -17,8 +17,9 @@ ruby-jsonapi-renderer (0.1.3-2) UNRELEASED; urgency=medium
     + ruby-jsonapi-renderer: Add :all qualifier for ruby dependency.
   * Update watch file format version to 4.
   * Bump debhelper from old 12 to 13.
+  * New upstream release.
 
- -- Utkarsh Gupta <guptautkarsh2102@gmail.com>  Tue, 13 Aug 2019 05:50:36 +0530
+ -- Utkarsh Gupta <guptautkarsh2102@gmail.com>  Wed, 01 Jun 2022 10:10:12 -0000
 
 ruby-jsonapi-renderer (0.1.3-1) unstable; urgency=medium
 
diff --git a/jsonapi-renderer.gemspec b/jsonapi-renderer.gemspec
index fa1c608..eccdd6f 100644
--- a/jsonapi-renderer.gemspec
+++ b/jsonapi-renderer.gemspec
@@ -2,39 +2,39 @@
 # This file has been automatically generated by gem2tgz #
 #########################################################
 # -*- encoding: utf-8 -*-
-# stub: jsonapi-renderer 0.1.3 ruby lib
+# stub: jsonapi-renderer 0.2.2 ruby lib
 
 Gem::Specification.new do |s|
   s.name = "jsonapi-renderer".freeze
-  s.version = "0.1.3"
+  s.version = "0.2.2"
 
   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.date = "2019-06-13"
   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.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/cached_resources_processor.rb".freeze, "lib/jsonapi/renderer/document.rb".freeze, "lib/jsonapi/renderer/resources_processor.rb".freeze, "lib/jsonapi/renderer/simple_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.rubygems_version = "2.5.2.1".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<codecov>.freeze, ["~> 0.1"])
       s.add_development_dependency(%q<rake>.freeze, ["~> 11.3"])
       s.add_development_dependency(%q<rspec>.freeze, ["~> 3.5"])
+      s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
     else
-      s.add_dependency(%q<codecov>.freeze, ["~> 0.1"])
       s.add_dependency(%q<rake>.freeze, ["~> 11.3"])
       s.add_dependency(%q<rspec>.freeze, ["~> 3.5"])
+      s.add_dependency(%q<simplecov>.freeze, [">= 0"])
     end
   else
-    s.add_dependency(%q<codecov>.freeze, ["~> 0.1"])
     s.add_dependency(%q<rake>.freeze, ["~> 11.3"])
     s.add_dependency(%q<rspec>.freeze, ["~> 3.5"])
+    s.add_dependency(%q<simplecov>.freeze, [">= 0"])
   end
 end
diff --git a/lib/jsonapi/include_directive.rb b/lib/jsonapi/include_directive.rb
index 32ec4f7..142633d 100644
--- a/lib/jsonapi/include_directive.rb
+++ b/lib/jsonapi/include_directive.rb
@@ -16,6 +16,8 @@ module JSONAPI
     def initialize(include_args, options = {})
       include_hash = Parser.parse_include_args(include_args)
       @hash = include_hash.each_with_object({}) do |(key, value), hash|
+        raise InvalidKey, key unless valid?(key)
+
         hash[key] = self.class.new(value, options)
       end
       @options = options
@@ -68,5 +70,18 @@ module JSONAPI
 
       string_array.join(',')
     end
+
+    class InvalidKey < StandardError; end
+
+    private
+
+    def valid?(key)
+      key.match(valid_json_key_name_regex)
+    end
+
+    def valid_json_key_name_regex
+      # https://jsonapi.org/format/#document-member-names
+      /^(?![\s\-_])[\u0080-\u10FFA-Za-z0-9* _-]+(?<![\s\-_])$/
+    end
   end
 end
diff --git a/lib/jsonapi/renderer/cached_resources_processor.rb b/lib/jsonapi/renderer/cached_resources_processor.rb
new file mode 100644
index 0000000..1518efc
--- /dev/null
+++ b/lib/jsonapi/renderer/cached_resources_processor.rb
@@ -0,0 +1,47 @@
+require 'jsonapi/renderer/resources_processor'
+
+module JSONAPI
+  class Renderer
+    # @private
+    class CachedResourcesProcessor < ResourcesProcessor
+      class JSONString < String
+        def to_json(*)
+          self
+        end
+      end
+
+      def initialize(cache)
+        @cache = cache
+      end
+
+      def process_resources
+        # NOTE(beauby): This is necessary for cache keys consistency.
+        @include_rels = @include_rels.each_with_object({}) do |(k, v), h|
+          h[k] = v.to_a.sort!
+        end
+
+        [@primary, @included].each do |resources|
+          cache_hash = cache_key_map(resources)
+          processed_resources = @cache.fetch_multi(*cache_hash.keys) do |key|
+            res, include, fields = cache_hash[key]
+            json = res.as_jsonapi(include: include, fields: fields).to_json
+
+            JSONString.new(json)
+          end
+
+          resources.replace(processed_resources.values)
+        end
+      end
+
+      def cache_key_map(resources)
+        resources.each_with_object({}) do |res, h|
+          ri = [res.jsonapi_type, res.jsonapi_id]
+          include_dir = @include_rels[ri]
+          fields = @fields[ri.first.to_sym]
+          h[res.jsonapi_cache_key(include: include_dir, fields: fields)] =
+            [res, include_dir, fields]
+        end
+      end
+    end
+  end
+end
diff --git a/lib/jsonapi/renderer/document.rb b/lib/jsonapi/renderer/document.rb
index 6b9d4ad..21a5ae7 100644
--- a/lib/jsonapi/renderer/document.rb
+++ b/lib/jsonapi/renderer/document.rb
@@ -1,17 +1,21 @@
 require 'jsonapi/include_directive'
-require 'jsonapi/renderer/resources_processor'
+require 'jsonapi/renderer/simple_resources_processor'
+require 'jsonapi/renderer/cached_resources_processor'
 
 module JSONAPI
   class Renderer
+    # @private
     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] || {})
+        @fields  = _canonize_fields(params[:fields] || {})
         @jsonapi = params[:jsonapi]
         @include = JSONAPI::IncludeDirective.new(params[:include] || {})
+        @relationship = params[:relationship]
+        @cache = params[:cache]
       end
 
       def to_hash
@@ -21,37 +25,75 @@ module JSONAPI
 
       private
 
+      # rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength
+      # rubocop:disable Metrics/CyclomaticComplexity
       def document_hash
         {}.tap do |hash|
-          if @data != :no_data
+          if @relationship
+            hash.merge!(relationship_hash)
+          elsif @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?
+          hash[:links]   = @links   if @links.any?
+          hash[:meta]    = @meta    unless @meta.nil?
+          hash[:jsonapi] = @jsonapi unless @jsonapi.nil?
         end
       end
+      # rubocop:enable Metrics/PerceivedComplexity, Metrics/MethodLength
+      # rubocop:enable Metrics/CyclomaticComplexity
 
       def data_hash
         primary, included =
-          ResourcesProcessor.new(Array(@data), @include, @fields).process
+          resources_processor.process(Array(@data), @include, @fields)
         {}.tap do |hash|
           hash[:data]     = @data.respond_to?(:to_ary) ? primary : primary[0]
           hash[:included] = included if included.any?
         end
       end
 
+      # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
+      def relationship_hash
+        rel_name = @relationship.to_sym
+        data = @data.jsonapi_related([rel_name])[rel_name]
+        included =
+          if @include.key?(rel_name)
+            resources_processor.process(data, @include[rel_name], @fields)
+                               .flatten!
+          else
+            []
+          end
+
+        res = @data.as_jsonapi(fields: [rel_name], include: [rel_name])
+        rel = res[:relationships][rel_name]
+        @links = rel[:links].merge!(@links)
+        @meta ||= rel[:meta]
+
+        {}.tap do |hash|
+          hash[:data]     = rel[:data]
+          hash[:included] = included if included.any?
+        end
+      end
+      # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
+
       def errors_hash
         {}.tap do |hash|
-          hash[:errors] = @errors.map(&:as_jsonapi)
+          hash[:errors] = @errors.flat_map(&:as_jsonapi)
+        end
+      end
+
+      def resources_processor
+        if @cache
+          CachedResourcesProcessor.new(@cache)
+        else
+          SimpleResourcesProcessor.new
         end
       end
 
-      def _symbolize_fields(fields)
+      def _canonize_fields(fields)
         fields.each_with_object({}) do |(k, v), h|
-          h[k.to_sym] = v.map(&:to_sym)
+          h[k.to_sym] = v.map(&:to_sym).sort!
         end
       end
     end
diff --git a/lib/jsonapi/renderer/resources_processor.rb b/lib/jsonapi/renderer/resources_processor.rb
index 9e6be30..dd37110 100644
--- a/lib/jsonapi/renderer/resources_processor.rb
+++ b/lib/jsonapi/renderer/resources_processor.rb
@@ -2,14 +2,13 @@ require 'set'
 
 module JSONAPI
   class Renderer
+    # @private
     class ResourcesProcessor
-      def initialize(resources, include, fields)
+      def process(resources, include, fields)
         @resources = resources
         @include   = include
         @fields    = fields
-      end
 
-      def process
         traverse_resources
         process_resources
 
@@ -73,14 +72,7 @@ module JSONAPI
       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
+        raise 'Not implemented'
       end
     end
   end
diff --git a/lib/jsonapi/renderer/simple_resources_processor.rb b/lib/jsonapi/renderer/simple_resources_processor.rb
new file mode 100644
index 0000000..cd2bc0c
--- /dev/null
+++ b/lib/jsonapi/renderer/simple_resources_processor.rb
@@ -0,0 +1,19 @@
+require 'jsonapi/renderer/resources_processor'
+
+module JSONAPI
+  class Renderer
+    # @api private
+    class SimpleResourcesProcessor < ResourcesProcessor
+      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

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/jsonapi/renderer/cached_resources_processor.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/jsonapi/renderer/simple_resources_processor.rb
-rw-r--r--  root/root   /usr/share/doc/ruby-jsonapi-renderer/README.md.gz
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/jsonapi-renderer-0.2.2.gemspec

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/doc/ruby-jsonapi-renderer/README.md
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/jsonapi-renderer-0.1.3.gemspec

No differences were encountered in the control files

More details

Full run details