lib/alba/resource.rb in alba-0.13.1 vs lib/alba/resource.rb in alba-1.0.0
- old
+ new
@@ -1,15 +1,14 @@
-require_relative 'serializer'
require_relative 'one'
require_relative 'many'
module Alba
# This module represents what should be serialized
module Resource
# @!parse include InstanceMethods
# @!parse extend ClassMethods
- DSLS = {_attributes: {}, _serializer: nil, _key: nil, _transform_keys: nil}.freeze
+ DSLS = {_attributes: {}, _key: nil, _transform_keys: nil, _on_error: nil}.freeze
private_constant :DSLS
# @private
def self.included(base)
super
@@ -23,96 +22,122 @@
base.extend ClassMethods
end
# Instance methods
module InstanceMethods
- attr_reader :object, :_key, :params
+ attr_reader :object, :params
# @param object [Object] the object to be serialized
# @param params [Hash] user-given Hash for arbitrary data
def initialize(object, params: {})
@object = object
@params = params.freeze
DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
end
- # Get serializer with `with` argument and serialize self with it
+ # Serialize object into JSON string
#
- # @param with [nil, Proc, Alba::Serializer] selializer
+ # @param key [Symbol]
# @return [String] serialized JSON string
- def serialize(with: nil)
- serializer = case with
- when nil
- @_serializer || empty_serializer
- when ->(obj) { obj.is_a?(Class) && obj <= Alba::Serializer }
- with
- when Proc
- inline_extended_serializer(with)
- else
- raise ArgumentError, 'Unexpected type for with, possible types are Class or Proc'
- end
- serializer.new(self).serialize
+ def serialize(key: nil)
+ key = key.nil? ? _key : key
+ hash = key && key != '' ? {key.to_s => serializable_hash} : serializable_hash
+ Alba.encoder.call(hash)
end
# A Hash for serialization
#
# @return [Hash]
def serializable_hash
collection? ? @object.map(&converter) : converter.call(@object)
end
alias to_hash serializable_hash
- # @return [Symbol]
- def key
- @_key || self.class.name.delete_suffix('Resource').downcase.gsub(/:{2}/, '_').to_sym
- end
-
private
- # rubocop:disable Style/MethodCalledOnDoEndBlock
+ # @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
+ end
+
def converter
- lambda do |resource|
- @_attributes.map do |key, attribute|
- [transform_key(key), fetch_attribute(resource, attribute)]
- end.to_h
+ 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
+ rescue ::Alba::Error, FrozenError
+ raise
+ rescue StandardError => e
+ handle_error(e, object, key, attribute)
+ end
+ arrays.reject(&:empty?).to_h
end
end
- # rubocop:enable Style/MethodCalledOnDoEndBlock
+ def conditional_attribute(object, key, attribute)
+ condition = attribute.last
+ arity = condition.arity
+ return [] if arity <= 1 && !condition.call(object)
+
+ 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)
+
+ [key, fetched_attribute]
+ end
+
+ def handle_error(error, object, key, attribute)
+ on_error = @_on_error || Alba._on_error
+ case on_error
+ when :raise, nil
+ raise
+ when :nullify
+ [key, nil]
+ when :ignore
+ []
+ when Proc
+ on_error.call(error, object, key, attribute, self.class)
+ else
+ raise ::Alba::Error, "Unknown on_error: #{on_error.inspect}"
+ end
+ end
+
# Override this method to supply custom key transform method
def transform_key(key)
return key unless @_transform_keys
require_relative 'key_transformer'
KeyTransformer.transform(key, @_transform_keys)
end
- def fetch_attribute(resource, attribute)
+ def fetch_attribute(object, attribute)
case attribute
when Symbol
- resource.public_send attribute
+ object.public_send attribute
when Proc
- instance_exec(resource, &attribute)
+ instance_exec(object, &attribute)
when Alba::One, Alba::Many
- attribute.to_hash(resource, params: params)
+ attribute.to_hash(object, params: params)
else
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
end
end
- def empty_serializer
- klass = Class.new
- klass.include Alba::Serializer
- klass
- end
-
- def inline_extended_serializer(with)
- klass = empty_serializer
- klass.class_eval(&with)
- klass
- end
-
def collection?
@object.is_a?(Enumerable)
end
end
@@ -127,65 +152,75 @@
end
# Set multiple attributes at once
#
# @param attrs [Array<String, Symbol>]
- def attributes(*attrs)
- attrs.each { |attr_name| @_attributes[attr_name.to_sym] = attr_name.to_sym }
+ # @param options [Hash] option hash including `if` that is a condition to render these attributes
+ def attributes(*attrs, **options)
+ attrs.each do |attr_name|
+ attr = options[:if] ? [attr_name.to_sym, options[:if]] : attr_name.to_sym
+ @_attributes[attr_name.to_sym] = attr
+ end
end
# 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
# @param block [Block] the block called during serialization
# @raise [ArgumentError] if block is absent
- def attribute(name, &block)
+ def attribute(name, **options, &block)
raise ArgumentError, 'No block given in attribute method' unless block
- @_attributes[name.to_sym] = block
+ @_attributes[name.to_sym] = options[:if] ? [block, options[:if]] : block
end
# Set One association
#
# @param name [String, Symbol]
# @param condition [Proc]
# @param resource [Class<Alba::Resource>]
# @param key [String, Symbol] used as key when given
+ # @param options [Hash] option hash including `if` that is a condition to render
# @param block [Block]
# @see Alba::One#initialize
- def one(name, condition = nil, resource: nil, key: nil, &block)
- @_attributes[key&.to_sym || name.to_sym] = One.new(name: name, condition: condition, resource: resource, &block)
+ def one(name, condition = nil, resource: nil, key: nil, **options, &block)
+ nesting = self.name&.rpartition('::')&.first
+ one = One.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
+ @_attributes[key&.to_sym || name.to_sym] = options[:if] ? [one, options[:if]] : one
end
alias has_one one
# Set Many association
#
# @param name [String, Symbol]
# @param condition [Proc]
# @param resource [Class<Alba::Resource>]
# @param key [String, Symbol] used as key when given
+ # @param options [Hash] option hash including `if` that is a condition to render
# @param block [Block]
# @see Alba::Many#initialize
- def many(name, condition = nil, resource: nil, key: nil, &block)
- @_attributes[key&.to_sym || name.to_sym] = Many.new(name: name, condition: condition, resource: resource, &block)
+ def many(name, condition = nil, resource: nil, key: nil, **options, &block)
+ nesting = self.name&.rpartition('::')&.first
+ many = Many.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
+ @_attributes[key&.to_sym || name.to_sym] = options[:if] ? [many, options[:if]] : many
end
alias has_many many
- # Set serializer for the resource
- #
- # @param name [Alba::Serializer]
- def serializer(name)
- @_serializer = name <= Alba::Serializer ? name : nil
- end
-
# Set key
#
# @param key [String, Symbol]
def key(key)
- @_key = key.to_sym
+ @_key = key.respond_to?(:to_sym) ? key.to_sym : key
end
+ # Set key to true
+ #
+ def key!
+ @_key = true
+ end
+
# Delete attributes
# Use this DSL in child class to ignore certain attributes
#
# @param attributes [Array<String, Symbol>]
def ignoring(*attributes)
@@ -197,9 +232,20 @@
# Transform keys as specified type
#
# @param type [String, Symbol]
def transform_keys(type)
@_transform_keys = type.to_sym
+ end
+
+ # Set error handler
+ #
+ # @param [Symbol] handler
+ # @param [Block]
+ def on_error(handler = nil, &block)
+ raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
+ raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
+
+ @_on_error = handler || block
end
end
end
end