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,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attributes::Date do
4
+ it_should_behave_like 'Attribute' do
5
+ let(:attribute_name) { :created_on }
6
+ let(:attribute_value) { Date.today }
7
+ let(:attribute_value_other) { (Date.today+1).to_s }
8
+ end
9
+
10
+ describe '#typecast' do
11
+ let(:model) { Class.new { include Virtus } }
12
+ let(:attribute) { model.attribute(:bday, Virtus::Attributes::Date) }
13
+
14
+ let(:year) { 2011 }
15
+ let(:month) { 4 }
16
+ let(:day) { 7 }
17
+
18
+ subject { attribute.typecast(value) }
19
+
20
+ shared_examples_for "a correct date" do
21
+ it { should be_kind_of(Date) }
22
+ its(:year) { should == year }
23
+ its(:month) { should == month }
24
+ its(:day) { should == day }
25
+ end
26
+
27
+ context 'with a time' do
28
+ it_should_behave_like "a correct date" do
29
+ let(:value) { Time.local(year, month, day) }
30
+ end
31
+ end
32
+
33
+ context 'with a hash' do
34
+ it_should_behave_like "a correct date" do
35
+ let(:value) do
36
+ { :year => year, :month => month, :day => day }
37
+ end
38
+ end
39
+ end
40
+
41
+ context 'with a string' do
42
+ it_should_behave_like "a correct date" do
43
+ let(:value) { "April #{day}th, #{year}" }
44
+ end
45
+ end
46
+
47
+ context 'with a on-date value' do
48
+ let(:value) { 'non-date' }
49
+ it { should == value }
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attributes::DateTime do
4
+ it_should_behave_like 'Attribute' do
5
+ let(:attribute_name) { :created_at }
6
+ let(:attribute_value) { DateTime.now }
7
+ let(:attribute_value_other) { DateTime.now.to_s }
8
+ end
9
+
10
+ describe '#typecast' do
11
+ let(:model) { Class.new { include Virtus } }
12
+ let(:attribute) { model.attribute(:bday, Virtus::Attributes::DateTime) }
13
+
14
+ let(:year) { 2011 }
15
+ let(:month) { 4 }
16
+ let(:day) { 7 }
17
+ let(:hour) { 1 }
18
+ let(:min) { 26 }
19
+ let(:sec) { 49 }
20
+
21
+ subject { attribute.typecast(value) }
22
+
23
+ shared_examples_for "a correct date time" do
24
+ it { should be_kind_of(DateTime) }
25
+ its(:year) { should == year }
26
+ its(:month) { should == month }
27
+ its(:day) { should == day }
28
+ its(:hour) { should == hour }
29
+ its(:min) { should == min }
30
+ its(:sec) { should == sec }
31
+ end
32
+
33
+ context 'with a hash' do
34
+ it_should_behave_like "a correct date time" do
35
+ let(:value) do
36
+ { :year => year, :month => month, :day => day,
37
+ :hour => hour, :min => min, :sec => sec }
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'with a string' do
43
+ context "without hour, min and sec" do
44
+ let(:hour) { 0 }
45
+ let(:min) { 0 }
46
+ let(:sec) { 0 }
47
+
48
+ it_should_behave_like "a correct date time" do
49
+ let(:value) { "April #{day}th, #{year}" }
50
+ end
51
+ end
52
+
53
+ context "with hour, min and sec" do
54
+ it_should_behave_like "a correct date time" do
55
+ let(:value) { "April #{day}th, #{year}, #{hour}:#{min}:#{sec}" }
56
+ end
57
+ end
58
+ end
59
+
60
+ context 'with a on-date value' do
61
+ let(:value) { 'non-date' }
62
+ it { should == value }
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attributes::Decimal do
4
+ it_should_behave_like 'Attribute' do
5
+ let(:attribute_name) { :price }
6
+ let(:attribute_value) { BigDecimal("12.3456789") }
7
+ let(:attribute_value_other) { "12.3456789" }
8
+ end
9
+
10
+ describe '#typecast' do
11
+ let(:model) { Class.new { include Virtus } }
12
+ let(:attribute) { model.attribute(:price, Virtus::Attributes::Decimal) }
13
+
14
+ subject { attribute.typecast(value) }
15
+
16
+ context "with 24.0 big decimal" do
17
+ let(:value) { BigDecimal('24.0') }
18
+ it { should == value }
19
+ end
20
+
21
+ context 'with a zero string integer' do
22
+ let(:value) { '0' }
23
+ it { should == BigDecimal('0.0') }
24
+ end
25
+
26
+ context 'with a positive string integer' do
27
+ let(:value) { '24' }
28
+ it { should == BigDecimal('24.0') }
29
+ end
30
+
31
+ context 'with a negative string integer' do
32
+ let(:value) { '-24' }
33
+ it { should == BigDecimal('-24.0') }
34
+ end
35
+
36
+ context 'with a zero string float' do
37
+ let(:value) { '0.0' }
38
+ it { should == BigDecimal('0.0') }
39
+ end
40
+
41
+ context 'with a positive string float' do
42
+ let(:value) { '24.35' }
43
+ it { should == BigDecimal('24.35') }
44
+ end
45
+
46
+ context 'with a negative string float' do
47
+ let(:value) { '-24.35' }
48
+ it { should == BigDecimal('-24.35') }
49
+ end
50
+
51
+ context 'with a zero string float, with no leading digits' do
52
+ let(:value) { '.0' }
53
+ it { should == BigDecimal('0.0') }
54
+ end
55
+
56
+ context 'with a positive string float, with no leading digits' do
57
+ let(:value) { '0.41' }
58
+ it { should == BigDecimal('0.41') }
59
+ end
60
+
61
+ context 'with a zero integer' do
62
+ let(:value) { 0 }
63
+ it { should == BigDecimal('0.0') }
64
+ end
65
+
66
+ context 'with a positive integer' do
67
+ let(:value) { 24 }
68
+ it { should == BigDecimal('24.0') }
69
+ end
70
+
71
+ context 'with a negative integer' do
72
+ let(:value) { -24 }
73
+ it { should == BigDecimal('-24.0') }
74
+ end
75
+
76
+ context 'with a zero float' do
77
+ let(:value) { 0.0 }
78
+ it { should == BigDecimal('0.0') }
79
+ end
80
+
81
+ context 'with a positive float' do
82
+ let(:value) { 24.35 }
83
+ it { should == BigDecimal('24.35') }
84
+ end
85
+
86
+ context 'with a negative float' do
87
+ let(:value) { -24.35 }
88
+ it { should == BigDecimal('-24.35') }
89
+ end
90
+
91
+ [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |non_num_value|
92
+ context "with a non-numeric value = #{non_num_value.inspect}" do
93
+ let(:value) { non_num_value }
94
+ it { should == non_num_value }
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attributes::Float do
4
+ it_should_behave_like 'Attribute' do
5
+ let(:attribute_name) { :score }
6
+ let(:attribute_value) { 12.34 }
7
+ let(:attribute_value_other) { "12.34" }
8
+ end
9
+
10
+ describe '#typecast' do
11
+ let(:model) { Class.new { include Virtus } }
12
+ let(:attribute) { model.attribute(:score, Virtus::Attributes::Float) }
13
+
14
+ subject { attribute.typecast(value) }
15
+
16
+ context "with a float" do
17
+ let(:value) { 24.0 }
18
+ it { should == value }
19
+ end
20
+
21
+ context 'with a zero string integer' do
22
+ let(:value) { '0' }
23
+ it { should == 0.0 }
24
+ end
25
+
26
+ context 'with a positive string integer' do
27
+ let(:value) { '24' }
28
+ it { should == 24.0 }
29
+ end
30
+
31
+ context 'with a negative string integer' do
32
+ let(:value) { '-24' }
33
+ it { should == -24.0 }
34
+ end
35
+
36
+ context 'with a zero string float' do
37
+ let(:value) { '0.0' }
38
+ it { should == 0.0 }
39
+ end
40
+
41
+ context 'with a positive string float' do
42
+ let(:value) { '24.35' }
43
+ it { should == 24.35 }
44
+ end
45
+
46
+ context 'with a negative string float' do
47
+ let(:value) { '-24.35' }
48
+ it { should == -24.35 }
49
+ end
50
+
51
+ context 'with a zero string float, with no leading digits' do
52
+ let(:value) { '.0' }
53
+ it { should == 0.0 }
54
+ end
55
+
56
+ context 'with a positive string float, with no leading digits' do
57
+ let(:value) { '.41' }
58
+ it { should == 0.41 }
59
+ end
60
+
61
+ context 'with a zero integer' do
62
+ let(:value) { 0 }
63
+ it { should == 0.0 }
64
+ end
65
+
66
+ context 'with a positive integer' do
67
+ let(:value) { 24 }
68
+ it { should == 24.0 }
69
+ end
70
+
71
+ context 'with a negative integer' do
72
+ let(:value) { -24 }
73
+ it { should == -24.0 }
74
+ end
75
+
76
+ context 'with a zero decimal' do
77
+ let(:value) { BigDecimal('0.0') }
78
+ it { should == 0.0 }
79
+ end
80
+
81
+ context 'with a positive decimal' do
82
+ let(:value) { BigDecimal('24.35') }
83
+ it { should == 24.35 }
84
+ end
85
+
86
+ context 'with a negative decimal' do
87
+ let(:value) { BigDecimal('-24.35') }
88
+ it { should == -24.35 }
89
+ end
90
+
91
+ [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |non_num_value|
92
+ context "does not typecast non-numeric value #{non_num_value.inspect}" do
93
+ let(:value) { non_num_value }
94
+ it { should == non_num_value }
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attributes::Hash do
4
+ it_should_behave_like 'Attribute' do
5
+ let(:attribute_name) { :settings }
6
+ let(:attribute_value) { Hash[:one => 1] }
7
+ let(:attribute_value_other) { Hash[:two => 2] }
8
+ end
9
+ end
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attributes::Integer do
4
+ it_should_behave_like 'Attribute' do
5
+ let(:attribute_name) { :age }
6
+ let(:attribute_value) { 28 }
7
+ let(:attribute_value_other) { "28" }
8
+ end
9
+
10
+ describe '#typecast' do
11
+ let(:model) { Class.new { include Virtus } }
12
+ let(:attribute) { model.attribute(:age, Virtus::Attributes::Integer) }
13
+
14
+ subject { attribute.typecast(value) }
15
+
16
+ context "with an integer" do
17
+ let(:value) { 24 }
18
+ it { should == value }
19
+ end
20
+
21
+ context 'with a zero string integer' do
22
+ let(:value) { 24 }
23
+ it { should == value }
24
+ end
25
+
26
+ context 'with a positive string integer' do
27
+ let(:value) { '24' }
28
+ it { should == 24 }
29
+ end
30
+
31
+ context 'with a negative string integer' do
32
+ let(:value) { '-24' }
33
+ it { should == -24 }
34
+ end
35
+
36
+ context 'with a zero string float' do
37
+ let(:value) { 0.0 }
38
+ it { should == 0 }
39
+ end
40
+
41
+ context 'with a positive string float' do
42
+ let(:value) { '24.35' }
43
+ it { should == 24 }
44
+ end
45
+
46
+ context 'with a negative string float' do
47
+ let(:value) { '-24.35' }
48
+ it { should == -24 }
49
+ end
50
+
51
+ context 'with a zero string float, with no leading digits' do
52
+ let(:value) { '.0' }
53
+ it { should == 0 }
54
+ end
55
+
56
+ context 'with a positive string float, with no leading digits' do
57
+ let(:value) { '.41' }
58
+ it { should == 0 }
59
+ end
60
+
61
+ context 'with a zero float' do
62
+ let(:value) { 0.0 }
63
+ it { should == 0 }
64
+ end
65
+
66
+ context 'with a positive float' do
67
+ let(:value) { 24.35 }
68
+ it { should == 24 }
69
+ end
70
+
71
+ context 'with a negative float' do
72
+ let(:value) { -24.35 }
73
+ it { should == -24 }
74
+ end
75
+
76
+ context 'with a zero decimal' do
77
+ let(:value) { BigDecimal('0.0') }
78
+ it { should == 0 }
79
+ end
80
+
81
+ context 'with a positive decimal' do
82
+ let(:value) { BigDecimal('24.35') }
83
+ it { should == 24 }
84
+ end
85
+
86
+ context 'with a negative decimal' do
87
+ let(:value) { BigDecimal('-24.35') }
88
+ it { should == -24 }
89
+ end
90
+
91
+ [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |non_num_value|
92
+ context "does not typecast non-numeric value #{non_num_value.inspect}" do
93
+ let(:value) { non_num_value }
94
+ it { should == non_num_value }
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attributes::Numeric, '.descendants' do
4
+ subject { described_class.descendants }
5
+
6
+ let(:known_descendants) do
7
+ [ Virtus::Attributes::Decimal,
8
+ Virtus::Attributes::Float,
9
+ Virtus::Attributes::Integer ]
10
+ end
11
+
12
+ it "should return all known attribute classes" do
13
+ subject.should == known_descendants
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attributes::Object, '.descendants' do
4
+ subject { described_class.descendants }
5
+
6
+ let(:known_descendants) do
7
+ [ Virtus::Attributes::Array, Virtus::Attributes::Boolean,
8
+ Virtus::Attributes::Date, Virtus::Attributes::DateTime,
9
+ Virtus::Attributes::Numeric, Virtus::Attributes::Hash,
10
+ Virtus::Attributes::String, Virtus::Attributes::Time ]
11
+ end
12
+
13
+ it "should return all known attribute classes" do
14
+ subject.should == known_descendants
15
+ end
16
+ end