virtus 1.0.0.beta8 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog.md +28 -1
- data/LICENSE +1 -1
- data/README.md +12 -22
- data/config/flay.yml +2 -2
- data/config/flog.yml +1 -1
- data/config/mutant.yml +2 -1
- data/config/reek.yml +67 -17
- data/lib/virtus.rb +61 -5
- data/lib/virtus/attribute.rb +107 -26
- data/lib/virtus/attribute/builder.rb +42 -34
- data/lib/virtus/attribute/collection.rb +11 -6
- data/lib/virtus/attribute/embedded_value.rb +12 -7
- data/lib/virtus/attribute/hash.rb +19 -8
- data/lib/virtus/attribute_set.rb +2 -13
- data/lib/virtus/builder.rb +137 -0
- data/lib/virtus/builder/hook_context.rb +51 -0
- data/lib/virtus/class_inclusions.rb +1 -0
- data/lib/virtus/class_methods.rb +1 -5
- data/lib/virtus/configuration.rb +12 -2
- data/lib/virtus/extensions.rb +28 -32
- data/lib/virtus/instance_methods.rb +2 -2
- data/lib/virtus/model.rb +3 -1
- data/lib/virtus/module_extensions.rb +1 -1
- data/lib/virtus/value_object.rb +1 -1
- data/lib/virtus/version.rb +1 -1
- data/spec/integration/inheritance_spec.rb +42 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +10 -0
- data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +14 -0
- data/spec/unit/virtus/class_methods/finalize_spec.rb +10 -4
- data/spec/unit/virtus/model_spec.rb +35 -3
- data/spec/unit/virtus/module_spec.rb +44 -3
- data/spec/unit/virtus/value_object_spec.rb +39 -22
- data/virtus.gemspec +2 -1
- metadata +9 -6
- data/lib/virtus/module_builder.rb +0 -192
@@ -13,6 +13,7 @@ module Virtus
|
|
13
13
|
def self.included(descendant)
|
14
14
|
super
|
15
15
|
descendant.extend(ClassMethods)
|
16
|
+
descendant.extend(Extensions::AllowedWriterMethods)
|
16
17
|
descendant.class_eval { include Methods }
|
17
18
|
descendant.class_eval { include InstanceMethods }
|
18
19
|
descendant.class_eval { include InstanceMethods::Constructor }
|
data/lib/virtus/class_methods.rb
CHANGED
@@ -14,11 +14,7 @@ module Virtus
|
|
14
14
|
# @api private
|
15
15
|
def self.extended(descendant)
|
16
16
|
super
|
17
|
-
AttributeSet.create(descendant)
|
18
|
-
descendant.module_eval do
|
19
|
-
extend DescendantsTracker
|
20
|
-
include attribute_set
|
21
|
-
end
|
17
|
+
descendant.send(:include, AttributeSet.create(descendant))
|
22
18
|
end
|
23
19
|
private_class_method :extended
|
24
20
|
|
data/lib/virtus/configuration.rb
CHANGED
@@ -28,8 +28,10 @@ module Virtus
|
|
28
28
|
# @return [Configuration]
|
29
29
|
#
|
30
30
|
# @api public
|
31
|
-
def self.build(&block)
|
32
|
-
new.call(&block)
|
31
|
+
def self.build(options = {}, &block)
|
32
|
+
config = new.call(&block)
|
33
|
+
options.each { |key, value| config.public_send("#{key}=", value) }
|
34
|
+
config
|
33
35
|
end
|
34
36
|
|
35
37
|
# Initialized a configuration instance
|
@@ -77,5 +79,13 @@ module Virtus
|
|
77
79
|
@coercer
|
78
80
|
end
|
79
81
|
|
82
|
+
# @api private
|
83
|
+
def to_h
|
84
|
+
{ :coerce => coerce,
|
85
|
+
:finalize => finalize,
|
86
|
+
:strict => strict,
|
87
|
+
:configured_coercer => coercer }.freeze
|
88
|
+
end
|
89
|
+
|
80
90
|
end # class Configuration
|
81
91
|
end # module Virtus
|
data/lib/virtus/extensions.rb
CHANGED
@@ -18,9 +18,8 @@ module Virtus
|
|
18
18
|
object.instance_eval do
|
19
19
|
extend Methods
|
20
20
|
extend InstanceMethods
|
21
|
-
extend
|
21
|
+
extend AllowedWriterMethods
|
22
22
|
extend InstanceMethods::MassAssignment
|
23
|
-
extend attribute_set
|
24
23
|
end
|
25
24
|
end
|
26
25
|
private_class_method :extended
|
@@ -29,18 +28,16 @@ module Virtus
|
|
29
28
|
|
30
29
|
# @api private
|
31
30
|
def self.extended(descendant)
|
32
|
-
|
33
|
-
descendant.
|
34
|
-
extend attribute_set
|
35
|
-
end
|
31
|
+
super
|
32
|
+
descendant.extend(AttributeSet.create(descendant))
|
36
33
|
end
|
37
34
|
private_class_method :extended
|
38
35
|
|
39
|
-
# Defines an attribute on an object's class
|
36
|
+
# Defines an attribute on an object's class or instance
|
40
37
|
#
|
41
38
|
# @example
|
42
39
|
# class Book
|
43
|
-
# include Virtus
|
40
|
+
# include Virtus.model
|
44
41
|
#
|
45
42
|
# attribute :title, String
|
46
43
|
# attribute :author, String
|
@@ -52,7 +49,7 @@ module Virtus
|
|
52
49
|
# @param [Symbol] name
|
53
50
|
# the name of an attribute
|
54
51
|
#
|
55
|
-
# @param [Class] type
|
52
|
+
# @param [Class,Array,Hash,Axiom::Types::Type,String,Symbol] type
|
56
53
|
# the type class of an attribute
|
57
54
|
#
|
58
55
|
# @param [#to_hash] options
|
@@ -63,32 +60,19 @@ module Virtus
|
|
63
60
|
# @see Attribute.build
|
64
61
|
#
|
65
62
|
# @api public
|
66
|
-
def attribute(name, type =
|
63
|
+
def attribute(name, type = nil, options = {})
|
67
64
|
assert_valid_name(name)
|
68
|
-
attribute_set << Attribute.build(type,
|
65
|
+
attribute_set << Attribute.build(type, options.merge(:name => name))
|
69
66
|
self
|
70
67
|
end
|
71
68
|
|
69
|
+
# @see Virtus.default_value
|
70
|
+
#
|
72
71
|
# @api public
|
73
72
|
def values(&block)
|
74
|
-
private :attributes=
|
73
|
+
private :attributes= if instance_methods.include?(:attributes=)
|
75
74
|
yield
|
76
75
|
include(::Equalizer.new(*attribute_set.map(&:name)))
|
77
|
-
self
|
78
|
-
end
|
79
|
-
|
80
|
-
# The list of writer methods that can be mass-assigned to in #attributes=
|
81
|
-
#
|
82
|
-
# @return [Set]
|
83
|
-
#
|
84
|
-
# @api private
|
85
|
-
def allowed_writer_methods
|
86
|
-
@allowed_writer_methods ||=
|
87
|
-
begin
|
88
|
-
allowed_writer_methods = allowed_methods.grep(WRITER_METHOD_REGEXP).to_set
|
89
|
-
allowed_writer_methods -= INVALID_WRITER_METHODS
|
90
|
-
allowed_writer_methods.freeze
|
91
|
-
end
|
92
76
|
end
|
93
77
|
|
94
78
|
private
|
@@ -102,16 +86,28 @@ module Virtus
|
|
102
86
|
@attribute_set
|
103
87
|
end
|
104
88
|
|
105
|
-
|
89
|
+
end # Methods
|
90
|
+
|
91
|
+
module AllowedWriterMethods
|
92
|
+
WRITER_METHOD_REGEXP = /=\z/.freeze
|
93
|
+
INVALID_WRITER_METHODS = %w[ == != === []= attributes= ].to_set.freeze
|
94
|
+
|
95
|
+
# The list of writer methods that can be mass-assigned to in #attributes=
|
106
96
|
#
|
107
|
-
# @return [
|
97
|
+
# @return [Set]
|
108
98
|
#
|
109
99
|
# @api private
|
110
|
-
def
|
111
|
-
|
100
|
+
def allowed_writer_methods
|
101
|
+
@allowed_writer_methods ||=
|
102
|
+
begin
|
103
|
+
allowed_writer_methods = allowed_methods.grep(WRITER_METHOD_REGEXP).to_set
|
104
|
+
allowed_writer_methods -= INVALID_WRITER_METHODS
|
105
|
+
allowed_writer_methods.freeze
|
106
|
+
end
|
112
107
|
end
|
113
108
|
|
114
|
-
end #
|
109
|
+
end # AllowedWriterMethods
|
115
110
|
|
116
111
|
end # module Extensions
|
112
|
+
|
117
113
|
end # module Virtus
|
@@ -14,7 +14,7 @@ module Virtus
|
|
14
14
|
#
|
15
15
|
# @api private
|
16
16
|
def initialize(attributes = nil)
|
17
|
-
attribute_set.set(self, attributes) if attributes
|
17
|
+
self.class.attribute_set.set(self, attributes) if attributes
|
18
18
|
set_default_attributes
|
19
19
|
end
|
20
20
|
|
@@ -171,7 +171,7 @@ module Virtus
|
|
171
171
|
# @api public
|
172
172
|
def reset_attribute(attribute_name)
|
173
173
|
attribute = attribute_set[attribute_name]
|
174
|
-
|
174
|
+
attribute.set_default_value(self) if attribute
|
175
175
|
self
|
176
176
|
end
|
177
177
|
|
data/lib/virtus/model.rb
CHANGED
@@ -5,7 +5,7 @@ module Virtus
|
|
5
5
|
# @api private
|
6
6
|
def self.included(descendant)
|
7
7
|
super
|
8
|
-
descendant.send(:include, ClassInclusions)
|
8
|
+
descendant.send(:include, ClassInclusions)
|
9
9
|
end
|
10
10
|
|
11
11
|
# @api private
|
@@ -51,6 +51,7 @@ module Virtus
|
|
51
51
|
# @api private
|
52
52
|
def self.included(descendant)
|
53
53
|
super
|
54
|
+
descendant.extend(Extensions::AllowedWriterMethods)
|
54
55
|
descendant.send(:include, InstanceMethods::MassAssignment)
|
55
56
|
end
|
56
57
|
private_class_method :included
|
@@ -58,6 +59,7 @@ module Virtus
|
|
58
59
|
# @api private
|
59
60
|
def self.extended(descendant)
|
60
61
|
super
|
62
|
+
descendant.extend(Extensions::AllowedWriterMethods)
|
61
63
|
descendant.extend(InstanceMethods::MassAssignment)
|
62
64
|
end
|
63
65
|
private_class_method :included
|
data/lib/virtus/value_object.rb
CHANGED
data/lib/virtus/version.rb
CHANGED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Inheritance' do
|
4
|
+
before do
|
5
|
+
module Examples
|
6
|
+
class Base
|
7
|
+
include Virtus.model
|
8
|
+
end
|
9
|
+
|
10
|
+
class First < Base
|
11
|
+
attribute :id, Fixnum
|
12
|
+
attribute :name, String, default: ->(first, _) { "Named: #{first.id}" }
|
13
|
+
attribute :description, String
|
14
|
+
end
|
15
|
+
|
16
|
+
class Second < Base
|
17
|
+
attribute :something, String
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'inherits model from the base class' do
|
23
|
+
expect(Examples::First.attribute_set.map(&:name)).to eql([:id, :name, :description])
|
24
|
+
expect(Examples::Second.attribute_set.map(&:name)).to eql([:something])
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'sets correct attributes on the descendant classes' do
|
28
|
+
first = Examples::First.new(:id => 1, :description => 'hello world')
|
29
|
+
|
30
|
+
expect(first.id).to be(1)
|
31
|
+
expect(first.name).to eql('Named: 1')
|
32
|
+
expect(first.description).to eql('hello world')
|
33
|
+
|
34
|
+
second = Examples::Second.new
|
35
|
+
|
36
|
+
expect(second.something).to be(nil)
|
37
|
+
|
38
|
+
second.something = 'foo bar'
|
39
|
+
|
40
|
+
expect(second.something).to eql('foo bar')
|
41
|
+
end
|
42
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -19,6 +19,16 @@ describe Virtus::Attribute, '.build' do
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
context 'when type is Array[Virtus::Attribute::Boolean]' do
|
23
|
+
let(:type) { Array[Virtus::Attribute::Boolean] }
|
24
|
+
|
25
|
+
it_behaves_like 'a valid collection attribute instance'
|
26
|
+
|
27
|
+
it 'sets member type' do
|
28
|
+
expect(subject.type.member_type).to be(Axiom::Types::Boolean)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
22
32
|
context 'when type is Array[Float]' do
|
23
33
|
let(:type) { Array[Float] }
|
24
34
|
|
@@ -37,6 +37,20 @@ describe Virtus::Attribute::Hash, '.build' do
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
context 'when type is Hash[Virtus::Attribute::Hash => Virtus::Attribute::Boolean]' do
|
41
|
+
let(:type) { Hash[Virtus::Attribute::Hash => Virtus::Attribute::Boolean] }
|
42
|
+
|
43
|
+
it { should be_instance_of(Virtus::Attribute::Hash) }
|
44
|
+
|
45
|
+
it 'sets key type' do
|
46
|
+
expect(subject.type.key_type).to be(Axiom::Types::Hash)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'sets value type' do
|
50
|
+
expect(subject.type.value_type).to be(Axiom::Types::Boolean)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
40
54
|
context 'when type is Hash[Struct.new(:id) => Integer]' do
|
41
55
|
let(:type) { Hash[key_type => Integer] }
|
42
56
|
let(:key_type) { Struct.new(:id) }
|
@@ -7,29 +7,35 @@ describe Virtus, '.finalize' do
|
|
7
7
|
include Virtus.model(:finalize => false)
|
8
8
|
|
9
9
|
attribute :articles, Array['Examples::Article']
|
10
|
-
attribute :address, 'Examples::Address'
|
10
|
+
attribute :address, :'Examples::Address'
|
11
11
|
end
|
12
12
|
|
13
13
|
class Article
|
14
14
|
include Virtus.model(:finalize => false)
|
15
15
|
|
16
16
|
attribute :posts, Hash['Examples::Person' => 'Examples::Post']
|
17
|
-
attribute :person, 'Examples::Person'
|
17
|
+
attribute :person, :'Examples::Person'
|
18
18
|
end
|
19
19
|
|
20
20
|
class Post
|
21
|
-
include Virtus.model
|
21
|
+
include Virtus.model
|
22
22
|
|
23
23
|
attribute :title, String
|
24
24
|
end
|
25
25
|
|
26
26
|
class Address
|
27
|
-
include Virtus.model
|
27
|
+
include Virtus.model
|
28
28
|
|
29
29
|
attribute :street, String
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
expect(Examples::Post.attribute_set[:title]).to be_finalized
|
34
|
+
expect(Examples::Address.attribute_set[:street]).to be_finalized
|
35
|
+
|
36
|
+
expect(Virtus::Builder.pending).not_to include(Examples::Post)
|
37
|
+
expect(Virtus::Builder.pending).not_to include(Examples::Address)
|
38
|
+
|
33
39
|
Virtus.finalize
|
34
40
|
end
|
35
41
|
|
@@ -10,7 +10,7 @@ describe Virtus, '.model' do
|
|
10
10
|
|
11
11
|
share_examples_for 'a model with mass-assignment' do
|
12
12
|
let(:attributes) do
|
13
|
-
{ :name => 'Jane' }
|
13
|
+
{ :name => 'Jane', :something => nil }
|
14
14
|
end
|
15
15
|
|
16
16
|
before do
|
@@ -32,11 +32,14 @@ describe Virtus, '.model' do
|
|
32
32
|
let(:mod) { Virtus.model }
|
33
33
|
|
34
34
|
context 'with a class' do
|
35
|
-
|
35
|
+
let(:model) { Class.new }
|
36
|
+
|
37
|
+
subject { model }
|
36
38
|
|
37
39
|
before do
|
38
40
|
subject.send(:include, mod)
|
39
|
-
subject.attribute :name
|
41
|
+
subject.attribute :name, String, :default => 'Jane'
|
42
|
+
subject.attribute :something
|
40
43
|
end
|
41
44
|
|
42
45
|
it_behaves_like 'a model with constructor'
|
@@ -46,6 +49,34 @@ describe Virtus, '.model' do
|
|
46
49
|
it_behaves_like 'a model with mass-assignment' do
|
47
50
|
let(:instance) { subject.new }
|
48
51
|
end
|
52
|
+
|
53
|
+
it 'defaults to Object for attribute type' do
|
54
|
+
expect(model.attribute_set[:something].type).to be(Axiom::Types::Object)
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with a sub-class' do
|
58
|
+
subject { Class.new(model) }
|
59
|
+
|
60
|
+
before do
|
61
|
+
subject.attribute :age, Integer
|
62
|
+
end
|
63
|
+
|
64
|
+
it_behaves_like 'a model with constructor'
|
65
|
+
|
66
|
+
it_behaves_like 'a model with strict mode turned off'
|
67
|
+
|
68
|
+
it_behaves_like 'a model with mass-assignment' do
|
69
|
+
let(:instance) { subject.new }
|
70
|
+
|
71
|
+
let(:attributes) {
|
72
|
+
{ :name => 'Jane', :something => nil, :age => 23 }
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'has its own attributes' do
|
77
|
+
expect(subject.attribute_set[:age]).to be_kind_of(Virtus::Attribute)
|
78
|
+
end
|
79
|
+
end
|
49
80
|
end
|
50
81
|
|
51
82
|
context 'with an instance' do
|
@@ -54,6 +85,7 @@ describe Virtus, '.model' do
|
|
54
85
|
before do
|
55
86
|
subject.extend(mod)
|
56
87
|
subject.attribute :name, String
|
88
|
+
subject.attribute :something
|
57
89
|
end
|
58
90
|
|
59
91
|
it_behaves_like 'a model with strict mode turned off'
|
@@ -3,8 +3,8 @@ require 'spec_helper'
|
|
3
3
|
describe Virtus, '.module' do
|
4
4
|
share_examples_for 'a valid virtus object' do
|
5
5
|
it 'reads and writes attribute' do
|
6
|
-
instance.name = '
|
7
|
-
expect(instance.name).to eql('
|
6
|
+
instance.name = 'John'
|
7
|
+
expect(instance.name).to eql('John')
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
@@ -15,6 +15,10 @@ describe Virtus, '.module' do
|
|
15
15
|
it_behaves_like 'a valid virtus object' do
|
16
16
|
let(:instance) { model.new }
|
17
17
|
end
|
18
|
+
|
19
|
+
it 'sets defaults' do
|
20
|
+
expect(instance.name).to eql('Jane')
|
21
|
+
end
|
18
22
|
end
|
19
23
|
|
20
24
|
context 'with constructor turned off' do
|
@@ -39,6 +43,16 @@ describe Virtus, '.module' do
|
|
39
43
|
expect(instance).not_to respond_to(:attributes=)
|
40
44
|
end
|
41
45
|
end
|
46
|
+
|
47
|
+
context 'with coercion turned off' do
|
48
|
+
subject { Virtus.module(:coerce => false) }
|
49
|
+
|
50
|
+
it_behaves_like 'a valid virtus object'
|
51
|
+
|
52
|
+
it 'builds non-coercible attributes' do
|
53
|
+
expect(object.send(:attribute_set)[:name]).not_to be_coercible
|
54
|
+
end
|
55
|
+
end
|
42
56
|
end
|
43
57
|
|
44
58
|
let(:mod) { Module.new }
|
@@ -47,10 +61,13 @@ describe Virtus, '.module' do
|
|
47
61
|
|
48
62
|
before do
|
49
63
|
mod.send(:include, subject)
|
50
|
-
mod.attribute :name, String
|
64
|
+
mod.attribute :name, String, :default => 'Jane'
|
65
|
+
mod.attribute :something
|
51
66
|
end
|
52
67
|
|
53
68
|
context 'with a class' do
|
69
|
+
let(:object) { model }
|
70
|
+
|
54
71
|
before do
|
55
72
|
model.send(:include, mod)
|
56
73
|
end
|
@@ -59,10 +76,16 @@ describe Virtus, '.module' do
|
|
59
76
|
expect(model.attribute_set[:name]).to be_kind_of(Virtus::Attribute)
|
60
77
|
end
|
61
78
|
|
79
|
+
it 'defaults to Object for attribute type' do
|
80
|
+
expect(model.attribute_set[:something].type).to be(Axiom::Types::Object)
|
81
|
+
end
|
82
|
+
|
62
83
|
it_behaves_like 'an object extended with virtus module'
|
63
84
|
end
|
64
85
|
|
65
86
|
context 'with a model instance' do
|
87
|
+
let(:object) { instance }
|
88
|
+
|
66
89
|
before do
|
67
90
|
instance.extend(mod)
|
68
91
|
end
|
@@ -73,4 +96,22 @@ describe Virtus, '.module' do
|
|
73
96
|
|
74
97
|
it_behaves_like 'an object extended with virtus module'
|
75
98
|
end
|
99
|
+
|
100
|
+
context 'with another module' do
|
101
|
+
let(:other) { Module.new }
|
102
|
+
|
103
|
+
let(:object) { instance }
|
104
|
+
|
105
|
+
before do
|
106
|
+
other.send(:include, mod)
|
107
|
+
model.send(:include, other)
|
108
|
+
end
|
109
|
+
|
110
|
+
it_behaves_like 'an object extended with virtus module'
|
111
|
+
|
112
|
+
it 'provides attributes for the model' do
|
113
|
+
expect(model.attribute_set[:name]).to be_kind_of(Virtus::Attribute)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
76
117
|
end
|