virtus 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/Gemfile +11 -15
  2. data/History.txt +10 -0
  3. data/TODO +4 -0
  4. data/VERSION +1 -1
  5. data/config/flay.yml +1 -1
  6. data/config/flog.yml +1 -1
  7. data/config/roodi.yml +6 -6
  8. data/config/site.reek +5 -5
  9. data/lib/virtus.rb +21 -11
  10. data/lib/virtus/attribute.rb +92 -59
  11. data/lib/virtus/attribute/array.rb +4 -3
  12. data/lib/virtus/attribute/boolean.rb +21 -9
  13. data/lib/virtus/attribute/date.rb +5 -3
  14. data/lib/virtus/attribute/date_time.rb +5 -3
  15. data/lib/virtus/attribute/decimal.rb +5 -3
  16. data/lib/virtus/attribute/float.rb +5 -3
  17. data/lib/virtus/attribute/hash.rb +4 -3
  18. data/lib/virtus/attribute/integer.rb +5 -3
  19. data/lib/virtus/attribute/numeric.rb +5 -3
  20. data/lib/virtus/attribute/object.rb +4 -4
  21. data/lib/virtus/attribute/string.rb +7 -9
  22. data/lib/virtus/attribute/time.rb +5 -3
  23. data/lib/virtus/attribute_set.rb +151 -0
  24. data/lib/virtus/class_methods.rb +19 -10
  25. data/lib/virtus/instance_methods.rb +51 -27
  26. data/lib/virtus/support/descendants_tracker.rb +30 -0
  27. data/lib/virtus/typecast/boolean.rb +7 -5
  28. data/lib/virtus/typecast/numeric.rb +13 -8
  29. data/lib/virtus/typecast/string.rb +24 -0
  30. data/lib/virtus/typecast/time.rb +7 -5
  31. data/spec/integration/virtus/class_methods/attribute_spec.rb +17 -5
  32. data/spec/integration/virtus/class_methods/attributes_spec.rb +2 -5
  33. data/spec/shared/idempotent_method_behaviour.rb +5 -0
  34. data/spec/spec_helper.rb +15 -0
  35. data/spec/unit/shared/attribute.rb +6 -155
  36. data/spec/unit/shared/attribute/accept_options.rb +55 -0
  37. data/spec/unit/shared/attribute/accepted_options.rb +11 -0
  38. data/spec/unit/shared/attribute/complex.rb +15 -0
  39. data/spec/unit/shared/attribute/get.rb +29 -0
  40. data/spec/unit/shared/attribute/options.rb +7 -0
  41. data/spec/unit/shared/attribute/set.rb +42 -0
  42. data/spec/unit/virtus/attribute/boolean_spec.rb +1 -2
  43. data/spec/unit/virtus/attribute/date_spec.rb +1 -2
  44. data/spec/unit/virtus/attribute/date_time_spec.rb +1 -2
  45. data/spec/unit/virtus/attribute/decimal_spec.rb +1 -2
  46. data/spec/unit/virtus/attribute/float_spec.rb +1 -2
  47. data/spec/unit/virtus/attribute/integer_spec.rb +1 -2
  48. data/spec/unit/virtus/attribute/numeric/class_methods/descendants_spec.rb +2 -2
  49. data/spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb +6 -4
  50. data/spec/unit/virtus/attribute/string_spec.rb +1 -2
  51. data/spec/unit/virtus/attribute/time_spec.rb +1 -2
  52. data/spec/unit/virtus/attribute_set/append_spec.rb +35 -0
  53. data/spec/unit/virtus/attribute_set/each_spec.rb +60 -0
  54. data/spec/unit/virtus/attribute_set/element_reference_spec.rb +13 -0
  55. data/spec/unit/virtus/attribute_set/element_set_spec.rb +35 -0
  56. data/spec/unit/virtus/attribute_set/merge_spec.rb +36 -0
  57. data/spec/unit/virtus/attribute_set/parent_spec.rb +11 -0
  58. data/spec/unit/virtus/attribute_set/reset_spec.rb +60 -0
  59. data/spec/unit/virtus/class_methods/attribute_spec.rb +11 -0
  60. data/spec/unit/virtus/descendants_tracker/descendants_spec.rb +22 -0
  61. data/spec/unit/virtus/descendants_tracker/inherited_spec.rb +24 -0
  62. data/spec/unit/virtus/determine_type_spec.rb +21 -9
  63. data/spec/unit/virtus/instance_methods/{attribute_get_spec.rb → element_reference_spec.rb} +4 -2
  64. data/spec/unit/virtus/instance_methods/{attribute_set_spec.rb → element_set_spec.rb} +5 -7
  65. data/virtus.gemspec +35 -13
  66. metadata +96 -14
  67. data/lib/virtus/support/chainable.rb +0 -13
@@ -0,0 +1,55 @@
1
+ shared_examples_for 'Attribute.accept_options' do
2
+ let(:sub_attribute) { Class.new(described_class) }
3
+ let(:new_option) { :width }
4
+
5
+ specify { described_class.should respond_to(:accept_options) }
6
+
7
+ before :all do
8
+ described_class.accepted_options.should_not include(new_option)
9
+ described_class.accept_options(new_option)
10
+ end
11
+
12
+ it "sets new accepted options on itself" do
13
+ described_class.accepted_options.should include(new_option)
14
+ end
15
+
16
+ it "sets new accepted option on its descendants" do
17
+ sub_attribute.accepted_options.should include(new_option)
18
+ end
19
+
20
+ it "creates option accessors" do
21
+ described_class.should respond_to(new_option)
22
+ end
23
+
24
+ it "creates option accessors on descendants" do
25
+ sub_attribute.should respond_to(new_option)
26
+ end
27
+
28
+ context 'with default option value' do
29
+ let(:option) { :height }
30
+ let(:value) { 10 }
31
+
32
+ before :all do
33
+ sub_attribute.accept_options(option)
34
+ sub_attribute.height(value)
35
+ end
36
+
37
+ context "when new attribute is created" do
38
+ subject { sub_attribute.new(attribute_name) }
39
+
40
+ it "sets the default value" do
41
+ subject.options[option].should eql(value)
42
+ end
43
+ end
44
+
45
+ context "when new attribute is created and overrides option's default value" do
46
+ let(:new_value) { 11 }
47
+
48
+ subject { sub_attribute.new(attribute_name, option => new_value) }
49
+
50
+ it "sets the new value" do
51
+ subject.options[option].should eql(new_value)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,11 @@
1
+ shared_examples_for 'Attribute.accepted_options' do
2
+ specify { described_class.should respond_to(:accepted_options) }
3
+
4
+ it "returns an array of accepted options" do
5
+ described_class.accepted_options.should be_instance_of(Array)
6
+ end
7
+
8
+ it "includes base options" do
9
+ described_class.accepted_options.should include(*Virtus::Attribute::OPTIONS)
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ shared_examples_for 'Attribute#complex?' do
2
+ let(:attribute) { described_class.new(attribute_name, :complex => complex) }
3
+
4
+ subject { attribute.complex? }
5
+
6
+ context "when set to true" do
7
+ let(:complex) { true }
8
+ it { should be(true) }
9
+ end
10
+
11
+ context "when set to false" do
12
+ let(:complex) { false }
13
+ it { should be(false) }
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ shared_examples_for 'Attribute#get' do
2
+ let(:model) do
3
+ Class.new { include Virtus }
4
+ end
5
+
6
+ let(:attribute) do
7
+ model.attribute(attribute_name, described_class).attributes[attribute_name]
8
+ end
9
+
10
+ let(:object) do
11
+ model.new
12
+ end
13
+
14
+ context "when a non-nil value is set" do
15
+ before { attribute.set(object, attribute_value) }
16
+
17
+ subject { attribute.get(object) }
18
+
19
+ it { should eql(attribute_value) }
20
+ end
21
+
22
+ context "when nil is set" do
23
+ before { attribute.set(object, nil) }
24
+
25
+ subject { attribute.get(object) }
26
+
27
+ it { should be(nil) }
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ shared_examples_for 'Attribute.options' do
2
+ specify { described_class.should respond_to(:options) }
3
+
4
+ it 'returns a hash with options' do
5
+ described_class.options.should be_instance_of(Hash)
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ shared_examples_for 'Attribute#set' do
2
+ let(:model) do
3
+ Class.new { include Virtus }
4
+ end
5
+
6
+ let(:attribute) do
7
+ model.attribute(attribute_name, described_class).attributes[attribute_name]
8
+ end
9
+
10
+ let(:object) do
11
+ model.new
12
+ end
13
+
14
+ context "with nil" do
15
+ subject { attribute.set(object, nil) }
16
+
17
+ it "doesn't set the ivar" do
18
+ subject
19
+ object.instance_variable_defined?(attribute.instance_variable_name).should be(false)
20
+ end
21
+
22
+ it "returns nil" do
23
+ subject.should be(nil)
24
+ end
25
+ end
26
+
27
+ context "with a primitive value" do
28
+ before { attribute.set(object, attribute_value) }
29
+
30
+ it "sets the value in an ivar" do
31
+ object.instance_variable_get(attribute.instance_variable_name).should eql(attribute_value)
32
+ end
33
+ end
34
+
35
+ context "with a non-primitive value" do
36
+ before { attribute.set(object, attribute_value_other) }
37
+
38
+ it "sets the value in an ivar converted to the primitive type" do
39
+ object.instance_variable_get(attribute.instance_variable_name).should be_kind_of(described_class.primitive)
40
+ end
41
+ end
42
+ end
@@ -24,8 +24,7 @@ describe Virtus::Attribute::Boolean do
24
24
  end
25
25
 
26
26
  describe '#typecast' do
27
- let(:model) { Class.new { include Virtus } }
28
- let(:attribute) { model.attribute(:is_admin, Virtus::Attribute::Boolean) }
27
+ let(:attribute) { Virtus::Attribute::Boolean.new(:is_admin) }
29
28
 
30
29
  subject { attribute.typecast(value) }
31
30
 
@@ -8,8 +8,7 @@ describe Virtus::Attribute::Date do
8
8
  end
9
9
 
10
10
  describe '#typecast' do
11
- let(:model) { Class.new { include Virtus } }
12
- let(:attribute) { model.attribute(:bday, Virtus::Attribute::Date) }
11
+ let(:attribute) { Virtus::Attribute::Date.new(:bday) }
13
12
 
14
13
  let(:year) { 2011 }
15
14
  let(:month) { 4 }
@@ -8,8 +8,7 @@ describe Virtus::Attribute::DateTime do
8
8
  end
9
9
 
10
10
  describe '#typecast' do
11
- let(:model) { Class.new { include Virtus } }
12
- let(:attribute) { model.attribute(:bday, Virtus::Attribute::DateTime) }
11
+ let(:attribute) { Virtus::Attribute::DateTime.new(:bday) }
13
12
 
14
13
  let(:year) { 2011 }
15
14
  let(:month) { 4 }
@@ -8,8 +8,7 @@ describe Virtus::Attribute::Decimal do
8
8
  end
9
9
 
10
10
  describe '#typecast' do
11
- let(:model) { Class.new { include Virtus } }
12
- let(:attribute) { model.attribute(:price, Virtus::Attribute::Decimal) }
11
+ let(:attribute) { Virtus::Attribute::Decimal.new(:price) }
13
12
 
14
13
  subject { attribute.typecast(value) }
15
14
 
@@ -8,8 +8,7 @@ describe Virtus::Attribute::Float do
8
8
  end
9
9
 
10
10
  describe '#typecast' do
11
- let(:model) { Class.new { include Virtus } }
12
- let(:attribute) { model.attribute(:score, Virtus::Attribute::Float) }
11
+ let(:attribute) { Virtus::Attribute::Float.new(:score) }
13
12
 
14
13
  subject { attribute.typecast(value) }
15
14
 
@@ -8,8 +8,7 @@ describe Virtus::Attribute::Integer do
8
8
  end
9
9
 
10
10
  describe '#typecast' do
11
- let(:model) { Class.new { include Virtus } }
12
- let(:attribute) { model.attribute(:age, Virtus::Attribute::Integer) }
11
+ let(:attribute) { Virtus::Attribute::Integer.new(:age) }
13
12
 
14
13
  subject { attribute.typecast(value) }
15
14
 
@@ -4,9 +4,9 @@ describe Virtus::Attribute::Numeric, '.descendants' do
4
4
  subject { described_class.descendants }
5
5
 
6
6
  let(:known_descendants) do
7
- [ Virtus::Attribute::Decimal,
7
+ [ Virtus::Attribute::Integer,
8
8
  Virtus::Attribute::Float,
9
- Virtus::Attribute::Integer ]
9
+ Virtus::Attribute::Decimal ]
10
10
  end
11
11
 
12
12
  it "should return all known attribute classes" do
@@ -4,10 +4,12 @@ describe Virtus::Attribute::Object, '.descendants' do
4
4
  subject { described_class.descendants }
5
5
 
6
6
  let(:known_descendants) do
7
- [ Virtus::Attribute::Array, Virtus::Attribute::Boolean,
8
- Virtus::Attribute::Date, Virtus::Attribute::DateTime,
9
- Virtus::Attribute::Numeric, Virtus::Attribute::Hash,
10
- Virtus::Attribute::String, Virtus::Attribute::Time ]
7
+ [ Virtus::Attribute::Time, Virtus::Attribute::String,
8
+ Virtus::Attribute::Integer, Virtus::Attribute::Hash,
9
+ Virtus::Attribute::Float, Virtus::Attribute::Decimal,
10
+ Virtus::Attribute::Numeric, Virtus::Attribute::DateTime,
11
+ Virtus::Attribute::Date, Virtus::Attribute::Boolean,
12
+ Virtus::Attribute::Array ]
11
13
  end
12
14
 
13
15
  it "should return all known attribute classes" do
@@ -8,8 +8,7 @@ describe Virtus::Attribute::String do
8
8
  end
9
9
 
10
10
  describe '#typecast' do
11
- let(:model) { Class.new { include Virtus } }
12
- let(:attribute) { model.attribute(:name, String) }
11
+ let(:attribute) { Virtus::Attribute::String.new(:name) }
13
12
  let(:value) { 1 }
14
13
  let(:typecast_value) { '1' }
15
14
 
@@ -8,8 +8,7 @@ describe Virtus::Attribute::Time do
8
8
  end
9
9
 
10
10
  describe '#typecast' do
11
- let(:model) { Class.new { include Virtus } }
12
- let(:attribute) { model.attribute(:birthday, Virtus::Attribute::Time) }
11
+ let(:attribute) { Virtus::Attribute::Time.new(:bday) }
13
12
 
14
13
  let(:year) { 1983 }
15
14
  let(:month) { 11 }
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::AttributeSet, '#<<' do
4
+ subject { object << attribute }
5
+
6
+ let(:attributes) { [] }
7
+ let(:parent) { described_class.new }
8
+ let(:object) { described_class.new(parent, attributes) }
9
+ let(:name) { :name }
10
+
11
+ context 'with a new attribute' do
12
+ let(:attribute) { mock('Attribute', :name => name) }
13
+
14
+ it { should equal(object) }
15
+
16
+ it 'adds an attribute' do
17
+ expect { subject }.to change { object.to_a }.
18
+ from(attributes).
19
+ to([ attribute ])
20
+ end
21
+ end
22
+
23
+ context 'with a duplicate attribute' do
24
+ let(:attributes) { [ mock('Attribute', :name => name) ] }
25
+ let(:attribute) { mock('Duplicate', :name => name) }
26
+
27
+ it { should equal(object) }
28
+
29
+ it 'replaces the original attribute' do
30
+ expect { subject }.to change { object.to_a }.
31
+ from(attributes).
32
+ to([ attribute ])
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::AttributeSet, '#each' do
4
+ let(:name) { :name }
5
+ let(:attribute) { mock('Attribute', :name => name) }
6
+ let(:attributes) { [ attribute ] }
7
+ let(:parent) { described_class.new }
8
+ let(:object) { described_class.new(parent, attributes) }
9
+ let(:yields) { Set[] }
10
+
11
+ context 'with no block' do
12
+ subject { object.each }
13
+
14
+ it { should be_instance_of(to_enum.class) }
15
+
16
+ it 'yields the expected attributes' do
17
+ subject.to_a.should eql(object.to_a)
18
+ end
19
+ end
20
+
21
+ context 'with a block' do
22
+ subject { object.each { |attribute| yields << attribute } }
23
+
24
+ context 'when the parent has no attributes' do
25
+ it { should equal(object) }
26
+
27
+ it 'yields the expected attributes' do
28
+ expect { subject }.to change { yields.dup }.
29
+ from(Set[]).
30
+ to(attributes.to_set)
31
+ end
32
+ end
33
+
34
+ context 'when the parent has attributes that are not duplicates' do
35
+ let(:parent_attribute) { mock('Parent Attribute', :name => :parent_name) }
36
+ let(:parent) { described_class.new([ parent_attribute ]) }
37
+
38
+ it { should equal(object) }
39
+
40
+ it 'yields the expected attributes' do
41
+ expect { subject }.to change { yields.dup }.
42
+ from(Set[]).
43
+ to(Set[ attribute, parent_attribute ])
44
+ end
45
+ end
46
+
47
+ context 'when the parent has attributes that are duplicates' do
48
+ let(:parent_attribute) { mock('Parent Attribute', :name => name) }
49
+ let(:parent) { described_class.new([ parent_attribute ]) }
50
+
51
+ it { should equal(object) }
52
+
53
+ it 'yields the expected attributes' do
54
+ expect { subject }.to change { yields.dup }.
55
+ from(Set[]).
56
+ to(Set[ attribute ])
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::AttributeSet, '#[]' do
4
+ subject { object[name] }
5
+
6
+ let(:name) { :name }
7
+ let(:attribute) { mock('Attribute', :name => name) }
8
+ let(:attributes) { [ attribute ] }
9
+ let(:parent) { described_class.new }
10
+ let(:object) { described_class.new(parent, attributes) }
11
+
12
+ it { should equal(attribute) }
13
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::AttributeSet, '#[]=' do
4
+ subject { object[name] = attribute }
5
+
6
+ let(:attributes) { [] }
7
+ let(:parent) { described_class.new }
8
+ let(:object) { described_class.new(parent, attributes) }
9
+ let(:name) { :name }
10
+
11
+ context 'with a new attribute' do
12
+ let(:attribute) { mock('Attribute', :name => name) }
13
+
14
+ it { should equal(attribute) }
15
+
16
+ it 'adds an attribute' do
17
+ expect { subject }.to change { object.to_a }.
18
+ from(attributes).
19
+ to([ attribute ])
20
+ end
21
+ end
22
+
23
+ context 'with a duplicate attribute' do
24
+ let(:attributes) { [ mock('Attribute', :name => name) ] }
25
+ let(:attribute) { mock('Duplicate', :name => name) }
26
+
27
+ it { should equal(attribute) }
28
+
29
+ it 'replaces the original attribute' do
30
+ expect { subject }.to change { object.to_a }.
31
+ from(attributes).
32
+ to([ attribute ])
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::AttributeSet, '#merge' do
4
+ subject { object.merge(other) }
5
+
6
+ let(:attributes) { [] }
7
+ let(:parent) { described_class.new }
8
+ let(:object) { described_class.new(parent, attributes) }
9
+ let(:name) { :name }
10
+ let(:other) { [ attribute ] }
11
+
12
+ context 'with a new attribute' do
13
+ let(:attribute) { mock('Attribute', :name => name) }
14
+
15
+ it { should equal(object) }
16
+
17
+ it 'adds an attribute' do
18
+ expect { subject }.to change { object.to_a }.
19
+ from(attributes).
20
+ to([ attribute ])
21
+ end
22
+ end
23
+
24
+ context 'with a duplicate attribute' do
25
+ let(:attributes) { [ mock('Attribute', :name => name) ] }
26
+ let(:attribute) { mock('Duplicate', :name => name) }
27
+
28
+ it { should equal(object) }
29
+
30
+ it 'replaces the original attribute' do
31
+ expect { subject }.to change { object.to_a }.
32
+ from(attributes).
33
+ to([ attribute ])
34
+ end
35
+ end
36
+ end