virtus 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rvmrc +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +7 -0
- data/LICENSE +20 -0
- data/README.markdown +83 -0
- data/Rakefile +27 -0
- data/VERSION +1 -0
- data/lib/virtus.rb +61 -0
- data/lib/virtus/attributes/array.rb +8 -0
- data/lib/virtus/attributes/attribute.rb +214 -0
- data/lib/virtus/attributes/boolean.rb +39 -0
- data/lib/virtus/attributes/date.rb +44 -0
- data/lib/virtus/attributes/date_time.rb +43 -0
- data/lib/virtus/attributes/decimal.rb +24 -0
- data/lib/virtus/attributes/float.rb +20 -0
- data/lib/virtus/attributes/hash.rb +8 -0
- data/lib/virtus/attributes/integer.rb +20 -0
- data/lib/virtus/attributes/numeric.rb +9 -0
- data/lib/virtus/attributes/object.rb +8 -0
- data/lib/virtus/attributes/string.rb +11 -0
- data/lib/virtus/attributes/time.rb +45 -0
- data/lib/virtus/attributes/typecast/numeric.rb +32 -0
- data/lib/virtus/attributes/typecast/time.rb +27 -0
- data/lib/virtus/class_methods.rb +60 -0
- data/lib/virtus/instance_methods.rb +80 -0
- data/lib/virtus/support/chainable.rb +15 -0
- data/spec/integration/virtus/class_methods/attribute_spec.rb +63 -0
- data/spec/integration/virtus/class_methods/attributes_spec.rb +24 -0
- data/spec/integration/virtus/class_methods/const_missing_spec.rb +44 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/unit/shared/attribute.rb +157 -0
- data/spec/unit/virtus/attributes/array_spec.rb +9 -0
- data/spec/unit/virtus/attributes/attribute_spec.rb +13 -0
- data/spec/unit/virtus/attributes/boolean_spec.rb +97 -0
- data/spec/unit/virtus/attributes/date_spec.rb +52 -0
- data/spec/unit/virtus/attributes/date_time_spec.rb +65 -0
- data/spec/unit/virtus/attributes/decimal_spec.rb +98 -0
- data/spec/unit/virtus/attributes/float_spec.rb +98 -0
- data/spec/unit/virtus/attributes/hash_spec.rb +9 -0
- data/spec/unit/virtus/attributes/integer_spec.rb +98 -0
- data/spec/unit/virtus/attributes/numeric/class_methods/descendants_spec.rb +15 -0
- data/spec/unit/virtus/attributes/object/class_methods/descendants_spec.rb +16 -0
- data/spec/unit/virtus/attributes/string_spec.rb +20 -0
- data/spec/unit/virtus/attributes/time_spec.rb +71 -0
- data/spec/unit/virtus/class_methods/new_spec.rb +41 -0
- data/spec/unit/virtus/determine_type_spec.rb +20 -0
- data/spec/unit/virtus/instance_methods/attribute_get_spec.rb +22 -0
- data/spec/unit/virtus/instance_methods/attribute_set_spec.rb +30 -0
- data/spec/unit/virtus/instance_methods/attributes_spec.rb +37 -0
- data/virtus.gemspec +95 -0
- 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,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,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
|