lib/serega/plugins/context_metadata/context_metadata.rb
# frozen_string_literal: true
class Serega
module SeregaPlugins
#
# Plugin :context_metadata
#
# Depends on: `:root` plugin, that must be loaded first
#
# Allows to specify metadata to be added to serialized response.
#
# @example
# class UserSerializer < Serega
# plugin :root, root: :data
# plugin :context_metadata, context_metadata_key: :meta
# end
#
# UserSerializer.to_h(nil, meta: { version: '1.0.1' })
# # => {:data=>nil, :version=>"1.0.1"}
#
module ContextMetadata
# Default context metadata option name
DEFAULT_CONTEXT_METADATA_KEY = :meta
# @return [Symbol] Plugin name
def self.plugin_name
:context_metadata
end
# Checks requirements and loads additional plugins
#
# @param serializer_class [Class<Serega>] Current serializer class
# @param opts [Hash] Plugin options
#
# @return [void]
#
def self.before_load_plugin(serializer_class, **opts)
allowed_keys = %i[context_metadata_key]
opts.each_key do |key|
next if allowed_keys.include?(key)
raise SeregaError,
"Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
" - :context_metadata_key [Symbol] - The key name that must be used to add metadata. Default is :meta."
end
unless serializer_class.plugin_used?(:root)
raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded after the :root plugin. Please load the :root plugin first"
end
end
#
# Applies plugin code to specific serializer
#
# @param serializer_class [Class<Serega>] Current serializer class
# @param _opts [Hash] Plugin options
#
# @return [void]
#
def self.load_plugin(serializer_class, **_opts)
serializer_class.include(InstanceMethods)
serializer_class::SeregaConfig.include(ConfigInstanceMethods)
serializer_class::CheckSerializeParams.include(CheckSerializeParamsInstanceMethods)
end
#
# Adds config options and runs other callbacks after plugin was loaded
#
# @param serializer_class [Class<Serega>] Current serializer class
# @param opts [Hash] Plugin options
#
# @return [void]
#
def self.after_load_plugin(serializer_class, **opts)
config = serializer_class.config
meta_key = opts[:context_metadata_key] || DEFAULT_CONTEXT_METADATA_KEY
config.opts[:context_metadata] = {key: meta_key}
config.serialize_keys << meta_key
end
#
# Config for `context_metadata` plugin
#
class ContextMetadataConfig
# @return [Hash] context_metadata options
attr_reader :opts
#
# Initializes context_metadata config object
#
# @param opts [Hash] options
#
# @return [Serega::SeregaPlugins::ContextMetadata::ContextMetadataConfig]
def initialize(opts)
@opts = opts
end
# Key that should be used to define metadata
def key
opts.fetch(:key)
end
# Sets key that should be used to define metadata
#
# @param new_key [Symbol] New key
#
# @return [Symbol] New key
def key=(new_key)
opts[:key] = new_key
end
end
#
# Config class additional/patched instance methods
#
# @see Serega::SeregaConfig
#
module ConfigInstanceMethods
# @return [Serega::SeregaPlugins::ContextMetadata::ContextMetadataConfig] context_metadata config
def context_metadata
@context_metadata ||= ContextMetadataConfig.new(opts.fetch(:context_metadata))
end
end
#
# CheckSerializeParams class additional/patched instance methods
#
# @see Serega::SeregaValidations::CheckSerializeParams
#
module CheckSerializeParamsInstanceMethods
private
def check_opts
super
meta_key = self.class.serializer_class.config.context_metadata.key
SeregaValidations::Utils::CheckOptIsHash.call(opts, meta_key)
end
end
#
# Serega additional/patched instance methods
#
# @see Serega
#
module InstanceMethods
private
def serialize(object, opts)
result = super
return result unless result.is_a?(Hash) # return earlier if not a hash, so no root was added
root = build_root(object, opts)
return result unless root # return earlier when no root
add_context_metadata(result, opts)
result
end
def add_context_metadata(hash, opts)
context_metadata_key = self.class.config.context_metadata.key
return unless context_metadata_key
metadata = opts[context_metadata_key]
return unless metadata
deep_merge_context_metadata(hash, metadata)
end
def deep_merge_context_metadata(hash, metadata)
hash.merge!(metadata) do |_key, this_val, other_val|
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
deep_merge_context_metadata(this_val, other_val)
else
other_val
end
end
end
end
end
register_plugin(ContextMetadata.plugin_name, ContextMetadata)
end
end