virtus 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +11 -15
- data/History.txt +10 -0
- data/TODO +4 -0
- data/VERSION +1 -1
- data/config/flay.yml +1 -1
- data/config/flog.yml +1 -1
- data/config/roodi.yml +6 -6
- data/config/site.reek +5 -5
- data/lib/virtus.rb +21 -11
- data/lib/virtus/attribute.rb +92 -59
- data/lib/virtus/attribute/array.rb +4 -3
- data/lib/virtus/attribute/boolean.rb +21 -9
- data/lib/virtus/attribute/date.rb +5 -3
- data/lib/virtus/attribute/date_time.rb +5 -3
- data/lib/virtus/attribute/decimal.rb +5 -3
- data/lib/virtus/attribute/float.rb +5 -3
- data/lib/virtus/attribute/hash.rb +4 -3
- data/lib/virtus/attribute/integer.rb +5 -3
- data/lib/virtus/attribute/numeric.rb +5 -3
- data/lib/virtus/attribute/object.rb +4 -4
- data/lib/virtus/attribute/string.rb +7 -9
- data/lib/virtus/attribute/time.rb +5 -3
- data/lib/virtus/attribute_set.rb +151 -0
- data/lib/virtus/class_methods.rb +19 -10
- data/lib/virtus/instance_methods.rb +51 -27
- data/lib/virtus/support/descendants_tracker.rb +30 -0
- data/lib/virtus/typecast/boolean.rb +7 -5
- data/lib/virtus/typecast/numeric.rb +13 -8
- data/lib/virtus/typecast/string.rb +24 -0
- data/lib/virtus/typecast/time.rb +7 -5
- data/spec/integration/virtus/class_methods/attribute_spec.rb +17 -5
- data/spec/integration/virtus/class_methods/attributes_spec.rb +2 -5
- data/spec/shared/idempotent_method_behaviour.rb +5 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/unit/shared/attribute.rb +6 -155
- data/spec/unit/shared/attribute/accept_options.rb +55 -0
- data/spec/unit/shared/attribute/accepted_options.rb +11 -0
- data/spec/unit/shared/attribute/complex.rb +15 -0
- data/spec/unit/shared/attribute/get.rb +29 -0
- data/spec/unit/shared/attribute/options.rb +7 -0
- data/spec/unit/shared/attribute/set.rb +42 -0
- data/spec/unit/virtus/attribute/boolean_spec.rb +1 -2
- data/spec/unit/virtus/attribute/date_spec.rb +1 -2
- data/spec/unit/virtus/attribute/date_time_spec.rb +1 -2
- data/spec/unit/virtus/attribute/decimal_spec.rb +1 -2
- data/spec/unit/virtus/attribute/float_spec.rb +1 -2
- data/spec/unit/virtus/attribute/integer_spec.rb +1 -2
- data/spec/unit/virtus/attribute/numeric/class_methods/descendants_spec.rb +2 -2
- data/spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb +6 -4
- data/spec/unit/virtus/attribute/string_spec.rb +1 -2
- data/spec/unit/virtus/attribute/time_spec.rb +1 -2
- data/spec/unit/virtus/attribute_set/append_spec.rb +35 -0
- data/spec/unit/virtus/attribute_set/each_spec.rb +60 -0
- data/spec/unit/virtus/attribute_set/element_reference_spec.rb +13 -0
- data/spec/unit/virtus/attribute_set/element_set_spec.rb +35 -0
- data/spec/unit/virtus/attribute_set/merge_spec.rb +36 -0
- data/spec/unit/virtus/attribute_set/parent_spec.rb +11 -0
- data/spec/unit/virtus/attribute_set/reset_spec.rb +60 -0
- data/spec/unit/virtus/class_methods/attribute_spec.rb +11 -0
- data/spec/unit/virtus/descendants_tracker/descendants_spec.rb +22 -0
- data/spec/unit/virtus/descendants_tracker/inherited_spec.rb +24 -0
- data/spec/unit/virtus/determine_type_spec.rb +21 -9
- data/spec/unit/virtus/instance_methods/{attribute_get_spec.rb → element_reference_spec.rb} +4 -2
- data/spec/unit/virtus/instance_methods/{attribute_set_spec.rb → element_set_spec.rb} +5 -7
- data/virtus.gemspec +35 -13
- metadata +96 -14
- 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,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(:
|
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(:
|
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(:
|
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(:
|
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(:
|
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(:
|
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::
|
7
|
+
[ Virtus::Attribute::Integer,
|
8
8
|
Virtus::Attribute::Float,
|
9
|
-
Virtus::Attribute::
|
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::
|
8
|
-
Virtus::Attribute::
|
9
|
-
Virtus::Attribute::
|
10
|
-
Virtus::Attribute::
|
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(:
|
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(:
|
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
|