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