gorillib-model 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +12 -0
- data/README.md +21 -0
- data/Rakefile +15 -0
- data/gorillib-model.gemspec +27 -0
- data/lib/gorillib/builder.rb +239 -0
- data/lib/gorillib/core_ext/datetime.rb +23 -0
- data/lib/gorillib/core_ext/exception.rb +153 -0
- data/lib/gorillib/core_ext/module.rb +10 -0
- data/lib/gorillib/core_ext/object.rb +14 -0
- data/lib/gorillib/model/base.rb +273 -0
- data/lib/gorillib/model/collection/model_collection.rb +157 -0
- data/lib/gorillib/model/collection.rb +200 -0
- data/lib/gorillib/model/defaults.rb +115 -0
- data/lib/gorillib/model/errors.rb +24 -0
- data/lib/gorillib/model/factories.rb +555 -0
- data/lib/gorillib/model/field.rb +168 -0
- data/lib/gorillib/model/lint.rb +24 -0
- data/lib/gorillib/model/named_schema.rb +53 -0
- data/lib/gorillib/model/positional_fields.rb +35 -0
- data/lib/gorillib/model/schema_magic.rb +163 -0
- data/lib/gorillib/model/serialization/csv.rb +60 -0
- data/lib/gorillib/model/serialization/json.rb +44 -0
- data/lib/gorillib/model/serialization/lines.rb +30 -0
- data/lib/gorillib/model/serialization/to_wire.rb +54 -0
- data/lib/gorillib/model/serialization/tsv.rb +53 -0
- data/lib/gorillib/model/serialization.rb +41 -0
- data/lib/gorillib/model/type/extended.rb +83 -0
- data/lib/gorillib/model/type/ip_address.rb +153 -0
- data/lib/gorillib/model/type/url.rb +11 -0
- data/lib/gorillib/model/validate.rb +22 -0
- data/lib/gorillib/model/version.rb +5 -0
- data/lib/gorillib/model.rb +34 -0
- data/spec/builder_spec.rb +193 -0
- data/spec/core_ext/datetime_spec.rb +41 -0
- data/spec/core_ext/exception.rb +98 -0
- data/spec/core_ext/object.rb +45 -0
- data/spec/model/collection_spec.rb +290 -0
- data/spec/model/defaults_spec.rb +104 -0
- data/spec/model/factories_spec.rb +323 -0
- data/spec/model/lint_spec.rb +28 -0
- data/spec/model/serialization/csv_spec.rb +30 -0
- data/spec/model/serialization/tsv_spec.rb +28 -0
- data/spec/model/serialization_spec.rb +41 -0
- data/spec/model/type/extended_spec.rb +166 -0
- data/spec/model/type/ip_address_spec.rb +141 -0
- data/spec/model_spec.rb +261 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/capture_output.rb +28 -0
- data/spec/support/nuke_constants.rb +9 -0
- data/spec/support/shared_context_for_builders.rb +59 -0
- data/spec/support/shared_context_for_models.rb +55 -0
- data/spec/support/shared_examples_for_factories.rb +71 -0
- data/spec/support/shared_examples_for_model_fields.rb +62 -0
- data/spec/support/shared_examples_for_models.rb +87 -0
- metadata +193 -0
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            require_relative 'serialization/to_wire'
         | 
| 2 | 
            +
            require_relative 'serialization/lines'
         | 
| 3 | 
            +
            require_relative 'serialization/csv'
         | 
| 4 | 
            +
            require_relative 'serialization/tsv'
         | 
| 5 | 
            +
            require_relative 'serialization/json'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class Array
         | 
| 8 | 
            +
              def to_tsv
         | 
| 9 | 
            +
                to_wire.join("\t")
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            module Gorillib
         | 
| 14 | 
            +
              module Model
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def to_wire(options={})
         | 
| 17 | 
            +
                  compact_attributes.merge(:_type => self.class.typename).inject({}) do |acc, (key,attr)|
         | 
| 18 | 
            +
                    acc[key] = attr.respond_to?(:to_wire) ? attr.to_wire(options) : attr
         | 
| 19 | 
            +
                    acc
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
                def as_json(*args) to_wire(*args) ; end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def to_json(options={})
         | 
| 25 | 
            +
                  MultiJson.dump(to_wire(options), options)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def to_tsv(options={})
         | 
| 29 | 
            +
                  attributes.map do |key, attr|
         | 
| 30 | 
            +
                    attr.respond_to?(:to_wire) ? attr.to_wire(options) : attr
         | 
| 31 | 
            +
                  end.join("\t")
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                module ClassMethods
         | 
| 35 | 
            +
                  def from_tuple(*vals)
         | 
| 36 | 
            +
                    receive Hash[field_names[0..vals.length-1].zip(vals)]
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| @@ -0,0 +1,83 @@ | |
| 1 | 
            +
            require_relative 'ip_address'
         | 
| 2 | 
            +
            require_relative 'url'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            ::Long      = Class.new ::Integer
         | 
| 5 | 
            +
            ::Double    = Class.new ::Float
         | 
| 6 | 
            +
            ::Binary    = Class.new ::String
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            ::Guid      = Class.new ::String
         | 
| 9 | 
            +
            ::Hostname  = Class.new ::String
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ::EpochTime = Class.new ::Integer
         | 
| 12 | 
            +
            ::IntTime   = Class.new ::EpochTime
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            module Gorillib
         | 
| 15 | 
            +
              module Factory
         | 
| 16 | 
            +
                class GuidFactory      < StringFactory ; self.product = ::Guid      ; register_factory! ; end
         | 
| 17 | 
            +
                class HostnameFactory  < StringFactory ; self.product = ::Hostname  ; register_factory! ; end
         | 
| 18 | 
            +
                class IpAddressFactory < StringFactory ; self.product = ::IpAddress ; register_factory! ; end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                class DateFactory  < ConvertingFactory
         | 
| 21 | 
            +
                  self.product = Date
         | 
| 22 | 
            +
                  FLAT_DATE_RE = /\A\d{8}Z?\z/
         | 
| 23 | 
            +
                  register_factory!
         | 
| 24 | 
            +
                  #
         | 
| 25 | 
            +
                  def convert(obj)
         | 
| 26 | 
            +
                    case obj
         | 
| 27 | 
            +
                    when FLAT_DATE_RE  then product.new(obj[0..3].to_i, obj[4..5].to_i, obj[6..7].to_i)
         | 
| 28 | 
            +
                    when Time          then Date.new(obj.year, obj.month, obj.day)
         | 
| 29 | 
            +
                    when String        then Date.parse(obj)
         | 
| 30 | 
            +
                    else                    mismatched!(obj)
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  rescue ArgumentError => err
         | 
| 33 | 
            +
                    raise if err.is_a?(TypeMismatchError)
         | 
| 34 | 
            +
                    warn "Cannot parse time #{obj}: #{err}"
         | 
| 35 | 
            +
                    return nil
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                class EpochTimeFactory < ConvertingFactory
         | 
| 40 | 
            +
                  self.product = Integer
         | 
| 41 | 
            +
                  def self.typename() :epoch_time ; end
         | 
| 42 | 
            +
                  register_factory!   :epoch_time, EpochTime
         | 
| 43 | 
            +
                  #
         | 
| 44 | 
            +
                  def convert(obj)
         | 
| 45 | 
            +
                    case obj
         | 
| 46 | 
            +
                    when Numeric           then obj.to_f
         | 
| 47 | 
            +
                    when Time              then obj.to_f
         | 
| 48 | 
            +
                    when /\A\d{14}Z?\z/    then Time.parse(obj)
         | 
| 49 | 
            +
                    when String            then Time.parse_safely(obj).to_f
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                class IntTimeFactory < EpochTimeFactory
         | 
| 55 | 
            +
                  def self.typename() :int_time ; end
         | 
| 56 | 
            +
                  register_factory!   :int_time, IntTime
         | 
| 57 | 
            +
                  #
         | 
| 58 | 
            +
                  def convert(obj)
         | 
| 59 | 
            +
                    result = super
         | 
| 60 | 
            +
                    result.nil? ? nil : result.to_i
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                class Boolean10Factory < BooleanFactory
         | 
| 65 | 
            +
                  def self.typename() :boolean_10 ; end
         | 
| 66 | 
            +
                  register_factory!   :boolean_10
         | 
| 67 | 
            +
                  #
         | 
| 68 | 
            +
                  def convert(obj)
         | 
| 69 | 
            +
                    case obj.to_s
         | 
| 70 | 
            +
                    when "0" then false
         | 
| 71 | 
            +
                    when "1" then true
         | 
| 72 | 
            +
                    else          super
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                class SetFactory < EnumerableFactory
         | 
| 78 | 
            +
                  self.product = Set
         | 
| 79 | 
            +
                  register_factory!
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
            end
         | 
| @@ -0,0 +1,153 @@ | |
| 1 | 
            +
            module IpAddresslike
         | 
| 2 | 
            +
              ONES = 0xFFFFFFFF
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              # Masks off all but the `bitness` most-significant-bits
         | 
| 5 | 
            +
              #
         | 
| 6 | 
            +
              # @example /24 keeps only the first three quads
         | 
| 7 | 
            +
              #   IpAddress.new('1.2.3.4').bitness_min(24) # '1.2.3.0'
         | 
| 8 | 
            +
              #
         | 
| 9 | 
            +
              def bitness_min(bitness)
         | 
| 10 | 
            +
                raise ArgumentError, "IP addresses have only 32 bits (got #{bitness.inspect})" unless (0..32).include?(bitness)
         | 
| 11 | 
            +
                lsbs = 32 - bitness
         | 
| 12 | 
            +
                (packed >> lsbs) << lsbs
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              # Masks off all but the `bitness` most-significant-bits, filling with ones
         | 
| 16 | 
            +
              #
         | 
| 17 | 
            +
              # @example /24 fills the last quad
         | 
| 18 | 
            +
              #   IpAddress.new('1.2.3.4').bitness_min(24) # '1.2.3.255'
         | 
| 19 | 
            +
              #
         | 
| 20 | 
            +
              def bitness_max(bitness)
         | 
| 21 | 
            +
                raise ArgumentError, "IP addresses have only 32 bits (got #{bitness.inspect})" unless (0..32).include?(bitness)
         | 
| 22 | 
            +
                packed | (ONES >> bitness)
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def to_hex
         | 
| 26 | 
            +
                "%08x" % packed
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def to_s
         | 
| 30 | 
            +
                dotted
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            class ::IpAddress < ::String
         | 
| 36 | 
            +
              include IpAddresslike
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              def dotted
         | 
| 39 | 
            +
                self
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              def to_i
         | 
| 43 | 
            +
                packed
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              # @return [Integer] the 32-bit integer for this IP address
         | 
| 47 | 
            +
              def packed
         | 
| 48 | 
            +
                ip_a, ip_b, ip_c, ip_d = quads
         | 
| 49 | 
            +
                ((ip_a << 24) + (ip_b << 16) + (ip_c << 8) + (ip_d))
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              def quads
         | 
| 53 | 
            +
                self.split(".", 4).map{|qq| Integer(qq) }
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              # === class methods ===
         | 
| 57 | 
            +
             | 
| 58 | 
            +
              def self.from_packed(pi)
         | 
| 59 | 
            +
                str = [ (pi >> 24) & 0xFF, (pi >> 16) & 0xFF, (pi >>  8) & 0xFF, (pi) & 0xFF ].join(".")
         | 
| 60 | 
            +
                new(str)
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              def self.from_dotted(str)
         | 
| 64 | 
            +
                new(str)
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            # Stores an IP address in numeric form.
         | 
| 69 | 
            +
            #
         | 
| 70 | 
            +
            # IpNumeric instances are immutable, and memoize most of their methods.
         | 
| 71 | 
            +
            class ::IpNumeric
         | 
| 72 | 
            +
              include IpAddresslike
         | 
| 73 | 
            +
              include Comparable
         | 
| 74 | 
            +
             | 
| 75 | 
            +
              def receive(val)
         | 
| 76 | 
            +
                new(val)
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              def initialize(addr)
         | 
| 80 | 
            +
                @packed = addr.to_int
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
              def to_i       ; packed ; end
         | 
| 84 | 
            +
              def to_int     ; packed ; end
         | 
| 85 | 
            +
              def ==(other)  ; packed  == other.to_int ; end
         | 
| 86 | 
            +
              def <=>(other) ; packed <=> other.to_int ; end
         | 
| 87 | 
            +
              def +(int)     ; self.class.new(to_int + int) ; end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
             | 
| 90 | 
            +
              def packed ; @packed ; end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
              def dotted
         | 
| 93 | 
            +
                @dotted ||= quads.join('.').freeze
         | 
| 94 | 
            +
              end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
              def quads
         | 
| 97 | 
            +
                @quads ||= [ (@packed >> 24) & 0xFF, (@packed >> 16) & 0xFF, (@packed >>  8) & 0xFF, (@packed) & 0xFF ].freeze
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
              # === class methods ===
         | 
| 101 | 
            +
             | 
| 102 | 
            +
              def self.from_packed(pi)
         | 
| 103 | 
            +
                new(pi)
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
              def self.from_dotted(dotted)
         | 
| 107 | 
            +
                ip_a, ip_b, ip_c, ip_d = quads = dotted.split(".", 4).map(&:to_i)
         | 
| 108 | 
            +
                obj = new((ip_a << 24) + (ip_b << 16) + (ip_c << 8) + (ip_d))
         | 
| 109 | 
            +
                obj.instance_variable_set('@dotted', dotted.freeze)
         | 
| 110 | 
            +
                obj.instance_variable_set('@quads',  quads.freeze)
         | 
| 111 | 
            +
                obj
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
            end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            class ::IpRange < Range
         | 
| 116 | 
            +
             | 
| 117 | 
            +
              def initialize(min_or_range, max=nil, exclusive=false)
         | 
| 118 | 
            +
                if max.nil?
         | 
| 119 | 
            +
                  min       = min_or_range.min
         | 
| 120 | 
            +
                  max       = min_or_range.max
         | 
| 121 | 
            +
                  exclusive = min_or_range.exclude_end?
         | 
| 122 | 
            +
                else
         | 
| 123 | 
            +
                  min       = min_or_range
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
                raise ArgumentError, "Only inclusive #{self.class.name}s are implemented" if exclusive
         | 
| 126 | 
            +
                super( IpNumeric.new(min), IpNumeric.new(max), false )
         | 
| 127 | 
            +
              end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
              def bitness_blocks(bitness)
         | 
| 130 | 
            +
                raise ArgumentError, "IP addresses have only 32 bits (got #{bitness.inspect})" unless (0..32).include?(bitness)
         | 
| 131 | 
            +
                return [] if min.nil?
         | 
| 132 | 
            +
                lsbs = 32 - bitness
         | 
| 133 | 
            +
                middle_min = min.bitness_max(bitness) + 1
         | 
| 134 | 
            +
                return [[min, max]] if max < middle_min
         | 
| 135 | 
            +
                middle_max = max.bitness_min(bitness)
         | 
| 136 | 
            +
                blks = []
         | 
| 137 | 
            +
                stride = 1 << lsbs
         | 
| 138 | 
            +
                #
         | 
| 139 | 
            +
                blks << [min, IpNumeric.new(middle_min-1)]
         | 
| 140 | 
            +
                (middle_min ... middle_max).step(stride){|beg| blks << [IpNumeric.new(beg), IpNumeric.new(beg+stride-1)] }
         | 
| 141 | 
            +
                blks << [IpNumeric.new(middle_max), max]
         | 
| 142 | 
            +
                blks
         | 
| 143 | 
            +
              end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
              CIDR_RE = %r{\A(\d+\.\d+\.\d+\.\d+)/([0-3]\d)\z}
         | 
| 146 | 
            +
             | 
| 147 | 
            +
              def self.from_cidr(cidr_str)
         | 
| 148 | 
            +
                cidr_str =~ CIDR_RE or raise ArgumentError, "CIDR string should look like an ip address and bitness, eg 1.2.3.4/24 (got #{cidr_str})"
         | 
| 149 | 
            +
                bitness    = $2.to_i
         | 
| 150 | 
            +
                ip_address = IpNumeric.from_dotted($1)
         | 
| 151 | 
            +
                new( ip_address.bitness_min(bitness), ip_address.bitness_max(bitness) )
         | 
| 152 | 
            +
              end
         | 
| 153 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            module Gorillib
         | 
| 2 | 
            +
              module Model
         | 
| 3 | 
            +
                module Validate
         | 
| 4 | 
            +
                  module_function
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  VALID_NAME_RE = /\A[A-Za-z_][A-Za-z0-9_]*\z/
         | 
| 7 | 
            +
                  def identifier!(name)
         | 
| 8 | 
            +
                    raise TypeError,     "can't convert #{name.class} into Symbol", caller unless name.respond_to? :to_sym
         | 
| 9 | 
            +
                    raise ArgumentError, "Name must start with [A-Za-z_] and subsequently contain only [A-Za-z0-9_]", caller unless name =~ VALID_NAME_RE
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def hashlike!(val)
         | 
| 13 | 
            +
                    return true if val.respond_to?(:[]) && val.respond_to?(:has_key?)
         | 
| 14 | 
            +
                    raise ArgumentError, "#{block_given? ? yield : 'value'} should be something that behaves like a hash: #{val.inspect}", caller
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def included_in!(desc, val, colxn)
         | 
| 18 | 
            +
                    raise ArgumentError, "#{desc} must be one of #{colxn.inspect}: got #{val.inspect}", caller unless colxn.include?(val)
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            require 'set'
         | 
| 2 | 
            +
            require 'time'
         | 
| 3 | 
            +
            require 'date'
         | 
| 4 | 
            +
            require 'pathname'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require 'active_support/concern'
         | 
| 7 | 
            +
            require 'active_support/core_ext/object/blank'
         | 
| 8 | 
            +
            require 'active_support/core_ext/object/try'
         | 
| 9 | 
            +
            require 'active_support/core_ext/array/extract_options'
         | 
| 10 | 
            +
            require 'active_support/core_ext/class/attribute'
         | 
| 11 | 
            +
            require 'active_support/core_ext/enumerable'
         | 
| 12 | 
            +
            require 'active_support/core_ext/hash/compact'
         | 
| 13 | 
            +
            require 'active_support/core_ext/hash/keys'
         | 
| 14 | 
            +
            require 'active_support/core_ext/hash/reverse_merge'
         | 
| 15 | 
            +
            require 'active_support/core_ext/hash/slice'
         | 
| 16 | 
            +
            require 'active_support/core_ext/module/delegation'
         | 
| 17 | 
            +
            require 'active_support/inflector'
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            require 'gorillib/core_ext/datetime'
         | 
| 20 | 
            +
            require 'gorillib/core_ext/exception'
         | 
| 21 | 
            +
            require 'gorillib/core_ext/module'
         | 
| 22 | 
            +
            require 'gorillib/core_ext/object'
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            require 'gorillib/model/version'
         | 
| 25 | 
            +
            require 'gorillib/model/factories'
         | 
| 26 | 
            +
            require 'gorillib/model/named_schema'
         | 
| 27 | 
            +
            require 'gorillib/model/validate'
         | 
| 28 | 
            +
            require 'gorillib/model/errors'
         | 
| 29 | 
            +
            require 'gorillib/model/base'
         | 
| 30 | 
            +
            require 'gorillib/model/schema_magic'
         | 
| 31 | 
            +
            require 'gorillib/model/field'
         | 
| 32 | 
            +
            require 'gorillib/model/defaults'
         | 
| 33 | 
            +
            require 'gorillib/model/collection'
         | 
| 34 | 
            +
             | 
| @@ -0,0 +1,193 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'gorillib/builder'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe Gorillib::Builder, :model_spec => true, :builder_spec => true do
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              it_behaves_like 'a model'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              context 'examples:' do
         | 
| 10 | 
            +
                let(:subject_class  ){ car_class }
         | 
| 11 | 
            +
                it 'type-converts values' do
         | 
| 12 | 
            +
                  obj = subject_class.receive( :name => 'wildcat', :make_model => 'Buick Wildcat', :year => "1968", :doors => "2" )
         | 
| 13 | 
            +
                  obj.attributes.should == {   :name => :wildcat,  :make_model => 'Buick Wildcat', :year =>  1968,  :doors =>  2, :engine => nil }
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
                it 'handles nested structures' do
         | 
| 16 | 
            +
                  obj = subject_class.receive(
         | 
| 17 | 
            +
                    :name => 'wildcat', :make_model => 'Buick Wildcat', :year => "1968", :doors => "2",
         | 
| 18 | 
            +
                    :engine => { :carburetor => 'edelbrock', :volume => "455", :cylinders => '8' })
         | 
| 19 | 
            +
                  obj.attributes.values_at(:name, :make_model, :year, :doors).should == [:wildcat, 'Buick Wildcat', 1968,  2 ]
         | 
| 20 | 
            +
                  obj.engine.attributes.values_at(:carburetor, :volume, :cylinders).should == [:edelbrock, 455, 8 ]
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
                it 'lets you dive down' do
         | 
| 23 | 
            +
                  wildcat.engine.attributes.values_at(:carburetor, :volume, :cylinders).should == [:stock, 455, 8 ]
         | 
| 24 | 
            +
                  wildcat.engine(:cylinders => 6) do
         | 
| 25 | 
            +
                    volume   383
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                  wildcat.engine.attributes.values_at(:carburetor, :volume, :cylinders).should == [:stock, 383, 6 ]
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
                it 'lazily autovivifies members' do
         | 
| 30 | 
            +
                  ford_39.read_attribute(:engine).should be_nil
         | 
| 31 | 
            +
                  ford_39.engine(:cylinders => 6)
         | 
| 32 | 
            +
                  ford_39.read_attribute(:engine).should be_a(Gorillib::Test::Engine)
         | 
| 33 | 
            +
                  ford_39.engine.read_attribute(:cylinders).should == 6
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              context 'receive!' do
         | 
| 38 | 
            +
                it 'with a block, instance evals the block' do
         | 
| 39 | 
            +
                  expect_7 = nil ; expect_obj = nil
         | 
| 40 | 
            +
                  wildcat.receive!({}){    expect_7 = 7 ; expect_obj = self }
         | 
| 41 | 
            +
                  expect_7.should == 7 ; expect_obj.should  == wildcat
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
                it 'with a block of arity 1, calls the block passing self' do
         | 
| 44 | 
            +
                  expect_7 = nil ; expect_obj = nil
         | 
| 45 | 
            +
                  wildcat.receive!({}){|c| expect_7 = 7 ; expect_obj = c }
         | 
| 46 | 
            +
                  expect_7.should    == 7 ; expect_obj.should  == wildcat
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
                it 'with a block, returns its return value' do
         | 
| 49 | 
            +
                  val = mock_val
         | 
| 50 | 
            +
                  wildcat.receive!{      val }.should == val
         | 
| 51 | 
            +
                  wildcat.receive!{|obj| val }.should == val
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
                it 'with no block, returns nil' do
         | 
| 54 | 
            +
                  wildcat.receive!.should be_nil
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
              context ".magic" do
         | 
| 59 | 
            +
                let(:subject_class){ car_class }
         | 
| 60 | 
            +
                context do
         | 
| 61 | 
            +
                  subject{ car_class.new }
         | 
| 62 | 
            +
                  let(:sample_val){ 'fiat' }
         | 
| 63 | 
            +
                  let(:raw_val   ){ :fiat  }
         | 
| 64 | 
            +
                  it_behaves_like "a model field", :make_model
         | 
| 65 | 
            +
                  it("#read_attribute is nil if never set"){ subject.read_attribute(:make_model).should == nil }
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                it "does not create a writer method #foo=" do
         | 
| 69 | 
            +
                  subject_class.should     be_method_defined(:doors)
         | 
| 70 | 
            +
                  subject_class.should_not be_method_defined(:doors=)
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                context 'calling the getset "#foo" method' do
         | 
| 74 | 
            +
                  subject{ wildcat }
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  it "with no args calls read_attribute(:foo)" do
         | 
| 77 | 
            +
                    subject.write_attribute(:doors, mock_val)
         | 
| 78 | 
            +
                    subject.should_receive(:read_attribute).with(:doors).at_least(:once).and_return(mock_val)
         | 
| 79 | 
            +
                    subject.doors.should == mock_val
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                  it "with an argument calls write_attribute(:foo)" do
         | 
| 82 | 
            +
                    subject.write_attribute(:doors, 'gone')
         | 
| 83 | 
            +
                    subject.should_receive(:write_attribute).with(:doors, mock_val).and_return('returned')
         | 
| 84 | 
            +
                    result = subject.doors(mock_val)
         | 
| 85 | 
            +
                    result.should == 'returned'
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
                  it "with multiple arguments is an error" do
         | 
| 88 | 
            +
                    expect{ subject.doors(1, 2) }.to raise_error(ArgumentError, "wrong number of arguments (2 for 0..1)")
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
              context ".member" do
         | 
| 94 | 
            +
                subject{ car_class.new }
         | 
| 95 | 
            +
                let(:sample_val){ example_engine }
         | 
| 96 | 
            +
                let(:raw_val   ){ example_engine.attributes }
         | 
| 97 | 
            +
                it_behaves_like "a model field", :engine
         | 
| 98 | 
            +
                it("#read_attribute is nil if never set"){ subject.read_attribute(:engine).should == nil }
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                it "calling the getset method #foo with no args calls read_attribute(:foo)" do
         | 
| 101 | 
            +
                  wildcat.write_attribute(:doors, mock_val)
         | 
| 102 | 
            +
                  wildcat.should_receive(:read_attribute).with(:doors).at_least(:once).and_return(mock_val)
         | 
| 103 | 
            +
                  wildcat.doors.should == mock_val
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
                it "calling the getset method #foo with an argument calls write_attribute(:foo)" do
         | 
| 106 | 
            +
                  wildcat.write_attribute(:doors, 'gone')
         | 
| 107 | 
            +
                  wildcat.should_receive(:write_attribute).with(:doors, mock_val).and_return('returned')
         | 
| 108 | 
            +
                  result = wildcat.doors(mock_val)
         | 
| 109 | 
            +
                  result.should == 'returned'
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
                it "calling the getset method #foo with multiple arguments is an error" do
         | 
| 112 | 
            +
                  ->{ wildcat.doors(1, 2) }.should raise_error(ArgumentError, "wrong number of arguments (2 for 0..1)")
         | 
| 113 | 
            +
                end
         | 
| 114 | 
            +
                it "does not create a writer method #foo=" do
         | 
| 115 | 
            +
                  wildcat.should     respond_to(:doors)
         | 
| 116 | 
            +
                  wildcat.should_not respond_to(:doors=)
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
             | 
| 120 | 
            +
              end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
              context 'collections' do
         | 
| 123 | 
            +
                subject{ garage }
         | 
| 124 | 
            +
                let(:sample_val){ Gorillib::ModelCollection.receive([wildcat], key_method: :name, item_type: car_class) }
         | 
| 125 | 
            +
                let(:raw_val   ){ [ wildcat.attributes ] }
         | 
| 126 | 
            +
                it_behaves_like "a model field", :cars
         | 
| 127 | 
            +
                it("#read_attribute is an empty collection if never set"){ subject.read_attribute(:cars).should == Gorillib::ModelCollection.new(key_method: :to_key) }
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                it 'a collection holds named objects' do
         | 
| 130 | 
            +
                  garage.cars.should be_empty
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                  # create a car with a hash of attributes
         | 
| 133 | 
            +
                  garage.car(:cadzilla, :make_model => 'Cadillac, Mostly')
         | 
| 134 | 
            +
                  # ...and retrieve it by name
         | 
| 135 | 
            +
                  cadzilla = garage.car(:cadzilla)
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  # add a car explicitly
         | 
| 138 | 
            +
                  garage.car(:wildcat,  wildcat)
         | 
| 139 | 
            +
                  garage.car(:wildcat).should     equal(wildcat)
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                  # duplicate a car
         | 
| 142 | 
            +
                  garage.car(:ford_39, ford_39.attributes.compact)
         | 
| 143 | 
            +
                  garage.car(:ford_39).should     ==(ford_39)
         | 
| 144 | 
            +
                  garage.car(:ford_39).should_not equal(ford_39)
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  # examine the whole collection
         | 
| 147 | 
            +
                  garage.cars.keys.should == [:cadzilla, :wildcat, :ford_39]
         | 
| 148 | 
            +
                  garage.cars.should == Gorillib::ModelCollection.receive([cadzilla, wildcat, ford_39], key_method: :name, item_type: car_class)
         | 
| 149 | 
            +
                end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                it 'lazily autovivifies collection items' do
         | 
| 152 | 
            +
                  garage.cars.should be_empty
         | 
| 153 | 
            +
                  garage.car(:chimera).should be_a(car_class)
         | 
| 154 | 
            +
                  garage.cars.should == Gorillib::ModelCollection.receive([{:name => :chimera}], key_method: :name, item_type: car_class)
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                context 'collection getset method' do
         | 
| 158 | 
            +
                  it 'clxn(:name, existing_object) -- replaces with given object, does not call block' do
         | 
| 159 | 
            +
                    test = nil
         | 
| 160 | 
            +
                    subject.car(:wildcat, wildcat).should equal(wildcat){ test = 3 }
         | 
| 161 | 
            +
                    test.should be_nil
         | 
| 162 | 
            +
                  end
         | 
| 163 | 
            +
                  it 'clxn(:name) (missing & no attributes given) -- autovivifies' do
         | 
| 164 | 
            +
                    subject.car(:cadzilla).should == Gorillib::Test::Car.new(:name => :cadzilla)
         | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
                  it 'clxn(:name, &block) (missing & no attributes given) -- autovivifies, execs block' do
         | 
| 167 | 
            +
                    test = nil
         | 
| 168 | 
            +
                    subject.car(:cadzilla){ test = 7 }
         | 
| 169 | 
            +
                    test.should == 7
         | 
| 170 | 
            +
                  end
         | 
| 171 | 
            +
                  it 'clxn(:name, :attr => val) (missing, attributes given) -- creates item' do
         | 
| 172 | 
            +
                    subject.car(:cadzilla, :doors => 3).should == Gorillib::Test::Car.new(:name => :cadzilla, :doors => 3)
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
                  it 'clxn(:name, :attr => val) (missing, attributes given) -- creates item, execs block' do
         | 
| 175 | 
            +
                    test = nil
         | 
| 176 | 
            +
                    subject.car(:cadzilla, :doors => 3){ test = 7 }
         | 
| 177 | 
            +
                    test.should == 7
         | 
| 178 | 
            +
                  end
         | 
| 179 | 
            +
                  it 'clxn(:name, :attr => val) (present, attributes given) -- updates item' do
         | 
| 180 | 
            +
                    subject.car(:wildcat, wildcat)
         | 
| 181 | 
            +
                    subject.car(:wildcat, :doors => 9)
         | 
| 182 | 
            +
                    wildcat.doors.should == 9
         | 
| 183 | 
            +
                  end
         | 
| 184 | 
            +
                  it 'clxn(:name, :attr => val) (present, attributes given) -- updates item, execs block' do
         | 
| 185 | 
            +
                    subject.car(:wildcat, wildcat)
         | 
| 186 | 
            +
                    subject.car(:wildcat, :doors => 9){ self.make_model 'WILDCAT' }
         | 
| 187 | 
            +
                    wildcat.doors.should == 9
         | 
| 188 | 
            +
                    wildcat.make_model.should == 'WILDCAT'
         | 
| 189 | 
            +
                  end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                end
         | 
| 192 | 
            +
              end
         | 
| 193 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Time, :datetime_spec => true do
         | 
| 4 | 
            +
              describe '#parse_safely' do
         | 
| 5 | 
            +
                before do
         | 
| 6 | 
            +
                  @time_utc  = Time.parse("2011-02-03T04:05:06 UTC")
         | 
| 7 | 
            +
                  @time_cst  = Time.parse("2011-02-02T22:05:06-06:00")
         | 
| 8 | 
            +
                  @time_flat    = "20110203040506"
         | 
| 9 | 
            +
                  @time_iso_utc = "2011-02-03T04:05:06+00:00"
         | 
| 10 | 
            +
                  @time_iso_cst = "2011-02-02T22:05:06-06:00"
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                it 'with a Time, passes it through.' do
         | 
| 14 | 
            +
                  Time.parse_safely(@time_utc).should == @time_utc
         | 
| 15 | 
            +
                  Time.parse_safely(@time_cst).should == @time_cst
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it 'with a Time, converts to UTC.' do
         | 
| 19 | 
            +
                  Time.parse_safely(@time_utc).utc_offset.should == 0
         | 
| 20 | 
            +
                  Time.parse_safely(@time_cst).utc_offset.should == 0
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                it 'with a flat time, converts to UTC Time instance' do
         | 
| 24 | 
            +
                  Time.parse_safely(@time_flat).should == @time_utc
         | 
| 25 | 
            +
                  Time.parse_safely(@time_flat).utc_offset.should == 0
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                it 'with a flat time and Z, converts to UTC Time instance' do
         | 
| 29 | 
            +
                  Time.parse_safely(@time_flat+'Z').should == @time_utc
         | 
| 30 | 
            +
                  Time.parse_safely(@time_flat+'Z').utc_offset.should == 0
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                it 'parses a regular time string, converting to UTC' do
         | 
| 34 | 
            +
                  Time.parse_safely(@time_iso_utc).should == @time_utc
         | 
| 35 | 
            +
                  Time.parse_safely(@time_iso_utc).utc_offset.should == 0
         | 
| 36 | 
            +
                  Time.parse_safely(@time_iso_cst).should == @time_utc
         | 
| 37 | 
            +
                  Time.parse_safely(@time_iso_cst).utc_offset.should == 0
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| @@ -0,0 +1,98 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe 'raisers' do
         | 
| 4 | 
            +
              def should_raise_my_error(msg=nil)
         | 
| 5 | 
            +
                msg ||= error_message
         | 
| 6 | 
            +
                expect{ yield }.to raise_error(described_class, msg)
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
              def should_return_true
         | 
| 9 | 
            +
                yield.should be_true
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
              # different rubies have different error messages ARRGH.
         | 
| 12 | 
            +
              def capture_error
         | 
| 13 | 
            +
                message = 'should have raised, did not'
         | 
| 14 | 
            +
                begin
         | 
| 15 | 
            +
                  yield
         | 
| 16 | 
            +
                rescue described_class => err
         | 
| 17 | 
            +
                  message = err.message
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
                return message.gsub(/of arguments\(/, 'of arguments (')
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              describe ArgumentError do
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                context '.check_arity!' do
         | 
| 25 | 
            +
                  let(:error_message){ /wrong number of arguments/ }
         | 
| 26 | 
            +
                  it 'checks against a range' do
         | 
| 27 | 
            +
                    should_raise_my_error{ described_class.check_arity!(['a'], 2..5) }
         | 
| 28 | 
            +
                    should_raise_my_error{ described_class.check_arity!(['a'], 2..2) }
         | 
| 29 | 
            +
                    should_return_true{    described_class.check_arity!(['a'], 0..5) }
         | 
| 30 | 
            +
                    should_return_true{    described_class.check_arity!(['a'], 1..1) }
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                  it 'checks against an array' do
         | 
| 33 | 
            +
                    should_raise_my_error{ described_class.check_arity!( ['a', 'b'], [1, 3, 5] ) }
         | 
| 34 | 
            +
                    should_return_true{    described_class.check_arity!( ['a', 'b'], [1, 2] ) }
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                  it 'given a single number, requires exactly that many args' do
         | 
| 37 | 
            +
                    should_raise_my_error{ described_class.check_arity!( ['a', 'b'], 1 ) }
         | 
| 38 | 
            +
                    should_raise_my_error{ described_class.check_arity!( ['a', 'b'], 3 ) }
         | 
| 39 | 
            +
                    should_return_true{    described_class.check_arity!( ['a', 'b'], 2 ) }
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                  it 'matches the message a native arity error would' do
         | 
| 42 | 
            +
                    should_raise_my_error(capture_error{ [].fill()   }){ described_class.check_arity!([],  1..3) }
         | 
| 43 | 
            +
                    should_raise_my_error(capture_error{ [].to_s(1)  }){ described_class.check_arity!([1], 0) }
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                  it 'appends result of block (if given) to message' do
         | 
| 46 | 
            +
                    str = "esiar no delave ylno"
         | 
| 47 | 
            +
                    ->{ described_class.check_arity!([],  1..3){ str.reverse! } }.should raise_error(/only evaled on raise/)
         | 
| 48 | 
            +
                    str.should == "only evaled on raise"
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                context '.arity_at_least!' do
         | 
| 53 | 
            +
                  let(:error_message){ /wrong number of arguments/ }
         | 
| 54 | 
            +
                  it 'raises if there are fewer than that many args' do
         | 
| 55 | 
            +
                    should_raise_my_error{ described_class.arity_at_least!(['a'], 2) }
         | 
| 56 | 
            +
                    should_raise_my_error{ described_class.arity_at_least!([],    1) }
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                  it ('returns true if there are that many args or more') do
         | 
| 59 | 
            +
                    should_return_true{    described_class.arity_at_least!([],    0) }
         | 
| 60 | 
            +
                    should_return_true{    described_class.arity_at_least!(['a'], 0) }
         | 
| 61 | 
            +
                    should_return_true{    described_class.arity_at_least!(['a'], 1) }
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              describe TypeMismatchError do
         | 
| 67 | 
            +
                context '.mismatched!' do
         | 
| 68 | 
            +
                  let(:error_message){ /.+ has mismatched type/ }
         | 
| 69 | 
            +
                  it 'raises an error' do
         | 
| 70 | 
            +
                    should_raise_my_error{ described_class.mismatched!("string", Integer) }
         | 
| 71 | 
            +
                    should_raise_my_error{ described_class.mismatched!(Object.new) }
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                context '.check_type!' do
         | 
| 76 | 
            +
                  let(:error_message){ /.+ has mismatched type; expected .+/ }
         | 
| 77 | 
            +
                  it 'raises true if any type matches' do
         | 
| 78 | 
            +
                    should_return_true{    described_class.check_type!("string", [Integer, String]) }
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
                  it 'raises an error if nothing matches' do
         | 
| 81 | 
            +
                    should_raise_my_error{ described_class.check_type!("string", [Integer, Float]) }
         | 
| 82 | 
            +
                    should_raise_my_error{ described_class.check_type!("string", [Integer]) }
         | 
| 83 | 
            +
                    should_raise_my_error{ described_class.check_type!("string", Integer) }
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                  it 'checks is_a? given a class' do
         | 
| 86 | 
            +
                    should_return_true{    described_class.check_type!("string", [Integer, String]) }
         | 
| 87 | 
            +
                    should_return_true{    described_class.check_type!(7,        [Integer, String]) }
         | 
| 88 | 
            +
                    should_raise_my_error{ described_class.check_type!(:symbol,  [Integer, String]) }
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
                  it 'checks responds_to? given a symbol' do
         | 
| 91 | 
            +
                    should_return_true{    described_class.check_type!("string", [:to_str, :to_int]) }
         | 
| 92 | 
            +
                    should_return_true{    described_class.check_type!(7,        [:to_str, :to_int]) }
         | 
| 93 | 
            +
                    should_raise_my_error{ described_class.check_type!(:symbol,  [:to_str, :to_int]) }
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            end
         |