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