lib/roxml/definition.rb in roxml-2.5.3 vs lib/roxml/definition.rb in roxml-3.1.0

- old
+ new

@@ -1,6 +1,6 @@ -require File.join(File.dirname(__FILE__), 'hash_definition') +require 'lib/roxml/hash_definition' class Module def bool_attr_reader(*attrs) attrs.each do |attr| define_method :"#{attr}?" do @@ -9,79 +9,80 @@ end end end module ROXML + class ContradictoryNamespaces < StandardError + end + class Definition # :nodoc: - attr_reader :name, :type, :wrapper, :hash, :blocks, :accessor, :to_xml + attr_reader :name, :type, :wrapper, :hash, :blocks, :accessor, :to_xml, :attr_name, :namespace bool_attr_reader :name_explicit, :array, :cdata, :required, :frozen - class << self - def silence_xml_name_warning? - @silence_xml_name_warning || (ROXML.const_defined?('SILENCE_XML_NAME_WARNING') && ROXML::SILENCE_XML_NAME_WARNING) - end + def initialize(sym, opts = {}, &block) + opts.assert_valid_keys(:from, :in, :as, :namespace, + :else, :required, :frozen, :cdata, :to_xml) + @default = opts.delete(:else) + @to_xml = opts.delete(:to_xml) + @name_explicit = opts.has_key?(:from) && opts[:from].is_a?(String) + @cdata = opts.delete(:cdata) + @required = opts.delete(:required) + @frozen = opts.delete(:frozen) + @wrapper = opts.delete(:in) + @namespace = opts.delete(:namespace) - def silence_xml_name_warning! - @silence_xml_name_warning = true - end - end + @accessor = sym.to_s + opts[:as] ||= + if @accessor.ends_with?('?') + :bool + elsif @accessor.ends_with?('_on') + Date + elsif @accessor.ends_with?('_at') + DateTime + end - def initialize(sym, *args, &block) - @accessor = sym - if @accessor.to_s.ends_with?('_on') - ActiveSupport::Deprecation.warn "In 3.0, attributes with names ending with _on will default to Date type, rather than :text" - end - if @accessor.to_s.ends_with?('_at') - ActiveSupport::Deprecation.warn "In 3.0, attributes with names ending with _at will default to DateTime type, rather than :text" - end - - opts = extract_options!(args) - opts[:as] ||= :bool if @accessor.to_s.ends_with?('?') - - @array = opts[:as].is_a?(Array) || extract_from_as(opts, :array, "Please use [] around your usual type declaration") + @array = opts[:as].is_a?(Array) @blocks = collect_blocks(block, opts[:as]) - if opts.has_key?(:readonly) - raise ArgumentError, "There is no 'readonly' option. You probably mean to use :frozen => true" + @type = extract_type(opts[:as]) + if @type.respond_to?(:roxml_tag_name) + opts[:from] ||= @type.roxml_tag_name end - @type = extract_type(args, opts) - if @type.respond_to?(:xml_name_without_deprecation?) && @type.xml_name_without_deprecation? - unless self.class.silence_xml_name_warning? - warn "WARNING: As of 2.3, a breaking change has been in the naming of sub-objects. " + - "ROXML now considers the xml_name of the sub-object before falling back to the accessor name of the parent. " + - "Use :from on the parent declaration to override this behavior. Set ROXML::SILENCE_XML_NAME_WARNING to avoid this message." - self.class.silence_xml_name_warning! - end - opts[:from] ||= @type.tag_name - end - if opts[:from] == :content opts[:from] = '.' elsif opts[:from] == :name opts[:from] = '*' elsif opts[:from] == :attr @type = :attr opts[:from] = nil + elsif opts[:from] == :name + opts[:from] = '*' elsif opts[:from].to_s.starts_with?('@') @type = :attr opts[:from].sub!('@', '') end - @name = (opts[:from] || variable_name).to_s + @attr_name = accessor.to_s.chomp('?') + @name = (opts[:from] || @attr_name).to_s @name = @name.singularize if hash? || array? if hash? && (hash.key.name? || hash.value.name?) @name = '*' end + raise ContradictoryNamespaces if @name.include?(':') && (@namespace.present? || @namespace == false) raise ArgumentError, "Can't specify both :else default and :required" if required? && @default end - def variable_name - accessor.to_s.chomp('?') + def instance_variable_name + :"@#{attr_name}" end + def setter + :"#{attr_name}=" + end + def hash if hash? @type.wrapper ||= name @type end @@ -125,39 +126,33 @@ end array ? results : results.first end - BLOCK_TO_FLOAT = lambda do |val| - all(val) do |v| - Float(v) unless v.blank? - end - end - - BLOCK_TO_INT = lambda do |val| - all(val) do |v| - Integer(v) unless v.blank? - end - end - def self.fetch_bool(value, default) value = value.to_s.downcase - if %w{true yes 1}.include? value + if %w{true yes 1 t}.include? value true - elsif %w{false no 0}.include? value + elsif %w{false no 0 f}.include? value false else default end end CORE_BLOCK_SHORTHANDS = { # Core Shorthands - :integer => BLOCK_TO_INT, # deprecated - Integer => BLOCK_TO_INT, - :float => BLOCK_TO_FLOAT, # deprecated - Float => BLOCK_TO_FLOAT, + Integer => lambda do |val| + all(val) do |v| + Integer(v) unless v.blank? + end + end, + Float => lambda do |val| + all(val) do |v| + Float(v) unless v.blank? + end + end, Fixnum => lambda do |val| all(val) do |v| v.to_i unless v.blank? end end, @@ -179,11 +174,11 @@ } def self.block_shorthands # dynamically load these shorthands at class definition time, but # only if they're already availbable - returning CORE_BLOCK_SHORTHANDS do |blocks| + CORE_BLOCK_SHORTHANDS.tap do |blocks| blocks.reverse_merge!(BigDecimal => lambda do |val| all(val) do |v| BigDecimal.new(v) unless v.blank? end end) if defined?(BigDecimal) @@ -201,15 +196,12 @@ end) if defined?(Date) end end def collect_blocks(block, as) - ActiveSupport::Deprecation.warn ":as => :float is deprecated. Use :as => Float instead" if as == :float - ActiveSupport::Deprecation.warn ":as => :integer is deprecated. Use :as => Integer instead" if as == :integer - if as.is_a?(Array) - unless as.one? || as.empty? + if as.size > 1 raise ArgumentError, "multiple :as types (#{as.map(&:inspect).join(', ')}) is not supported. Use a block if you want more complicated behavior." end as = as.first end @@ -219,106 +211,26 @@ # to bool, we need to be able to pass it to the user-provided block as = (block ? :bool_combined : :bool_standalone) end as = self.class.block_shorthands.fetch(as) do unless as.respond_to?(:from_xml) || (as.respond_to?(:first) && as.first.respond_to?(:from_xml)) || (as.is_a?(Hash) && !(as.keys & [:key, :value]).empty?) - ActiveSupport::Deprecation.warn "#{as.inspect} is not a valid type declaration. ROXML will raise in this case in version 3.0" unless as.nil? + raise ArgumentError, "Invalid :as argument #{as}" unless as.nil? end nil end [as, block].compact end - def extract_options!(args) - opts = args.extract_options! - unless (opts.keys & HASH_KEYS).empty? - args.push(opts) - opts = {} - end - - @default = opts.delete(:else) - @to_xml = opts.delete(:to_xml) - @name_explicit = opts.has_key?(:from) && opts[:from].is_a?(String) - @cdata = opts.delete(:cdata) - @required = opts.delete(:required) - @frozen = opts.delete(:frozen) - @wrapper = opts.delete(:in) - - @cdata ||= extract_from_as(opts, :cdata, "Please use :cdata => true") - - if opts[:as].is_a?(Array) && opts[:as].size > 1 - ActiveSupport::Deprecation.warn ":as should point to a single item. #{opts[:as].join(', ')} should be declared some other way." - end - - opts - end - - def extract_from_as(opts, entry, message) - # remove with deprecateds... - if [*opts[:as]].include?(entry) - ActiveSupport::Deprecation.warn ":as => #{entry.inspect} is deprecated. #{message}" - if opts[:as] == entry - opts[:as] = nil - else - opts[:as].delete(entry) - end - true - end - end - - def extract_type(args, opts) - types = (opts.keys & TYPE_KEYS) - # type arg - if args.one? && types.empty? - type = args.first - if type.is_a? Array - ActiveSupport::Deprecation.warn "Array declarations should be passed as the :as parameter, for future release." - @array = true - return type.first || :text - elsif type.is_a? Hash - ActiveSupport::Deprecation.warn "Hash declarations should be passed as the :as parameter, for future release." - return HashDefinition.new(type) - elsif type == :content - ActiveSupport::Deprecation.warn ":content as a type declaration is deprecated. Use :from => '.' or :from => :content instead" - opts[:from] = :content - return :text - elsif type == :attr - ActiveSupport::Deprecation.warn ":attr as a type declaration is deprecated. Use :from => '@attr_name' or :from => :attr instead" - opts[:from].sub!('@', '') if opts[:from].to_s.starts_with?('@') # this is added back next line... - opts[:from] = opts[:from].nil? ? :attr : "@#{opts[:from]}" - return :attr - else - ActiveSupport::Deprecation.warn "Type declarations should be passed as the :as parameter, for future release." - return type - end - end - - unless args.empty? - raise ArgumentError, "too many arguments (#{(args + types).join(', ')}). Should be name, type, and " + - "an options hash, with the type and options optional" - end - - if opts[:as].is_a?(Hash) - return HashDefinition.new(opts[:as]) - elsif opts[:as].respond_to?(:from_xml) - return opts[:as] - elsif opts[:as].is_a?(Array) && opts[:as].first.respond_to?(:from_xml) + def extract_type(as) + if as.is_a?(Hash) + return HashDefinition.new(as) + elsif as.respond_to?(:from_xml) + return as + elsif as.is_a?(Array) && as.first.respond_to?(:from_xml) @array = true - return opts[:as].first - end - - # type options - if types.one? - opts[:from] = opts.delete(types.first) - if opts[:from] == :content - opts[:from] = 'content' - ActiveSupport::Deprecation.warn ":content is now a reserved as an alias for '.'. Use the string 'content' instead" - end - types.first - elsif types.empty? - :text + return as.first else - raise ArgumentError, "more than one type option specified: #{types.join(', ')}" + :text end end end end