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