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,24 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce Symbol values
5
+ class Symbol < Object
6
+ primitive ::Symbol
7
+
8
+ # Coerce given value to String
9
+ #
10
+ # @example
11
+ # Virtus::Coercion::Symbol.to_string(:name) # => "name"
12
+ #
13
+ # @param [Symbol] 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 Symbol
23
+ end # class Coercion
24
+ end # module Virtus
@@ -0,0 +1,26 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce Time values
5
+ class Time < Object
6
+ extend TimeCoercions
7
+
8
+ primitive ::Time
9
+
10
+ # Passthrough the value
11
+ #
12
+ # @example
13
+ # Virtus::Coercion::DateTime.to_time(time) # => Time object
14
+ #
15
+ # @param [DateTime] value
16
+ #
17
+ # @return [Date]
18
+ #
19
+ # @api public
20
+ def self.to_time(value)
21
+ value
22
+ end
23
+
24
+ end # class Time
25
+ end # class Coercion
26
+ end # module Virtus
@@ -0,0 +1,85 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Common time coercion methods
5
+ module TimeCoercions
6
+
7
+ # Coerce given value to String
8
+ #
9
+ # @example
10
+ # Virtus::Coercion::Time.to_string(time) # => "Wed Jul 20 10:30:41 -0700 2011"
11
+ #
12
+ # @param [Date,Time,DateTime] value
13
+ #
14
+ # @return [String]
15
+ #
16
+ # @api public
17
+ def to_string(value)
18
+ value.to_s
19
+ end
20
+
21
+ # Coerce given value to Time
22
+ #
23
+ # @example
24
+ # Virtus::Coercion::DateTime.to_time(datetime) # => Time object
25
+ #
26
+ # @param [Date,DateTime] value
27
+ #
28
+ # @return [Time]
29
+ #
30
+ # @api public
31
+ def to_time(value)
32
+ coerce_with_method(value, :to_time)
33
+ end
34
+
35
+ # Coerce given value to DateTime
36
+ #
37
+ # @example
38
+ # Virtus::Coercion::Time.to_datetime(time) # => DateTime object
39
+ #
40
+ # @param [Date,Time] value
41
+ #
42
+ # @return [DateTime]
43
+ #
44
+ # @api public
45
+ def to_datetime(value)
46
+ coerce_with_method(value, :to_datetime)
47
+ end
48
+
49
+ # Coerce given value to Date
50
+ #
51
+ # @example
52
+ # Virtus::Coercion::Time.to_date(time) # => Date object
53
+ #
54
+ # @param [Time,DateTime] value
55
+ #
56
+ # @return [Date]
57
+ #
58
+ # @api public
59
+ def to_date(value)
60
+ coerce_with_method(value, :to_date)
61
+ end
62
+
63
+ private
64
+
65
+ # Try to use native coercion method on the given value
66
+ #
67
+ # Falls back to String-based parsing
68
+ #
69
+ # @param [Date,DateTime,Time] value
70
+ # @param [Symbol] method
71
+ #
72
+ # @return [Date,DateTime,Time]
73
+ #
74
+ # @api private
75
+ def coerce_with_method(value, method)
76
+ if value.respond_to?(method)
77
+ value.send(method)
78
+ else
79
+ String.send(method, to_string(value))
80
+ end
81
+ end
82
+
83
+ end # module TimeCoercions
84
+ end # class Coercion
85
+ end # module Virtus
@@ -0,0 +1,24 @@
1
+ module Virtus
2
+ class Coercion
3
+
4
+ # Coerce true values
5
+ class TrueClass < Object
6
+ primitive ::TrueClass
7
+
8
+ # Coerce given value to String
9
+ #
10
+ # @example
11
+ # Virtus::Coercion::TrueClass.to_string(true) # => "true"
12
+ #
13
+ # @param [TrueClass] 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 TrueClass
23
+ end # class Coercion
24
+ end # module Virtus
@@ -117,6 +117,13 @@ module Virtus
117
117
  end
118
118
  end
119
119
 
120
+ # @see Virtus::InstanceMethods#attributes
121
+ #
122
+ # @api public
123
+ def to_hash
124
+ attributes
125
+ end
126
+
120
127
  private
121
128
 
122
129
  # Returns a value of the attribute with the given name
@@ -32,7 +32,7 @@ module Virtus
32
32
  #
33
33
  # @param [Class] descendant
34
34
  #
35
- # @return [self]
35
+ # @return [undefined]
36
36
  #
37
37
  # @api private
38
38
  def inherited(descendant)
@@ -0,0 +1,114 @@
1
+ module Virtus
2
+
3
+ # A module that adds class and instance level options
4
+ module Options
5
+
6
+ # Returns default options hash for a given attribute class
7
+ #
8
+ # @example
9
+ # Virtus::Attribute::String.options
10
+ # # => {:primitive => String}
11
+ #
12
+ # @return [Hash]
13
+ # a hash of default option values
14
+ #
15
+ # @api public
16
+ def options
17
+ options = {}
18
+ accepted_options.each do |option_name|
19
+ option_value = send(option_name)
20
+ options[option_name] = option_value unless option_value.nil?
21
+ end
22
+ options
23
+ end
24
+
25
+ # Returns an array of valid options
26
+ #
27
+ # @example
28
+ # Virtus::Attribute::String.accepted_options
29
+ # # => [:primitive, :accessor, :reader, :writer]
30
+ #
31
+ # @return [Array]
32
+ # the array of valid option names
33
+ #
34
+ # @api public
35
+ def accepted_options
36
+ @accepted_options ||= []
37
+ end
38
+
39
+ # Defines which options are valid for a given attribute class
40
+ #
41
+ # @example
42
+ # class MyAttribute < Virtus::Attribute::Object
43
+ # accept_options :foo, :bar
44
+ # end
45
+ #
46
+ # @return [Array]
47
+ # All accepted options
48
+ #
49
+ # @api public
50
+ def accept_options(*new_options)
51
+ add_accepted_options(new_options)
52
+ new_options.each { |option| define_option_method(option) }
53
+ descendants.each { |descendant| descendant.add_accepted_options(new_options) }
54
+ self
55
+ end
56
+
57
+ protected
58
+
59
+ # Adds a reader/writer method for the give option name
60
+ #
61
+ # @return [undefined]
62
+ #
63
+ # @api private
64
+ def define_option_method(option)
65
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
66
+ def self.#{option}(value = Undefined) # def self.primitive(value = Undefined)
67
+ return @#{option} if value.equal?(Undefined) # return @primitive if value.equal?(Undefined)
68
+ @#{option} = value # @primitive = value
69
+ end # end
70
+ RUBY
71
+ end
72
+
73
+ # Sets default options
74
+ #
75
+ # @param [#to_hash] new_options
76
+ # options to be set
77
+ #
78
+ # @return [self]
79
+ #
80
+ # @api private
81
+ def set_options(new_options)
82
+ new_options.to_hash.each { |pair| send(*pair) }
83
+ self
84
+ end
85
+
86
+ # Adds new options that an attribute class can accept
87
+ #
88
+ # @param [#to_ary] new_options
89
+ # new options to be added
90
+ #
91
+ # @return [self]
92
+ #
93
+ # @api private
94
+ def add_accepted_options(new_options)
95
+ accepted_options.concat(new_options.to_ary)
96
+ self
97
+ end
98
+
99
+ private
100
+
101
+ # Adds descendant to descendants array and inherits default options
102
+ #
103
+ # @param [Class] descendant
104
+ #
105
+ # @return [undefined]
106
+ #
107
+ # @api private
108
+ def inherited(descendant)
109
+ super
110
+ descendant.add_accepted_options(accepted_options).set_options(options)
111
+ end
112
+
113
+ end # module Options
114
+ end # module Virtus
@@ -0,0 +1,95 @@
1
+ module Virtus
2
+
3
+ # A module that adds type lookup to a class
4
+ module TypeLookup
5
+
6
+ TYPE_FORMAT = /\A(?:[A-Z]\w*)\z/.freeze
7
+
8
+ # Returns a descendant based on a name or class
9
+ #
10
+ # @example
11
+ # MyClass.determine_type('String') # => MyClass::String
12
+ #
13
+ # @param [Class, #to_s] class_or_name
14
+ # name of a class or a class itself
15
+ #
16
+ # @return [Class]
17
+ # a descendant
18
+ #
19
+ # @return [nil]
20
+ # nil if the type cannot be determined by the class_or_name
21
+ #
22
+ # @api public
23
+ def determine_type(class_or_name)
24
+ case class_or_name
25
+ when singleton_class
26
+ determine_type_from_descendant(class_or_name)
27
+ when Class
28
+ determine_type_from_primitive(class_or_name)
29
+ else
30
+ determine_type_from_string(class_or_name.to_s)
31
+ end
32
+ end
33
+
34
+ # Return the default primitive supported
35
+ #
36
+ # @return [Class]
37
+ #
38
+ # @api private
39
+ def primitive
40
+ raise NotImplementedError, "#{self}.primitive must be implemented"
41
+ end
42
+
43
+ private
44
+
45
+ # Return the class given a descendant
46
+ #
47
+ # @param [Class] descendant
48
+ #
49
+ # @return [Class]
50
+ #
51
+ # @api private
52
+ def determine_type_from_descendant(descendant)
53
+ descendant if descendant < self
54
+ end
55
+
56
+ # Return the class given a primitive
57
+ #
58
+ # @param [Class] primitive
59
+ #
60
+ # @return [Class]
61
+ #
62
+ # @return [nil]
63
+ # nil if the type cannot be determined by the primitive
64
+ #
65
+ # @api private
66
+ def determine_type_from_primitive(primitive)
67
+ descendants.detect { |descendant| primitive <= descendant.primitive }
68
+ end
69
+
70
+ # Return the class given a string
71
+ #
72
+ # @param [String] string
73
+ #
74
+ # @return [Class]
75
+ #
76
+ # @return [nil]
77
+ # nil if the type cannot be determined by the string
78
+ #
79
+ # @api private
80
+ def determine_type_from_string(string)
81
+ if string =~ TYPE_FORMAT && const_defined?(string, false)
82
+ const_get(string, false)
83
+ end
84
+ end
85
+
86
+ if RUBY_VERSION < '1.9'
87
+ def determine_type_from_string(string)
88
+ if string =~ TYPE_FORMAT && const_defined?(string)
89
+ const_get(string)
90
+ end
91
+ end
92
+ end
93
+
94
+ end # module TypeLookup
95
+ end # module Virtus
@@ -1,10 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Virtus::Attribute, '#typecast' do
3
+ describe Virtus::Attribute, '#set' do
4
4
  let(:attribute_class) do
5
5
  Class.new(Virtus::Attribute::Integer) do
6
- def typecast(value)
7
- super + 1
6
+ def set(instance, value)
7
+ super(instance, coerce(value) + 1) unless value.nil?
8
8
  end
9
9
  end
10
10
  end
@@ -23,14 +23,14 @@ describe Virtus::Attribute, '#typecast' do
23
23
 
24
24
  context 'when overridden' do
25
25
  let(:input_value) { 1 }
26
- let(:output_value) { 2 }
26
+ let(:output_value) { 2 }
27
27
 
28
28
  before do
29
29
  object.count = input_value
30
30
  end
31
31
 
32
- it "peforms custom typecasting" do
33
- object.count.should eql(output_value)
34
- end
32
+ subject { object }
33
+
34
+ its(:count) { should eql(output_value) }
35
35
  end
36
36
  end
@@ -1,8 +1,8 @@
1
- shared_examples_for "Attribute" do
2
- it_behaves_like 'Attribute.options'
1
+ shared_examples_for 'Attribute' do
3
2
  it_behaves_like 'Attribute.accept_options'
4
3
  it_behaves_like 'Attribute.accepted_options'
4
+ it_behaves_like 'Attribute.primitive?'
5
+ it_behaves_like 'Attribute#inspect'
5
6
  it_behaves_like 'Attribute#set'
6
7
  it_behaves_like 'Attribute#get'
7
- it_behaves_like 'Attribute#complex?'
8
8
  end