virtus 1.0.1 → 2.0.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.
- checksums.yaml +5 -5
- data/.travis.yml +19 -15
- data/Changelog.md +43 -2
- data/Gemfile +5 -5
- data/README.md +113 -78
- data/Rakefile +13 -3
- data/lib/virtus.rb +46 -6
- data/lib/virtus/attribute.rb +21 -3
- data/lib/virtus/attribute/accessor.rb +11 -0
- data/lib/virtus/attribute/builder.rb +8 -13
- data/lib/virtus/attribute/collection.rb +12 -3
- data/lib/virtus/attribute/default_value.rb +2 -0
- data/lib/virtus/attribute/hash.rb +3 -3
- data/lib/virtus/attribute/nullify_blank.rb +24 -0
- data/lib/virtus/attribute/strict.rb +1 -1
- data/lib/virtus/attribute_set.rb +2 -2
- data/lib/virtus/builder.rb +2 -6
- data/lib/virtus/class_inclusions.rb +0 -1
- data/lib/virtus/coercer.rb +1 -0
- data/lib/virtus/configuration.rb +17 -36
- data/lib/virtus/extensions.rb +13 -21
- data/lib/virtus/instance_methods.rb +3 -2
- data/lib/virtus/model.rb +1 -3
- data/lib/virtus/module_extensions.rb +8 -2
- data/lib/virtus/support/equalizer.rb +1 -1
- data/lib/virtus/support/options.rb +2 -1
- data/lib/virtus/support/type_lookup.rb +1 -1
- data/lib/virtus/version.rb +1 -1
- data/spec/integration/attributes_attribute_spec.rb +28 -0
- data/spec/integration/building_module_spec.rb +22 -0
- data/spec/integration/collection_member_coercion_spec.rb +34 -13
- data/spec/integration/custom_attributes_spec.rb +2 -2
- data/spec/integration/custom_collection_attributes_spec.rb +6 -6
- data/spec/integration/default_values_spec.rb +8 -8
- data/spec/integration/defining_attributes_spec.rb +25 -18
- data/spec/integration/embedded_value_spec.rb +5 -5
- data/spec/integration/extending_objects_spec.rb +5 -5
- data/spec/integration/hash_attributes_coercion_spec.rb +16 -12
- data/spec/integration/mass_assignment_with_accessors_spec.rb +5 -5
- data/spec/integration/overriding_virtus_spec.rb +4 -4
- data/spec/integration/required_attributes_spec.rb +1 -1
- data/spec/integration/struct_as_embedded_value_spec.rb +4 -4
- data/spec/integration/using_modules_spec.rb +8 -8
- data/spec/integration/value_object_with_custom_constructor_spec.rb +4 -4
- data/spec/integration/virtus/instance_level_attributes_spec.rb +1 -1
- data/spec/integration/virtus/value_object_spec.rb +14 -14
- data/spec/shared/freeze_method_behavior.rb +6 -3
- data/spec/shared/idempotent_method_behaviour.rb +1 -1
- data/spec/shared/options_class_method.rb +3 -3
- data/spec/spec_helper.rb +2 -18
- data/spec/unit/virtus/attribute/boolean/coerce_spec.rb +3 -3
- data/spec/unit/virtus/attribute/boolean/value_coerced_predicate_spec.rb +3 -3
- data/spec/unit/virtus/attribute/class_methods/build_spec.rb +64 -24
- data/spec/unit/virtus/attribute/class_methods/coerce_spec.rb +2 -2
- data/spec/unit/virtus/attribute/coerce_spec.rb +58 -10
- data/spec/unit/virtus/attribute/coercible_predicate_spec.rb +2 -2
- data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +15 -4
- data/spec/unit/virtus/attribute/collection/coerce_spec.rb +25 -4
- data/spec/unit/virtus/attribute/collection/value_coerced_predicate_spec.rb +31 -0
- data/spec/unit/virtus/attribute/comparison_spec.rb +20 -0
- data/spec/unit/virtus/attribute/custom_collection_spec.rb +8 -2
- data/spec/unit/virtus/attribute/defined_spec.rb +20 -0
- data/spec/unit/virtus/attribute/embedded_value/class_methods/build_spec.rb +30 -15
- data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +25 -11
- data/spec/unit/virtus/attribute/get_spec.rb +2 -2
- data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +21 -9
- data/spec/unit/virtus/attribute/hash/coerce_spec.rb +9 -9
- data/spec/unit/virtus/attribute/lazy_predicate_spec.rb +2 -2
- data/spec/unit/virtus/attribute/rename_spec.rb +6 -3
- data/spec/unit/virtus/attribute/required_predicate_spec.rb +2 -2
- data/spec/unit/virtus/attribute/set_default_value_spec.rb +43 -10
- data/spec/unit/virtus/attribute/set_spec.rb +1 -1
- data/spec/unit/virtus/attribute/value_coerced_predicate_spec.rb +2 -2
- data/spec/unit/virtus/attribute_set/append_spec.rb +6 -6
- data/spec/unit/virtus/attribute_set/define_reader_method_spec.rb +12 -11
- data/spec/unit/virtus/attribute_set/define_writer_method_spec.rb +13 -12
- data/spec/unit/virtus/attribute_set/each_spec.rb +21 -16
- data/spec/unit/virtus/attribute_set/element_reference_spec.rb +2 -2
- data/spec/unit/virtus/attribute_set/element_set_spec.rb +17 -9
- data/spec/unit/virtus/attribute_set/merge_spec.rb +7 -5
- data/spec/unit/virtus/attribute_set/reset_spec.rb +22 -11
- data/spec/unit/virtus/attribute_spec.rb +8 -7
- data/spec/unit/virtus/attributes_reader_spec.rb +1 -1
- data/spec/unit/virtus/attributes_writer_spec.rb +1 -1
- data/spec/unit/virtus/element_reader_spec.rb +1 -1
- data/spec/unit/virtus/freeze_spec.rb +23 -3
- data/spec/unit/virtus/model_spec.rb +38 -7
- data/spec/unit/virtus/module_spec.rb +59 -2
- data/spec/unit/virtus/set_default_attributes_spec.rb +10 -3
- data/spec/unit/virtus/value_object_spec.rb +15 -5
- data/virtus.gemspec +7 -5
- metadata +46 -44
- data/.ruby-version +0 -1
- data/Gemfile.devtools +0 -54
- data/config/flay.yml +0 -3
- data/config/flog.yml +0 -2
- data/config/mutant.yml +0 -15
- data/config/reek.yml +0 -146
- data/config/yardstick.yml +0 -2
@@ -15,7 +15,9 @@ module Virtus
|
|
15
15
|
# @api private
|
16
16
|
def self.setup(mod, inclusions = [Model], attribute_definitions = [])
|
17
17
|
mod.instance_variable_set('@inclusions', inclusions)
|
18
|
-
mod.
|
18
|
+
existing_attributes = mod.instance_variable_get('@attribute_definitions')
|
19
|
+
new_attributes = (existing_attributes || []) + attribute_definitions
|
20
|
+
mod.instance_variable_set('@attribute_definitions', new_attributes)
|
19
21
|
end
|
20
22
|
|
21
23
|
# Define an attribute in the module
|
@@ -57,7 +59,11 @@ module Virtus
|
|
57
59
|
super
|
58
60
|
|
59
61
|
if Class === object
|
60
|
-
@inclusions.
|
62
|
+
@inclusions.reject do |mod|
|
63
|
+
object.ancestors.include?(mod)
|
64
|
+
end.each do |mod|
|
65
|
+
object.send(:include, mod)
|
66
|
+
end
|
61
67
|
define_attributes(object)
|
62
68
|
else
|
63
69
|
object.extend(ModuleExtensions)
|
@@ -37,7 +37,7 @@ module Virtus
|
|
37
37
|
# Defines which options are valid for a given attribute class
|
38
38
|
#
|
39
39
|
# @example
|
40
|
-
# class MyAttribute < Virtus::Attribute
|
40
|
+
# class MyAttribute < Virtus::Attribute
|
41
41
|
# accept_options :foo, :bar
|
42
42
|
# end
|
43
43
|
#
|
@@ -61,6 +61,7 @@ module Virtus
|
|
61
61
|
def define_option_method(option)
|
62
62
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
63
63
|
def self.#{option}(value = Undefined) # def self.primitive(value = Undefined)
|
64
|
+
@#{option} = nil unless defined?(@#{option}) # @primitive = nil unless defined?(@primitive)
|
64
65
|
return @#{option} if value.equal?(Undefined) # return @primitive if value.equal?(Undefined)
|
65
66
|
@#{option} = value # @primitive = value
|
66
67
|
self # self
|
data/lib/virtus/version.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Adding attribute called 'attributes'" do
|
4
|
+
|
5
|
+
context "when mass assignment is disabled" do
|
6
|
+
before do
|
7
|
+
module Examples
|
8
|
+
class User
|
9
|
+
include Virtus.model(mass_assignment: false)
|
10
|
+
|
11
|
+
attribute :attributes
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "allows model to use `attributes` attribute" do
|
17
|
+
user = Examples::User.new
|
18
|
+
expect(user.attributes).to eq(nil)
|
19
|
+
user.attributes = "attributes string"
|
20
|
+
expect(user.attributes).to eq("attributes string")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "doesn't accept `attributes` key in initializer" do
|
24
|
+
user = Examples::User.new(attributes: 'attributes string')
|
25
|
+
expect(user.attributes).to eq(nil)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -19,6 +19,10 @@ describe 'I can create a Virtus module' do
|
|
19
19
|
config.strict = true
|
20
20
|
}
|
21
21
|
|
22
|
+
BlankModule = Virtus.model { |config|
|
23
|
+
config.nullify_blank = true
|
24
|
+
}
|
25
|
+
|
22
26
|
class NoncoercedUser
|
23
27
|
include NoncoercingModule
|
24
28
|
|
@@ -39,6 +43,13 @@ describe 'I can create a Virtus module' do
|
|
39
43
|
attribute :stuff, Hash
|
40
44
|
attribute :happy, Boolean, :strict => false
|
41
45
|
end
|
46
|
+
|
47
|
+
class BlankModel
|
48
|
+
include BlankModule
|
49
|
+
|
50
|
+
attribute :stuff, Hash
|
51
|
+
attribute :happy, Boolean, :nullify_blank => false
|
52
|
+
end
|
42
53
|
end
|
43
54
|
end
|
44
55
|
|
@@ -65,4 +76,15 @@ describe 'I can create a Virtus module' do
|
|
65
76
|
|
66
77
|
expect(model.happy).to eql('foo')
|
67
78
|
end
|
79
|
+
|
80
|
+
specify 'including a custom module with nullify blank enabled' do
|
81
|
+
model = Examples::BlankModel.new
|
82
|
+
|
83
|
+
model.stuff = ''
|
84
|
+
expect(model.stuff).to be_nil
|
85
|
+
|
86
|
+
model.happy = 'foo'
|
87
|
+
|
88
|
+
expect(model.happy).to eql('foo')
|
89
|
+
end
|
68
90
|
end
|
@@ -25,10 +25,10 @@ class User
|
|
25
25
|
end
|
26
26
|
|
27
27
|
describe User do
|
28
|
-
it {
|
29
|
-
it {
|
30
|
-
it {
|
31
|
-
it {
|
28
|
+
it { is_expected.to respond_to(:phone_numbers) }
|
29
|
+
it { is_expected.to respond_to(:phone_numbers=) }
|
30
|
+
it { is_expected.to respond_to(:addresses) }
|
31
|
+
it { is_expected.to respond_to(:addresses=) }
|
32
32
|
|
33
33
|
let(:instance) do
|
34
34
|
described_class.new(:phone_numbers => phone_numbers_attributes,
|
@@ -48,28 +48,49 @@ describe User do
|
|
48
48
|
describe 'first entry' do
|
49
49
|
subject { instance.phone_numbers.first }
|
50
50
|
|
51
|
-
it {
|
51
|
+
it { is_expected.to be_instance_of(PhoneNumber) }
|
52
52
|
|
53
|
-
|
53
|
+
describe '#number' do
|
54
|
+
subject { super().number }
|
55
|
+
it { is_expected.to eql('212-555-1212') }
|
56
|
+
end
|
54
57
|
end
|
55
58
|
|
56
59
|
describe 'last entry' do
|
57
60
|
subject { instance.phone_numbers.last }
|
58
61
|
|
59
|
-
it {
|
62
|
+
it { is_expected.to be_instance_of(PhoneNumber) }
|
60
63
|
|
61
|
-
|
64
|
+
describe '#number' do
|
65
|
+
subject { super().number }
|
66
|
+
it { is_expected.to eql('919-444-3265') }
|
67
|
+
end
|
62
68
|
end
|
63
69
|
end
|
64
70
|
|
65
71
|
describe '#addresses' do
|
66
72
|
subject { instance.addresses.first }
|
67
73
|
|
68
|
-
it {
|
74
|
+
it { is_expected.to be_instance_of(Address) }
|
69
75
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
76
|
+
describe '#address' do
|
77
|
+
subject { super().address }
|
78
|
+
it { is_expected.to eql('1234 Any St.') }
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#locality' do
|
82
|
+
subject { super().locality }
|
83
|
+
it { is_expected.to eql('Anytown') }
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#region' do
|
87
|
+
subject { super().region }
|
88
|
+
it { is_expected.to eql('DC') }
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#postal_code' do
|
92
|
+
subject { super().postal_code }
|
93
|
+
it { is_expected.to eql('21234') }
|
94
|
+
end
|
74
95
|
end
|
75
96
|
end
|
@@ -31,12 +31,12 @@ describe 'custom attributes' do
|
|
31
31
|
specify 'allows you to define custom attributes' do
|
32
32
|
regexp = /awesome/
|
33
33
|
subject.expression = regexp
|
34
|
-
subject.expression.
|
34
|
+
expect(subject.expression).to eq(regexp)
|
35
35
|
end
|
36
36
|
|
37
37
|
specify 'allows you to define coercion methods' do
|
38
38
|
subject.scream = 'welcome'
|
39
|
-
subject.scream.
|
39
|
+
expect(subject.scream).to eq('WELCOME')
|
40
40
|
end
|
41
41
|
|
42
42
|
end
|
@@ -36,7 +36,7 @@ describe 'custom collection attributes' do
|
|
36
36
|
shared_examples_for 'a collection' do
|
37
37
|
it 'can be used as Virtus attributes' do
|
38
38
|
attribute = Examples::Library.attribute_set[:books]
|
39
|
-
attribute.
|
39
|
+
expect(attribute).to be_kind_of(Examples::BookCollectionAttribute)
|
40
40
|
end
|
41
41
|
|
42
42
|
it 'defaults to an empty collection' do
|
@@ -55,18 +55,18 @@ describe 'custom collection attributes' do
|
|
55
55
|
|
56
56
|
it 'coerces an array of attribute hashes' do
|
57
57
|
library.books = [{ :title => 'Foo' }]
|
58
|
-
books.
|
58
|
+
expect(books).to be_kind_of(Examples::BookCollection)
|
59
59
|
end
|
60
60
|
|
61
61
|
it 'coerces its members' do
|
62
62
|
library.books = [{ :title => 'Foo' }]
|
63
|
-
books.count.
|
64
|
-
books.first.
|
63
|
+
expect(books.count).to eq(1)
|
64
|
+
expect(books.first).to be_kind_of(Examples::Book)
|
65
65
|
end
|
66
66
|
|
67
67
|
def books_should_be_an_empty_collection
|
68
|
-
books.
|
69
|
-
books.count.
|
68
|
+
expect(books).to be_kind_of(Examples::BookCollection)
|
69
|
+
expect(books.count).to eq(0)
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -35,21 +35,21 @@ describe "default values" do
|
|
35
35
|
subject { Examples::Page.new }
|
36
36
|
|
37
37
|
specify 'without a default the value is nil' do
|
38
|
-
subject.title.
|
38
|
+
expect(subject.title).to be_nil
|
39
39
|
end
|
40
40
|
|
41
41
|
specify 'can be supplied with the :default option' do
|
42
|
-
subject.view_count.
|
42
|
+
expect(subject.view_count).to eq(0)
|
43
43
|
end
|
44
44
|
|
45
45
|
specify "you can pass a 'callable-object' to the :default option" do
|
46
46
|
subject.title = 'Example Blog Post'
|
47
|
-
subject.slug.
|
47
|
+
expect(subject.slug).to eq('example-blog-post')
|
48
48
|
end
|
49
49
|
|
50
50
|
specify 'you can set defaults for private attributes' do
|
51
51
|
subject.title = 'Top Secret'
|
52
|
-
subject.editor_title.
|
52
|
+
expect(subject.editor_title).to eq('UNPUBLISHED: Top Secret')
|
53
53
|
end
|
54
54
|
|
55
55
|
specify 'you can reset attribute to its default' do
|
@@ -63,25 +63,25 @@ describe "default values" do
|
|
63
63
|
it 'does not duplicate the ValueObject' do
|
64
64
|
page1 = Examples::Page.new
|
65
65
|
page2 = Examples::Page.new
|
66
|
-
page1.reference.
|
66
|
+
expect(page1.reference).to equal(page2.reference)
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
70
|
context 'an Array' do
|
71
71
|
specify 'without a default the value is an empty Array' do
|
72
|
-
subject.revisions.
|
72
|
+
expect(subject.revisions).to eql([])
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
76
|
context 'a Hash' do
|
77
77
|
specify 'without a default the value is an empty Hash' do
|
78
|
-
subject.index.
|
78
|
+
expect(subject.index).to eql({})
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
82
|
context 'a Set' do
|
83
83
|
specify 'without a default the value is an empty Set' do
|
84
|
-
subject.authors.
|
84
|
+
expect(subject.authors).to eql(Set.new)
|
85
85
|
end
|
86
86
|
end
|
87
87
|
end
|
@@ -19,61 +19,68 @@ describe "virtus attribute definitions" do
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
subject { Examples::Person.new }
|
22
|
+
subject(:person) { Examples::Person.new(attributes) }
|
23
|
+
|
24
|
+
let(:attributes) { {} }
|
23
25
|
|
24
26
|
specify 'virtus creates accessor methods' do
|
25
|
-
|
26
|
-
|
27
|
+
person.name = 'Peter'
|
28
|
+
expect(person.name).to eq('Peter')
|
27
29
|
end
|
28
30
|
|
29
31
|
specify 'the constructor accepts a hash for mass-assignment' do
|
30
32
|
john = Examples::Person.new(:name => 'John', :age => 13)
|
31
|
-
john.name.
|
32
|
-
john.age.
|
33
|
+
expect(john.name).to eq('John')
|
34
|
+
expect(john.age).to eq(13)
|
33
35
|
end
|
34
36
|
|
35
|
-
specify 'Boolean attributes have a
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
specify 'Boolean attributes have a predicate method' do
|
38
|
+
expect(person).not_to be_doctor
|
39
|
+
person.doctor = true
|
40
|
+
expect(person).to be_doctor
|
39
41
|
end
|
40
42
|
|
41
43
|
context 'with attributes' do
|
42
44
|
let(:attributes) { {:name => 'Jane', :age => 45, :doctor => true, :salary => 4500} }
|
43
|
-
subject { Examples::Person.new(attributes) }
|
44
45
|
|
45
46
|
specify "#attributes returns the object's attributes as a hash" do
|
46
|
-
|
47
|
+
expect(person.attributes).to eq(attributes)
|
47
48
|
end
|
48
49
|
|
49
50
|
specify "#to_hash returns the object's attributes as a hash" do
|
50
|
-
|
51
|
+
expect(person.to_hash).to eq(attributes)
|
52
|
+
end
|
53
|
+
|
54
|
+
specify "#to_h returns the object's attributes as a hash" do
|
55
|
+
expect(person.to_h).to eql(attributes)
|
51
56
|
end
|
52
57
|
end
|
53
58
|
|
54
59
|
context 'inheritance' do
|
55
60
|
specify 'inherits all the attributes from the base class' do
|
56
61
|
fred = Examples::Manager.new(:name => 'Fred', :age => 29)
|
57
|
-
fred.name.
|
58
|
-
fred.age.
|
62
|
+
expect(fred.name).to eq('Fred')
|
63
|
+
expect(fred.age).to eq(29)
|
59
64
|
end
|
60
65
|
|
61
66
|
specify 'lets you add attributes to the base class at runtime' do
|
62
67
|
frank = Examples::Manager.new(:name => 'Frank')
|
63
68
|
Examples::Person.attribute :just_added, String
|
64
69
|
frank.just_added = 'it works!'
|
65
|
-
frank.just_added.
|
70
|
+
expect(frank.just_added).to eq('it works!')
|
66
71
|
end
|
67
72
|
|
68
73
|
specify 'lets you add attributes to the subclass at runtime' do
|
69
74
|
person_jack = Examples::Person.new(:name => 'Jack')
|
70
75
|
manager_frank = Examples::Manager.new(:name => 'Frank')
|
76
|
+
|
71
77
|
Examples::Manager.attribute :just_added, String
|
72
78
|
|
73
79
|
manager_frank.just_added = 'awesome!'
|
74
|
-
|
75
|
-
|
76
|
-
person_jack.
|
80
|
+
|
81
|
+
expect(manager_frank.just_added).to eq('awesome!')
|
82
|
+
expect(person_jack).not_to respond_to(:just_added)
|
83
|
+
expect(person_jack).not_to respond_to(:just_added=)
|
77
84
|
end
|
78
85
|
end
|
79
86
|
end
|
@@ -33,18 +33,18 @@ describe 'embedded values' do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
specify '#attributes returns instances of the embedded values' do
|
36
|
-
subject.attributes.
|
36
|
+
expect(subject.attributes).to eq({
|
37
37
|
:name => 'the guy',
|
38
38
|
:address => subject.address
|
39
|
-
}
|
39
|
+
})
|
40
40
|
end
|
41
41
|
|
42
42
|
specify 'allows you to pass a hash for the embedded value' do
|
43
43
|
user = Examples::User.new
|
44
44
|
user.address = address_attributes
|
45
|
-
user.address.street.
|
46
|
-
user.address.zipcode.
|
47
|
-
user.address.city.name.
|
45
|
+
expect(user.address.street).to eq('Street 1/2')
|
46
|
+
expect(user.address.zipcode).to eq('12345')
|
47
|
+
expect(user.address.city.name).to eq('NYC')
|
48
48
|
end
|
49
49
|
|
50
50
|
end
|
@@ -21,15 +21,15 @@ describe 'I can extend objects' do
|
|
21
21
|
admin.name = 'John'
|
22
22
|
admin.age = 29
|
23
23
|
|
24
|
-
admin.name.
|
25
|
-
admin.age.
|
24
|
+
expect(admin.name).to eql('John')
|
25
|
+
expect(admin.age).to eql(29)
|
26
26
|
|
27
|
-
admin.attributes.
|
27
|
+
expect(admin.attributes).to eql(attributes)
|
28
28
|
|
29
29
|
new_attributes = { :name => 'Jane', :age => 28 }
|
30
30
|
admin.attributes = new_attributes
|
31
31
|
|
32
|
-
admin.name.
|
33
|
-
admin.age.
|
32
|
+
expect(admin.name).to eql('Jane')
|
33
|
+
expect(admin.age).to eql(28)
|
34
34
|
end
|
35
35
|
end
|