virtus 0.0.5 → 0.0.6

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.
Files changed (113) hide show
  1. data/.travis.yml +9 -2
  2. data/.yardopts +1 -0
  3. data/History.md +51 -0
  4. data/{README.markdown → README.md} +63 -7
  5. data/TODO +2 -4
  6. data/VERSION +1 -1
  7. data/config/flay.yml +2 -2
  8. data/config/flog.yml +1 -1
  9. data/config/roodi.yml +5 -6
  10. data/config/site.reek +3 -3
  11. data/examples/custom_coercion_spec.rb +50 -0
  12. data/examples/default_values_spec.rb +21 -0
  13. data/lib/virtus.rb +21 -6
  14. data/lib/virtus/attribute.rb +113 -253
  15. data/lib/virtus/attribute/array.rb +6 -3
  16. data/lib/virtus/attribute/boolean.rb +9 -28
  17. data/lib/virtus/attribute/date.rb +9 -12
  18. data/lib/virtus/attribute/date_time.rb +10 -12
  19. data/lib/virtus/attribute/decimal.rb +4 -11
  20. data/lib/virtus/attribute/float.rb +4 -11
  21. data/lib/virtus/attribute/hash.rb +5 -3
  22. data/lib/virtus/attribute/integer.rb +4 -11
  23. data/lib/virtus/attribute/numeric.rb +1 -0
  24. data/lib/virtus/attribute/object.rb +1 -0
  25. data/lib/virtus/attribute/string.rb +4 -11
  26. data/lib/virtus/attribute/time.rb +9 -16
  27. data/lib/virtus/class_methods.rb +42 -7
  28. data/lib/virtus/coercion.rb +32 -0
  29. data/lib/virtus/coercion/date.rb +26 -0
  30. data/lib/virtus/coercion/date_time.rb +26 -0
  31. data/lib/virtus/coercion/decimal.rb +40 -0
  32. data/lib/virtus/coercion/false_class.rb +24 -0
  33. data/lib/virtus/coercion/float.rb +24 -0
  34. data/lib/virtus/coercion/hash.rb +82 -0
  35. data/lib/virtus/coercion/integer.rb +60 -0
  36. data/lib/virtus/coercion/numeric.rb +66 -0
  37. data/lib/virtus/coercion/object.rb +25 -0
  38. data/lib/virtus/coercion/string.rb +155 -0
  39. data/lib/virtus/coercion/symbol.rb +24 -0
  40. data/lib/virtus/coercion/time.rb +26 -0
  41. data/lib/virtus/coercion/time_coercions.rb +85 -0
  42. data/lib/virtus/coercion/true_class.rb +24 -0
  43. data/lib/virtus/instance_methods.rb +7 -0
  44. data/lib/virtus/support/descendants_tracker.rb +1 -1
  45. data/lib/virtus/support/options.rb +114 -0
  46. data/lib/virtus/support/type_lookup.rb +95 -0
  47. data/spec/integration/virtus/attributes/attribute/{typecast_spec.rb → set_spec.rb} +7 -7
  48. data/spec/unit/shared/attribute.rb +3 -3
  49. data/spec/unit/shared/attribute/accept_options.rb +0 -18
  50. data/spec/unit/shared/attribute/accepted_options.rb +0 -6
  51. data/spec/unit/shared/attribute/get.rb +32 -17
  52. data/spec/unit/shared/attribute/inspect.rb +7 -0
  53. data/spec/unit/shared/attribute/primitive.rb +15 -0
  54. data/spec/unit/shared/attribute/set.rb +16 -21
  55. data/spec/unit/virtus/attribute/array_spec.rb +18 -3
  56. data/spec/unit/virtus/attribute/boolean_spec.rb +8 -6
  57. data/spec/unit/virtus/attribute/date_spec.rb +8 -6
  58. data/spec/unit/virtus/attribute/date_time_spec.rb +8 -6
  59. data/spec/unit/virtus/attribute/decimal_spec.rb +18 -6
  60. data/spec/unit/virtus/attribute/float_spec.rb +19 -7
  61. data/spec/unit/virtus/attribute/hash_spec.rb +5 -3
  62. data/spec/unit/virtus/attribute/integer_spec.rb +10 -8
  63. data/spec/unit/virtus/attribute/string_spec.rb +10 -8
  64. data/spec/unit/virtus/attribute/time_spec.rb +8 -6
  65. data/spec/unit/virtus/class_methods/attributes_spec.rb +11 -0
  66. data/spec/unit/virtus/coercion/class_name_reference_spec.rb +17 -0
  67. data/spec/unit/virtus/coercion/date/class_methods/to_datetime_spec.rb +30 -0
  68. data/spec/unit/virtus/coercion/date/class_methods/to_string_spec.rb +12 -0
  69. data/spec/unit/virtus/coercion/date/class_methods/to_time_spec.rb +12 -0
  70. data/spec/unit/virtus/coercion/date_time/class_methods/to_date_spec.rb +30 -0
  71. data/spec/unit/virtus/coercion/date_time/class_methods/to_string_spec.rb +12 -0
  72. data/spec/unit/virtus/coercion/date_time/class_methods/to_time_spec.rb +30 -0
  73. data/spec/unit/virtus/coercion/decimal/class_methods/to_float_spec.rb +12 -0
  74. data/spec/unit/virtus/coercion/decimal/class_methods/to_integer_spec.rb +12 -0
  75. data/spec/unit/virtus/coercion/decimal/class_methods/to_string_spec.rb +12 -0
  76. data/spec/unit/virtus/coercion/false_class/class_methods/to_string_spec.rb +12 -0
  77. data/spec/unit/virtus/coercion/float/class_methods/to_decimal_spec.rb +12 -0
  78. data/spec/unit/virtus/coercion/float/class_methods/to_integer_spec.rb +12 -0
  79. data/spec/unit/virtus/coercion/float/class_methods/to_string_spec.rb +12 -0
  80. data/spec/unit/virtus/coercion/hash/class_methods/to_array_spec.rb +12 -0
  81. data/spec/unit/virtus/coercion/hash/class_methods/to_date_spec.rb +31 -0
  82. data/spec/unit/virtus/coercion/hash/class_methods/to_datetime_spec.rb +31 -0
  83. data/spec/unit/virtus/coercion/hash/class_methods/to_time_spec.rb +31 -0
  84. data/spec/unit/virtus/coercion/integer/class_methods/to_boolean_spec.rb +25 -0
  85. data/spec/unit/virtus/coercion/integer/class_methods/to_decimal_spec.rb +12 -0
  86. data/spec/unit/virtus/coercion/integer/class_methods/to_float_spec.rb +12 -0
  87. data/spec/unit/virtus/coercion/integer/class_methods/to_string_spec.rb +12 -0
  88. data/spec/unit/virtus/coercion/object/class_methods/method_missing_spec.rb +33 -0
  89. data/spec/unit/virtus/coercion/string/class_methods/to_boolean_spec.rb +29 -0
  90. data/spec/unit/virtus/coercion/string/class_methods/to_date_spec.rb +23 -0
  91. data/spec/unit/virtus/coercion/string/class_methods/to_datetime_spec.rb +50 -0
  92. data/spec/unit/virtus/coercion/string/class_methods/to_decimal_spec.rb +23 -0
  93. data/spec/unit/virtus/coercion/string/class_methods/to_float_spec.rb +21 -0
  94. data/spec/unit/virtus/coercion/string/class_methods/to_integer_spec.rb +21 -0
  95. data/spec/unit/virtus/coercion/string/class_methods/to_time_spec.rb +50 -0
  96. data/spec/unit/virtus/coercion/symbol/class_methods/to_string_spec.rb +12 -0
  97. data/spec/unit/virtus/coercion/true_class/class_methods/to_string_spec.rb +12 -0
  98. data/spec/unit/virtus/instance_methods/attributes_spec.rb +7 -0
  99. data/spec/unit/virtus/options/accept_options_spec.rb +38 -0
  100. data/spec/unit/virtus/options/accepted_options_spec.rb +21 -0
  101. data/spec/unit/virtus/options/options_spec.rb +11 -0
  102. data/spec/unit/virtus/type_lookup/determine_type_spec.rb +68 -0
  103. data/spec/unit/virtus/type_lookup/primitive_spec.rb +9 -0
  104. data/virtus.gemspec +70 -17
  105. metadata +78 -27
  106. data/History.txt +0 -38
  107. data/lib/virtus/typecast/boolean.rb +0 -29
  108. data/lib/virtus/typecast/numeric.rb +0 -87
  109. data/lib/virtus/typecast/string.rb +0 -24
  110. data/lib/virtus/typecast/time.rb +0 -192
  111. data/spec/unit/shared/attribute/complex.rb +0 -15
  112. data/spec/unit/shared/attribute/options.rb +0 -7
  113. data/spec/unit/virtus/attribute/attribute_spec.rb +0 -12
@@ -0,0 +1,32 @@
1
+ module Virtus
2
+
3
+ # Coerce abstract class
4
+ #
5
+ # @abstract
6
+ #
7
+ class Coercion
8
+ extend DescendantsTracker
9
+ extend TypeLookup
10
+ extend Options
11
+
12
+ accept_options :primitive
13
+
14
+ # Return a class that matches given name
15
+ #
16
+ # Defaults to Virtus::Coercion::Object
17
+ #
18
+ # @example
19
+ # Virtus::Coercion['String'] # => Virtus::Coercion::String
20
+ # Virtus::Coercion[String] # => Virtus::Coercion::String
21
+ #
22
+ # @param [String]
23
+ #
24
+ # @return [Class]
25
+ #
26
+ # @api private
27
+ def self.[](name)
28
+ determine_type(name) || Coercion::Object
29
+ end
30
+
31
+ end # Coerce
32
+ end # Virtus
@@ -0,0 +1,26 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce Date values
5
+ class Date < Object
6
+ extend TimeCoercions
7
+
8
+ primitive ::Date
9
+
10
+ # Passthrough the value
11
+ #
12
+ # @example
13
+ # Virtus::Coercion::DateTime.to_date(date) # => Date object
14
+ #
15
+ # @param [DateTime] value
16
+ #
17
+ # @return [Date]
18
+ #
19
+ # @api public
20
+ def self.to_date(value)
21
+ value
22
+ end
23
+
24
+ end # class Date
25
+ end # class Coercion
26
+ end # module Virtus
@@ -0,0 +1,26 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce DateTime values
5
+ class DateTime < Object
6
+ primitive ::DateTime
7
+
8
+ extend TimeCoercions
9
+
10
+ # Passthrough the value
11
+ #
12
+ # @example
13
+ # Virtus::Coercion::DateTime.to_datetime(datetime) # => DateTime object
14
+ #
15
+ # @param [DateTime] value
16
+ #
17
+ # @return [Date]
18
+ #
19
+ # @api public
20
+ def self.to_datetime(value)
21
+ value
22
+ end
23
+
24
+ end # class DateTime
25
+ end # class Coercion
26
+ end # module Virtus
@@ -0,0 +1,40 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce BigDecimal values
5
+ class Decimal < Numeric
6
+ primitive ::BigDecimal
7
+
8
+ FLOAT_FORMAT = 'F'.freeze
9
+
10
+ # Coerce given value to String
11
+ #
12
+ # @example
13
+ # Virtus::Coercion::BigDecimal.to_string(BigDecimal('1.0')) # => "1.0"
14
+ #
15
+ # @param [BigDecimal] value
16
+ #
17
+ # @return [String]
18
+ #
19
+ # @api public
20
+ def self.to_string(value)
21
+ value.to_s(FLOAT_FORMAT)
22
+ end
23
+
24
+ # Passthrough the value
25
+ #
26
+ # @example
27
+ # Virtus::Coercion::BigDecimal.to_decimal(BigDecimal('1.0')) # => BigDecimal('1.0')
28
+ #
29
+ # @param [BigDecimal] value
30
+ #
31
+ # @return [Fixnum]
32
+ #
33
+ # @api public
34
+ def self.to_decimal(value)
35
+ value
36
+ end
37
+
38
+ end # class BigDecimal
39
+ end # class Coercion
40
+ end # module Virtus
@@ -0,0 +1,24 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce false values
5
+ class FalseClass < Object
6
+ primitive ::FalseClass
7
+
8
+ # Coerce given value to String
9
+ #
10
+ # @example
11
+ # Virtus::Coercion::FalseClass.to_string(false) # => "false"
12
+ #
13
+ # @param [FalseClass] value
14
+ #
15
+ # @return [String]
16
+ #
17
+ # @api public
18
+ def self.to_string(value)
19
+ value.to_s
20
+ end
21
+
22
+ end # class FalseClass
23
+ end # class Coercion
24
+ end # module Virtus
@@ -0,0 +1,24 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce Float values
5
+ class Float < Numeric
6
+ primitive ::Float
7
+
8
+ # Passthrough the value
9
+ #
10
+ # @example
11
+ # Virtus::Coercion::Float.to_float(1.0) # => 1.0
12
+ #
13
+ # @param [Float] value
14
+ #
15
+ # @return [Integer]
16
+ #
17
+ # @api public
18
+ def self.to_float(value)
19
+ value
20
+ end
21
+
22
+ end # class Float
23
+ end # class Coercion
24
+ end # module Virtus
@@ -0,0 +1,82 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce Hash values
5
+ class Hash < Object
6
+ primitive ::Hash
7
+
8
+ TIME_SEGMENTS = [ :year, :month, :day, :hour, :min, :sec ].freeze
9
+
10
+ # Creates an Array instance from a Hash
11
+ #
12
+ # @param [Hash] value
13
+ #
14
+ # @return [Array]
15
+ #
16
+ # @api private
17
+ def self.to_array(value)
18
+ value.to_a
19
+ end
20
+
21
+ # Creates a Time instance from a Hash
22
+ #
23
+ # Valid keys are: :year, :month, :day, :hour, :min, :sec
24
+ #
25
+ # @param [Hash] value
26
+ #
27
+ # @return [Time]
28
+ #
29
+ # @api private
30
+ def self.to_time(value)
31
+ ::Time.local(*extract(value))
32
+ end
33
+
34
+ # Creates a Date instance from a Hash
35
+ #
36
+ # Valid keys are: :year, :month, :day, :hour
37
+ #
38
+ # @param [Hash] value
39
+ #
40
+ # @return [Date]
41
+ #
42
+ # @api private
43
+ def self.to_date(value)
44
+ ::Date.new(*extract(value).first(3))
45
+ end
46
+
47
+ # Creates a DateTime instance from a Hash
48
+ #
49
+ # Valid keys are: :year, :month, :day, :hour, :min, :sec
50
+ #
51
+ # @param [Hash] value
52
+ #
53
+ # @return [DateTime]
54
+ #
55
+ # @api private
56
+ def self.to_datetime(value)
57
+ ::DateTime.new(*extract(value))
58
+ end
59
+
60
+ # Extracts the given args from a Hash
61
+ #
62
+ # If a value does not exist, it uses the value of Time.now
63
+ #
64
+ # @param [Hash] value
65
+ #
66
+ # @return [Array]
67
+ #
68
+ # @api private
69
+ def self.extract(value)
70
+ now = ::Time.now
71
+
72
+ TIME_SEGMENTS.map do |segment|
73
+ val = value.fetch(segment, now.send(segment))
74
+ Coercion[val.class.name].to_integer(val)
75
+ end
76
+ end
77
+
78
+ private_class_method :extract
79
+
80
+ end # class Hash
81
+ end # class Coercion
82
+ end # module Virtus
@@ -0,0 +1,60 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce Fixnum values
5
+ class Integer < Numeric
6
+ primitive ::Integer
7
+
8
+ # Coerce given value to String
9
+ #
10
+ # @example
11
+ # Virtus::Coercion::Fixnum.to_string(1) # => "1"
12
+ #
13
+ # @param [Fixnum] value
14
+ #
15
+ # @return [String]
16
+ #
17
+ # @api public
18
+ def self.to_string(value)
19
+ value.to_s
20
+ end
21
+
22
+ # Passthrough the value
23
+ #
24
+ # @example
25
+ # Virtus::Coercion::Fixnum.to_integer(1) # => 1
26
+ #
27
+ # @param [Fixnum] value
28
+ #
29
+ # @return [Float]
30
+ #
31
+ # @api public
32
+ def self.to_integer(value)
33
+ value
34
+ end
35
+
36
+ # Coerce given value to a Boolean
37
+ #
38
+ # @example with a 1
39
+ # Virtus::Coercion::Fixnum.to_boolean(1) # => true
40
+ #
41
+ # @example with a 0
42
+ # Virtus::Coercion::Fixnum.to_boolean(0) # => false
43
+ #
44
+ # @param [Fixnum] value
45
+ #
46
+ # @return [BigDecimal]
47
+ #
48
+ # @api public
49
+ def self.to_boolean(value)
50
+ case value
51
+ when 1 then true
52
+ when 0 then false
53
+ else
54
+ value
55
+ end
56
+ end
57
+
58
+ end # class Fixnum
59
+ end # class Coercion
60
+ end # module Virtus
@@ -0,0 +1,66 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Base class for all numeric Coercion classes
5
+ class Numeric < Object
6
+ primitive ::Numeric
7
+
8
+ # Coerce given value to String
9
+ #
10
+ # @example
11
+ # Virtus::Coercion::Float.to_string(1.0) # => "1.0"
12
+ #
13
+ # @param [Float] value
14
+ #
15
+ # @return [String]
16
+ #
17
+ # @api public
18
+ def self.to_string(value)
19
+ value.to_s
20
+ end
21
+
22
+ # Creates a Fixnum instance from a numeric object
23
+ #
24
+ # @example
25
+ # Virtus::Coercion::BigDecimal.to_integer(BigDecimal('1.0')) # => 1
26
+ #
27
+ # @param [BigDecimal] value
28
+ #
29
+ # @return [Fixnum]
30
+ #
31
+ # @api public
32
+ def self.to_integer(value)
33
+ value.to_i
34
+ end
35
+
36
+ # Creates a Float instance from a numeric object
37
+ #
38
+ # @example
39
+ # Virtus::Coercion::BigDecimal.to_float(BigDecimal('1.0')) # => 1.0
40
+ #
41
+ # @param [BigDecimal] value
42
+ #
43
+ # @return [Fixnum]
44
+ #
45
+ # @api public
46
+ def self.to_float(value)
47
+ value.to_f
48
+ end
49
+
50
+ # Coerce given value to BigDecimal
51
+ #
52
+ # @example
53
+ # Virtus::Coercion::Float.to_decimal(1.0) # => BigDecimal('1.0')
54
+ #
55
+ # @param [Float] value
56
+ #
57
+ # @return [BigDecimal]
58
+ #
59
+ # @api public
60
+ def self.to_decimal(value)
61
+ to_string(value).to_d
62
+ end
63
+
64
+ end
65
+ end # class Coercion
66
+ end # module Virtus
@@ -0,0 +1,25 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce Object values
5
+ class Object < Coercion
6
+ primitive ::Object
7
+
8
+ # Passthrough given value
9
+ #
10
+ # @param [Object] value
11
+ #
12
+ # @return [Object]
13
+ #
14
+ # @api private
15
+ def self.method_missing(method, *args)
16
+ if method.to_s[0, 3] == 'to_' && args.size == 1
17
+ args.first
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ end # class Object
24
+ end # class Coercion
25
+ end # module Virtus
@@ -0,0 +1,155 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce String values
5
+ class String < Object
6
+ primitive ::String
7
+
8
+ TRUE_VALUES = %w[ 1 t true ].freeze
9
+ FALSE_VALUES = %w[ 0 f false ].freeze
10
+ BOOLEAN_MAP = ::Hash[ TRUE_VALUES.product([ true ]) + FALSE_VALUES.product([ false ]) ].freeze
11
+
12
+ NUMERIC_REGEXP = /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/.freeze
13
+
14
+ # Coerce given value to Time
15
+ #
16
+ # @example
17
+ # Virtus::Coercion::String.to_time(string) # => Time object
18
+ #
19
+ # @param [String] value
20
+ #
21
+ # @return [Time]
22
+ #
23
+ # @api public
24
+ def self.to_time(value)
25
+ parse_value(::Time, value)
26
+ end
27
+
28
+ # Coerce given value to Date
29
+ #
30
+ # @example
31
+ # Virtus::Coercion::String.to_date(string) # => Date object
32
+ #
33
+ # @param [String] value
34
+ #
35
+ # @return [Date]
36
+ #
37
+ # @api public
38
+ def self.to_date(value)
39
+ parse_value(::Date, value)
40
+ end
41
+
42
+ # Coerce given value to DateTime
43
+ #
44
+ # @example
45
+ # Virtus::Coercion::String.to_datetime(string) # => DateTime object
46
+ #
47
+ # @param [String] value
48
+ #
49
+ # @return [DateTime]
50
+ #
51
+ # @api public
52
+ def self.to_datetime(value)
53
+ parse_value(::DateTime, value)
54
+ end
55
+
56
+ # Coerce value to TrueClass or FalseClass
57
+ #
58
+ # @example with "T"
59
+ # Virtus::Coercion::String.to_boolean('T') # => true
60
+ #
61
+ # @example with "F"
62
+ # Virtus::Coercion::String.to_boolean('F') # => false
63
+ #
64
+ # @param [#to_s]
65
+ #
66
+ # @return [Boolean]
67
+ #
68
+ # @api public
69
+ def self.to_boolean(value)
70
+ BOOLEAN_MAP.fetch(value.downcase, value)
71
+ end
72
+
73
+ # Coerce value to integer
74
+ #
75
+ # @example
76
+ # Virtus::Coercion::String.to_integer('1') # => 1
77
+ #
78
+ # @param [Object] value
79
+ #
80
+ # @return [Integer]
81
+ #
82
+ # @api public
83
+ def self.to_integer(value)
84
+ to_numeric(value, :to_i)
85
+ end
86
+
87
+ # Coerce value to float
88
+ #
89
+ # @example
90
+ # Virtus::Coercion::String.to_float('1.2') # => 1.2
91
+ #
92
+ # @param [Object] value
93
+ #
94
+ # @return [Float]
95
+ #
96
+ # @api public
97
+ def self.to_float(value)
98
+ to_numeric(value, :to_f)
99
+ end
100
+
101
+ # Coerce value to decimal
102
+ #
103
+ # @example
104
+ # Virtus::Coercion::String.to_decimal('1.2') # => #<BigDecimal:b72157d4,'0.12E1',8(8)>
105
+ #
106
+ # @param [Object] value
107
+ #
108
+ # @return [BigDecimal]
109
+ #
110
+ # @api public
111
+ def self.to_decimal(value)
112
+ to_numeric(value, :to_d)
113
+ end
114
+
115
+ # Match numeric string
116
+ #
117
+ # @param [String] value
118
+ # value to typecast
119
+ # @param [Symbol] method
120
+ # method to typecast with
121
+ #
122
+ # @return [Numeric]
123
+ # number if matched, value if no match
124
+ #
125
+ # @api private
126
+ def self.to_numeric(value, method)
127
+ if value =~ NUMERIC_REGEXP
128
+ $1.send(method)
129
+ else
130
+ value
131
+ end
132
+ end
133
+
134
+ private_class_method :to_numeric
135
+
136
+ # Parse the value or return it as-is if it is invalid
137
+ #
138
+ # @param [#parse] parser
139
+ #
140
+ # @param [String] value
141
+ #
142
+ # @return [Time]
143
+ #
144
+ # @api private
145
+ def self.parse_value(parser, value)
146
+ parser.parse(value)
147
+ rescue ArgumentError
148
+ return value
149
+ end
150
+
151
+ private_class_method :parse_value
152
+
153
+ end # class String
154
+ end # class Coercion
155
+ end # module Virtus