# frozen_string_literal: true
#
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#
module CinnamonSerial
# Class that allows an engineer to specify what to do about mapping a key for a serializer.
class Resolver
attr_accessor :as,
:blank,
# DEPRECATED: Please use false_alias instead.
:false,
:false_alias,
:for,
:manual,
:mask,
:mask_char,
:mask_len,
:method,
:null,
:percent,
:present,
:through,
:transform,
# DEPRECATED: Please use true_alias instead.
:true,
:true_alias
def initialize(options = {})
@option_keys = options.keys.map(&:to_s).to_set
options.each do |key, value|
raise ArgumentError, "Illegal option: #{key}" unless respond_to?(key)
send("#{key}=", value)
end
end
def resolve(presenter, key)
raise ArgumentError, 'Presenter is required' unless presenter
return if manual
# Get the value
value = resolve_value(presenter, key)
# Transform the value
value = resolve_transform(presenter, key, value)
value = resolve_alias(value)
value = resolve_as(presenter, value)
# Format the value
value = resolve_percent(value)
resolve_mask(value)
end
private
# (method) and (for/through) are mutually exlusive use-cases.
# Example: you would never use for and method.
def resolve_value(presenter, key)
# If you pass in something that is not true boolean value then use that as a method name
# to call on the presenter.
return presenter.send(key) if method.is_a?(TrueClass)
return presenter.send(method) if method.to_s.length.positive?
# User for/through
model_key = self.for || key
model = presenter.obj
Array(through).each do |association|
model = model.respond_to?(association) ? model.send(association) : nil
break unless model
end
model&.respond_to?(model_key) ? model.send(model_key) : nil
end
def resolve_transform(presenter, key, value)
return presenter.send(key, value) if transform.is_a?(TrueClass)
return presenter.send(transform, value) if Formatting.present?(transform)
value
end
def resolve_alias(value)
if @option_keys.include?('true_alias') && value.is_a?(TrueClass)
true_alias
# DEPRECATED: Please use true_alias instead.
elsif @option_keys.include?('true') && value.is_a?(TrueClass)
self.true
elsif @option_keys.include?('false_alias') && value.is_a?(FalseClass)
false_alias
# DEPRECATED: Please use false_alias instead.
elsif @option_keys.include?('false') && value.is_a?(FalseClass)
self.false
elsif @option_keys.include?('null') && value.nil?
null
elsif @option_keys.include?('blank') && Formatting.blank?(value)
blank
elsif @option_keys.include?('present') && Formatting.present?(value)
present
else
value
end
end
def resolve_mask(value)
mask ? Formatting.mask(value, mask_len || 4, mask_char || 'X') : value
end
def resolve_percent(value)
percent ? Formatting.percent(value) : value
end
def resolve_as(presenter, value)
return value unless as
return nil unless value
class_constant = as_class_constant
# If we already serialized this type, lets not do it again.
# This will prevent endless cycles / loops.
return nil if presenter.klasses.include?(class_constant.to_s)
# We do not want to create a hard dependency on ActiveRecord/Rails in this gem,
# but we can still create a soft dependency in case it was included as a peer.
value = value.to_a if value.class.name == 'ActiveRecord::Relation'
new_klasses = presenter.klasses + Set[class_constant.to_s]
if value.is_a?(Array)
value.map { |v| class_constant.new(v, presenter.opts, new_klasses) }
else
class_constant.new(value, presenter.opts, new_klasses)
end
end
def as_class_name
return nil unless as
non_constant_types = %w[String Symbol]
# If we have a peer dependency for ActiveSupport then lets use it.
if non_constant_types.include?(as.class.name) && as.to_s.respond_to?(:classify)
as.to_s.classify
elsif non_constant_types.include?(as.class.name)
as.to_s
else
as
end
end
def as_class_constant
return nil unless as
class_name = as_class_name
# If we have a peer dependency for ActiveSupport then lets use it.
if class_name.is_a?(String) && class_name.respond_to?(:constantize)
class_name.constantize
elsif class_name.is_a?(String)
Object.const_get(class_name)
else
class_name
end
end
end
end