virtus 0.0.10 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. data/.gitignore +33 -7
  2. data/.travis.yml +4 -74
  3. data/Changelog.md +11 -2
  4. data/Gemfile +10 -9
  5. data/README.md +85 -1
  6. data/TODO +18 -0
  7. data/config/flay.yml +2 -2
  8. data/config/flog.yml +1 -1
  9. data/config/roodi.yml +3 -3
  10. data/config/site.reek +8 -3
  11. data/lib/virtus.rb +5 -0
  12. data/lib/virtus/attribute.rb +137 -30
  13. data/lib/virtus/attribute/array.rb +17 -1
  14. data/lib/virtus/attribute/boolean.rb +8 -13
  15. data/lib/virtus/attribute/collection.rb +96 -0
  16. data/lib/virtus/attribute/default_value.rb +11 -7
  17. data/lib/virtus/attribute/embedded_value.rb +70 -0
  18. data/lib/virtus/attribute/set.rb +25 -0
  19. data/lib/virtus/attribute_set.rb +18 -16
  20. data/lib/virtus/attributes_accessor.rb +66 -0
  21. data/lib/virtus/class_methods.rb +50 -28
  22. data/lib/virtus/coercion/array.rb +23 -0
  23. data/lib/virtus/coercion/string.rb +10 -4
  24. data/lib/virtus/instance_methods.rb +38 -31
  25. data/lib/virtus/support/options.rb +6 -8
  26. data/lib/virtus/support/type_lookup.rb +4 -11
  27. data/lib/virtus/version.rb +1 -1
  28. data/spec/integration/collection_member_coercion_spec.rb +75 -0
  29. data/spec/integration/custom_attributes_spec.rb +49 -0
  30. data/spec/integration/default_values_spec.rb +32 -0
  31. data/spec/integration/defining_attributes_spec.rb +79 -0
  32. data/spec/integration/embedded_value_spec.rb +50 -0
  33. data/spec/integration/overriding_virtus_spec.rb +46 -0
  34. data/spec/integration/virtus/instance_level_attributes_spec.rb +23 -0
  35. data/spec/rcov.opts +1 -0
  36. data/spec/shared/constants_helpers.rb +9 -0
  37. data/spec/shared/options_class_method.rb +19 -0
  38. data/spec/spec_helper.rb +20 -7
  39. data/spec/unit/virtus/attribute/array/coerce_spec.rb +13 -0
  40. data/spec/unit/virtus/attribute/boolean/coerce_spec.rb +85 -0
  41. data/spec/unit/virtus/attribute/boolean/define_reader_method_spec.rb +15 -0
  42. data/spec/unit/virtus/attribute/boolean/value_coerced_spec.rb +97 -0
  43. data/spec/unit/virtus/attribute/boolean_spec.rb +2 -81
  44. data/spec/unit/virtus/attribute/class/coerce_spec.rb +13 -0
  45. data/spec/unit/virtus/attribute/class_methods/accessor_spec.rb +12 -0
  46. data/spec/unit/virtus/attribute/class_methods/build_spec.rb +37 -0
  47. data/spec/unit/virtus/attribute/class_methods/coercion_method_spec.rb +9 -0
  48. data/spec/unit/virtus/attribute/class_methods/default_spec.rb +9 -0
  49. data/spec/unit/virtus/attribute/class_methods/determine_type_spec.rb +31 -1
  50. data/spec/unit/virtus/attribute/class_methods/merge_options_spec.rb +11 -0
  51. data/spec/unit/virtus/attribute/class_methods/primitive_spec.rb +9 -0
  52. data/spec/unit/virtus/attribute/class_methods/reader_spec.rb +9 -0
  53. data/spec/unit/virtus/attribute/class_methods/writer_spec.rb +9 -0
  54. data/spec/unit/virtus/attribute/coerce_spec.rb +30 -0
  55. data/spec/unit/virtus/attribute/coercion_method_spec.rb +12 -0
  56. data/spec/unit/virtus/attribute/collection/class_methods/merge_options_spec.rb +40 -0
  57. data/spec/unit/virtus/attribute/collection/coerce_spec.rb +26 -0
  58. data/spec/unit/virtus/attribute/date/coerce_spec.rb +47 -0
  59. data/spec/unit/virtus/attribute/date/value_coerced_spec.rb +46 -0
  60. data/spec/unit/virtus/attribute/date_time/coerce_spec.rb +68 -0
  61. data/spec/unit/virtus/attribute/decimal/coerce_spec.rb +117 -0
  62. data/spec/unit/virtus/attribute/default_spec.rb +32 -0
  63. data/spec/unit/virtus/attribute/default_value/attribute_spec.rb +11 -0
  64. data/spec/unit/virtus/attribute/default_value/class_methods/new_spec.rb +4 -2
  65. data/spec/unit/virtus/attribute/default_value/evaluate_spec.rb +51 -0
  66. data/spec/unit/virtus/attribute/default_value/instance_methods/evaluate_spec.rb +9 -6
  67. data/spec/unit/virtus/attribute/default_value/value_spec.rb +11 -0
  68. data/spec/unit/virtus/attribute/define_accessor_methods_spec.rb +26 -0
  69. data/spec/unit/virtus/attribute/define_reader_method_spec.rb +24 -0
  70. data/spec/unit/virtus/attribute/define_writer_method_spec.rb +24 -0
  71. data/spec/unit/virtus/attribute/embedded_value/class_methods/merge_options_spec.rb +17 -0
  72. data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +50 -0
  73. data/spec/unit/virtus/attribute/float/coerce_spec.rb +117 -0
  74. data/spec/unit/virtus/attribute/get_spec.rb +80 -0
  75. data/spec/unit/virtus/attribute/inspect_spec.rb +27 -0
  76. data/spec/unit/virtus/attribute/instance_variable_name_spec.rb +12 -0
  77. data/spec/unit/virtus/attribute/integer/coerce_spec.rb +105 -0
  78. data/spec/unit/virtus/attribute/name_spec.rb +12 -0
  79. data/spec/unit/virtus/attribute/numeric/class_methods/descendants_spec.rb +1 -1
  80. data/spec/unit/virtus/attribute/numeric/class_methods/max_spec.rb +9 -0
  81. data/spec/unit/virtus/attribute/numeric/class_methods/min_spec.rb +9 -0
  82. data/spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb +9 -7
  83. data/spec/unit/virtus/attribute/options_spec.rb +14 -0
  84. data/spec/unit/virtus/attribute/public_reader_spec.rb +24 -0
  85. data/spec/unit/virtus/attribute/public_writer_spec.rb +24 -0
  86. data/spec/unit/virtus/attribute/reader_visibility_spec.rb +24 -0
  87. data/spec/unit/virtus/attribute/set/coerce_spec.rb +13 -0
  88. data/spec/unit/virtus/attribute/set_spec.rb +49 -0
  89. data/spec/unit/virtus/attribute/string/coerce_spec.rb +11 -0
  90. data/spec/unit/virtus/attribute/time/coerce_spec.rb +67 -0
  91. data/spec/unit/virtus/attribute/value_coerced_spec.rb +19 -0
  92. data/spec/unit/virtus/attribute/writer_visibility_spec.rb +24 -0
  93. data/spec/unit/virtus/attribute_set/append_spec.rb +12 -0
  94. data/spec/unit/virtus/attribute_set/element_reference_spec.rb +4 -0
  95. data/spec/unit/virtus/attribute_set/element_set_spec.rb +29 -9
  96. data/spec/unit/virtus/attributes_accessor/inspect_spec.rb +9 -0
  97. data/spec/unit/virtus/class_methods/attribute_spec.rb +23 -5
  98. data/spec/unit/virtus/class_methods/attributes_spec.rb +3 -5
  99. data/spec/unit/virtus/class_methods/const_missing_spec.rb +27 -0
  100. data/spec/unit/virtus/class_methods/inherited_spec.rb +21 -0
  101. data/spec/unit/virtus/coercion/array/to_set_spec.rb +12 -0
  102. data/spec/unit/virtus/coercion/date/class_methods/to_date_spec.rb +10 -0
  103. data/spec/unit/virtus/coercion/date_time/class_methods/to_datetime_spec.rb +10 -0
  104. data/spec/unit/virtus/coercion/hash/class_methods/to_date_spec.rb +10 -3
  105. data/spec/unit/virtus/coercion/hash/class_methods/to_datetime_spec.rb +10 -3
  106. data/spec/unit/virtus/coercion/hash/class_methods/to_time_spec.rb +10 -3
  107. data/spec/unit/virtus/coercion/object/class_methods/method_missing_spec.rb +8 -8
  108. data/spec/unit/virtus/coercion/string/class_methods/to_boolean_spec.rb +2 -2
  109. data/spec/unit/virtus/coercion/string/class_methods/to_constant_spec.rb +1 -1
  110. data/spec/unit/virtus/coercion/string/class_methods/to_date_spec.rb +1 -1
  111. data/spec/unit/virtus/coercion/string/class_methods/to_datetime_spec.rb +13 -13
  112. data/spec/unit/virtus/coercion/string/class_methods/to_decimal_spec.rb +25 -1
  113. data/spec/unit/virtus/coercion/string/class_methods/to_float_spec.rb +25 -1
  114. data/spec/unit/virtus/coercion/string/class_methods/to_integer_spec.rb +30 -1
  115. data/spec/unit/virtus/coercion/string/class_methods/to_time_spec.rb +13 -13
  116. data/spec/unit/virtus/coercion/time/class_methods/to_time_spec.rb +10 -0
  117. data/spec/unit/virtus/coercion/true_class/class_methods/to_string_spec.rb +1 -1
  118. data/spec/unit/virtus/instance_methods/attributes_spec.rb +77 -20
  119. data/spec/unit/virtus/instance_methods/element_reference_spec.rb +1 -1
  120. data/spec/unit/virtus/instance_methods/element_set_spec.rb +2 -2
  121. data/spec/unit/virtus/instance_methods/to_hash_spec.rb +23 -0
  122. data/spec/unit/virtus/options/accept_options_spec.rb +10 -11
  123. data/spec/unit/virtus/options/accepted_options_spec.rb +1 -1
  124. data/spec/unit/virtus/options/options_spec.rb +27 -4
  125. data/tasks/metrics/ci.rake +2 -1
  126. data/tasks/metrics/heckle.rake +207 -0
  127. data/tasks/spec.rake +14 -7
  128. data/virtus.gemspec +1 -1
  129. metadata +111 -97
  130. data/VERSION +0 -1
  131. data/examples/custom_coercion_spec.rb +0 -50
  132. data/examples/default_values_spec.rb +0 -21
  133. data/examples/override_attribute_methods_spec.rb +0 -40
  134. data/spec/integration/virtus/attributes/attribute/set_spec.rb +0 -36
  135. data/spec/integration/virtus/class_methods/attribute_spec.rb +0 -82
  136. data/spec/integration/virtus/class_methods/attributes_spec.rb +0 -22
  137. data/spec/integration/virtus/class_methods/const_missing_spec.rb +0 -44
  138. data/spec/unit/shared/attribute.rb +0 -7
  139. data/spec/unit/shared/attribute/accept_options.rb +0 -37
  140. data/spec/unit/shared/attribute/accepted_options.rb +0 -5
  141. data/spec/unit/shared/attribute/get.rb +0 -44
  142. data/spec/unit/shared/attribute/inspect.rb +0 -7
  143. data/spec/unit/shared/attribute/set.rb +0 -37
  144. data/spec/unit/virtus/attribute/array_spec.rb +0 -24
  145. data/spec/unit/virtus/attribute/class_spec.rb +0 -24
  146. data/spec/unit/virtus/attribute/date_spec.rb +0 -59
  147. data/spec/unit/virtus/attribute/date_time_spec.rb +0 -87
  148. data/spec/unit/virtus/attribute/decimal_spec.rb +0 -109
  149. data/spec/unit/virtus/attribute/float_spec.rb +0 -109
  150. data/spec/unit/virtus/attribute/hash_spec.rb +0 -11
  151. data/spec/unit/virtus/attribute/integer_spec.rb +0 -99
  152. data/spec/unit/virtus/attribute/string_spec.rb +0 -21
  153. data/spec/unit/virtus/attribute/time_spec.rb +0 -82
  154. data/spec/unit/virtus/class_methods/new_spec.rb +0 -41
@@ -12,8 +12,10 @@ module Virtus
12
12
  # @api private
13
13
  def self.extended(descendant)
14
14
  super
15
- descendant.extend(DescendantsTracker)
16
- descendant.const_set(:AttributeMethods, Module.new)
15
+ descendant.module_eval do
16
+ extend DescendantsTracker
17
+ virtus_setup_attributes_accessor_module
18
+ end
17
19
  end
18
20
 
19
21
  private_class_method :extended
@@ -41,10 +43,12 @@ module Virtus
41
43
  #
42
44
  # @return [self]
43
45
  #
46
+ # @see Attribute.build
47
+ #
44
48
  # @api public
45
- def attribute(name, type, options = {})
46
- attribute = Attribute.determine_type(type).new(name, options)
47
- virtus_define_attribute_methods(attribute)
49
+ def attribute(*args)
50
+ attribute = Attribute.build(*args)
51
+ attribute.define_accessor_methods(virtus_attributes_accessor_module)
48
52
  virtus_add_attribute(attribute)
49
53
  self
50
54
  end
@@ -67,44 +71,62 @@ module Virtus
67
71
  #
68
72
  # @api public
69
73
  def attributes
70
- @attributes ||= begin
71
- superclass = self.superclass
72
- parent = superclass.attributes if superclass.respond_to?(:attributes)
73
- AttributeSet.new(parent)
74
- end
74
+ return @attributes if defined?(@attributes)
75
+ superclass = self.superclass
76
+ method = __method__
77
+ parent = superclass.send(method) if superclass.respond_to?(method)
78
+ @attributes = AttributeSet.new(parent)
75
79
  end
76
80
 
77
- private
81
+ protected
78
82
 
79
- # Hooks into const missing process to determine types of attributes
80
- #
81
- # It is used when an attribute is defined and a global class like String
82
- # or Integer is provided as the type which needs to be mapped to
83
- # Virtus::Attribute::String and Virtus::Attribute::Integer
84
- #
85
- # @param [String] name
83
+ # Set up the anonymous module which will host Attribute accessor methods
86
84
  #
87
- # @return [Class]
85
+ # @return [self]
88
86
  #
89
87
  # @api private
90
- def const_missing(name)
91
- Attribute.determine_type(name) || super
88
+ def virtus_setup_attributes_accessor_module
89
+ @virtus_attributes_accessor_module = AttributesAccessor.new(inspect)
90
+ include virtus_attributes_accessor_module
91
+ self
92
92
  end
93
93
 
94
- # Define the attribute reader and writer methods in the class
94
+ private
95
+
96
+ # Setup descendants' own Attribute-accessor-method-hosting modules
95
97
  #
96
- # @param [Attribute]
98
+ # Descendants inherit Attribute accessor methods via Ruby's inheritance
99
+ # mechanism: Attribute accessor methods are defined in a module included
100
+ # in a superclass. Attributes defined on descendants add methods to the
101
+ # descendant's Attributes accessor module, leaving the superclass's method
102
+ # table unaffected.
103
+ #
104
+ # @param [Class] descendant
97
105
  #
98
106
  # @return [undefined]
99
107
  #
100
108
  # @api private
101
- def virtus_define_attribute_methods(attribute)
102
- module_with_methods = self::AttributeMethods
109
+ def inherited(descendant)
110
+ super
111
+ descendant.virtus_setup_attributes_accessor_module
112
+ end
103
113
 
104
- attribute.define_reader_method(module_with_methods)
105
- attribute.define_writer_method(module_with_methods)
114
+ # Holds the anonymous module which hosts this class's Attribute accessors
115
+ #
116
+ # @return [Module]
117
+ #
118
+ # @api private
119
+ attr_reader :virtus_attributes_accessor_module
106
120
 
107
- include module_with_methods
121
+ # Hooks into const missing process to determine types of attributes
122
+ #
123
+ # @param [String] name
124
+ #
125
+ # @return [Class]
126
+ #
127
+ # @api private
128
+ def const_missing(name)
129
+ Attribute.determine_type(name) || super
108
130
  end
109
131
 
110
132
  # Add the attribute to the class' and descendants' attributes
@@ -0,0 +1,23 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce Array values
5
+ class Array < Object
6
+ primitive ::Array
7
+
8
+ TIME_SEGMENTS = [ :year, :month, :day, :hour, :min, :sec ].freeze
9
+
10
+ # Creates a Set instance from an Array
11
+ #
12
+ # @param [Array] value
13
+ #
14
+ # @return [Array]
15
+ #
16
+ # @api private
17
+ def self.to_set(value)
18
+ value.to_set
19
+ end
20
+
21
+ end # class Array
22
+ end # class Coercion
23
+ end # module Virtus
@@ -5,11 +5,14 @@ module Virtus
5
5
  class String < Object
6
6
  primitive ::String
7
7
 
8
- TRUE_VALUES = %w[ 1 t true ].freeze
9
- FALSE_VALUES = %w[ 0 f false ].freeze
8
+ TRUE_VALUES = %w[ 1 on t true y yes ].freeze
9
+ FALSE_VALUES = %w[ 0 off f false n no ].freeze
10
10
  BOOLEAN_MAP = ::Hash[ TRUE_VALUES.product([ true ]) + FALSE_VALUES.product([ false ]) ].freeze
11
11
 
12
- NUMERIC_REGEXP = /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/.freeze
12
+ INTEGER_REGEXP = /[-+]?(?:0|[1-9]\d*)/.freeze
13
+ EXPONENT_REGEXP = /(?:[eE][-+]?\d+)/.freeze
14
+ FRACTIONAL_REGEXP = /(?:\.\d+#{EXPONENT_REGEXP}?)/.freeze
15
+ NUMERIC_REGEXP = /\A(#{INTEGER_REGEXP}#{FRACTIONAL_REGEXP}?|#{FRACTIONAL_REGEXP})\z/.freeze
13
16
 
14
17
  # Coerce give value to a constant
15
18
  #
@@ -96,7 +99,10 @@ module Virtus
96
99
  #
97
100
  # @api public
98
101
  def self.to_integer(value)
99
- to_numeric(value, :to_i)
102
+ # coerce to a Float first to evaluate scientific notation (if any)
103
+ # that may change the integer part, then convert to an integer
104
+ coerced = to_float(value)
105
+ ::Float === coerced ? coerced.to_i : coerced
100
106
  end
101
107
 
102
108
  # Coerce value to float
@@ -11,8 +11,8 @@ module Virtus
11
11
  # @return [undefined]
12
12
  #
13
13
  # @api private
14
- def initialize(attributes = {})
15
- self.attributes = attributes
14
+ def initialize(attribute_values = {})
15
+ self.attributes = attribute_values
16
16
  end
17
17
 
18
18
  # Returns a value of the attribute with the given name
@@ -35,7 +35,7 @@ module Virtus
35
35
  #
36
36
  # @api public
37
37
  def [](name)
38
- attribute_get(name)
38
+ __send__(name)
39
39
  end
40
40
 
41
41
  # Sets a value of the attribute with the given name
@@ -62,7 +62,7 @@ module Virtus
62
62
  #
63
63
  # @api public
64
64
  def []=(name, value)
65
- attribute_set(name, value)
65
+ __send__("#{name}=", value)
66
66
  end
67
67
 
68
68
  # Returns a hash of all publicly accessible attributes
@@ -82,17 +82,17 @@ module Virtus
82
82
  #
83
83
  # @api public
84
84
  def attributes
85
- attributes = {}
86
-
87
- self.class.attributes.each do |attribute|
85
+ self.class.attributes.each_with_object({}) do |attribute, attributes|
88
86
  name = attribute.name
89
- attributes[name] = attribute_get(name) if respond_to?(name)
87
+ attributes[name] = self[name] if attribute.public_reader?
90
88
  end
91
-
92
- attributes
93
89
  end
94
90
 
95
- # Mass-assign of attribute values
91
+ # Mass-assign attribute values
92
+ #
93
+ # Keys in the +attribute_values+ param can be symbols or strings.
94
+ # Only non-private referenced Attribute writer methods will be called.
95
+ # Non-attribute setter methods on the receiver will not be called.
96
96
  #
97
97
  # @example
98
98
  # class User
@@ -103,18 +103,20 @@ module Virtus
103
103
  # end
104
104
  #
105
105
  # user = User.new
106
- # user.attributes = { :name => 'John', :age => 28 }
106
+ # user.attributes = { :name => 'John', 'age' => 28 }
107
107
  #
108
- # @param [#to_hash] attributes
109
- # a hash of attribute values to be set on an object
108
+ # @param [#to_hash] attribute_values
109
+ # a hash of attribute names and values to set on the receiver
110
110
  #
111
111
  # @return [Hash]
112
112
  #
113
113
  # @api public
114
- def attributes=(attributes)
115
- attributes.to_hash.each do |name, value|
116
- attribute_set(name, value) if respond_to?("#{name}=")
117
- end
114
+ def attributes=(attribute_values)
115
+ attributes = self.class.attributes
116
+ set_attributes(attribute_values.select { |name,|
117
+ attribute = attributes[name]
118
+ attribute && attribute.public_writer?
119
+ })
118
120
  end
119
121
 
120
122
  # Returns a hash of all publicly accessible attributes
@@ -139,26 +141,31 @@ module Virtus
139
141
 
140
142
  private
141
143
 
142
- # Returns a value of the attribute with the given name
144
+ # Mass-assign attribute values
143
145
  #
144
- # @see Virtus::InstanceMethods#[]
146
+ # Keys in the +attribute_values+ param can be symbols or strings.
147
+ # All referenced Attribute writer methods *will* be called.
148
+ # Non-attribute setter methods on the receiver *will* be called.
145
149
  #
146
- # @return [Object]
150
+ # @example
151
+ # class User
152
+ # include Virtus
147
153
  #
148
- # @api private
149
- def attribute_get(name)
150
- __send__(name)
151
- end
152
-
153
- # Sets a value of the attribute with the given name
154
+ # attribute :name, String
155
+ # attribute :age, Integer
156
+ # end
154
157
  #
155
- # @see Virtus::InstanceMethods#[]=
158
+ # user = User.new
159
+ # user.attributes = { :name => 'John', 'age' => 28 }
156
160
  #
157
- # @return [Object]
161
+ # @param [#to_hash] attribute_values
162
+ # a hash of attribute names and values to set on the receiver
163
+ #
164
+ # @return [Hash]
158
165
  #
159
166
  # @api private
160
- def attribute_set(name, value)
161
- __send__("#{name}=", value)
167
+ def set_attributes(attribute_values)
168
+ attribute_values.each { |name, value| self[name] = value }
162
169
  end
163
170
 
164
171
  end # module InstanceMethods
@@ -14,12 +14,10 @@ module Virtus
14
14
  #
15
15
  # @api public
16
16
  def options
17
- options = {}
18
- accepted_options.each do |option_name|
17
+ accepted_options.each_with_object({}) do |option_name, options|
19
18
  option_value = send(option_name)
20
19
  options[option_name] = option_value unless option_value.nil?
21
20
  end
22
- options
23
21
  end
24
22
 
25
23
  # Returns an array of valid options
@@ -43,8 +41,7 @@ module Virtus
43
41
  # accept_options :foo, :bar
44
42
  # end
45
43
  #
46
- # @return [Array]
47
- # All accepted options
44
+ # @return [self]
48
45
  #
49
46
  # @api public
50
47
  def accept_options(*new_options)
@@ -66,20 +63,21 @@ module Virtus
66
63
  def self.#{option}(value = Undefined) # def self.primitive(value = Undefined)
67
64
  return @#{option} if value.equal?(Undefined) # return @primitive if value.equal?(Undefined)
68
65
  @#{option} = value # @primitive = value
66
+ self # self
69
67
  end # end
70
68
  RUBY
71
69
  end
72
70
 
73
71
  # Sets default options
74
72
  #
75
- # @param [#to_hash] new_options
73
+ # @param [#each] new_options
76
74
  # options to be set
77
75
  #
78
76
  # @return [self]
79
77
  #
80
78
  # @api private
81
79
  def set_options(new_options)
82
- new_options.to_hash.each { |pair| send(*pair) }
80
+ new_options.each { |pair| send(*pair) }
83
81
  self
84
82
  end
85
83
 
@@ -92,7 +90,7 @@ module Virtus
92
90
  #
93
91
  # @api private
94
92
  def add_accepted_options(new_options)
95
- accepted_options.concat(new_options.to_ary)
93
+ accepted_options.concat(new_options)
96
94
  self
97
95
  end
98
96
 
@@ -3,7 +3,8 @@ module Virtus
3
3
  # A module that adds type lookup to a class
4
4
  module TypeLookup
5
5
 
6
- TYPE_FORMAT = /\A(?:[A-Z]\w*)\z/.freeze
6
+ TYPE_FORMAT = /\A[A-Z]\w*\z/.freeze
7
+ EXTRA_CONST_ARGS = RUBY_VERSION < '1.9' || RUBY_ENGINE == 'rbx' ? [] : [ false ]
7
8
 
8
9
  # Returns a descendant based on a name or class
9
10
  #
@@ -84,16 +85,8 @@ module Virtus
84
85
  #
85
86
  # @api private
86
87
  def determine_type_from_string(string)
87
- if string =~ TYPE_FORMAT && const_defined?(string, false)
88
- const_get(string, false)
89
- end
90
- end
91
-
92
- if RUBY_VERSION < '1.9' || RUBY_ENGINE == 'rbx'
93
- def determine_type_from_string(string)
94
- if string =~ TYPE_FORMAT && const_defined?(string)
95
- const_get(string)
96
- end
88
+ if string =~ TYPE_FORMAT && const_defined?(string, *EXTRA_CONST_ARGS)
89
+ const_get(string, *EXTRA_CONST_ARGS)
97
90
  end
98
91
  end
99
92
 
@@ -1,3 +1,3 @@
1
1
  module Virtus
2
- VERSION = '0.0.10'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ # TODO: refactor to make it inline with the new style of integration specs
4
+
5
+ class Address
6
+ include Virtus
7
+
8
+ attribute :address, String
9
+ attribute :locality, String
10
+ attribute :region, String
11
+ attribute :postal_code, String
12
+ end
13
+
14
+ class PhoneNumber
15
+ include Virtus
16
+
17
+ attribute :number, String
18
+ end
19
+
20
+ class User
21
+ include Virtus
22
+
23
+ attribute :phone_numbers, Array[PhoneNumber]
24
+ attribute :addresses, Set[Address]
25
+ end
26
+
27
+ describe User do
28
+ it { should respond_to(:phone_numbers) }
29
+ it { should respond_to(:phone_numbers=) }
30
+ it { should respond_to(:addresses) }
31
+ it { should respond_to(:addresses=) }
32
+
33
+ let(:instance) do
34
+ described_class.new(:phone_numbers => phone_numbers_attributes,
35
+ :addresses => addresses_attributes)
36
+ end
37
+
38
+ let(:phone_numbers_attributes) { [
39
+ { :number => '212-555-1212' },
40
+ { :number => '919-444-3265' },
41
+ ] }
42
+
43
+ let(:addresses_attributes) { [
44
+ { :address => '1234 Any St.', :locality => 'Anytown', :region => "DC", :postal_code => "21234" },
45
+ ] }
46
+
47
+ describe '#phone_numbers' do
48
+ describe 'first entry' do
49
+ subject { instance.phone_numbers.first }
50
+
51
+ it { should be_instance_of(PhoneNumber) }
52
+
53
+ its(:number) { should eql('212-555-1212') }
54
+ end
55
+
56
+ describe 'last entry' do
57
+ subject { instance.phone_numbers.last }
58
+
59
+ it { should be_instance_of(PhoneNumber) }
60
+
61
+ its(:number) { should eql('919-444-3265') }
62
+ end
63
+ end
64
+
65
+ describe '#addresses' do
66
+ subject { instance.addresses.first }
67
+
68
+ it { should be_instance_of(Address) }
69
+
70
+ its(:address) { should eql('1234 Any St.') }
71
+ its(:locality) { should eql('Anytown') }
72
+ its(:region) { should eql('DC') }
73
+ its(:postal_code) { should eql('21234') }
74
+ end
75
+ end