virtus 0.3.0 → 0.4.0
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/Changelog.md +10 -2
- data/Guardfile +9 -0
- data/README.md +12 -8
- data/TODO +0 -35
- data/config/flay.yml +1 -1
- data/config/site.reek +3 -1
- data/lib/virtus.rb +1 -0
- data/lib/virtus/attribute.rb +1 -1
- data/lib/virtus/attribute/collection.rb +2 -2
- data/lib/virtus/attribute/default_value/from_symbol.rb +2 -2
- data/lib/virtus/attribute_set.rb +4 -17
- data/lib/virtus/attributes_accessor.rb +22 -20
- data/lib/virtus/class_methods.rb +1 -1
- data/lib/virtus/coercion/hash.rb +1 -1
- data/lib/virtus/coercion/object.rb +1 -1
- data/lib/virtus/coercion/string.rb +20 -7
- data/lib/virtus/coercion/time_coercions.rb +5 -1
- data/lib/virtus/instance_methods.rb +8 -8
- data/lib/virtus/support/type_lookup.rb +24 -8
- data/lib/virtus/value_object/equalizer.rb +3 -2
- data/lib/virtus/version.rb +1 -1
- data/spec/shared/options_class_method.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/virtus/attribute/class_methods/determine_type_spec.rb +1 -7
- data/spec/unit/virtus/attribute/coerce_spec.rb +2 -2
- data/spec/unit/virtus/attribute/collection/coerce_and_append_member_spec.rb +16 -0
- data/spec/unit/virtus/attribute/collection/coerce_spec.rb +20 -7
- data/spec/unit/virtus/attribute/collection/member_coercion/coerce_and_append_member_spec.rb +18 -0
- data/spec/unit/virtus/attribute/collection/member_type_spec.rb +17 -0
- data/spec/unit/virtus/attribute/collection/new_collection_spec.rb +9 -0
- data/spec/unit/virtus/attribute/default_value/class_methods/build_spec.rb +25 -0
- data/spec/unit/virtus/attribute/default_value/evaluate_spec.rb +0 -43
- data/spec/unit/virtus/attribute/default_value/from_callable/class_methods/handle_spec.rb +20 -0
- data/spec/unit/virtus/attribute/default_value/from_callable/evaluate_spec.rb +21 -0
- data/spec/unit/virtus/attribute/default_value/from_clonable/class_methods/handle_spec.rb +20 -0
- data/spec/unit/virtus/attribute/default_value/from_clonable/evaluate_spec.rb +22 -0
- data/spec/unit/virtus/attribute/default_value/from_symbol/class_methods/handle_spec.rb +20 -0
- data/spec/unit/virtus/attribute/default_value/from_symbol/evaluate_spec.rb +18 -0
- data/spec/unit/virtus/attribute_set/element_set_spec.rb +5 -4
- data/spec/unit/virtus/attributes_accessor/define_reader_method_spec.rb +36 -0
- data/spec/unit/virtus/attributes_accessor/define_writer_method_spec.rb +36 -0
- data/spec/unit/virtus/coercion/array/{to_set_spec.rb → class_methods/to_set_spec.rb} +0 -0
- data/spec/unit/virtus/coercion/{class_name_reference_spec.rb → class_methods/element_reference_spec.rb} +0 -0
- data/spec/unit/virtus/coercion/class_methods/primitive_spec.rb +13 -0
- data/spec/unit/virtus/coercion/decimal/class_methods/to_decimal_spec.rb +10 -0
- data/spec/unit/virtus/coercion/float/class_methods/to_float_spec.rb +10 -0
- data/spec/unit/virtus/coercion/integer/class_methods/to_integer_spec.rb +10 -0
- data/spec/unit/virtus/coercion/numeric/class_methods/to_decimal_spec.rb +0 -0
- data/spec/unit/virtus/coercion/numeric/class_methods/to_float_spec.rb +0 -0
- data/spec/unit/virtus/coercion/numeric/class_methods/to_integer_spec.rb +0 -0
- data/spec/unit/virtus/coercion/numeric/class_methods/to_string_spec.rb +0 -0
- data/spec/unit/virtus/coercion/object/class_methods/to_string_spec.rb +1 -1
- data/spec/unit/virtus/coercion/string/class_methods/to_symbol_spec.rb +10 -0
- data/spec/unit/virtus/coercion/time/class_methods/to_integer_spec.rb +11 -0
- data/spec/unit/virtus/instance_methods/attributes_spec.rb +25 -0
- data/spec/unit/virtus/instance_methods/initialize_spec.rb +42 -0
- data/spec/unit/virtus/type_lookup/class_methods/extended_spec.rb +10 -0
- data/spec/unit/virtus/value_object/class_methods/attribute_spec.rb +16 -0
- data/spec/unit/virtus/value_object/equalizer/append_spec.rb +61 -0
- data/virtus.gemspec +4 -3
- metadata +80 -75
- data/spec/unit/virtus/attribute_set/parent_spec.rb +0 -11
@@ -24,6 +24,8 @@ module Virtus
|
|
24
24
|
compile
|
25
25
|
end
|
26
26
|
|
27
|
+
private
|
28
|
+
|
27
29
|
# Compile the equalizer methods based on #keys
|
28
30
|
#
|
29
31
|
# @return [self]
|
@@ -37,8 +39,6 @@ module Virtus
|
|
37
39
|
self
|
38
40
|
end
|
39
41
|
|
40
|
-
private
|
41
|
-
|
42
42
|
# Define an inspect method that reports the values of the instance's keys
|
43
43
|
#
|
44
44
|
# @return [undefined]
|
@@ -122,3 +122,4 @@ module Virtus
|
|
122
122
|
end # class Equalizer
|
123
123
|
end # module ValueObject
|
124
124
|
end # module Virtus
|
125
|
+
|
data/lib/virtus/version.rb
CHANGED
@@ -13,7 +13,7 @@ shared_examples_for 'an options class method' do
|
|
13
13
|
it { should equal(object) }
|
14
14
|
|
15
15
|
it 'sets the default value for the class method' do
|
16
|
-
expect { subject }.to change { object.send(method) }.from(
|
16
|
+
expect { subject }.to change { object.send(method) }.from(default).to(value)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -13,7 +13,7 @@ require 'virtus'
|
|
13
13
|
ENV['TZ'] = 'UTC'
|
14
14
|
|
15
15
|
# require spec support files and shared behavior
|
16
|
-
Dir[File.expand_path('
|
16
|
+
Dir[File.expand_path('../shared/**/*.rb', __FILE__)].each { |file| require file }
|
17
17
|
|
18
18
|
RSpec.configure do |config|
|
19
19
|
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Virtus::Attribute, '.determine_type' do
|
4
4
|
let(:object) { described_class }
|
5
5
|
|
6
|
-
described_class.descendants.each do |attribute_class|
|
6
|
+
(described_class.descendants - [ Virtus::Attribute::Collection ]).each do |attribute_class|
|
7
7
|
context "with class #{attribute_class.inspect}" do
|
8
8
|
subject { object.determine_type(attribute_class) }
|
9
9
|
|
@@ -16,12 +16,6 @@ describe Virtus::Attribute, '.determine_type' do
|
|
16
16
|
context "with #{attribute_class.inspect} and primitive #{primitive.inspect}" do
|
17
17
|
subject { object.determine_type(primitive) }
|
18
18
|
|
19
|
-
before do
|
20
|
-
if [Virtus::Attribute::Collection].include? attribute_class
|
21
|
-
pending
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
19
|
it 'returns the corresponding attribute class' do
|
26
20
|
should be(attribute_class)
|
27
21
|
end
|
@@ -9,7 +9,7 @@ describe Virtus::Attribute, '#coerce' do
|
|
9
9
|
let(:coercion_method) { stub('coercion_method') }
|
10
10
|
let(:value) { mock('value', :class => value_class) }
|
11
11
|
let(:value_class) { stub('value_class') }
|
12
|
-
let(:coercer) { mock('coercer', :
|
12
|
+
let(:coercer) { mock('coercer', :public_send => coerced) }
|
13
13
|
let(:coerced) { stub('coerced') }
|
14
14
|
|
15
15
|
before do
|
@@ -24,7 +24,7 @@ describe Virtus::Attribute, '#coerce' do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
it 'asks the coercer to coerce the value' do
|
27
|
-
coercer.should_receive(:
|
27
|
+
coercer.should_receive(:public_send).with(coercion_method, value)
|
28
28
|
subject
|
29
29
|
end
|
30
30
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute::Collection, '#coerce_and_append_member' do
|
4
|
+
subject { object.coerce_and_append_member(collection, entry) }
|
5
|
+
|
6
|
+
let(:object) { described_class.new('stuff') }
|
7
|
+
let(:collection) { mock('collection') }
|
8
|
+
let(:entry) { mock('entry') }
|
9
|
+
|
10
|
+
specify do
|
11
|
+
expect { subject }.to raise_error(
|
12
|
+
NotImplementedError,
|
13
|
+
"Virtus::Attribute::Collection#coerce_and_append_member has not been implemented"
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
@@ -1,26 +1,39 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Virtus::Attribute::Collection, '#coerce' do
|
4
|
-
subject {
|
4
|
+
subject { object.coerce(value) }
|
5
5
|
|
6
|
-
let(:
|
7
|
-
let(:
|
8
|
-
let(:
|
9
|
-
let(:
|
6
|
+
let(:described_class) { Class.new(Virtus::Attribute::Collection) }
|
7
|
+
let(:object) { described_class.new(:things) }
|
8
|
+
let(:value) { [ entry ] }
|
9
|
+
let(:entry) { mock('entry') }
|
10
10
|
|
11
11
|
before do
|
12
|
+
described_class.primitive ::Array
|
12
13
|
value.should_receive(:respond_to?).with(:inject).and_return(respond_to_inject)
|
13
14
|
end
|
14
15
|
|
15
16
|
context 'when coerced value responds to #inject' do
|
16
17
|
let(:respond_to_inject) { true }
|
17
18
|
|
18
|
-
|
19
|
+
context 'when the object has not implemented #coerce_and_append_member' do
|
20
|
+
specify { expect { subject }.to raise_error(NotImplementedError, "#{object.class}#coerce_and_append_member has not been implemented") }
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when the object has implemented #coerce_and_append_member' do
|
24
|
+
before do
|
25
|
+
def object.coerce_and_append_member(collection, entry)
|
26
|
+
collection << entry
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it { should eql(value) }
|
31
|
+
end
|
19
32
|
end
|
20
33
|
|
21
34
|
context 'when coerced value does not respond to #inject' do
|
22
35
|
let(:respond_to_inject) { false }
|
23
36
|
|
24
|
-
|
37
|
+
it { should eql(value) }
|
25
38
|
end
|
26
39
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute::Collection::MemberCoercion, '#coerce_and_append_member' do
|
4
|
+
subject { object.coerce_and_append_member(collection, entry) }
|
5
|
+
|
6
|
+
let(:attribute_class) do
|
7
|
+
Class.new(Virtus::Attribute::Collection) do
|
8
|
+
include Virtus::Attribute::Collection::MemberCoercion
|
9
|
+
self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:object) { attribute_class.new('stuff', :member_type => Integer) }
|
14
|
+
let(:collection) { [] }
|
15
|
+
let(:entry) { '1' }
|
16
|
+
|
17
|
+
it { should eql([ 1 ]) }
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute::Collection, '#member_type' do
|
4
|
+
subject { object.member_type }
|
5
|
+
|
6
|
+
context 'when specified' do
|
7
|
+
let(:object) { Virtus::Attribute::Set.new('stuff', :member_type => Integer) }
|
8
|
+
|
9
|
+
it { should be(Integer) }
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'when not specified' do
|
13
|
+
let(:object) { Virtus::Attribute::Set.new('stuff') }
|
14
|
+
|
15
|
+
it { should be(Virtus::Attribute::Object) }
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute::DefaultValue, '.build' do
|
4
|
+
subject { described_class.build(attribute, default) }
|
5
|
+
|
6
|
+
let(:attribute) { mock('attribute') }
|
7
|
+
|
8
|
+
context 'when default is a symbol' do
|
9
|
+
let(:default) { :symbol }
|
10
|
+
|
11
|
+
it { should be_instance_of(Virtus::Attribute::DefaultValue::FromSymbol) }
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when default is a callable' do
|
15
|
+
let(:default) { Proc.new {} }
|
16
|
+
|
17
|
+
it { should be_instance_of(Virtus::Attribute::DefaultValue::FromCallable) }
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when default is a clonable' do
|
21
|
+
let(:default) { "I can be cloned" }
|
22
|
+
|
23
|
+
it { should be_instance_of(Virtus::Attribute::DefaultValue::FromClonable) }
|
24
|
+
end
|
25
|
+
end
|
@@ -9,49 +9,6 @@ describe Virtus::Attribute::DefaultValue, '#evaluate' do
|
|
9
9
|
let(:instance) { mock('instance') }
|
10
10
|
let(:response) { stub('response') }
|
11
11
|
|
12
|
-
context 'when the value is callable' do
|
13
|
-
before { value.stub(:call => response) }
|
14
|
-
|
15
|
-
specify { object.should be_instance_of(Virtus::Attribute::DefaultValue::FromCallable) }
|
16
|
-
|
17
|
-
it { should be(response) }
|
18
|
-
|
19
|
-
it 'calls the value with the instance and attribute' do
|
20
|
-
value.should_receive(:call).with(instance, attribute)
|
21
|
-
subject
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
context 'when the value is cloneable' do
|
26
|
-
let(:clone) { stub('clone') }
|
27
|
-
|
28
|
-
before { value.stub(:clone => clone) }
|
29
|
-
|
30
|
-
specify { object.should be_instance_of(Virtus::Attribute::DefaultValue::FromClonable) }
|
31
|
-
|
32
|
-
it { should be(clone) }
|
33
|
-
|
34
|
-
it 'clones the value' do
|
35
|
-
value.should_receive(:clone).with(no_args)
|
36
|
-
subject
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
context 'when the value is a method name' do
|
41
|
-
let(:instance) { mock('instance', value => retval) }
|
42
|
-
let(:value) { :set_default }
|
43
|
-
let(:retval) { stub('retval') }
|
44
|
-
|
45
|
-
specify { object.should be_instance_of(Virtus::Attribute::DefaultValue::FromSymbol) }
|
46
|
-
|
47
|
-
it { should be(retval) }
|
48
|
-
|
49
|
-
it 'calls the method' do
|
50
|
-
instance.should_receive(value).with(no_args)
|
51
|
-
subject
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
12
|
# smallest number that is Bignum across major ruby impls
|
56
13
|
bignum = 0x7fffffffffffffff + 1
|
57
14
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute::DefaultValue::FromCallable, '.handle?' do
|
4
|
+
subject { described_class.handle?(attribute, default) }
|
5
|
+
|
6
|
+
let(:attribute) { mock('attribute') }
|
7
|
+
|
8
|
+
context 'with a callable' do
|
9
|
+
let(:default) { Proc.new {} }
|
10
|
+
|
11
|
+
it { should be(true) }
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'with a non-callable' do
|
15
|
+
let(:default) { '' }
|
16
|
+
|
17
|
+
it { should be(false) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute::DefaultValue::FromCallable, '#evaluate' do
|
4
|
+
subject { object.evaluate(instance) }
|
5
|
+
|
6
|
+
let(:object) { described_class.new(attribute, value) }
|
7
|
+
let(:attribute) { mock('attribute') }
|
8
|
+
let(:value) { mock('value') }
|
9
|
+
let(:instance) { mock('instance') }
|
10
|
+
let(:response) { stub('response') }
|
11
|
+
|
12
|
+
before { value.stub(:call => response) }
|
13
|
+
|
14
|
+
it { should be(response) }
|
15
|
+
|
16
|
+
it 'calls the value with the instance and attribute' do
|
17
|
+
value.should_receive(:call).with(instance, attribute)
|
18
|
+
subject
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute::DefaultValue::FromClonable, '.handle?' do
|
4
|
+
subject { described_class.handle?(attribute, default) }
|
5
|
+
|
6
|
+
let(:attribute) { mock('attribute') }
|
7
|
+
|
8
|
+
context 'with a clonable' do
|
9
|
+
let(:default) { 'clonable' }
|
10
|
+
|
11
|
+
it { should be(true) }
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'with a non-clonable' do
|
15
|
+
let(:default) { 1 }
|
16
|
+
|
17
|
+
it { should be(false) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute::DefaultValue::FromClonable, '#evaluate' do
|
4
|
+
subject { object.evaluate(instance) }
|
5
|
+
|
6
|
+
let(:object) { described_class.new(attribute, value) }
|
7
|
+
let(:attribute) { mock('attribute') }
|
8
|
+
let(:value) { mock('value') }
|
9
|
+
let(:instance) { mock('instance') }
|
10
|
+
let(:response) { mock('response') }
|
11
|
+
let(:clone) { mock('clone') }
|
12
|
+
|
13
|
+
before { value.stub(:clone => clone) }
|
14
|
+
|
15
|
+
it { should be(clone) }
|
16
|
+
|
17
|
+
it 'clones the value' do
|
18
|
+
value.should_receive(:clone).with(no_args)
|
19
|
+
subject
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute::DefaultValue::FromSymbol, '.handle?' do
|
4
|
+
subject { described_class.handle?(attribute, default) }
|
5
|
+
|
6
|
+
let(:attribute) { mock('attribute') }
|
7
|
+
|
8
|
+
context 'with a symbol' do
|
9
|
+
let(:default) { :method_name }
|
10
|
+
|
11
|
+
it { should be(true) }
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'with a non-symbol' do
|
15
|
+
let(:default) { 'method_name' }
|
16
|
+
|
17
|
+
it { should be(false) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute::DefaultValue::FromSymbol, '#evaluate' do
|
4
|
+
subject { object.evaluate(instance) }
|
5
|
+
|
6
|
+
let(:object) { described_class.new(attribute, value) }
|
7
|
+
let(:attribute) { mock('attribute') }
|
8
|
+
let(:instance) { mock('instance', value => retval) }
|
9
|
+
let(:value) { :set_default }
|
10
|
+
let(:retval) { mock('retval') }
|
11
|
+
|
12
|
+
it { should be(retval) }
|
13
|
+
|
14
|
+
it 'calls the method' do
|
15
|
+
instance.should_receive(value).with(no_args)
|
16
|
+
subject
|
17
|
+
end
|
18
|
+
end
|
@@ -31,8 +31,9 @@ describe Virtus::AttributeSet, '#[]=' do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
context 'with a duplicate attribute' do
|
34
|
-
let(:
|
35
|
-
let(:
|
34
|
+
let(:original) { mock('original', :name => name) }
|
35
|
+
let(:attributes) { [ original ] }
|
36
|
+
let(:attribute) { mock('attribute', :name => name) }
|
36
37
|
|
37
38
|
it { should equal(attribute) }
|
38
39
|
|
@@ -41,11 +42,11 @@ describe Virtus::AttributeSet, '#[]=' do
|
|
41
42
|
end
|
42
43
|
|
43
44
|
it 'allows #[] to access the attribute with a symbol' do
|
44
|
-
expect { subject }.to change { object['name'] }.from(
|
45
|
+
expect { subject }.to change { object['name'] }.from(original).to(attribute)
|
45
46
|
end
|
46
47
|
|
47
48
|
it 'allows #[] to access the attribute with a string' do
|
48
|
-
expect { subject }.to change { object[:name] }.from(
|
49
|
+
expect { subject }.to change { object[:name] }.from(original).to(attribute)
|
49
50
|
end
|
50
51
|
|
51
52
|
it 'allows #reset to track overridden attributes' do
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::AttributesAccessor, '#define_reader_method' do
|
4
|
+
subject { described_class.new('Test') }
|
5
|
+
|
6
|
+
let(:attribute) { mock('attribute') }
|
7
|
+
|
8
|
+
if RUBY_VERSION < '1.9'
|
9
|
+
let(:method_name) { 'foo_bar' }
|
10
|
+
else
|
11
|
+
let(:method_name) { :foo_bar }
|
12
|
+
end
|
13
|
+
|
14
|
+
before do
|
15
|
+
subject.define_reader_method(attribute, method_name, visibility)
|
16
|
+
end
|
17
|
+
|
18
|
+
context "with public visibility" do
|
19
|
+
let(:visibility) { :public }
|
20
|
+
|
21
|
+
its(:public_instance_methods) { should include(method_name) }
|
22
|
+
end
|
23
|
+
|
24
|
+
context "with private visibility" do
|
25
|
+
let(:visibility) { :private }
|
26
|
+
|
27
|
+
its(:private_instance_methods) { should include(method_name) }
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with protected visibility" do
|
31
|
+
let(:visibility) { :protected }
|
32
|
+
|
33
|
+
its(:protected_instance_methods) { should include(method_name) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|