virtus 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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