lib/alba/resource.rb in alba-1.1.0 vs lib/alba/resource.rb in alba-1.2.0
- old
+ new
@@ -4,11 +4,11 @@
module Alba
# This module represents what should be serialized
module Resource
# @!parse include InstanceMethods
# @!parse extend ClassMethods
- DSLS = {_attributes: {}, _key: nil, _transform_keys: nil, _on_error: nil}.freeze
+ DSLS = {_attributes: {}, _key: nil, _transform_keys: nil, _transforming_root_key: false, _on_error: nil}.freeze
private_constant :DSLS
# @private
def self.included(base)
super
@@ -56,17 +56,16 @@
private
# @return [String]
def _key
- if @_key == true && Alba.inferring
- demodulized = ActiveSupport::Inflector.demodulize(self.class.name)
- meth = collection? ? :tableize : :singularize
- ActiveSupport::Inflector.public_send(meth, demodulized.delete_suffix('Resource').downcase)
- else
- @_key.to_s
- end
+ 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
end
def converter
lambda do |object|
arrays = @_attributes.map do |key, attribute|
@@ -74,11 +73,11 @@
if attribute.is_a?(Array) # Conditional
conditional_attribute(object, key, attribute)
else
[key, fetch_attribute(object, attribute)]
end
- rescue ::Alba::Error, FrozenError
+ rescue ::Alba::Error, FrozenError, TypeError
raise
rescue StandardError => e
handle_error(e, object, key, attribute)
end
arrays.reject(&:empty?).to_h
@@ -134,15 +133,59 @@
when Alba::One, Alba::Many
within = check_within
return unless within
attribute.to_hash(object, params: params, within: within)
+ when Hash # Typed Attribute
+ typed_attribute(object, attribute)
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
case @within
when Hash # Traverse within tree
@within.fetch(_key.to_sym, nil)
when Array # within tree ends with Array
@@ -175,15 +218,23 @@
# 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
- def attributes(*attrs, **options)
+ def attributes(*attrs, if: nil, **attrs_with_types) # rubocop:disable Naming/MethodParameterName
+ if_value = binding.local_variable_get(:if)
attrs.each do |attr_name|
- attr = options[:if] ? [attr_name.to_sym, options[:if]] : attr_name.to_sym
+ attr = if_value ? [attr_name.to_sym, if_value] : attr_name.to_sym
@_attributes[attr_name.to_sym] = attr
end
+ 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}
+ attr = if_value ? [typed_attr, if_value] : typed_attr
+ @_attributes[attr_name] = attr
+ end
end
# Set an attribute with the given block
#
# @param name [String, Symbol] key name
@@ -252,11 +303,13 @@
end
# Transform keys as specified type
#
# @param type [String, Symbol]
- def transform_keys(type)
+ # @param root [Boolean] decides if root key also should be transformed
+ def transform_keys(type, root: nil)
@_transform_keys = type.to_sym
+ @_transforming_root_key = root
end
# Set error handler
#
# @param [Symbol] handler