virtus 0.0.1

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 (52) hide show
  1. data/.gitignore +4 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE +20 -0
  6. data/README.markdown +83 -0
  7. data/Rakefile +27 -0
  8. data/VERSION +1 -0
  9. data/lib/virtus.rb +61 -0
  10. data/lib/virtus/attributes/array.rb +8 -0
  11. data/lib/virtus/attributes/attribute.rb +214 -0
  12. data/lib/virtus/attributes/boolean.rb +39 -0
  13. data/lib/virtus/attributes/date.rb +44 -0
  14. data/lib/virtus/attributes/date_time.rb +43 -0
  15. data/lib/virtus/attributes/decimal.rb +24 -0
  16. data/lib/virtus/attributes/float.rb +20 -0
  17. data/lib/virtus/attributes/hash.rb +8 -0
  18. data/lib/virtus/attributes/integer.rb +20 -0
  19. data/lib/virtus/attributes/numeric.rb +9 -0
  20. data/lib/virtus/attributes/object.rb +8 -0
  21. data/lib/virtus/attributes/string.rb +11 -0
  22. data/lib/virtus/attributes/time.rb +45 -0
  23. data/lib/virtus/attributes/typecast/numeric.rb +32 -0
  24. data/lib/virtus/attributes/typecast/time.rb +27 -0
  25. data/lib/virtus/class_methods.rb +60 -0
  26. data/lib/virtus/instance_methods.rb +80 -0
  27. data/lib/virtus/support/chainable.rb +15 -0
  28. data/spec/integration/virtus/class_methods/attribute_spec.rb +63 -0
  29. data/spec/integration/virtus/class_methods/attributes_spec.rb +24 -0
  30. data/spec/integration/virtus/class_methods/const_missing_spec.rb +44 -0
  31. data/spec/spec_helper.rb +20 -0
  32. data/spec/unit/shared/attribute.rb +157 -0
  33. data/spec/unit/virtus/attributes/array_spec.rb +9 -0
  34. data/spec/unit/virtus/attributes/attribute_spec.rb +13 -0
  35. data/spec/unit/virtus/attributes/boolean_spec.rb +97 -0
  36. data/spec/unit/virtus/attributes/date_spec.rb +52 -0
  37. data/spec/unit/virtus/attributes/date_time_spec.rb +65 -0
  38. data/spec/unit/virtus/attributes/decimal_spec.rb +98 -0
  39. data/spec/unit/virtus/attributes/float_spec.rb +98 -0
  40. data/spec/unit/virtus/attributes/hash_spec.rb +9 -0
  41. data/spec/unit/virtus/attributes/integer_spec.rb +98 -0
  42. data/spec/unit/virtus/attributes/numeric/class_methods/descendants_spec.rb +15 -0
  43. data/spec/unit/virtus/attributes/object/class_methods/descendants_spec.rb +16 -0
  44. data/spec/unit/virtus/attributes/string_spec.rb +20 -0
  45. data/spec/unit/virtus/attributes/time_spec.rb +71 -0
  46. data/spec/unit/virtus/class_methods/new_spec.rb +41 -0
  47. data/spec/unit/virtus/determine_type_spec.rb +20 -0
  48. data/spec/unit/virtus/instance_methods/attribute_get_spec.rb +22 -0
  49. data/spec/unit/virtus/instance_methods/attribute_set_spec.rb +30 -0
  50. data/spec/unit/virtus/instance_methods/attributes_spec.rb +37 -0
  51. data/virtus.gemspec +95 -0
  52. metadata +131 -0
@@ -0,0 +1,39 @@
1
+ module Virtus
2
+ module Attributes
3
+ class Boolean < Object
4
+ primitive TrueClass
5
+
6
+ TRUE_VALUES = [ 1, '1', 't', 'T', 'true', 'TRUE' ].freeze
7
+ FALSE_VALUES = [ 0, '0', 'f', 'F', 'false', 'FALSE' ].freeze
8
+ BOOLEAN_MAP = Hash[
9
+ TRUE_VALUES.product([ true ]) + FALSE_VALUES.product([ false ]) ].freeze
10
+
11
+ def primitive?(value)
12
+ value == true || value == false
13
+ end
14
+
15
+ def typecast_to_primitive(value, model = nil)
16
+ BOOLEAN_MAP.fetch(value, value)
17
+ end
18
+
19
+ private
20
+
21
+ # Creates standard and boolean attribute reader methods.
22
+ #
23
+ # @api private
24
+ def _create_reader
25
+ super
26
+
27
+ model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
28
+ chainable(:attribute) do
29
+ #{reader_visibility}
30
+
31
+ def #{name}?
32
+ #{name}
33
+ end
34
+ end
35
+ RUBY
36
+ end
37
+ end # Boolean
38
+ end # Attributes
39
+ end # Virtus
@@ -0,0 +1,44 @@
1
+ module Virtus
2
+ module Attributes
3
+ class Date < Object
4
+ include Typecast::Time
5
+
6
+ primitive ::Date
7
+
8
+ # Typecasts an arbitrary value to a Date
9
+ # Handles both Hashes and Date instances.
10
+ #
11
+ # @param [Hash, #to_mash, #to_s] value
12
+ # value to be typecast
13
+ #
14
+ # @return [Date]
15
+ # Date constructed from value
16
+ #
17
+ # @api private
18
+ def typecast_to_primitive(value)
19
+ if value.respond_to?(:to_date)
20
+ value.to_date
21
+ elsif value.is_a?(::Hash)
22
+ typecast_hash_to_date(value)
23
+ else
24
+ ::Date.parse(value.to_s)
25
+ end
26
+ rescue ArgumentError
27
+ value
28
+ end
29
+
30
+ # Creates a Date instance from a Hash with keys :year, :month, :day
31
+ #
32
+ # @param [Hash, #to_mash] value
33
+ # value to be typecast
34
+ #
35
+ # @return [Date]
36
+ # Date constructed from hash
37
+ #
38
+ # @api private
39
+ def typecast_hash_to_date(value)
40
+ ::Date.new(*extract_time(value)[0, 3])
41
+ end
42
+ end # Date
43
+ end # Attributes
44
+ end # Virtus
@@ -0,0 +1,43 @@
1
+ module Virtus
2
+ module Attributes
3
+ class DateTime < Object
4
+ primitive ::DateTime
5
+
6
+ include Typecast::Time
7
+
8
+ # Typecasts an arbitrary value to a DateTime.
9
+ # Handles both Hashes and DateTime instances.
10
+ #
11
+ # @param [Hash, #to_mash, #to_s] value
12
+ # value to be typecast
13
+ #
14
+ # @return [DateTime]
15
+ # DateTime constructed from value
16
+ #
17
+ # @api private
18
+ def typecast_to_primitive(value, model = nil)
19
+ if value.is_a?(::Hash)
20
+ typecast_hash_to_datetime(value)
21
+ else
22
+ ::DateTime.parse(value.to_s)
23
+ end
24
+ rescue ArgumentError
25
+ value
26
+ end
27
+
28
+ # Creates a DateTime instance from a Hash with keys :year, :month, :day,
29
+ # :hour, :min, :sec
30
+ #
31
+ # @param [Hash, #to_mash] value
32
+ # value to be typecast
33
+ #
34
+ # @return [DateTime]
35
+ # DateTime constructed from hash
36
+ #
37
+ # @api private
38
+ def typecast_hash_to_datetime(value)
39
+ ::DateTime.new(*extract_time(value))
40
+ end
41
+ end # DateTime
42
+ end # Attributes
43
+ end # Virtus
@@ -0,0 +1,24 @@
1
+ module Virtus
2
+ module Attributes
3
+ class Decimal < Numeric
4
+ primitive ::BigDecimal
5
+
6
+ # Typecast a value to a BigDecimal
7
+ #
8
+ # @param [#to_str, #to_d, Integer] value
9
+ # value to typecast
10
+ #
11
+ # @return [BigDecimal]
12
+ # BigDecimal constructed from value
13
+ #
14
+ # @api private
15
+ def typecast_to_primitive(value, model = nil)
16
+ if value.kind_of?(::Integer)
17
+ value.to_s.to_d
18
+ else
19
+ typecast_to_numeric(value, :to_d)
20
+ end
21
+ end
22
+ end # Decimal
23
+ end # Attributes
24
+ end # Virtus
@@ -0,0 +1,20 @@
1
+ module Virtus
2
+ module Attributes
3
+ class Float < Numeric
4
+ primitive ::Float
5
+
6
+ # Typecast a value to a Float
7
+ #
8
+ # @param [#to_str, #to_f] value
9
+ # value to typecast
10
+ #
11
+ # @return [Float]
12
+ # Float constructed from value
13
+ #
14
+ # @api private
15
+ def typecast_to_primitive(value, model = nil)
16
+ typecast_to_numeric(value, :to_f)
17
+ end
18
+ end # Float
19
+ end # Attributes
20
+ end # Virtus
@@ -0,0 +1,8 @@
1
+ module Virtus
2
+ module Attributes
3
+ class Hash < Object
4
+ primitive ::Hash
5
+ complex true
6
+ end # Integer
7
+ end # Attributes
8
+ end # Virtus
@@ -0,0 +1,20 @@
1
+ module Virtus
2
+ module Attributes
3
+ class Integer < Numeric
4
+ primitive ::Integer
5
+
6
+ # Typecast a value to an Integer
7
+ #
8
+ # @param [#to_str, #to_i] value
9
+ # value to typecast
10
+ #
11
+ # @return [Integer]
12
+ # Integer constructed from value
13
+ #
14
+ # @api private
15
+ def typecast_to_primitive(value, model = nil)
16
+ typecast_to_numeric(value, :to_i)
17
+ end
18
+ end # Integer
19
+ end # Attributes
20
+ end # Virtus
@@ -0,0 +1,9 @@
1
+ module Virtus
2
+ module Attributes
3
+ class Numeric < Object
4
+ include Typecast::Numeric
5
+
6
+ accept_options :min, :max
7
+ end # Numeric
8
+ end # Attributes
9
+ end # Virtus
@@ -0,0 +1,8 @@
1
+ module Virtus
2
+ module Attributes
3
+ class Object < Attribute
4
+ primitive ::Object
5
+ complex false
6
+ end # Object
7
+ end # Attributes
8
+ end # Virtus
@@ -0,0 +1,11 @@
1
+ module Virtus
2
+ module Attributes
3
+ class String < Object
4
+ primitive ::String
5
+
6
+ def typecast_to_primitive(value, model = nil)
7
+ value.to_s
8
+ end
9
+ end # String
10
+ end # Attributes
11
+ end # Virtus
@@ -0,0 +1,45 @@
1
+ module Virtus
2
+ module Attributes
3
+ class Time < Object
4
+ include Typecast::Time
5
+
6
+ primitive ::Time
7
+
8
+ # Typecasts an arbitrary value to a Time
9
+ # Handles both Hashes and Time instances.
10
+ #
11
+ # @param [Hash, #to_mash, #to_s] value
12
+ # value to be typecast
13
+ #
14
+ # @return [Time]
15
+ # Time constructed from value
16
+ #
17
+ # @api private
18
+ def typecast_to_primitive(value, model = nil)
19
+ if value.respond_to?(:to_time)
20
+ value.to_time
21
+ elsif value.is_a?(::Hash)
22
+ typecast_hash_to_time(value)
23
+ else
24
+ ::Time.parse(value.to_s)
25
+ end
26
+ rescue ArgumentError
27
+ value
28
+ end
29
+
30
+ # Creates a Time instance from a Hash with keys :year, :month, :day,
31
+ # :hour, :min, :sec
32
+ #
33
+ # @param [Hash, #to_mash] value
34
+ # value to be typecast
35
+ #
36
+ # @return [Time]
37
+ # Time constructed from hash
38
+ #
39
+ # @api private
40
+ def typecast_hash_to_time(value)
41
+ ::Time.local(*extract_time(value))
42
+ end
43
+ end # Time
44
+ end # Attributes
45
+ end # Virtus
@@ -0,0 +1,32 @@
1
+ module Virtus
2
+ module Attributes
3
+ module Typecast
4
+ module Numeric
5
+ # Match numeric string
6
+ #
7
+ # @param [#to_str, Numeric] value
8
+ # value to typecast
9
+ # @param [Symbol] method
10
+ # method to typecast with
11
+ #
12
+ # @return [Numeric]
13
+ # number if matched, value if no match
14
+ #
15
+ # @api private
16
+ def typecast_to_numeric(value, method)
17
+ if value.respond_to?(:to_str)
18
+ if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
19
+ $1.send(method)
20
+ else
21
+ value
22
+ end
23
+ elsif value.respond_to?(method)
24
+ value.send(method)
25
+ else
26
+ value
27
+ end
28
+ end
29
+ end # Numeric
30
+ end # Typecast
31
+ end # Attributes
32
+ end # Virtus
@@ -0,0 +1,27 @@
1
+ module Virtus
2
+ module Attributes
3
+ module Typecast
4
+ module Time
5
+ include Numeric
6
+
7
+ # Extracts the given args from the hash. If a value does not exist, it
8
+ # uses the value of Time.now.
9
+ #
10
+ # @param [Hash, #to_mash] value
11
+ # value to extract time args from
12
+ #
13
+ # @return [Array]
14
+ # Extracted values
15
+ #
16
+ # @api private
17
+ def extract_time(value)
18
+ now = ::Time.now
19
+
20
+ [ :year, :month, :day, :hour, :min, :sec ].map do |segment|
21
+ typecast_to_numeric(value.fetch(segment, now.send(segment)), :to_i)
22
+ end
23
+ end
24
+ end # Time
25
+ end # Typecast
26
+ end # Attributes
27
+ end # Virtus
@@ -0,0 +1,60 @@
1
+ module Virtus
2
+ module ClassMethods
3
+ # Defines an attribute on an object's class.
4
+ #
5
+ # Usage:
6
+ #
7
+ # class Book
8
+ # include Virtus
9
+ #
10
+ # attribute :title, String
11
+ # attribute :author, String
12
+ # attribute :published_at, DateTime
13
+ # attribute :page_count, Integer
14
+ # end
15
+ #
16
+ # @param [Symbol] name
17
+ # the name of an attribute
18
+ #
19
+ # @param [Class] type
20
+ # the type class of an attribute
21
+ #
22
+ # @param [Hash] options
23
+ # the extra options hash
24
+ #
25
+ # @return [Virtus::Attributes::Object]
26
+ #
27
+ # @api public
28
+ def attribute(name, type, options = {})
29
+ attribute_klass = Virtus.determine_type(type)
30
+ attributes[name] = attribute_klass.new(name, self, options)
31
+ end
32
+
33
+ # Returns all the attributes defined on a Class.
34
+ #
35
+ # @return [Hash]
36
+ # an attributes hash indexed by attribute names
37
+ #
38
+ # @api public
39
+ def attributes
40
+ @attributes ||= {}
41
+ end
42
+
43
+ private
44
+
45
+ # Hooks into const missing process to determine types of attributes.
46
+ #
47
+ # It is used when an attribute is defined and a global class like String
48
+ # or Integer is provided as the type which needs to be mapped to
49
+ # Virtus::Attributes::String and Virtus::Attributes::Integer.
50
+ #
51
+ # @param [String] name
52
+ #
53
+ # @return [Class]
54
+ #
55
+ # @api private
56
+ def const_missing(name)
57
+ Virtus.determine_type(name) || super
58
+ end
59
+ end # ClassMethods
60
+ end # Virtus
@@ -0,0 +1,80 @@
1
+ module Virtus
2
+ module InstanceMethods
3
+ # Chains Class.new to be able to set attributes during initialization of
4
+ # an object.
5
+ #
6
+ # @param [Hash] attributes
7
+ # the attributes hash to be set
8
+ #
9
+ # @return [Object]
10
+ #
11
+ # @api private
12
+ def initialize(attributes = {})
13
+ self.attributes = attributes
14
+ end
15
+
16
+ # Returns a value of the attribute with the given name
17
+ #
18
+ # @param [Symbol] name
19
+ # a name of an attribute
20
+ #
21
+ # @return [Object]
22
+ # a value of an attribute
23
+ #
24
+ # @api public
25
+ def attribute_get(name)
26
+ __send__(name)
27
+ end
28
+
29
+ # Sets a value of the attribute with the given name
30
+ #
31
+ # @param [Symbol] name
32
+ # a name of an attribute
33
+ #
34
+ # @param [Object] value
35
+ # a value to be set
36
+ #
37
+ # @return [Object]
38
+ # the value set on an object
39
+ #
40
+ # @api public
41
+ def attribute_set(name, value)
42
+ __send__("#{name}=", value)
43
+ end
44
+
45
+ # Mass-assign of attribute values
46
+ #
47
+ # @param [Hash] attributes
48
+ # a hash of attribute values to be set on an object
49
+ #
50
+ # @return [Hash]
51
+ # the attributes
52
+ #
53
+ # @api public
54
+ def attributes=(attributes)
55
+ attributes.each do |name, value|
56
+ if self.class.public_method_defined?(writer_name = "#{name}=")
57
+ __send__(writer_name, value)
58
+ end
59
+ end
60
+ end
61
+
62
+ # Returns a hash of all publicly accessible attributes
63
+ #
64
+ # @return [Hash]
65
+ # the attributes
66
+ #
67
+ # @api public
68
+ def attributes
69
+ attributes = {}
70
+
71
+ self.class.attributes.each do |name, attribute|
72
+ if self.class.public_method_defined?(name)
73
+ attributes[name] = __send__(attribute.name)
74
+ end
75
+ end
76
+
77
+ attributes
78
+ end
79
+ end # InstanceMethods
80
+ end # Virtus