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