module Virtus # Attribute placeholder used when type constant is passed as a string or symbol # # @private class PendingAttribute attr_reader :type, :options, :name # @api private def initialize(type, options) @type, @options = type.to_s, options @name = options[:name] end # @api private def finalize Attribute::Builder.call(determine_type, options).finalize end # @api private def finalized? false end # @api private def determine_type if type.include?('::') # TODO: wrap it up in Virtus.constantize and use feature-detection to # pick up either Inflecto or ActiveSupport, whateve is available if defined?(Inflecto) Inflecto.constantize(type) else raise NotImplementedError, 'Virtus needs inflecto gem to constantize namespaced constant names' end else Object.const_get(type) end end end # PendingAttribute # Extracts the actual type primitive from input type # # @private class TypeDefinition attr_reader :type, :primitive # @api private def initialize(type) @type = type initialize_primitive end # @api private def pending? @pending if defined?(@pending) end private # @api private def initialize_primitive @primitive = if type.instance_of?(String) || type.instance_of?(Symbol) if !type.to_s.include?('::') && Object.const_defined?(type) Object.const_get(type) elsif not Attribute::Builder.determine_type(type) @pending = true type else type end elsif not type.is_a?(Class) type.class else type end end end class Attribute # Builder is used to set up an attribute instance based on input type and options # # @private class Builder attr_reader :attribute, :options, :type_definition, :klass, :type # @api private def self.call(type, options = {}) type_definition = TypeDefinition.new(type) if type_definition.pending? PendingAttribute.new(type, options) else new(type_definition, options).attribute end end # @api private def self.determine_type(klass, default = nil) type = Attribute.determine_type(klass) if klass.is_a?(Class) type ||= if klass < Axiom::Types::Type determine_type(klass.primitive) elsif EmbeddedValue.handles?(klass) EmbeddedValue elsif klass < Enumerable && !(klass <= Range) Collection end end type || default end # @api private def initialize(type_definition, options) @type_definition = type_definition initialize_class initialize_type initialize_options(options) initialize_default_value initialize_coercer initialize_attribute end private # @api private def initialize_class @klass = self.class.determine_type(type_definition.primitive, Attribute) end # @api private def initialize_type @type = klass.build_type(type_definition) end # @api private def initialize_options(options) @options = klass.options.merge(:coerce => Virtus.coerce).update(options) klass.merge_options!(type, @options) determine_visibility end # @api private def initialize_default_value options.update(:default_value => DefaultValue.build(options[:default])) end # @api private def initialize_coercer options.update(:coercer => determine_coercer) end # @api private def initialize_attribute @attribute = klass.new(type, options) @attribute.extend(Accessor) if options[:name] @attribute.extend(Coercible) if options[:coerce] @attribute.extend(NullifyBlank) if options[:nullify_blank] @attribute.extend(Strict) if options[:strict] @attribute.extend(LazyDefault) if options[:lazy] @attribute.finalize if options[:finalize] end # @api private def determine_coercer options.fetch(:coercer) { klass.build_coercer(type, options) } end # @api private def determine_visibility default_accessor = options.fetch(:accessor) reader_visibility = options.fetch(:reader, default_accessor) writer_visibility = options.fetch(:writer, default_accessor) options.update(:reader => reader_visibility, :writer => writer_visibility) end end # class Builder end # class Attribute end # module Virtus