virtus 0.0.3 → 0.0.4

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