virtus 0.0.3 → 0.0.4

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 (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