lib/alba/resource.rb in alba-1.2.0 vs lib/alba/resource.rb in alba-1.3.0
- old
+ new
@@ -1,16 +1,21 @@
require_relative 'one'
require_relative 'many'
+require_relative 'key_transform_factory'
+require_relative 'typed_attribute'
module Alba
# This module represents what should be serialized
module Resource
# @!parse include InstanceMethods
# @!parse extend ClassMethods
- DSLS = {_attributes: {}, _key: nil, _transform_keys: nil, _transforming_root_key: false, _on_error: nil}.freeze
+ DSLS = {_attributes: {}, _key: nil, _transform_key_function: nil, _transforming_root_key: false, _on_error: nil}.freeze
private_constant :DSLS
+ WITHIN_DEFAULT = Object.new.freeze
+ private_constant :WITHIN_DEFAULT
+
# @private
def self.included(base)
super
base.class_eval do
# Initialize
@@ -27,11 +32,11 @@
attr_reader :object, :params
# @param object [Object] the object to be serialized
# @param params [Hash] user-given Hash for arbitrary data
# @param within [Hash] determines what associations to be serialized. If not set, it serializes all associations.
- def initialize(object, params: {}, within: true)
+ def initialize(object, params: {}, within: WITHIN_DEFAULT)
@object = object
@params = params.freeze
@within = within
DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
end
@@ -58,46 +63,59 @@
# @return [String]
def _key
return @_key.to_s unless @_key == true && Alba.inferring
- resource_name = self.class.name.demodulize.delete_suffix('Resource').underscore
- key = collection? ? resource_name.pluralize : resource_name
- transforming_root_key = @_transforming_root_key.nil? ? Alba.transforming_root_key : @_transforming_root_key
- transforming_root_key ? transform_key(key) : key
+ transforming_root_key? ? transform_key(key_from_resource_name) : key_from_resource_name
end
+ def key_from_resource_name
+ collection? ? resource_name.pluralize : resource_name
+ end
+
+ def resource_name
+ self.class.name.demodulize.delete_suffix('Resource').underscore
+ end
+
+ def transforming_root_key?
+ @_transforming_root_key.nil? ? Alba.transforming_root_key : @_transforming_root_key
+ end
+
def converter
lambda do |object|
arrays = @_attributes.map do |key, attribute|
- key = transform_key(key)
- if attribute.is_a?(Array) # Conditional
- conditional_attribute(object, key, attribute)
- else
- [key, fetch_attribute(object, attribute)]
- end
+ key_and_attribute_body_from(object, key, attribute)
rescue ::Alba::Error, FrozenError, TypeError
raise
rescue StandardError => e
handle_error(e, object, key, attribute)
end
arrays.reject(&:empty?).to_h
end
end
+ def key_and_attribute_body_from(object, key, attribute)
+ key = transform_key(key)
+ if attribute.is_a?(Array) # Conditional
+ conditional_attribute(object, key, attribute)
+ else
+ [key, fetch_attribute(object, attribute)]
+ end
+ end
+
def conditional_attribute(object, key, attribute)
condition = attribute.last
arity = condition.arity
- return [] if arity <= 1 && !condition.call(object)
+ return [] if arity <= 1 && !instance_exec(object, &condition)
fetched_attribute = fetch_attribute(object, attribute.first)
attr = if attribute.first.is_a?(Alba::Association)
attribute.first.object
else
fetched_attribute
end
- return [] if arity >= 2 && !condition.call(object, attr)
+ return [] if arity >= 2 && !instance_exec(object, attr, &condition)
[key, fetched_attribute]
end
def handle_error(error, object, key, attribute)
@@ -116,87 +134,44 @@
end
end
# Override this method to supply custom key transform method
def transform_key(key)
- return key unless @_transform_keys
+ return key if @_transform_key_function.nil?
- require_relative 'key_transformer'
- KeyTransformer.transform(key, @_transform_keys)
+ @_transform_key_function.call(key.to_s)
end
def fetch_attribute(object, attribute)
case attribute
when Symbol
object.public_send attribute
when Proc
instance_exec(object, &attribute)
when Alba::One, Alba::Many
- within = check_within
+ within = check_within(attribute.name.to_sym)
return unless within
attribute.to_hash(object, params: params, within: within)
- when Hash # Typed Attribute
- typed_attribute(object, attribute)
+ when TypedAttribute
+ attribute.value(object)
else
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
end
end
- def typed_attribute(object, hash)
- attr_name = hash[:attr_name]
- type = hash[:type]
- type_converter = hash[:type_converter]
- value, result = type_check(object, attr_name, type)
- return value if result
- raise TypeError if !result && !type_converter
-
- type_converter = type_converter_for(type) if type_converter == true
- type_converter.call(value)
- rescue TypeError
- raise TypeError, "Attribute #{attr_name} is expected to be #{type} but actually #{value.nil? ? 'nil' : value.class.name}."
- end
-
- def type_check(object, attr_name, type)
- value = object.public_send(attr_name)
- type_correct = case type
- when :String, ->(klass) { klass == String }
- value.is_a?(String)
- when :Integer, ->(klass) { klass == Integer }
- value.is_a?(Integer)
- when :Boolean
- [true, false].include?(attr_name)
- else
- raise Alba::UnsupportedType, "Unknown type: #{type}"
- end
- [value, type_correct]
- end
-
- def type_converter_for(type)
- case type
- when :String, ->(klass) { klass == String }
- ->(object) { object.to_s }
- when :Integer, ->(klass) { klass == Integer }
- ->(object) { Integer(object) }
- when :Boolean
- ->(object) { !!object }
- else
- raise Alba::UnsupportedType, "Unknown type: #{type}"
- end
- end
-
- def check_within
+ def check_within(association_name)
case @within
+ when WITHIN_DEFAULT # Default value, doesn't check within tree
+ WITHIN_DEFAULT
when Hash # Traverse within tree
- @within.fetch(_key.to_sym, nil)
+ @within.fetch(association_name, nil)
when Array # within tree ends with Array
- @within.find { |item| item.to_sym == _key.to_sym } # Check if at least one item in the array matches current resource
+ @within.find { |item| item.to_sym == association_name }
when Symbol # within tree could end with Symbol
- @within == _key.to_sym # Check if the symbol matches current resource
- when true # In this case, Alba serializes all associations.
- true
- when nil, false # In these cases, Alba stops serialization here.
+ @within == association_name
+ when nil, true, false # In these cases, Alba stops serialization here.
false
else
raise Alba::Error, "Unknown type for within option: #{@within.class}"
end
end
@@ -217,25 +192,36 @@
end
# Set multiple attributes at once
#
# @param attrs [Array<String, Symbol>]
- # @param options [Hash] option hash including `if` that is a condition to render these attributes
+ # @param if [Boolean] condition to decide if it should render these attributes
+ # @param attrs_with_types [Hash] attributes with name in its key and type and optional type converter in its value
def attributes(*attrs, if: nil, **attrs_with_types) # rubocop:disable Naming/MethodParameterName
if_value = binding.local_variable_get(:if)
+ assign_attributes(attrs, if_value)
+ assign_attributes_with_types(attrs_with_types, if_value)
+ end
+
+ def assign_attributes(attrs, if_value)
attrs.each do |attr_name|
attr = if_value ? [attr_name.to_sym, if_value] : attr_name.to_sym
@_attributes[attr_name.to_sym] = attr
end
+ end
+ private :assign_attributes
+
+ def assign_attributes_with_types(attrs_with_types, if_value)
attrs_with_types.each do |attr_name, type_and_converter|
attr_name = attr_name.to_sym
type, type_converter = type_and_converter
- typed_attr = {attr_name: attr_name, type: type, type_converter: type_converter}
+ typed_attr = TypedAttribute.new(name: attr_name, type: type, converter: type_converter)
attr = if_value ? [typed_attr, if_value] : typed_attr
@_attributes[attr_name] = attr
end
end
+ private :assign_attributes_with_types
# Set an attribute with the given block
#
# @param name [String, Symbol] key name
# @param options [Hash] option hash including `if` that is a condition to render
@@ -305,10 +291,10 @@
# Transform keys as specified type
#
# @param type [String, Symbol]
# @param root [Boolean] decides if root key also should be transformed
def transform_keys(type, root: nil)
- @_transform_keys = type.to_sym
+ @_transform_key_function = KeyTransformFactory.create(type.to_sym)
@_transforming_root_key = root
end
# Set error handler
#