lib/alba/resource.rb in alba-1.4.0 vs lib/alba/resource.rb in alba-1.5.0
- old
+ new
@@ -1,16 +1,17 @@
require_relative 'one'
require_relative 'many'
require_relative 'key_transform_factory'
require_relative 'typed_attribute'
+require_relative 'deprecation'
module Alba
# This module represents what should be serialized
module Resource
# @!parse include InstanceMethods
# @!parse extend ClassMethods
- DSLS = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_key_function: nil, _transforming_root_key: false, _on_error: nil}.freeze # rubocop:disable Layout/LineLength
+ DSLS = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_key_function: nil, _transforming_root_key: false, _on_error: nil, _on_nil: nil, _layout: nil}.freeze # rubocop:disable Layout/LineLength
private_constant :DSLS
WITHIN_DEFAULT = Object.new.freeze
private_constant :WITHIN_DEFAULT
@@ -46,20 +47,21 @@
# @param key [Symbol, nil, true] DEPRECATED, use root_key instead
# @param root_key [Symbol, nil, true]
# @param meta [Hash] metadata for this seialization
# @return [String] serialized JSON string
def serialize(key: nil, root_key: nil, meta: {})
- warn '`key` option to `serialize` method is deprecated, use `root_key` instead.' if key
+ Alba::Deprecation.warn '`key` option to `serialize` method is deprecated, use `root_key` instead.' if key
key = key.nil? && root_key.nil? ? fetch_key : root_key || key
hash = if key && key != ''
h = {key.to_s => serializable_hash}
hash_with_metadata(h, meta)
else
serializable_hash
end
- Alba.encoder.call(hash)
+ serialize_with(hash)
end
+ alias to_json serialize
# A Hash for serialization
#
# @return [Hash]
def serializable_hash
@@ -67,10 +69,29 @@
end
alias to_hash serializable_hash
private
+ attr_reader :serialized_json # Mainly for layout
+
+ def encode(hash)
+ Alba.encoder.call(hash)
+ end
+
+ def serialize_with(hash)
+ @serialized_json = encode(hash)
+ case @_layout
+ when String # file
+ ERB.new(File.read(@_layout)).result(binding)
+ when Proc # inline
+ inline = instance_eval(&@_layout)
+ inline.is_a?(Hash) ? encode(inline) : inline
+ else # no layout
+ @serialized_json
+ end
+ end
+
def hash_with_metadata(hash, meta)
base = @_meta ? instance_eval(&@_meta) : {}
metadata = base.merge(meta)
hash[:meta] = metadata unless metadata.empty?
hash
@@ -118,27 +139,42 @@
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)]
+ fetched_attribute = fetch_attribute(object, key, attribute)
+ [key, fetched_attribute]
end
end
def conditional_attribute(object, key, attribute)
condition = attribute.last
+ if condition.is_a?(Proc)
+ conditional_attribute_with_proc(object, key, attribute.first, condition)
+ else
+ conditional_attribute_with_symbol(object, key, attribute.first, condition)
+ end
+ end
+
+ def conditional_attribute_with_proc(object, key, attribute, condition)
arity = condition.arity
# We can return early to skip fetch_attribute
return [] if arity <= 1 && !instance_exec(object, &condition)
- fetched_attribute = fetch_attribute(object, attribute.first)
- attr = attribute.first.is_a?(Alba::Association) ? attribute.first.object : fetched_attribute
+ fetched_attribute = fetch_attribute(object, key, attribute)
+ attr = attribute.is_a?(Alba::Association) ? attribute.object : fetched_attribute
return [] if arity >= 2 && !instance_exec(object, attr, &condition)
[key, fetched_attribute]
end
+ def conditional_attribute_with_symbol(object, key, attribute, condition)
+ return [] unless __send__(condition)
+
+ [key, fetch_attribute(object, key, attribute)]
+ end
+
def handle_error(error, object, key, attribute)
on_error = @_on_error || Alba._on_error
case on_error
when :raise, nil then raise
when :nullify then [key, nil]
@@ -154,21 +190,26 @@
return key if @_transform_key_function.nil?
@_transform_key_function.call(key.to_s)
end
- def fetch_attribute(object, attribute)
- case attribute
- when Symbol then object.public_send attribute
- when Proc then instance_exec(object, &attribute)
- when Alba::One, Alba::Many then yield_if_within(attribute.name.to_sym) { |within| attribute.to_hash(object, params: params, within: within) }
- when TypedAttribute then attribute.value(object)
- else
- raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
- end
+ def fetch_attribute(object, key, attribute)
+ value = case attribute
+ when Symbol then object.public_send attribute
+ when Proc then instance_exec(object, &attribute)
+ when Alba::One, Alba::Many then yield_if_within(attribute.name.to_sym) { |within| attribute.to_hash(object, params: params, within: within) }
+ when TypedAttribute then attribute.value(object)
+ else
+ raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
+ end
+ value.nil? && nil_handler ? instance_exec(object, key, attribute, &nil_handler) : value
end
+ def nil_handler
+ @nil_handler ||= (@_on_nil || Alba._on_nil)
+ end
+
def yield_if_within(association_name)
within = check_within(association_name)
yield(within) if within
end
@@ -287,11 +328,11 @@
# Set key
#
# @param key [String, Symbol]
# @deprecated Use {#root_key} instead
def key(key)
- warn '[DEPRECATION] `key` is deprecated, use `root_key` instead.'
+ Alba::Deprecation.warn '[DEPRECATION] `key` is deprecated, use `root_key` instead.'
@_key = key.respond_to?(:to_sym) ? key.to_sym : key
end
# Set root key
#
@@ -305,11 +346,11 @@
# Set key to true
#
# @deprecated Use {#root_key!} instead
def key!
- warn '[DEPRECATION] `key!` is deprecated, use `root_key!` instead.'
+ Alba::Deprecation.warn '[DEPRECATION] `key!` is deprecated, use `root_key!` instead.'
@_key = true
@_key_for_collection = true
end
# Set root key to true
@@ -321,10 +362,18 @@
# Set metadata
def meta(&block)
@_meta = block
end
+ # Set layout
+ #
+ # @params file [String] name of the layout file
+ # @params inline [Proc] a proc returning JSON string or a Hash representing JSON
+ def layout(file: nil, inline: nil)
+ @_layout = file || inline
+ end
+
# Delete attributes
# Use this DSL in child class to ignore certain attributes
#
# @param attributes [Array<String, Symbol>]
def ignoring(*attributes)
@@ -350,9 +399,16 @@
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
+
+ # Set nil handler
+ #
+ # @param block [Block]
+ def on_nil(&block)
+ @_on_nil = block
end
# rubocop:enable Metrics/ParameterLists
end
end