joyful_jsonapi 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +201 -0
- data/README.md +603 -0
- data/lib/extensions/has_one.rb +18 -0
- data/lib/generators/serializer/USAGE +8 -0
- data/lib/generators/serializer/serializer_generator.rb +19 -0
- data/lib/generators/serializer/templates/serializer.rb.tt +6 -0
- data/lib/joyful_jsonapi.rb +11 -0
- data/lib/joyful_jsonapi/attribute.rb +29 -0
- data/lib/joyful_jsonapi/error_serializer.rb +41 -0
- data/lib/joyful_jsonapi/instrumentation.rb +2 -0
- data/lib/joyful_jsonapi/instrumentation/serializable_hash.rb +15 -0
- data/lib/joyful_jsonapi/instrumentation/serialized_json.rb +15 -0
- data/lib/joyful_jsonapi/instrumentation/skylight.rb +2 -0
- data/lib/joyful_jsonapi/instrumentation/skylight/normalizers/base.rb +7 -0
- data/lib/joyful_jsonapi/instrumentation/skylight/normalizers/serializable_hash.rb +22 -0
- data/lib/joyful_jsonapi/instrumentation/skylight/normalizers/serialized_json.rb +22 -0
- data/lib/joyful_jsonapi/link.rb +18 -0
- data/lib/joyful_jsonapi/multi_to_json.rb +100 -0
- data/lib/joyful_jsonapi/object_serializer.rb +314 -0
- data/lib/joyful_jsonapi/railtie.rb +11 -0
- data/lib/joyful_jsonapi/relationship.rb +120 -0
- data/lib/joyful_jsonapi/serialization_core.rb +156 -0
- data/lib/joyful_jsonapi/version.rb +3 -0
- metadata +254 -0
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ::ActiveRecord::Associations::Builder::HasOne.class_eval do
         | 
| 4 | 
            +
              # Based on
         | 
| 5 | 
            +
              # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/collection_association.rb#L50
         | 
| 6 | 
            +
              # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/singular_association.rb#L11
         | 
| 7 | 
            +
              def self.define_accessors(mixin, reflection)
         | 
| 8 | 
            +
                super
         | 
| 9 | 
            +
                name = reflection.name
         | 
| 10 | 
            +
                mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
         | 
| 11 | 
            +
                  def #{name}_id
         | 
| 12 | 
            +
                    # if an attribute is already defined with this methods name we should just use it
         | 
| 13 | 
            +
                    return read_attribute(__method__) if has_attribute?(__method__)
         | 
| 14 | 
            +
                    association(:#{name}).reader.try(:id)
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                CODE
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'rails/generators/base'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class SerializerGenerator < Rails::Generators::NamedBase
         | 
| 6 | 
            +
              source_root File.expand_path('templates', __dir__)
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              argument :attributes, type: :array, default: [], banner: 'field field'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def create_serializer_file
         | 
| 11 | 
            +
                template 'serializer.rb.tt', File.join('app', 'serializers', class_path, "#{file_name}_serializer.rb")
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              private
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def attributes_names
         | 
| 17 | 
            +
                  attributes.map { |a| a.name.to_sym.inspect }
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module JoyfulJsonapi
         | 
| 4 | 
            +
              require 'joyful_jsonapi/object_serializer'
         | 
| 5 | 
            +
              require 'joyful_jsonapi/error_serializer'
         | 
| 6 | 
            +
              if defined?(::Rails)
         | 
| 7 | 
            +
                require 'joyful_jsonapi/railtie'
         | 
| 8 | 
            +
              elsif defined?(::ActiveRecord)
         | 
| 9 | 
            +
                require 'extensions/has_one'
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            module JoyfulJsonapi
         | 
| 2 | 
            +
              class Attribute
         | 
| 3 | 
            +
                attr_reader :key, :method, :conditional_proc
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(key:, method:, options: {})
         | 
| 6 | 
            +
                  @key = key
         | 
| 7 | 
            +
                  @method = method
         | 
| 8 | 
            +
                  @conditional_proc = options[:if]
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def serialize(record, serialization_params, output_hash)
         | 
| 12 | 
            +
                  if include_attribute?(record, serialization_params)
         | 
| 13 | 
            +
                    output_hash[key] = if method.is_a?(Proc)
         | 
| 14 | 
            +
                      method.arity.abs == 1 ? method.call(record) : method.call(record, serialization_params)
         | 
| 15 | 
            +
                    else
         | 
| 16 | 
            +
                      record.public_send(method)
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def include_attribute?(record, serialization_params)
         | 
| 22 | 
            +
                  if conditional_proc.present?
         | 
| 23 | 
            +
                    conditional_proc.call(record, serialization_params)
         | 
| 24 | 
            +
                  else
         | 
| 25 | 
            +
                    true
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            module JoyfulJsonapi
         | 
| 3 | 
            +
              class ErrorSerializer
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(model)
         | 
| 6 | 
            +
                  @model = model
         | 
| 7 | 
            +
                  @model.valid?
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def serializable_hash
         | 
| 11 | 
            +
                  { errors: errors_for(@model) }
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def serialized_json(options = nil)
         | 
| 15 | 
            +
                  serializable_hash.to_json(options)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                private
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def errors_for(resource)
         | 
| 21 | 
            +
                  resource.errors.messages.flat_map do |field, errors|
         | 
| 22 | 
            +
                    build_hashes_for(field, errors)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def build_hashes_for(field, errors)
         | 
| 27 | 
            +
                  errors.map do |error_message|
         | 
| 28 | 
            +
                    build_hash_for(field, error_message)
         | 
| 29 | 
            +
                  end.flatten
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def build_hash_for(field, error_message)
         | 
| 33 | 
            +
                  {}.tap do |hash|
         | 
| 34 | 
            +
                    hash[:status] = "422"
         | 
| 35 | 
            +
                    hash[:source] = { pointer: "/data/attributes/#{field}" }
         | 
| 36 | 
            +
                    hash[:detail] = error_message
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            require 'active_support/notifications'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module JoyfulJsonapi
         | 
| 4 | 
            +
              module ObjectSerializer
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                alias_method :serializable_hash_without_instrumentation, :serializable_hash
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def serializable_hash
         | 
| 9 | 
            +
                  ActiveSupport::Notifications.instrument(SERIALIZABLE_HASH_NOTIFICATION, { name: self.class.name }) do
         | 
| 10 | 
            +
                    serializable_hash_without_instrumentation
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            require 'active_support/notifications'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module JoyfulJsonapi
         | 
| 4 | 
            +
              module ObjectSerializer
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                alias_method :serialized_json_without_instrumentation, :serialized_json
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def serialized_json
         | 
| 9 | 
            +
                  ActiveSupport::Notifications.instrument(SERIALIZED_JSON_NOTIFICATION, { name: self.class.name }) do
         | 
| 10 | 
            +
                    serialized_json_without_instrumentation
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            require 'joyful_jsonapi/instrumentation/skylight/normalizers/base'
         | 
| 2 | 
            +
            require 'joyful_jsonapi/instrumentation/serializable_hash'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module JoyfulJsonapi
         | 
| 5 | 
            +
              module Instrumentation
         | 
| 6 | 
            +
                module Skylight
         | 
| 7 | 
            +
                  module Normalizers
         | 
| 8 | 
            +
                    class SerializableHash < SKYLIGHT_NORMALIZER_BASE_CLASS
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                      register JoyfulJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      CAT = "view.#{JoyfulJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION}".freeze
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                      def normalize(trace, name, payload)
         | 
| 15 | 
            +
                        [ CAT, payload[:name], nil ]
         | 
| 16 | 
            +
                      end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            require 'joyful_jsonapi/instrumentation/skylight/normalizers/base'
         | 
| 2 | 
            +
            require 'joyful_jsonapi/instrumentation/serializable_hash'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module JoyfulJsonapi
         | 
| 5 | 
            +
              module Instrumentation
         | 
| 6 | 
            +
                module Skylight
         | 
| 7 | 
            +
                  module Normalizers
         | 
| 8 | 
            +
                    class SerializedJson < SKYLIGHT_NORMALIZER_BASE_CLASS
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                      register JoyfulJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      CAT = "view.#{JoyfulJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION}".freeze
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                      def normalize(trace, name, payload)
         | 
| 15 | 
            +
                        [ CAT, payload[:name], nil ]
         | 
| 16 | 
            +
                      end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            module JoyfulJsonapi
         | 
| 2 | 
            +
              class Link
         | 
| 3 | 
            +
                attr_reader :key, :method
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(key:, method:)
         | 
| 6 | 
            +
                  @key = key
         | 
| 7 | 
            +
                  @method = method
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def serialize(record, serialization_params, output_hash)
         | 
| 11 | 
            +
                  output_hash[key] = if method.is_a?(Proc)
         | 
| 12 | 
            +
                    method.arity == 1 ? method.call(record) : method.call(record, serialization_params)
         | 
| 13 | 
            +
                  else
         | 
| 14 | 
            +
                    record.public_send(method)
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,100 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'logger'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # Usage:
         | 
| 6 | 
            +
            #   class Movie
         | 
| 7 | 
            +
            #     def to_json(payload)
         | 
| 8 | 
            +
            #       JoyfulJsonapi::MultiToJson.to_json(payload)
         | 
| 9 | 
            +
            #     end
         | 
| 10 | 
            +
            #   end
         | 
| 11 | 
            +
            module JoyfulJsonapi
         | 
| 12 | 
            +
              module MultiToJson
         | 
| 13 | 
            +
                # Result object pattern is from https://johnnunemaker.com/resilience-in-ruby/
         | 
| 14 | 
            +
                # e.g. https://github.com/github/github-ds/blob/fbda5389711edfb4c10b6c6bad19311dfcb1bac1/lib/github/result.rb
         | 
| 15 | 
            +
                class Result
         | 
| 16 | 
            +
                  def initialize(*rescued_exceptions)
         | 
| 17 | 
            +
                    @rescued_exceptions = if rescued_exceptions.empty?
         | 
| 18 | 
            +
                      [StandardError]
         | 
| 19 | 
            +
                    else
         | 
| 20 | 
            +
                      rescued_exceptions
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    @value = yield
         | 
| 24 | 
            +
                    @error = nil
         | 
| 25 | 
            +
                  rescue *rescued_exceptions => e
         | 
| 26 | 
            +
                    @error = e
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def ok?
         | 
| 30 | 
            +
                    @error.nil?
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def value!
         | 
| 34 | 
            +
                    if ok?
         | 
| 35 | 
            +
                      @value
         | 
| 36 | 
            +
                    else
         | 
| 37 | 
            +
                      raise @error
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def rescue
         | 
| 42 | 
            +
                    return self if ok?
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    Result.new(*@rescued_exceptions) { yield(@error) }
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def self.logger(device=nil)
         | 
| 49 | 
            +
                  return @logger = Logger.new(device) if device
         | 
| 50 | 
            +
                  @logger ||= Logger.new(IO::NULL)
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                # Encoder-compatible with default MultiJSON adapters and defaults
         | 
| 54 | 
            +
                def self.to_json_method
         | 
| 55 | 
            +
                  encode_method = String.new(%(def _fast_to_json(object)\n ))
         | 
| 56 | 
            +
                  encode_method << Result.new(LoadError) {
         | 
| 57 | 
            +
                    require 'oj'
         | 
| 58 | 
            +
                    %(::Oj.dump(object, mode: :compat, time_format: :ruby, use_to_json: true))
         | 
| 59 | 
            +
                  }.rescue {
         | 
| 60 | 
            +
                    require 'yajl'
         | 
| 61 | 
            +
                    %(::Yajl::Encoder.encode(object))
         | 
| 62 | 
            +
                  }.rescue {
         | 
| 63 | 
            +
                    require 'jrjackson' unless defined?(::JrJackson)
         | 
| 64 | 
            +
                    %(::JrJackson::Json.dump(object))
         | 
| 65 | 
            +
                  }.rescue {
         | 
| 66 | 
            +
                    require 'json'
         | 
| 67 | 
            +
                    %(JSON.fast_generate(object, create_additions: false, quirks_mode: true))
         | 
| 68 | 
            +
                  }.rescue {
         | 
| 69 | 
            +
                    require 'gson'
         | 
| 70 | 
            +
                    %(::Gson::Encoder.new({}).encode(object))
         | 
| 71 | 
            +
                  }.rescue {
         | 
| 72 | 
            +
                    require 'active_support/json/encoding'
         | 
| 73 | 
            +
                    %(::ActiveSupport::JSON.encode(object))
         | 
| 74 | 
            +
                  }.rescue {
         | 
| 75 | 
            +
                    warn "No JSON encoder found. Falling back to `object.to_json`"
         | 
| 76 | 
            +
                    %(object.to_json)
         | 
| 77 | 
            +
                  }.value!
         | 
| 78 | 
            +
                  encode_method << "\nend"
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def self.to_json(object)
         | 
| 82 | 
            +
                  _fast_to_json(object)
         | 
| 83 | 
            +
                rescue NameError
         | 
| 84 | 
            +
                  define_to_json(JoyfulJsonapi::MultiToJson)
         | 
| 85 | 
            +
                  _fast_to_json(object)
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def self.define_to_json(receiver)
         | 
| 89 | 
            +
                  cl = caller_locations[0]
         | 
| 90 | 
            +
                  method_body = to_json_method
         | 
| 91 | 
            +
                  logger.debug { "Defining #{receiver}._fast_to_json as #{method_body.inspect}" }
         | 
| 92 | 
            +
                  receiver.instance_eval method_body, cl.absolute_path, cl.lineno
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def self.reset_to_json!
         | 
| 96 | 
            +
                  undef :_fast_to_json if method_defined?(:_fast_to_json)
         | 
| 97 | 
            +
                  logger.debug { "Undefining #{receiver}._fast_to_json" }
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
              end
         | 
| 100 | 
            +
            end
         | 
| @@ -0,0 +1,314 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'active_support/time'
         | 
| 4 | 
            +
            require 'active_support/json'
         | 
| 5 | 
            +
            require 'active_support/concern'
         | 
| 6 | 
            +
            require 'active_support/inflector'
         | 
| 7 | 
            +
            require 'active_support/core_ext/numeric/time'
         | 
| 8 | 
            +
            require 'joyful_jsonapi/attribute'
         | 
| 9 | 
            +
            require 'joyful_jsonapi/relationship'
         | 
| 10 | 
            +
            require 'joyful_jsonapi/link'
         | 
| 11 | 
            +
            require 'joyful_jsonapi/serialization_core'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            module JoyfulJsonapi
         | 
| 14 | 
            +
              module ObjectSerializer
         | 
| 15 | 
            +
                extend ActiveSupport::Concern
         | 
| 16 | 
            +
                include SerializationCore
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                SERIALIZABLE_HASH_NOTIFICATION = 'render.joyful_jsonapi.serializable_hash'
         | 
| 19 | 
            +
                SERIALIZED_JSON_NOTIFICATION = 'render.joyful_jsonapi.serialized_json'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                included do
         | 
| 22 | 
            +
                  # Set record_type based on the name of the serializer class
         | 
| 23 | 
            +
                  set_type(reflected_record_type) if reflected_record_type
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def initialize(resource, options = {})
         | 
| 27 | 
            +
                  process_options(options)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  @resource = resource
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def serializable_hash
         | 
| 33 | 
            +
                  return hash_for_collection if is_collection?(@resource, @is_collection)
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  hash_for_one_record
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
                alias_method :to_hash, :serializable_hash
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def hash_for_one_record
         | 
| 40 | 
            +
                  serializable_hash = { data: nil }
         | 
| 41 | 
            +
                  serializable_hash[:meta] = @meta if @meta.present?
         | 
| 42 | 
            +
                  serializable_hash[:links] = @links if @links.present?
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  return serializable_hash unless @resource
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.record_type.to_sym], @params)
         | 
| 47 | 
            +
                  serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
         | 
| 48 | 
            +
                  serializable_hash
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def hash_for_collection
         | 
| 52 | 
            +
                  serializable_hash = {}
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  data = []
         | 
| 55 | 
            +
                  included = []
         | 
| 56 | 
            +
                  fieldset = @fieldsets[self.class.record_type.to_sym]
         | 
| 57 | 
            +
                  @resource.each do |record|
         | 
| 58 | 
            +
                    data << self.class.record_hash(record, fieldset, @params)
         | 
| 59 | 
            +
                    included.concat self.class.get_included_records(record, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  serializable_hash[:data] = data
         | 
| 63 | 
            +
                  serializable_hash[:included] = included if @includes.present?
         | 
| 64 | 
            +
                  serializable_hash[:meta] = @meta if @meta.present?
         | 
| 65 | 
            +
                  serializable_hash[:links] = @links if @links.present?
         | 
| 66 | 
            +
                  serializable_hash
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def serialized_json
         | 
| 70 | 
            +
                  ActiveSupport::JSON.encode(serializable_hash)
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                private
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def process_options(options)
         | 
| 76 | 
            +
                  @fieldsets = deep_symbolize(options[:fields].presence || {})
         | 
| 77 | 
            +
                  @params = {}
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  return if options.blank?
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  @known_included_objects = {}
         | 
| 82 | 
            +
                  @meta = options[:meta]
         | 
| 83 | 
            +
                  @links = options[:links]
         | 
| 84 | 
            +
                  @is_collection = options[:is_collection]
         | 
| 85 | 
            +
                  @params = options[:params] || {}
         | 
| 86 | 
            +
                  raise ArgumentError.new("`params` option passed to serializer must be a hash") unless @params.is_a?(Hash)
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  if options[:include].present?
         | 
| 89 | 
            +
                    @includes = options[:include].delete_if(&:blank?).map(&:to_sym)
         | 
| 90 | 
            +
                    self.class.validate_includes!(@includes)
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                def deep_symbolize(collection)
         | 
| 95 | 
            +
                  if collection.is_a? Hash
         | 
| 96 | 
            +
                    Hash[collection.map do |k, v|
         | 
| 97 | 
            +
                      [k.to_sym, deep_symbolize(v)]
         | 
| 98 | 
            +
                    end]
         | 
| 99 | 
            +
                  elsif collection.is_a? Array
         | 
| 100 | 
            +
                    collection.map { |i| deep_symbolize(i) }
         | 
| 101 | 
            +
                  else
         | 
| 102 | 
            +
                    collection.to_sym
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                def is_collection?(resource, force_is_collection = nil)
         | 
| 107 | 
            +
                  return force_is_collection unless force_is_collection.nil?
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  resource.respond_to?(:size) && !resource.respond_to?(:each_pair)
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                class_methods do
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  def inherited(subclass)
         | 
| 115 | 
            +
                    super(subclass)
         | 
| 116 | 
            +
                    subclass.attributes_to_serialize = attributes_to_serialize.dup if attributes_to_serialize.present?
         | 
| 117 | 
            +
                    subclass.relationships_to_serialize = relationships_to_serialize.dup if relationships_to_serialize.present?
         | 
| 118 | 
            +
                    subclass.cachable_relationships_to_serialize = cachable_relationships_to_serialize.dup if cachable_relationships_to_serialize.present?
         | 
| 119 | 
            +
                    subclass.uncachable_relationships_to_serialize = uncachable_relationships_to_serialize.dup if uncachable_relationships_to_serialize.present?
         | 
| 120 | 
            +
                    subclass.transform_method = transform_method
         | 
| 121 | 
            +
                    subclass.cache_length = cache_length
         | 
| 122 | 
            +
                    subclass.race_condition_ttl = race_condition_ttl
         | 
| 123 | 
            +
                    subclass.data_links = data_links.dup if data_links.present?
         | 
| 124 | 
            +
                    subclass.cached = cached
         | 
| 125 | 
            +
                    subclass.set_type(subclass.reflected_record_type) if subclass.reflected_record_type
         | 
| 126 | 
            +
                    subclass.meta_to_serialize = meta_to_serialize
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  def reflected_record_type
         | 
| 130 | 
            +
                    return @reflected_record_type if defined?(@reflected_record_type)
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    @reflected_record_type ||= begin
         | 
| 133 | 
            +
                      if self.name.end_with?('Serializer')
         | 
| 134 | 
            +
                        self.name.split('::').last.chomp('Serializer').underscore.to_sym
         | 
| 135 | 
            +
                      end
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                  def set_key_transform(transform_name)
         | 
| 140 | 
            +
                    mapping = {
         | 
| 141 | 
            +
                      camel: :camelize,
         | 
| 142 | 
            +
                      camel_lower: [:camelize, :lower],
         | 
| 143 | 
            +
                      dash: :dasherize,
         | 
| 144 | 
            +
                      underscore: :underscore
         | 
| 145 | 
            +
                    }
         | 
| 146 | 
            +
                    self.transform_method = mapping[transform_name.to_sym]
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                    # ensure that the record type is correctly transformed
         | 
| 149 | 
            +
                    if record_type
         | 
| 150 | 
            +
                      set_type(record_type)
         | 
| 151 | 
            +
                    elsif reflected_record_type
         | 
| 152 | 
            +
                      set_type(reflected_record_type)
         | 
| 153 | 
            +
                    end
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                  def run_key_transform(input)
         | 
| 157 | 
            +
                    if self.transform_method.present?
         | 
| 158 | 
            +
                      input.to_s.send(*@transform_method).to_sym
         | 
| 159 | 
            +
                    else
         | 
| 160 | 
            +
                      input.to_sym
         | 
| 161 | 
            +
                    end
         | 
| 162 | 
            +
                  end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  def use_hyphen
         | 
| 165 | 
            +
                    warn('DEPRECATION WARNING: use_hyphen is deprecated and will be removed from joyful_jsonapi 2.0 use (set_key_transform :dash) instead')
         | 
| 166 | 
            +
                    set_key_transform :dash
         | 
| 167 | 
            +
                  end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                  def set_type(type_name)
         | 
| 170 | 
            +
                    self.record_type = run_key_transform(type_name)
         | 
| 171 | 
            +
                  end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                  def set_id(id_name = nil, &block)
         | 
| 174 | 
            +
                    self.record_id = block || id_name
         | 
| 175 | 
            +
                  end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                  def cache_options(cache_options)
         | 
| 178 | 
            +
                    self.cached = cache_options[:enabled] || false
         | 
| 179 | 
            +
                    self.cache_length = cache_options[:cache_length] || 5.minutes
         | 
| 180 | 
            +
                    self.race_condition_ttl = cache_options[:race_condition_ttl] || 5.seconds
         | 
| 181 | 
            +
                  end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                  def attributes(*attributes_list, &block)
         | 
| 184 | 
            +
                    attributes_list = attributes_list.first if attributes_list.first.class.is_a?(Array)
         | 
| 185 | 
            +
                    options = attributes_list.last.is_a?(Hash) ? attributes_list.pop : {}
         | 
| 186 | 
            +
                    self.attributes_to_serialize = {} if self.attributes_to_serialize.nil?
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                    attributes_list.each do |attr_name|
         | 
| 189 | 
            +
                      method_name = attr_name
         | 
| 190 | 
            +
                      key = run_key_transform(method_name)
         | 
| 191 | 
            +
                      attributes_to_serialize[key] = Attribute.new(
         | 
| 192 | 
            +
                        key: key,
         | 
| 193 | 
            +
                        method: block || method_name,
         | 
| 194 | 
            +
                        options: options
         | 
| 195 | 
            +
                      )
         | 
| 196 | 
            +
                    end
         | 
| 197 | 
            +
                  end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                  alias_method :attribute, :attributes
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                  def add_relationship(relationship)
         | 
| 202 | 
            +
                    self.relationships_to_serialize = {} if relationships_to_serialize.nil?
         | 
| 203 | 
            +
                    self.cachable_relationships_to_serialize = {} if cachable_relationships_to_serialize.nil?
         | 
| 204 | 
            +
                    self.uncachable_relationships_to_serialize = {} if uncachable_relationships_to_serialize.nil?
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                    if !relationship.cached
         | 
| 207 | 
            +
                      self.uncachable_relationships_to_serialize[relationship.name] = relationship
         | 
| 208 | 
            +
                    else
         | 
| 209 | 
            +
                      self.cachable_relationships_to_serialize[relationship.name] = relationship
         | 
| 210 | 
            +
                    end
         | 
| 211 | 
            +
                    self.relationships_to_serialize[relationship.name] = relationship
         | 
| 212 | 
            +
                  end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                  def has_many(relationship_name, options = {}, &block)
         | 
| 215 | 
            +
                    relationship = create_relationship(relationship_name, :has_many, options, block)
         | 
| 216 | 
            +
                    add_relationship(relationship)
         | 
| 217 | 
            +
                  end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                  def has_one(relationship_name, options = {}, &block)
         | 
| 220 | 
            +
                    relationship = create_relationship(relationship_name, :has_one, options, block)
         | 
| 221 | 
            +
                    add_relationship(relationship)
         | 
| 222 | 
            +
                  end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                  def belongs_to(relationship_name, options = {}, &block)
         | 
| 225 | 
            +
                    relationship = create_relationship(relationship_name, :belongs_to, options, block)
         | 
| 226 | 
            +
                    add_relationship(relationship)
         | 
| 227 | 
            +
                  end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                  def meta(&block)
         | 
| 230 | 
            +
                    self.meta_to_serialize = block
         | 
| 231 | 
            +
                  end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                  def create_relationship(base_key, relationship_type, options, block)
         | 
| 234 | 
            +
                    name = base_key.to_sym
         | 
| 235 | 
            +
                    if relationship_type == :has_many
         | 
| 236 | 
            +
                      base_serialization_key = base_key.to_s.singularize
         | 
| 237 | 
            +
                      base_key_sym = base_serialization_key.to_sym
         | 
| 238 | 
            +
                      id_postfix = '_ids'
         | 
| 239 | 
            +
                    else
         | 
| 240 | 
            +
                      base_serialization_key = base_key
         | 
| 241 | 
            +
                      base_key_sym = name
         | 
| 242 | 
            +
                      id_postfix = '_id'
         | 
| 243 | 
            +
                    end
         | 
| 244 | 
            +
                    Relationship.new(
         | 
| 245 | 
            +
                      key: options[:key] || run_key_transform(base_key),
         | 
| 246 | 
            +
                      name: name,
         | 
| 247 | 
            +
                      id_method_name: compute_id_method_name(
         | 
| 248 | 
            +
                        options[:id_method_name],
         | 
| 249 | 
            +
                        "#{base_serialization_key}#{id_postfix}".to_sym,
         | 
| 250 | 
            +
                        block
         | 
| 251 | 
            +
                      ),
         | 
| 252 | 
            +
                      record_type: options[:record_type] || run_key_transform(base_key_sym),
         | 
| 253 | 
            +
                      object_method_name: options[:object_method_name] || name,
         | 
| 254 | 
            +
                      object_block: block,
         | 
| 255 | 
            +
                      serializer: compute_serializer_name(options[:serializer] || base_key_sym),
         | 
| 256 | 
            +
                      relationship_type: relationship_type,
         | 
| 257 | 
            +
                      cached: options[:cached],
         | 
| 258 | 
            +
                      polymorphic: fetch_polymorphic_option(options),
         | 
| 259 | 
            +
                      conditional_proc: options[:if],
         | 
| 260 | 
            +
                      transform_method: @transform_method,
         | 
| 261 | 
            +
                      links: options[:links],
         | 
| 262 | 
            +
                      lazy_load_data: options[:lazy_load_data]
         | 
| 263 | 
            +
                    )
         | 
| 264 | 
            +
                  end
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                  def compute_id_method_name(custom_id_method_name, id_method_name_from_relationship, block)
         | 
| 267 | 
            +
                    if block.present?
         | 
| 268 | 
            +
                      custom_id_method_name || :id
         | 
| 269 | 
            +
                    else
         | 
| 270 | 
            +
                      custom_id_method_name || id_method_name_from_relationship
         | 
| 271 | 
            +
                    end
         | 
| 272 | 
            +
                  end
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                  def compute_serializer_name(serializer_key)
         | 
| 275 | 
            +
                    return serializer_key unless serializer_key.is_a? Symbol
         | 
| 276 | 
            +
                    namespace = self.name.gsub(/()?\w+Serializer$/, '')
         | 
| 277 | 
            +
                    serializer_name = serializer_key.to_s.classify + 'Serializer'
         | 
| 278 | 
            +
                    (namespace + serializer_name).to_sym
         | 
| 279 | 
            +
                  end
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                  def fetch_polymorphic_option(options)
         | 
| 282 | 
            +
                    option = options[:polymorphic]
         | 
| 283 | 
            +
                    return false unless option.present?
         | 
| 284 | 
            +
                    return option if option.respond_to? :keys
         | 
| 285 | 
            +
                    {}
         | 
| 286 | 
            +
                  end
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                  def link(link_name, link_method_name = nil, &block)
         | 
| 289 | 
            +
                    self.data_links = {} if self.data_links.nil?
         | 
| 290 | 
            +
                    link_method_name = link_name if link_method_name.nil?
         | 
| 291 | 
            +
                    key = run_key_transform(link_name)
         | 
| 292 | 
            +
             | 
| 293 | 
            +
                    self.data_links[key] = Link.new(
         | 
| 294 | 
            +
                      key: key,
         | 
| 295 | 
            +
                      method: block || link_method_name
         | 
| 296 | 
            +
                    )
         | 
| 297 | 
            +
                  end
         | 
| 298 | 
            +
             | 
| 299 | 
            +
                  def validate_includes!(includes)
         | 
| 300 | 
            +
                    return if includes.blank?
         | 
| 301 | 
            +
             | 
| 302 | 
            +
                    includes.detect do |include_item|
         | 
| 303 | 
            +
                      klass = self
         | 
| 304 | 
            +
                      parse_include_item(include_item).each do |parsed_include|
         | 
| 305 | 
            +
                        relationships_to_serialize = klass.relationships_to_serialize || {}
         | 
| 306 | 
            +
                        relationship_to_include = relationships_to_serialize[parsed_include]
         | 
| 307 | 
            +
                        raise ArgumentError, "#{parsed_include} is not specified as a relationship on #{klass.name}" unless relationship_to_include
         | 
| 308 | 
            +
                        klass = relationship_to_include.serializer.to_s.constantize unless relationship_to_include.polymorphic.is_a?(Hash)
         | 
| 309 | 
            +
                      end
         | 
| 310 | 
            +
                    end
         | 
| 311 | 
            +
                  end
         | 
| 312 | 
            +
                end
         | 
| 313 | 
            +
              end
         | 
| 314 | 
            +
            end
         |