virtus 0.0.2 → 0.0.3
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 +3 -0
- data/.rvmrc +1 -1
- data/Gemfile +20 -1
- data/History.txt +21 -0
- data/README.markdown +2 -2
- data/Rakefile +1 -2
- data/VERSION +1 -1
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/roodi.yml +18 -0
- data/config/site.reek +91 -0
- data/config/yardstick.yml +2 -0
- data/lib/virtus.rb +51 -45
- data/lib/virtus/attribute.rb +301 -0
- data/lib/virtus/attribute/array.rb +17 -0
- data/lib/virtus/attribute/boolean.rb +60 -0
- data/lib/virtus/attribute/date.rb +35 -0
- data/lib/virtus/attribute/date_time.rb +34 -0
- data/lib/virtus/attribute/decimal.rb +24 -0
- data/lib/virtus/attribute/float.rb +33 -0
- data/lib/virtus/attribute/hash.rb +18 -0
- data/lib/virtus/attribute/integer.rb +30 -0
- data/lib/virtus/{attributes → attribute}/numeric.rb +2 -3
- data/lib/virtus/{attributes → attribute}/object.rb +2 -1
- data/lib/virtus/attribute/string.rb +31 -0
- data/lib/virtus/attribute/time.rb +34 -0
- data/lib/virtus/class_methods.rb +25 -8
- data/lib/virtus/instance_methods.rb +48 -9
- data/lib/virtus/support/chainable.rb +4 -6
- data/lib/virtus/typecast/boolean.rb +27 -0
- data/lib/virtus/typecast/numeric.rb +82 -0
- data/lib/virtus/typecast/time.rb +162 -0
- data/spec/integration/virtus/attributes/attribute/typecast_spec.rb +4 -4
- data/spec/integration/virtus/class_methods/attribute_spec.rb +1 -1
- data/spec/integration/virtus/class_methods/attributes_spec.rb +3 -2
- data/spec/integration/virtus/class_methods/const_missing_spec.rb +2 -2
- data/spec/rcov.opts +6 -0
- data/spec/spec_helper.rb +0 -9
- data/spec/unit/shared/attribute.rb +8 -8
- data/spec/unit/virtus/{attributes → attribute}/array_spec.rb +1 -1
- data/spec/unit/virtus/attribute/attribute_spec.rb +12 -0
- data/spec/unit/virtus/{attributes → attribute}/boolean_spec.rb +4 -4
- data/spec/unit/virtus/{attributes → attribute}/date_spec.rb +13 -7
- data/spec/unit/virtus/{attributes → attribute}/date_time_spec.rb +31 -10
- data/spec/unit/virtus/{attributes → attribute}/decimal_spec.rb +18 -18
- data/spec/unit/virtus/{attributes → attribute}/float_spec.rb +18 -18
- data/spec/unit/virtus/{attributes → attribute}/hash_spec.rb +1 -1
- data/spec/unit/virtus/{attributes → attribute}/integer_spec.rb +18 -18
- data/spec/unit/virtus/attribute/numeric/class_methods/descendants_spec.rb +15 -0
- data/spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb +16 -0
- data/spec/unit/virtus/{attributes → attribute}/string_spec.rb +2 -2
- data/spec/unit/virtus/{attributes → attribute}/time_spec.rb +19 -9
- data/spec/unit/virtus/class_methods/new_spec.rb +7 -7
- data/spec/unit/virtus/determine_type_spec.rb +4 -4
- data/spec/unit/virtus/instance_methods/attribute_get_spec.rb +1 -1
- data/spec/unit/virtus/instance_methods/attribute_set_spec.rb +2 -2
- data/spec/unit/virtus/instance_methods/attributes_spec.rb +2 -2
- data/tasks/metrics/ci.rake +7 -0
- data/tasks/metrics/flay.rake +41 -0
- data/tasks/metrics/flog.rake +43 -0
- data/tasks/metrics/heckle.rake +261 -0
- data/tasks/metrics/metric_fu.rake +29 -0
- data/tasks/metrics/reek.rake +9 -0
- data/tasks/metrics/roodi.rake +15 -0
- data/tasks/metrics/yardstick.rake +23 -0
- data/tasks/spec.rake +26 -0
- data/tasks/yard.rake +9 -0
- data/virtus.gemspec +48 -33
- metadata +51 -41
- data/lib/virtus/attributes/array.rb +0 -8
- data/lib/virtus/attributes/attribute.rb +0 -214
- data/lib/virtus/attributes/boolean.rb +0 -39
- data/lib/virtus/attributes/date.rb +0 -44
- data/lib/virtus/attributes/date_time.rb +0 -43
- data/lib/virtus/attributes/decimal.rb +0 -24
- data/lib/virtus/attributes/float.rb +0 -20
- data/lib/virtus/attributes/hash.rb +0 -8
- data/lib/virtus/attributes/integer.rb +0 -20
- data/lib/virtus/attributes/string.rb +0 -11
- data/lib/virtus/attributes/time.rb +0 -45
- data/lib/virtus/attributes/typecast/numeric.rb +0 -32
- data/lib/virtus/attributes/typecast/time.rb +0 -27
- data/spec/unit/virtus/attributes/attribute_spec.rb +0 -13
- data/spec/unit/virtus/attributes/numeric/class_methods/descendants_spec.rb +0 -15
- data/spec/unit/virtus/attributes/object/class_methods/descendants_spec.rb +0 -16
@@ -3,12 +3,10 @@ module Virtus
|
|
3
3
|
module Chainable
|
4
4
|
MODULES = {}
|
5
5
|
|
6
|
-
def chainable(key
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
mod
|
11
|
-
end.module_eval(&block)
|
6
|
+
def chainable(key)
|
7
|
+
mod = MODULES[key] ||= Module.new
|
8
|
+
include mod
|
9
|
+
mod.module_eval { yield }
|
12
10
|
end
|
13
11
|
end
|
14
12
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Virtus
|
2
|
+
module Typecast
|
3
|
+
# Typecast defined values into true or false.
|
4
|
+
# See TRUE_VALUES and FALSE_VALUES constants for a reference.
|
5
|
+
class Boolean
|
6
|
+
TRUE_VALUES = [ 1, '1', 't', 'T', 'true', 'TRUE' ].freeze
|
7
|
+
FALSE_VALUES = [ 0, '0', 'f', 'F', 'false', 'FALSE' ].freeze
|
8
|
+
|
9
|
+
BOOLEAN_MAP = Hash[TRUE_VALUES.product([ true ]) + FALSE_VALUES.product([ false ]) ].freeze
|
10
|
+
|
11
|
+
# Typecast value to TrueClass or FalseClass
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# Virtus::Typecast::Boolean.call('T') # => true
|
15
|
+
# Virtus::Typecast::Boolean.call('F') # => false
|
16
|
+
#
|
17
|
+
# @param [Integer, String]
|
18
|
+
#
|
19
|
+
# @return [TrueClass, FalseClass]
|
20
|
+
#
|
21
|
+
# @api public
|
22
|
+
def self.call(value)
|
23
|
+
BOOLEAN_MAP.fetch(value, value)
|
24
|
+
end
|
25
|
+
end # Boolean
|
26
|
+
end # Typecast
|
27
|
+
end # Virtus
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Virtus
|
2
|
+
module Typecast
|
3
|
+
# Typecast numeric values. Supports Integer, Float and BigDecimal
|
4
|
+
class Numeric
|
5
|
+
# Typecast value to integer
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# Virtus::Typecast::Numeric.to_i('1') # => 1
|
9
|
+
# Virtus::Typecast::Numeric.to_i(1.2) # => 1
|
10
|
+
#
|
11
|
+
# @param [Object]
|
12
|
+
#
|
13
|
+
# @return [Integer]
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
def self.to_i(value)
|
17
|
+
call(value, :to_i)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Typecast value to float
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# Virtus::Typecast::Numeric.to_f('1.2') # => 1.2
|
24
|
+
# Virtus::Typecast::Numeric.to_f(1) # => 1.0
|
25
|
+
#
|
26
|
+
# @param [Object]
|
27
|
+
#
|
28
|
+
# @return [Float]
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def self.to_f(value)
|
32
|
+
call(value, :to_f)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Typecast value to decimal
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# Virtus::Typecast::Numeric.to_d('1.2') # => #<BigDecimal:b72157d4,'0.12E1',8(8)>
|
39
|
+
# Virtus::Typecast::Numeric.to_d(1) # => #<BigDecimal:b7212e08,'0.1E1',4(8)>
|
40
|
+
#
|
41
|
+
# @param [Object]
|
42
|
+
#
|
43
|
+
# @return [BigDecimal]
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def self.to_d(value)
|
47
|
+
if value.kind_of?(::Integer)
|
48
|
+
value.to_s.to_d
|
49
|
+
else
|
50
|
+
call(value, :to_d)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Match numeric string
|
57
|
+
#
|
58
|
+
# @param [#to_str, Numeric] value
|
59
|
+
# value to typecast
|
60
|
+
# @param [Symbol] method
|
61
|
+
# method to typecast with
|
62
|
+
#
|
63
|
+
# @return [Numeric]
|
64
|
+
# number if matched, value if no match
|
65
|
+
#
|
66
|
+
# @api private
|
67
|
+
def self.call(value, method)
|
68
|
+
if value.respond_to?(:to_str)
|
69
|
+
if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
|
70
|
+
$1.send(method)
|
71
|
+
else
|
72
|
+
value
|
73
|
+
end
|
74
|
+
elsif value.respond_to?(method)
|
75
|
+
value.send(method)
|
76
|
+
else
|
77
|
+
value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end # Numeric
|
81
|
+
end # Typecast
|
82
|
+
end # Virtus
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module Virtus
|
2
|
+
module Typecast
|
3
|
+
# Typecast various values into Date, DateTime or Time
|
4
|
+
class Time
|
5
|
+
SEGMENTS = [ :year, :month, :day, :hour, :min, :sec ].freeze
|
6
|
+
|
7
|
+
METHOD_TO_CLASS = {
|
8
|
+
:to_time => ::Time,
|
9
|
+
:to_date => ::Date,
|
10
|
+
:to_datetime => ::DateTime
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
# Typecasts an arbitrary value to a Time
|
14
|
+
#
|
15
|
+
# Handles both Hashes and Time instances
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# Virtus::Typecast::Time.to_time('2011/06/09 12:01')
|
19
|
+
# # => Thu Jun 09 12:01:00 +0200 2011
|
20
|
+
#
|
21
|
+
# @param [Hash, #to_mash, #to_s] value
|
22
|
+
# value to be typecast
|
23
|
+
#
|
24
|
+
# @return [Time]
|
25
|
+
# Time constructed from value
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
def self.to_time(value)
|
29
|
+
call(value, :to_time)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Typecasts an arbitrary value to a Date
|
33
|
+
#
|
34
|
+
# Handles both Hashes and Date instances
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# Virtus::Typecast::Time.to_date('2011/06/09')
|
38
|
+
# # => #<Date: 4911443/2,0,2299161>
|
39
|
+
#
|
40
|
+
# @param [Hash, #to_mash, #to_s] value
|
41
|
+
# value to be typecast
|
42
|
+
#
|
43
|
+
# @return [Date]
|
44
|
+
# Date constructed from value
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def self.to_date(value)
|
48
|
+
call(value, :to_date)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Typecasts an arbitrary value to a DateTime
|
52
|
+
#
|
53
|
+
# Handles both Hashes and DateTime instances
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# Virtus::Typecast::Time.to_datetime('2011/06/09 12:01')
|
57
|
+
# # => #<DateTime: 3536239681/1440,0,2299161>
|
58
|
+
#
|
59
|
+
# @param [Hash, #to_mash, #to_s] value
|
60
|
+
# value to be typecast
|
61
|
+
#
|
62
|
+
# @return [DateTime]
|
63
|
+
# DateTime constructed from value
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def self.to_datetime(value)
|
67
|
+
call(value, :to_datetime)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# @api private
|
73
|
+
def self.call(value, method)
|
74
|
+
return value.send(method) if value.respond_to?(method)
|
75
|
+
|
76
|
+
begin
|
77
|
+
if value.is_a?(::Hash)
|
78
|
+
from_hash(value, method)
|
79
|
+
else
|
80
|
+
from_string(value.to_s, method)
|
81
|
+
end
|
82
|
+
rescue ArgumentError
|
83
|
+
return value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# @api private
|
88
|
+
def self.from_string(value, method)
|
89
|
+
METHOD_TO_CLASS[method].parse(value.to_s)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @api private
|
93
|
+
def self.from_hash(value, method)
|
94
|
+
send("hash_#{method}", value)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Creates a Time instance from a Hash
|
98
|
+
#
|
99
|
+
# Valid keys are: :year, :month, :day, :hour, :min, :sec
|
100
|
+
#
|
101
|
+
# @param [Hash, #to_mash] value
|
102
|
+
# value to be typecast
|
103
|
+
#
|
104
|
+
# @return [Time]
|
105
|
+
# Time constructed from hash
|
106
|
+
#
|
107
|
+
# @api private
|
108
|
+
def self.hash_to_time(value)
|
109
|
+
::Time.local(*extract(value))
|
110
|
+
end
|
111
|
+
|
112
|
+
# Creates a Date instance from a Hash
|
113
|
+
#
|
114
|
+
# Valid keys are: :year, :month, :day, :hour
|
115
|
+
#
|
116
|
+
# @param [Hash, #to_mash] value
|
117
|
+
# value to be typecast
|
118
|
+
#
|
119
|
+
# @return [Date]
|
120
|
+
# Date constructed from hash
|
121
|
+
#
|
122
|
+
# @api private
|
123
|
+
def self.hash_to_date(value)
|
124
|
+
::Date.new(*extract(value).first(3))
|
125
|
+
end
|
126
|
+
|
127
|
+
# Creates a DateTime instance from a Hash
|
128
|
+
#
|
129
|
+
# Valid keys are: :year, :month, :day, :hour, :min, :sec
|
130
|
+
#
|
131
|
+
# @param [Hash, #to_mash] value
|
132
|
+
# value to be typecast
|
133
|
+
#
|
134
|
+
# @return [DateTime]
|
135
|
+
# DateTime constructed from hash
|
136
|
+
#
|
137
|
+
# @api private
|
138
|
+
def self.hash_to_datetime(value)
|
139
|
+
::DateTime.new(*extract(value))
|
140
|
+
end
|
141
|
+
|
142
|
+
# Extracts the given args from the hash
|
143
|
+
#
|
144
|
+
# If a value does not exist, it uses the value of Time.now
|
145
|
+
#
|
146
|
+
# @param [Hash, #to_mash] value
|
147
|
+
# value to extract time args from
|
148
|
+
#
|
149
|
+
# @return [Array]
|
150
|
+
# Extracted values
|
151
|
+
#
|
152
|
+
# @api private
|
153
|
+
def self.extract(value)
|
154
|
+
now = ::Time.now
|
155
|
+
|
156
|
+
SEGMENTS.map do |segment|
|
157
|
+
Numeric.to_i(value.fetch(segment, now.send(segment)))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end # Time
|
161
|
+
end # Typecast
|
162
|
+
end # Virtus
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Virtus::
|
3
|
+
describe Virtus::Attribute, '#typecast' do
|
4
4
|
let(:attribute_class) do
|
5
|
-
Class.new(Virtus::
|
6
|
-
def typecast(value
|
5
|
+
Class.new(Virtus::Attribute::Integer) do
|
6
|
+
def typecast(value)
|
7
7
|
super + 1
|
8
8
|
end
|
9
9
|
end
|
@@ -30,7 +30,7 @@ describe Virtus::Attributes::Attribute, '#typecast' do
|
|
30
30
|
end
|
31
31
|
|
32
32
|
it "peforms custom typecasting" do
|
33
|
-
object.count.should
|
33
|
+
object.count.should eql(output_value)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -25,7 +25,7 @@ describe Virtus::ClassMethods, '.attribute' do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
it "should create an attribute of a correct type" do
|
28
|
-
described_class.attributes[:name].should be_instance_of(Virtus::
|
28
|
+
described_class.attributes[:name].should be_instance_of(Virtus::Attribute::String)
|
29
29
|
end
|
30
30
|
|
31
31
|
it "creates attribute writer" do
|
@@ -16,9 +16,10 @@ describe Virtus::ClassMethods, '.attributes' do
|
|
16
16
|
subject { described_class.attributes }
|
17
17
|
|
18
18
|
it "returns an attributes hash" do
|
19
|
-
subject.should
|
19
|
+
subject.should eql(
|
20
20
|
:name => described_class.attributes[:name],
|
21
|
-
:age => described_class.attributes[:age]
|
21
|
+
:age => described_class.attributes[:age]
|
22
|
+
)
|
22
23
|
end
|
23
24
|
end
|
24
25
|
end
|
@@ -14,7 +14,7 @@ describe Virtus::ClassMethods, '.const_missing' do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
it "should create attribute of the correct type" do
|
17
|
-
User.attributes[:name].should be_instance_of(Virtus::
|
17
|
+
User.attributes[:name].should be_instance_of(Virtus::Attribute::String)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -27,7 +27,7 @@ describe Virtus::ClassMethods, '.const_missing' do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
it "should create attribute of the correct type" do
|
30
|
-
User.attributes[:admin].should be_instance_of(Virtus::
|
30
|
+
User.attributes[:admin].should be_instance_of(Virtus::Attribute::Boolean)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
data/spec/rcov.opts
ADDED
data/spec/spec_helper.rb
CHANGED
@@ -2,15 +2,6 @@ require 'pathname'
|
|
2
2
|
require 'rubygems'
|
3
3
|
require 'rspec'
|
4
4
|
|
5
|
-
if ENV['SPEC_COV'] && RUBY_VERSION >= '1.9.2'
|
6
|
-
require 'simplecov'
|
7
|
-
|
8
|
-
SimpleCov.start do
|
9
|
-
add_filter "/spec/"
|
10
|
-
add_group "lib", "lib"
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
5
|
require 'virtus'
|
15
6
|
|
16
7
|
ENV['TZ'] = 'UTC'
|
@@ -27,7 +27,7 @@ shared_examples_for "Attribute" do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
it "accepts base options" do
|
30
|
-
described_class.accepted_options.should include(*Virtus::
|
30
|
+
described_class.accepted_options.should include(*Virtus::Attribute::OPTIONS)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -65,20 +65,20 @@ shared_examples_for "Attribute" do
|
|
65
65
|
end
|
66
66
|
|
67
67
|
context "when new attribute is created" do
|
68
|
-
subject { sub_attribute.new(attribute_name
|
68
|
+
subject { sub_attribute.new(attribute_name) }
|
69
69
|
|
70
70
|
it "sets the default value" do
|
71
|
-
subject.options[option].should
|
71
|
+
subject.options[option].should eql(value)
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
75
|
context "when new attribute is created and overrides option's default value" do
|
76
76
|
let(:new_value) { 11 }
|
77
77
|
|
78
|
-
subject { sub_attribute.new(attribute_name,
|
78
|
+
subject { sub_attribute.new(attribute_name, option => new_value) }
|
79
79
|
|
80
80
|
it "sets the new value" do
|
81
|
-
subject.options[option].should
|
81
|
+
subject.options[option].should eql(new_value)
|
82
82
|
end
|
83
83
|
end
|
84
84
|
end
|
@@ -105,7 +105,7 @@ shared_examples_for "Attribute" do
|
|
105
105
|
before { attribute.set(object, attribute_value) }
|
106
106
|
|
107
107
|
it "sets the value in an ivar" do
|
108
|
-
object.instance_variable_get(attribute.instance_variable_name).should
|
108
|
+
object.instance_variable_get(attribute.instance_variable_name).should eql(attribute_value)
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
@@ -127,7 +127,7 @@ shared_examples_for "Attribute" do
|
|
127
127
|
|
128
128
|
subject { attribute.get(object) }
|
129
129
|
|
130
|
-
it { should
|
130
|
+
it { should eql(attribute_value) }
|
131
131
|
end
|
132
132
|
|
133
133
|
context "when nil is set" do
|
@@ -139,7 +139,7 @@ shared_examples_for "Attribute" do
|
|
139
139
|
end
|
140
140
|
end
|
141
141
|
|
142
|
-
describe "#complex" do
|
142
|
+
describe "#complex?" do
|
143
143
|
let(:attribute) { model.attribute(attribute_name, described_class, :complex => complex) }
|
144
144
|
|
145
145
|
subject { attribute.complex? }
|