virtus 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/Gemfile +11 -15
  2. data/History.txt +10 -0
  3. data/TODO +4 -0
  4. data/VERSION +1 -1
  5. data/config/flay.yml +1 -1
  6. data/config/flog.yml +1 -1
  7. data/config/roodi.yml +6 -6
  8. data/config/site.reek +5 -5
  9. data/lib/virtus.rb +21 -11
  10. data/lib/virtus/attribute.rb +92 -59
  11. data/lib/virtus/attribute/array.rb +4 -3
  12. data/lib/virtus/attribute/boolean.rb +21 -9
  13. data/lib/virtus/attribute/date.rb +5 -3
  14. data/lib/virtus/attribute/date_time.rb +5 -3
  15. data/lib/virtus/attribute/decimal.rb +5 -3
  16. data/lib/virtus/attribute/float.rb +5 -3
  17. data/lib/virtus/attribute/hash.rb +4 -3
  18. data/lib/virtus/attribute/integer.rb +5 -3
  19. data/lib/virtus/attribute/numeric.rb +5 -3
  20. data/lib/virtus/attribute/object.rb +4 -4
  21. data/lib/virtus/attribute/string.rb +7 -9
  22. data/lib/virtus/attribute/time.rb +5 -3
  23. data/lib/virtus/attribute_set.rb +151 -0
  24. data/lib/virtus/class_methods.rb +19 -10
  25. data/lib/virtus/instance_methods.rb +51 -27
  26. data/lib/virtus/support/descendants_tracker.rb +30 -0
  27. data/lib/virtus/typecast/boolean.rb +7 -5
  28. data/lib/virtus/typecast/numeric.rb +13 -8
  29. data/lib/virtus/typecast/string.rb +24 -0
  30. data/lib/virtus/typecast/time.rb +7 -5
  31. data/spec/integration/virtus/class_methods/attribute_spec.rb +17 -5
  32. data/spec/integration/virtus/class_methods/attributes_spec.rb +2 -5
  33. data/spec/shared/idempotent_method_behaviour.rb +5 -0
  34. data/spec/spec_helper.rb +15 -0
  35. data/spec/unit/shared/attribute.rb +6 -155
  36. data/spec/unit/shared/attribute/accept_options.rb +55 -0
  37. data/spec/unit/shared/attribute/accepted_options.rb +11 -0
  38. data/spec/unit/shared/attribute/complex.rb +15 -0
  39. data/spec/unit/shared/attribute/get.rb +29 -0
  40. data/spec/unit/shared/attribute/options.rb +7 -0
  41. data/spec/unit/shared/attribute/set.rb +42 -0
  42. data/spec/unit/virtus/attribute/boolean_spec.rb +1 -2
  43. data/spec/unit/virtus/attribute/date_spec.rb +1 -2
  44. data/spec/unit/virtus/attribute/date_time_spec.rb +1 -2
  45. data/spec/unit/virtus/attribute/decimal_spec.rb +1 -2
  46. data/spec/unit/virtus/attribute/float_spec.rb +1 -2
  47. data/spec/unit/virtus/attribute/integer_spec.rb +1 -2
  48. data/spec/unit/virtus/attribute/numeric/class_methods/descendants_spec.rb +2 -2
  49. data/spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb +6 -4
  50. data/spec/unit/virtus/attribute/string_spec.rb +1 -2
  51. data/spec/unit/virtus/attribute/time_spec.rb +1 -2
  52. data/spec/unit/virtus/attribute_set/append_spec.rb +35 -0
  53. data/spec/unit/virtus/attribute_set/each_spec.rb +60 -0
  54. data/spec/unit/virtus/attribute_set/element_reference_spec.rb +13 -0
  55. data/spec/unit/virtus/attribute_set/element_set_spec.rb +35 -0
  56. data/spec/unit/virtus/attribute_set/merge_spec.rb +36 -0
  57. data/spec/unit/virtus/attribute_set/parent_spec.rb +11 -0
  58. data/spec/unit/virtus/attribute_set/reset_spec.rb +60 -0
  59. data/spec/unit/virtus/class_methods/attribute_spec.rb +11 -0
  60. data/spec/unit/virtus/descendants_tracker/descendants_spec.rb +22 -0
  61. data/spec/unit/virtus/descendants_tracker/inherited_spec.rb +24 -0
  62. data/spec/unit/virtus/determine_type_spec.rb +21 -9
  63. data/spec/unit/virtus/instance_methods/{attribute_get_spec.rb → element_reference_spec.rb} +4 -2
  64. data/spec/unit/virtus/instance_methods/{attribute_set_spec.rb → element_set_spec.rb} +5 -7
  65. data/virtus.gemspec +35 -13
  66. metadata +96 -14
  67. data/lib/virtus/support/chainable.rb +0 -13
@@ -1,5 +1,6 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Example usage:
4
5
  #
5
6
  # class Post
@@ -12,6 +13,6 @@ module Virtus
12
13
  class Array < Object
13
14
  primitive ::Array
14
15
  complex true
15
- end # Integer
16
- end # Attributes
17
- end # Virtus
16
+ end # class Array
17
+ end # class Attribute
18
+ end # module Virtus
@@ -1,5 +1,6 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Bolean attribute allows true or false values to be set
4
5
  # Additionally it adds boolean reader method, like "admin?"
5
6
  #
@@ -19,10 +20,16 @@ module Virtus
19
20
 
20
21
  # Returns if the given value is either true or false
21
22
  #
23
+ # @example
24
+ # Virtus::Attribute::Boolean.primitive?(true) # => true
25
+ # Virtus::Attribute::Boolean.primitive?(false) # => true
26
+ # Virtus::Attribute::Boolean.primitive?(1) # => false
27
+ # Virtus::Attribute::Boolean.primitive?('true') # => false
28
+ #
22
29
  # @return [TrueClass,FalseClass]
23
30
  #
24
- # @api private
25
- def primitive?(value)
31
+ # @api semipublic
32
+ def self.primitive?(value)
26
33
  value.equal?(true) || value.equal?(false)
27
34
  end
28
35
 
@@ -45,16 +52,21 @@ module Virtus
45
52
  def add_reader_method(model)
46
53
  super
47
54
 
48
- model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
49
- chainable(:attribute) do
50
- #{reader_visibility}
55
+ name = self.name
56
+ method_name = "#{name}?"
51
57
 
52
- def #{name}?
58
+ model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
59
+ module AttributeMethods
60
+ def #{method_name}
53
61
  #{name}
54
62
  end
55
63
  end
64
+ include AttributeMethods
56
65
  RUBY
66
+
67
+ model.send(reader_visibility, method_name)
57
68
  end
58
- end # Boolean
59
- end # Attributes
60
- end # Virtus
69
+
70
+ end # class Boolean
71
+ end # class Attribute
72
+ end # module Virtus
@@ -1,5 +1,6 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Example usage:
4
5
  #
5
6
  # class Post
@@ -30,6 +31,7 @@ module Virtus
30
31
  def typecast_to_primitive(value)
31
32
  Typecast::Time.to_date(value)
32
33
  end
33
- end # Date
34
- end # Attributes
35
- end # Virtus
34
+
35
+ end # class Date
36
+ end # class Attribute
37
+ end # module Virtus
@@ -1,5 +1,6 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Example usage:
4
5
  #
5
6
  # class Post
@@ -29,6 +30,7 @@ module Virtus
29
30
  def typecast_to_primitive(value)
30
31
  Typecast::Time.to_datetime(value)
31
32
  end
32
- end # DateTime
33
- end # Attributes
34
- end # Virtus
33
+
34
+ end # class DateTim
35
+ end # class Attribute
36
+ end # module Virtus
@@ -1,5 +1,6 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Example usage:
4
5
  #
5
6
  # class ExchangeRate
@@ -19,6 +20,7 @@ module Virtus
19
20
  def typecast_to_primitive(value)
20
21
  Typecast::Numeric.to_d(value)
21
22
  end
22
- end # Decimal
23
- end # Attributes
24
- end # Virtus
23
+
24
+ end # class Decimal
25
+ end # class Attribute
26
+ end # module Virtus
@@ -1,5 +1,6 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Example usage:
4
5
  #
5
6
  # class ExchangeRate
@@ -28,6 +29,7 @@ module Virtus
28
29
  def typecast_to_primitive(value)
29
30
  Typecast::Numeric.to_f(value)
30
31
  end
31
- end # Float
32
- end # Attributes
33
- end # Virtus
32
+
33
+ end # class Float
34
+ end # class Attribute
35
+ end # module Virtus
@@ -1,5 +1,6 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Example usage:
4
5
  #
5
6
  # class Post
@@ -13,6 +14,6 @@ module Virtus
13
14
  class Hash < Object
14
15
  primitive ::Hash
15
16
  complex true
16
- end # Integer
17
- end # Attributes
18
- end # Virtus
17
+ end # class Hash
18
+ end # class Attribute
19
+ end # module Virtus
@@ -1,5 +1,6 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Example usage:
4
5
  #
5
6
  # class Post
@@ -25,6 +26,7 @@ module Virtus
25
26
  def typecast_to_primitive(value)
26
27
  Typecast::Numeric.to_i(value)
27
28
  end
28
- end # Integer
29
- end # Attributes
30
- end # Virtus
29
+
30
+ end # class Integer
31
+ end # class Attribute
32
+ end # module Virtus
@@ -1,8 +1,10 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Base class for all numerical attributes
4
5
  class Numeric < Object
6
+ primitive ::Numeric
5
7
  accept_options :min, :max
6
- end # Numeric
7
- end # Attributes
8
- end # Virtus
8
+ end # class Numeric
9
+ end # class Attribute
10
+ end # module Virtus
@@ -1,9 +1,9 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Base class for every attribute
4
5
  class Object < Attribute
5
6
  primitive ::Object
6
- complex false
7
- end # Object
8
- end # Attributes
9
- end # Virtus
7
+ end # class Object
8
+ end # class Attribute
9
+ end # module Virtus
@@ -1,5 +1,6 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Example usage
4
5
  #
5
6
  # class User
@@ -16,16 +17,13 @@ module Virtus
16
17
  class String < Object
17
18
  primitive ::String
18
19
 
19
- # Typecast the given value to a string
20
- #
21
- # @param [Object]
22
- #
23
- # @return [String]
20
+ # @see Virtus::Typecast::String.call
24
21
  #
25
22
  # @api private
26
23
  def typecast_to_primitive(value)
27
- value.to_s
24
+ Virtus::Typecast::String.call(value)
28
25
  end
29
- end # String
30
- end # Attributes
31
- end # Virtus
26
+
27
+ end # class String
28
+ end # class Attribute
29
+ end # module Virtus
@@ -1,5 +1,6 @@
1
1
  module Virtus
2
2
  class Attribute
3
+
3
4
  # Example usage:
4
5
  #
5
6
  # class Post
@@ -29,6 +30,7 @@ module Virtus
29
30
  def typecast_to_primitive(value)
30
31
  Typecast::Time.to_time(value)
31
32
  end
32
- end # Time
33
- end # Attributes
34
- end # Virtus
33
+
34
+ end # class Time
35
+ end # class Attribute
36
+ end # module Virtus
@@ -0,0 +1,151 @@
1
+ module Virtus
2
+
3
+ # A set of Attribute objects
4
+ class AttributeSet
5
+ include Enumerable
6
+
7
+ # Return the parent attributes
8
+ #
9
+ # @return [AttributeSet]
10
+ # the parent attributes
11
+ #
12
+ # @return [nil]
13
+ # nil if there are no parent attributes
14
+ #
15
+ # @api private
16
+ attr_reader :parent
17
+
18
+ # Initialize an AttributeSet
19
+ #
20
+ # @param [AttributeSet] parent
21
+ # @param [Array] attributes
22
+ #
23
+ # @return [undefined]
24
+ #
25
+ # @api private
26
+ def initialize(parent = nil, attributes = [])
27
+ @parent = parent
28
+ @attributes = attributes.dup
29
+ @index = {}
30
+ reset
31
+ end
32
+
33
+ # Iterate over each attribute in the set
34
+ #
35
+ # @example
36
+ # attribute_set = AttributeSet.new(attributes, parent)
37
+ # attribute_set.each { |attribute| ... }
38
+ #
39
+ # @yield [attribute]
40
+ #
41
+ # @yieldparam [Attribute] attribute
42
+ # each attribute in the set
43
+ #
44
+ # @return [self]
45
+ #
46
+ # @api public
47
+ def each
48
+ return to_enum unless block_given?
49
+ @index.each_value { |attribute| yield attribute }
50
+ self
51
+ end
52
+
53
+ # Adds the attributes to the set
54
+ #
55
+ # @example
56
+ # attribute_set.merge(attributes)
57
+ #
58
+ # @param [Array<Attribute>] attributes
59
+ #
60
+ # @return [self]
61
+ #
62
+ # @api public
63
+ def merge(attributes)
64
+ attributes.each { |attribute| self << attribute }
65
+ self
66
+ end
67
+
68
+ # Adds an attribute to the set
69
+ #
70
+ # @example
71
+ # attribute_set << attribute
72
+ #
73
+ # @param [Attribute] attribute
74
+ #
75
+ # @return [self]
76
+ #
77
+ # @api public
78
+ def <<(attribute)
79
+ self[attribute.name] = attribute
80
+ self
81
+ end
82
+
83
+ # Get an attribute by name
84
+ #
85
+ # @example
86
+ # attribute_set[:name] # => Attribute object
87
+ #
88
+ # @param [Symbol] name
89
+ #
90
+ # @return [Attribute]
91
+ #
92
+ # @api public
93
+ def [](name)
94
+ @index[name]
95
+ end
96
+
97
+ # Set an attribute by name
98
+ #
99
+ # @example
100
+ # attribute_set[:name] = attribute
101
+ #
102
+ # @param [Symbol] name
103
+ # @param [Attribute] attribute
104
+ #
105
+ # @return [Attribute]
106
+ #
107
+ # @api public
108
+ def []=(name, attribute)
109
+ delete(name)
110
+ @attributes << attribute
111
+ @index[name] = attribute
112
+ end
113
+
114
+ # Reset the index when the parent is updated
115
+ #
116
+ # @return [self]
117
+ #
118
+ # @api private
119
+ def reset
120
+ parent = self.parent
121
+ merge_index(parent) if parent
122
+ merge_index(@attributes)
123
+ self
124
+ end
125
+
126
+ private
127
+
128
+ # Delete the Attribute by name
129
+ #
130
+ # @param [Symbol] name
131
+ #
132
+ # @return [undefined]
133
+ #
134
+ # @api private
135
+ def delete(name)
136
+ @attributes.delete(@index.delete(name))
137
+ end
138
+
139
+ # Add the attributes to the index
140
+ #
141
+ # @param [Array<Attribute>] attributes
142
+ #
143
+ # @return [undefined]
144
+ #
145
+ # @api private
146
+ def merge_index(attributes)
147
+ attributes.each { |attribute| @index[attribute.name] = attribute }
148
+ end
149
+
150
+ end # class AttributeSet
151
+ end # module Virtus
@@ -1,6 +1,8 @@
1
1
  module Virtus
2
+
2
3
  # Class methods that are added when you include Virtus
3
4
  module ClassMethods
5
+
4
6
  # Defines an attribute on an object's class
5
7
  #
6
8
  # @example
@@ -19,20 +21,22 @@ module Virtus
19
21
  # @param [Class] type
20
22
  # the type class of an attribute
21
23
  #
22
- # @param [Hash] options
24
+ # @param [#to_hash] options
23
25
  # the extra options hash
24
26
  #
25
- # @return [Virtus::Attributes::Object]
27
+ # @return [self]
26
28
  #
27
29
  # @api public
28
30
  def attribute(name, type, options = {})
29
- attribute_klass = Virtus.determine_type(type)
30
- attribute = attribute_klass.new(name, options)
31
+ attribute = Virtus.determine_type(type).new(name, options)
31
32
 
32
33
  attribute.add_reader_method(self)
33
34
  attribute.add_writer_method(self)
34
35
 
35
- attributes[name] = attribute
36
+ attributes << attribute
37
+ descendants.each { |descendant| descendant.attributes.reset }
38
+
39
+ self
36
40
  end
37
41
 
38
42
  # Returns all the attributes defined on a Class
@@ -54,16 +58,20 @@ module Virtus
54
58
  #
55
59
  # @api public
56
60
  def attributes
57
- @attributes ||= {}
61
+ @attributes ||= begin
62
+ superclass = self.superclass
63
+ parent = superclass.attributes if superclass.respond_to?(:attributes)
64
+ AttributeSet.new(parent)
65
+ end
58
66
  end
59
67
 
60
- private
68
+ private
61
69
 
62
70
  # Hooks into const missing process to determine types of attributes
63
71
  #
64
72
  # It is used when an attribute is defined and a global class like String
65
73
  # or Integer is provided as the type which needs to be mapped to
66
- # Virtus::Attributes::String and Virtus::Attributes::Integer
74
+ # Virtus::Attribute::String and Virtus::Attribute::Integer
67
75
  #
68
76
  # @param [String] name
69
77
  #
@@ -73,5 +81,6 @@ module Virtus
73
81
  def const_missing(name)
74
82
  Virtus.determine_type(name) || super
75
83
  end
76
- end # ClassMethods
77
- end # Virtus
84
+
85
+ end # module ClassMethods
86
+ end # module Virtus