virtus 0.0.1

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