virtus 1.0.0.beta6 → 1.0.0.beta7
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/lib/virtus/attribute/builder.rb +2 -10
- data/lib/virtus/attribute/embedded_value.rb +26 -12
- data/lib/virtus/attribute/strict.rb +8 -4
- data/lib/virtus/attribute.rb +20 -1
- data/lib/virtus/const_missing_extensions.rb +3 -1
- data/lib/virtus/version.rb +1 -1
- data/lib/virtus.rb +9 -0
- data/spec/integration/building_module_spec.rb +1 -1
- data/spec/integration/injectible_coercers_spec.rb +48 -0
- data/spec/integration/required_attributes_spec.rb +25 -0
- data/spec/unit/virtus/attribute/coerce_spec.rb +38 -4
- data/spec/unit/virtus/attribute/embedded_value/class_methods/build_spec.rb +15 -5
- data/spec/unit/virtus/attribute/rename_spec.rb +13 -0
- data/spec/unit/virtus/attribute/required_predicate_spec.rb +19 -0
- metadata +6 -3
- data/spec/unit/virtus/attribute_set/inspect_spec.rb +0 -9
@@ -23,7 +23,7 @@ module Virtus
|
|
23
23
|
if klass < Axiom::Types::Type
|
24
24
|
determine_type(klass.primitive)
|
25
25
|
elsif EmbeddedValue.handles?(klass)
|
26
|
-
EmbeddedValue
|
26
|
+
EmbeddedValue
|
27
27
|
elsif klass < Enumerable
|
28
28
|
Collection
|
29
29
|
end
|
@@ -62,7 +62,7 @@ module Virtus
|
|
62
62
|
|
63
63
|
# @api private
|
64
64
|
def initialize_coercer
|
65
|
-
@options.update(:coercer => @options.fetch(:coercer) { build_coercer })
|
65
|
+
@options.update(:coercer => @options.fetch(:coercer) { @klass.build_coercer(@type, @options) })
|
66
66
|
end
|
67
67
|
|
68
68
|
# @api private
|
@@ -106,14 +106,6 @@ module Virtus
|
|
106
106
|
@options.update(:reader => reader_visibility, :writer => writer_visibility)
|
107
107
|
end
|
108
108
|
|
109
|
-
# @api private
|
110
|
-
def build_coercer
|
111
|
-
Coercer.new(
|
112
|
-
@options.fetch(:configured_coercer) { Virtus.coercer },
|
113
|
-
@type.coercion_method
|
114
|
-
)
|
115
|
-
end
|
116
|
-
|
117
109
|
end # class Builder
|
118
110
|
|
119
111
|
end # class Attribute
|
@@ -25,28 +25,39 @@ module Virtus
|
|
25
25
|
class EmbeddedValue < Attribute
|
26
26
|
TYPES = [Struct, OpenStruct, Virtus, Model::Constructor].freeze
|
27
27
|
|
28
|
-
class
|
28
|
+
class Coercer
|
29
|
+
attr_reader :primitive
|
30
|
+
|
31
|
+
def initialize(primitive)
|
32
|
+
@primitive = primitive
|
33
|
+
end
|
34
|
+
|
35
|
+
end # Coercer
|
36
|
+
|
37
|
+
class FromStruct < Coercer
|
29
38
|
|
30
39
|
# @api public
|
31
|
-
def
|
40
|
+
def call(input)
|
32
41
|
if input.kind_of?(primitive)
|
33
42
|
input
|
34
43
|
elsif not input.nil?
|
35
44
|
primitive.new(*input)
|
36
45
|
end
|
37
46
|
end
|
47
|
+
|
38
48
|
end # FromStruct
|
39
49
|
|
40
|
-
class FromOpenStruct <
|
50
|
+
class FromOpenStruct < Coercer
|
41
51
|
|
42
52
|
# @api public
|
43
|
-
def
|
53
|
+
def call(input)
|
44
54
|
if input.kind_of?(primitive)
|
45
55
|
input
|
46
56
|
elsif not input.nil?
|
47
57
|
primitive.new(input)
|
48
58
|
end
|
49
59
|
end
|
60
|
+
|
50
61
|
end # FromOpenStruct
|
51
62
|
|
52
63
|
# @api private
|
@@ -55,17 +66,19 @@ module Virtus
|
|
55
66
|
end
|
56
67
|
|
57
68
|
# @api private
|
58
|
-
def self.
|
59
|
-
|
60
|
-
FromOpenStruct
|
61
|
-
elsif klass < Struct
|
62
|
-
FromStruct
|
63
|
-
end
|
69
|
+
def self.build_type(options)
|
70
|
+
Axiom::Types::Object.new { primitive options[:type] }
|
64
71
|
end
|
65
72
|
|
66
73
|
# @api private
|
67
|
-
def self.
|
68
|
-
|
74
|
+
def self.build_coercer(type, _options)
|
75
|
+
primitive = type.primitive
|
76
|
+
|
77
|
+
if primitive < Virtus || primitive < Model::Constructor || primitive <= OpenStruct
|
78
|
+
FromOpenStruct.new(primitive)
|
79
|
+
elsif primitive < Struct
|
80
|
+
FromStruct.new(primitive)
|
81
|
+
end
|
69
82
|
end
|
70
83
|
|
71
84
|
# @api public
|
@@ -74,5 +87,6 @@ module Virtus
|
|
74
87
|
end
|
75
88
|
|
76
89
|
end # class EmbeddedValue
|
90
|
+
|
77
91
|
end # class Attribute
|
78
92
|
end # module Virtus
|
@@ -4,10 +4,14 @@ module Virtus
|
|
4
4
|
module Strict
|
5
5
|
|
6
6
|
# @api public
|
7
|
-
def coerce(
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
def coerce(*)
|
8
|
+
output = super
|
9
|
+
|
10
|
+
if coercer.success?(primitive, output) || !required? && output.nil?
|
11
|
+
output
|
12
|
+
else
|
13
|
+
raise CoercionError.new(output, primitive)
|
14
|
+
end
|
11
15
|
end
|
12
16
|
|
13
17
|
end # Strict
|
data/lib/virtus/attribute.rb
CHANGED
@@ -5,9 +5,10 @@ module Virtus
|
|
5
5
|
|
6
6
|
include ::Equalizer.new(:type, :options)
|
7
7
|
|
8
|
-
accept_options :primitive, :accessor, :default, :lazy, :strict
|
8
|
+
accept_options :primitive, :accessor, :default, :lazy, :strict, :required
|
9
9
|
|
10
10
|
strict false
|
11
|
+
required true
|
11
12
|
accessor :public
|
12
13
|
|
13
14
|
# @see Virtus.coerce
|
@@ -42,6 +43,14 @@ module Virtus
|
|
42
43
|
Builder.call(type, options)
|
43
44
|
end
|
44
45
|
|
46
|
+
# @api private
|
47
|
+
def self.build_coercer(type, options = {})
|
48
|
+
Coercer.new(
|
49
|
+
options.fetch(:configured_coercer) { Virtus.coercer },
|
50
|
+
type.coercion_method
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
45
54
|
# @api private
|
46
55
|
def self.new(*args)
|
47
56
|
attribute = super
|
@@ -83,6 +92,11 @@ module Virtus
|
|
83
92
|
coercer.call(value)
|
84
93
|
end
|
85
94
|
|
95
|
+
# @api public
|
96
|
+
def rename(name)
|
97
|
+
self.class.build(type, options.merge(:name => name))
|
98
|
+
end
|
99
|
+
|
86
100
|
# @api public
|
87
101
|
def value_coerced?(value)
|
88
102
|
coercer.success?(primitive, value)
|
@@ -107,6 +121,11 @@ module Virtus
|
|
107
121
|
kind_of?(Strict)
|
108
122
|
end
|
109
123
|
|
124
|
+
# @api public
|
125
|
+
def required?
|
126
|
+
options[:required]
|
127
|
+
end
|
128
|
+
|
110
129
|
# @api private
|
111
130
|
def define_accessor_methods(attribute_set)
|
112
131
|
attribute_set.define_reader_method(self, name, options[:reader])
|
@@ -9,7 +9,9 @@ module Virtus
|
|
9
9
|
#
|
10
10
|
# @api private
|
11
11
|
def const_missing(name)
|
12
|
-
Attribute::Builder.determine_type(name) or
|
12
|
+
Attribute::Builder.determine_type(name) or
|
13
|
+
Axiom::Types.const_defined?(name) && Axiom::Types.const_get(name) or
|
14
|
+
super
|
13
15
|
end
|
14
16
|
|
15
17
|
end
|
data/lib/virtus/version.rb
CHANGED
data/lib/virtus.rb
CHANGED
@@ -10,6 +10,15 @@ module Virtus
|
|
10
10
|
# Represents an undefined parameter used by auto-generated option methods
|
11
11
|
Undefined = Object.new.freeze
|
12
12
|
|
13
|
+
class CoercionError < StandardError
|
14
|
+
attr_reader :output, :primitive
|
15
|
+
|
16
|
+
def initialize(output, primitive)
|
17
|
+
@output, @private = output, primitive
|
18
|
+
super("Failed to coerce #{output.inspect} into #{primitive.inspect}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
13
22
|
# Extends base class or a module with virtus methods
|
14
23
|
#
|
15
24
|
# @param [Object] object
|
@@ -59,7 +59,7 @@ describe 'I can create a Virtus module' do
|
|
59
59
|
specify 'including a custom module with strict enabled' do
|
60
60
|
model = Examples::StrictModel.new
|
61
61
|
|
62
|
-
expect { model.stuff = 'foo' }.to raise_error(
|
62
|
+
expect { model.stuff = 'foo' }.to raise_error(Virtus::CoercionError)
|
63
63
|
|
64
64
|
model.happy = 'foo'
|
65
65
|
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
|
3
|
+
describe 'Injectible coercer' do
|
4
|
+
before do
|
5
|
+
module Examples
|
6
|
+
class EmailAddress
|
7
|
+
include Virtus.value_object
|
8
|
+
|
9
|
+
values do
|
10
|
+
attribute :address, String, :coercer => lambda { |add| add.downcase }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.coerce(input)
|
14
|
+
if input.is_a?(String)
|
15
|
+
new(:address => input)
|
16
|
+
else
|
17
|
+
new(input)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class User
|
23
|
+
include Virtus.model
|
24
|
+
|
25
|
+
attribute :email, EmailAddress,
|
26
|
+
:coercer => lambda { |input| Examples::EmailAddress.coerce(input) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
after do
|
32
|
+
Examples.send(:remove_const, :EmailAddress)
|
33
|
+
Examples.send(:remove_const, :User)
|
34
|
+
end
|
35
|
+
|
36
|
+
let(:doe) { Examples::EmailAddress.new(:address => 'john.doe@example.com') }
|
37
|
+
|
38
|
+
it 'accepts an email hash' do
|
39
|
+
user = Examples::User.new :email => { :address => 'John.Doe@Example.Com' }
|
40
|
+
expect(user.email).to eq(doe)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'coerces an embedded string' do
|
44
|
+
user = Examples::User.new :email => 'John.Doe@Example.Com'
|
45
|
+
expect(user.email).to eq(doe)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Using required attributes' do
|
4
|
+
before do
|
5
|
+
module Examples
|
6
|
+
class User
|
7
|
+
include Virtus.model(:strict => true)
|
8
|
+
|
9
|
+
attribute :name, String
|
10
|
+
attribute :age, Integer, :required => false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'raises coercion error when required attribute is nil' do
|
16
|
+
expect { Examples::User.new(:name => nil) }.to raise_error(Virtus::CoercionError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'does not raise coercion error when not required attribute is nil' do
|
20
|
+
user = Examples::User.new(:name => 'Jane', :age => nil)
|
21
|
+
|
22
|
+
expect(user.name).to eql('Jane')
|
23
|
+
expect(user.age).to be(nil)
|
24
|
+
end
|
25
|
+
end
|
@@ -5,9 +5,14 @@ describe Virtus::Attribute, '#coerce' do
|
|
5
5
|
|
6
6
|
fake(:coercer) { Virtus::Attribute::Coercer }
|
7
7
|
|
8
|
-
let(:object)
|
9
|
-
|
10
|
-
|
8
|
+
let(:object) {
|
9
|
+
described_class.build(String,
|
10
|
+
:coercer => coercer, :strict => strict, :required => required)
|
11
|
+
}
|
12
|
+
|
13
|
+
let(:required) { true }
|
14
|
+
let(:input) { 1 }
|
15
|
+
let(:output) { '1' }
|
11
16
|
|
12
17
|
context 'when strict mode is turned off' do
|
13
18
|
let(:strict) { false }
|
@@ -34,11 +39,40 @@ describe Virtus::Attribute, '#coerce' do
|
|
34
39
|
expect(coercer).to have_received.success?(String, output)
|
35
40
|
end
|
36
41
|
|
42
|
+
context 'when attribute is not required and input is nil' do
|
43
|
+
let(:required) { false }
|
44
|
+
let(:input) { nil }
|
45
|
+
|
46
|
+
it 'returns nil' do
|
47
|
+
stub(coercer).call(input) { input }
|
48
|
+
stub(coercer).success?(String, input) { false }
|
49
|
+
|
50
|
+
expect(subject).to be(nil)
|
51
|
+
|
52
|
+
expect(coercer).to have_received.call(input)
|
53
|
+
expect(coercer).to have_received.success?(String, input)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when attribute is required and input is nil' do
|
58
|
+
let(:input) { nil }
|
59
|
+
|
60
|
+
it 'returns raises error' do
|
61
|
+
stub(coercer).call(input) { input }
|
62
|
+
stub(coercer).success?(String, input) { false }
|
63
|
+
|
64
|
+
expect { subject }.to raise_error(Virtus::CoercionError)
|
65
|
+
|
66
|
+
expect(coercer).to have_received.call(input)
|
67
|
+
expect(coercer).to have_received.success?(String, input)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
37
71
|
it 'raises error when input was not coerced' do
|
38
72
|
stub(coercer).call(input) { input }
|
39
73
|
stub(coercer).success?(String, input) { false }
|
40
74
|
|
41
|
-
expect { subject }.to raise_error(
|
75
|
+
expect { subject }.to raise_error(Virtus::CoercionError)
|
42
76
|
|
43
77
|
expect(coercer).to have_received.call(input)
|
44
78
|
expect(coercer).to have_received.success?(String, input)
|
@@ -8,7 +8,9 @@ describe Virtus::Attribute::EmbeddedValue, '.build' do
|
|
8
8
|
|
9
9
|
it { should be_frozen }
|
10
10
|
|
11
|
-
it { should be_instance_of(Virtus::Attribute::EmbeddedValue
|
11
|
+
it { should be_instance_of(Virtus::Attribute::EmbeddedValue) }
|
12
|
+
|
13
|
+
its(:coercer) { should be_instance_of(described_class::FromOpenStruct) }
|
12
14
|
end
|
13
15
|
|
14
16
|
context 'when type includes Virtus' do
|
@@ -16,7 +18,9 @@ describe Virtus::Attribute::EmbeddedValue, '.build' do
|
|
16
18
|
|
17
19
|
it { should be_frozen }
|
18
20
|
|
19
|
-
it { should be_instance_of(Virtus::Attribute::EmbeddedValue
|
21
|
+
it { should be_instance_of(Virtus::Attribute::EmbeddedValue) }
|
22
|
+
|
23
|
+
its(:coercer) { should be_instance_of(described_class::FromOpenStruct) }
|
20
24
|
end
|
21
25
|
|
22
26
|
context 'when type is an OpenStruct subclass' do
|
@@ -24,7 +28,9 @@ describe Virtus::Attribute::EmbeddedValue, '.build' do
|
|
24
28
|
|
25
29
|
it { should be_frozen }
|
26
30
|
|
27
|
-
it { should be_instance_of(Virtus::Attribute::EmbeddedValue
|
31
|
+
it { should be_instance_of(Virtus::Attribute::EmbeddedValue) }
|
32
|
+
|
33
|
+
its(:coercer) { should be_instance_of(described_class::FromOpenStruct) }
|
28
34
|
end
|
29
35
|
|
30
36
|
context 'when type is OpenStruct' do
|
@@ -32,7 +38,9 @@ describe Virtus::Attribute::EmbeddedValue, '.build' do
|
|
32
38
|
|
33
39
|
it { should be_frozen }
|
34
40
|
|
35
|
-
it { should be_instance_of(Virtus::Attribute::EmbeddedValue
|
41
|
+
it { should be_instance_of(Virtus::Attribute::EmbeddedValue) }
|
42
|
+
|
43
|
+
its(:coercer) { should be_instance_of(described_class::FromOpenStruct) }
|
36
44
|
end
|
37
45
|
|
38
46
|
context 'when type is Struct' do
|
@@ -40,6 +48,8 @@ describe Virtus::Attribute::EmbeddedValue, '.build' do
|
|
40
48
|
|
41
49
|
it { should be_frozen }
|
42
50
|
|
43
|
-
it { should be_instance_of(Virtus::Attribute::EmbeddedValue
|
51
|
+
it { should be_instance_of(Virtus::Attribute::EmbeddedValue) }
|
52
|
+
|
53
|
+
its(:coercer) { should be_instance_of(described_class::FromStruct) }
|
44
54
|
end
|
45
55
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute, '#rename' do
|
4
|
+
subject { object.rename(:bar) }
|
5
|
+
|
6
|
+
let(:object) { described_class.build(String, :name => :foo, :strict => true) }
|
7
|
+
let(:other) { described_class.build(String, :name => :bar, :strict => true) }
|
8
|
+
|
9
|
+
its(:name) { should be(:bar) }
|
10
|
+
|
11
|
+
it { should_not be(object) }
|
12
|
+
it { should be_strict }
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute, '#required?' do
|
4
|
+
subject { object.required? }
|
5
|
+
|
6
|
+
let(:object) { described_class.build(String, :required => required) }
|
7
|
+
|
8
|
+
context 'when required option is true' do
|
9
|
+
let(:required) { true }
|
10
|
+
|
11
|
+
it { should be(true) }
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when required option is false' do
|
15
|
+
let(:required) { false }
|
16
|
+
|
17
|
+
it { should be(false) }
|
18
|
+
end
|
19
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: virtus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.beta7
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-09-
|
12
|
+
date: 2013-09-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: descendants_tracker
|
@@ -145,8 +145,10 @@ files:
|
|
145
145
|
- spec/integration/embedded_value_spec.rb
|
146
146
|
- spec/integration/extending_objects_spec.rb
|
147
147
|
- spec/integration/hash_attributes_coercion_spec.rb
|
148
|
+
- spec/integration/injectible_coercers_spec.rb
|
148
149
|
- spec/integration/mass_assignment_with_accessors_spec.rb
|
149
150
|
- spec/integration/overriding_virtus_spec.rb
|
151
|
+
- spec/integration/required_attributes_spec.rb
|
150
152
|
- spec/integration/struct_as_embedded_value_spec.rb
|
151
153
|
- spec/integration/using_modules_spec.rb
|
152
154
|
- spec/integration/value_object_with_custom_constructor_spec.rb
|
@@ -171,6 +173,8 @@ files:
|
|
171
173
|
- spec/unit/virtus/attribute/hash/class_methods/build_spec.rb
|
172
174
|
- spec/unit/virtus/attribute/hash/coerce_spec.rb
|
173
175
|
- spec/unit/virtus/attribute/lazy_predicate_spec.rb
|
176
|
+
- spec/unit/virtus/attribute/rename_spec.rb
|
177
|
+
- spec/unit/virtus/attribute/required_predicate_spec.rb
|
174
178
|
- spec/unit/virtus/attribute/set_default_value_spec.rb
|
175
179
|
- spec/unit/virtus/attribute/set_spec.rb
|
176
180
|
- spec/unit/virtus/attribute/value_coerced_predicate_spec.rb
|
@@ -180,7 +184,6 @@ files:
|
|
180
184
|
- spec/unit/virtus/attribute_set/each_spec.rb
|
181
185
|
- spec/unit/virtus/attribute_set/element_reference_spec.rb
|
182
186
|
- spec/unit/virtus/attribute_set/element_set_spec.rb
|
183
|
-
- spec/unit/virtus/attribute_set/inspect_spec.rb
|
184
187
|
- spec/unit/virtus/attribute_set/merge_spec.rb
|
185
188
|
- spec/unit/virtus/attribute_set/reset_spec.rb
|
186
189
|
- spec/unit/virtus/attribute_spec.rb
|