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.
@@ -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.determine_type(klass)
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 FromStruct < self
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 coerce(input)
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 < self
50
+ class FromOpenStruct < Coercer
41
51
 
42
52
  # @api public
43
- def coerce(input)
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.determine_type(klass)
59
- if klass < Virtus || klass < Model::Constructor || klass <= OpenStruct
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.build_type(options)
68
- Axiom::Types::Object.new { primitive options[:type] }
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(input)
8
- coerced = super
9
- raise ArgumentError unless coercer.success?(primitive, coerced)
10
- coerced
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
@@ -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 Axiom::Types.const_get(name) or super
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
@@ -1,3 +1,3 @@
1
1
  module Virtus
2
- VERSION = '1.0.0.beta6'
2
+ VERSION = '1.0.0.beta7'
3
3
  end
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(ArgumentError)
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) { described_class.build(String, :coercer => coercer, :strict => strict) }
9
- let(:input) { 1 }
10
- let(:output) { '1' }
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(ArgumentError)
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::FromOpenStruct) }
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::FromOpenStruct) }
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::FromOpenStruct) }
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::FromOpenStruct) }
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::FromStruct) }
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.beta6
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-25 00:00:00.000000000 Z
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
@@ -1,9 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Virtus::AttributeSet, '#inspect' do
4
- subject { object.inspect }
5
-
6
- let(:object) { described_class.new('Test') }
7
-
8
- it { pending; should eql('Test::AttributeSet') }
9
- end