virtus 1.0.2 → 1.0.3

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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +2 -2
  4. data/Changelog.md +11 -0
  5. data/Gemfile +4 -2
  6. data/Gemfile.devtools +25 -19
  7. data/README.md +56 -4
  8. data/lib/virtus.rb +26 -5
  9. data/lib/virtus/attribute/builder.rb +2 -2
  10. data/lib/virtus/attribute/collection.rb +7 -3
  11. data/lib/virtus/attribute/hash.rb +3 -3
  12. data/lib/virtus/attribute/strict.rb +1 -1
  13. data/lib/virtus/builder.rb +1 -1
  14. data/lib/virtus/configuration.rb +7 -36
  15. data/lib/virtus/instance_methods.rb +1 -0
  16. data/lib/virtus/module_extensions.rb +8 -2
  17. data/lib/virtus/support/options.rb +1 -0
  18. data/lib/virtus/support/type_lookup.rb +1 -1
  19. data/lib/virtus/version.rb +1 -1
  20. data/spec/integration/custom_attributes_spec.rb +2 -2
  21. data/spec/integration/custom_collection_attributes_spec.rb +6 -6
  22. data/spec/integration/default_values_spec.rb +8 -8
  23. data/spec/integration/defining_attributes_spec.rb +25 -18
  24. data/spec/integration/embedded_value_spec.rb +5 -5
  25. data/spec/integration/extending_objects_spec.rb +5 -5
  26. data/spec/integration/hash_attributes_coercion_spec.rb +11 -7
  27. data/spec/integration/mass_assignment_with_accessors_spec.rb +5 -5
  28. data/spec/integration/overriding_virtus_spec.rb +4 -4
  29. data/spec/integration/required_attributes_spec.rb +1 -1
  30. data/spec/integration/struct_as_embedded_value_spec.rb +4 -4
  31. data/spec/integration/using_modules_spec.rb +8 -8
  32. data/spec/integration/value_object_with_custom_constructor_spec.rb +4 -4
  33. data/spec/integration/virtus/instance_level_attributes_spec.rb +1 -1
  34. data/spec/integration/virtus/value_object_spec.rb +14 -14
  35. data/spec/shared/freeze_method_behavior.rb +1 -1
  36. data/spec/spec_helper.rb +1 -0
  37. data/spec/unit/virtus/attribute/class_methods/build_spec.rb +9 -1
  38. data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +13 -2
  39. data/spec/unit/virtus/attribute/collection/coerce_spec.rb +21 -0
  40. data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +14 -2
  41. data/spec/unit/virtus/attribute_set/each_spec.rb +21 -16
  42. data/spec/unit/virtus/attribute_set/element_reference_spec.rb +1 -1
  43. data/spec/unit/virtus/attribute_set/reset_spec.rb +5 -3
  44. data/spec/unit/virtus/attribute_spec.rb +4 -3
  45. data/spec/unit/virtus/attributes_reader_spec.rb +1 -1
  46. data/spec/unit/virtus/attributes_writer_spec.rb +1 -1
  47. data/spec/unit/virtus/model_spec.rb +3 -3
  48. data/spec/unit/virtus/module_spec.rb +59 -2
  49. data/spec/unit/virtus/value_object_spec.rb +2 -2
  50. data/virtus.gemspec +2 -2
  51. metadata +34 -32
@@ -32,6 +32,6 @@ shared_examples_for 'a #freeze method' do
32
32
  its(:frozen?) { should be(true) }
33
33
 
34
34
  it 'allows to access attribute' do
35
- subject.name.should eql('John')
35
+ expect(subject.name).to eql('John')
36
36
  end
37
37
  end
@@ -16,6 +16,7 @@ if ENV['COVERAGE'] == 'true'
16
16
  end
17
17
 
18
18
  require 'rspec'
19
+ require 'rspec/its'
19
20
  require 'bogus/rspec'
20
21
  require 'virtus'
21
22
  require 'inflecto' # for resolving namespaced constant names
@@ -7,7 +7,7 @@ describe Virtus::Attribute, '.build' do
7
7
  let(:type) { String }
8
8
  let(:options) { {} }
9
9
 
10
- share_examples_for 'a valid attribute instance' do
10
+ shared_examples_for 'a valid attribute instance' do
11
11
  it { should be_instance_of(Virtus::Attribute) }
12
12
 
13
13
  it { should be_frozen }
@@ -83,6 +83,14 @@ describe Virtus::Attribute, '.build' do
83
83
  its(:type) { should be(Axiom::Types::Integer) }
84
84
  end
85
85
 
86
+ context 'when type is a range' do
87
+ let(:type) { 0..10 }
88
+
89
+ it_behaves_like 'a valid attribute instance'
90
+
91
+ its(:type) { should be(Axiom::Types.infer(Range)) }
92
+ end
93
+
86
94
  context 'when type is a symbol of an existing class constant' do
87
95
  let(:type) { :String }
88
96
 
@@ -1,9 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Virtus::Attribute, '.build' do
4
- subject { described_class.build(type) }
4
+ subject { described_class.build(type, options) }
5
5
 
6
- share_examples_for 'a valid collection attribute instance' do
6
+ let(:options) { {} }
7
+
8
+ shared_examples_for 'a valid collection attribute instance' do
7
9
  it { should be_instance_of(Virtus::Attribute::Collection) }
8
10
 
9
11
  it { should be_frozen }
@@ -91,4 +93,13 @@ describe Virtus::Attribute, '.build' do
91
93
  expect(subject.type.member_type).to be(Axiom::Types::String)
92
94
  end
93
95
  end
96
+
97
+ context 'when strict mode is used' do
98
+ let(:type) { Array[String] }
99
+ let(:options) { { strict: true } }
100
+
101
+ it 'sets strict mode for member type' do
102
+ expect(subject.member_type).to be_strict
103
+ end
104
+ end
94
105
  end
@@ -50,4 +50,25 @@ describe Virtus::Attribute::Collection, '#coerce' do
50
50
  end
51
51
  end
52
52
  end
53
+
54
+ context 'when input is nil' do
55
+ let(:input) { nil }
56
+
57
+ fake(:coercer) { Virtus::Attribute::Coercer }
58
+ fake(:member_type) { Virtus::Attribute }
59
+
60
+ let(:member_primitive) { Integer }
61
+
62
+ let(:object) {
63
+ described_class.build(
64
+ Array[member_primitive], coercer: coercer, member_type: member_type
65
+ )
66
+ }
67
+
68
+ it 'returns nil' do
69
+ stub(coercer).call(input) { input }
70
+
71
+ expect(subject).to be(input)
72
+ end
73
+ end
53
74
  end
@@ -1,9 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Virtus::Attribute::Hash, '.build' do
4
- subject { described_class.build(type) }
4
+ subject { described_class.build(type, options) }
5
5
 
6
- share_examples_for 'a valid hash attribute instance' do
6
+ let(:options) { {} }
7
+
8
+ shared_examples_for 'a valid hash attribute instance' do
7
9
  it { should be_instance_of(Virtus::Attribute::Hash) }
8
10
 
9
11
  it { should be_frozen }
@@ -91,4 +93,14 @@ describe Virtus::Attribute::Hash, '.build' do
91
93
  )
92
94
  end
93
95
  end
96
+
97
+ context 'when strict mode is used' do
98
+ let(:type) { Hash[String => Integer] }
99
+ let(:options) { { :strict => true } }
100
+
101
+ it 'sets the strict mode for key/value types' do
102
+ expect(subject.key_type).to be_strict
103
+ expect(subject.value_type).to be_strict
104
+ end
105
+ end
94
106
  end
@@ -1,28 +1,31 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Virtus::AttributeSet, '#each' do
4
- let(:name) { :name }
4
+ subject(:attribute_set) { described_class.new(parent, attributes) }
5
+
6
+ let(:name) { :name }
5
7
  let(:attribute) { Virtus::Attribute.build(String, :name => :name) }
6
- let(:attributes) { [ attribute ] }
7
- let(:parent) { described_class.new }
8
- let(:object) { described_class.new(parent, attributes) }
9
- let(:yields) { Set[] }
8
+ let(:attributes) { [ attribute ] }
9
+ let(:parent) { described_class.new }
10
+ let(:yields) { Set[] }
10
11
 
11
12
  context 'with no block' do
12
- subject { object.each }
13
-
14
- it { should be_instance_of(to_enum.class) }
13
+ it 'returns an enumerator when block is not provided' do
14
+ expect(attribute_set.each).to be_kind_of(Enumerator)
15
+ end
15
16
 
16
17
  it 'yields the expected attributes' do
17
- subject.to_a.should eql(object.to_a)
18
+ result = []
19
+ attribute_set.each { |attribute| result << attribute }
20
+ expect(result).to eql(attributes)
18
21
  end
19
22
  end
20
23
 
21
24
  context 'with a block' do
22
- subject { object.each { |attribute| yields << attribute } }
25
+ subject { attribute_set.each { |attribute| yields << attribute } }
23
26
 
24
27
  context 'when the parent has no attributes' do
25
- it { should equal(object) }
28
+ it { should equal(attribute_set) }
26
29
 
27
30
  it 'yields the expected attributes' do
28
31
  expect { subject }.to change { yields.dup }.
@@ -35,12 +38,14 @@ describe Virtus::AttributeSet, '#each' do
35
38
  let(:parent_attribute) { Virtus::Attribute.build(String, :name => :parent_name) }
36
39
  let(:parent) { described_class.new([ parent_attribute ]) }
37
40
 
38
- it { should equal(object) }
41
+ it { should equal(attribute_set) }
39
42
 
40
43
  it 'yields the expected attributes' do
41
- expect { subject }.to change { yields.dup }.
42
- from(Set[]).
43
- to(Set[ attribute, parent_attribute ])
44
+ result = []
45
+
46
+ attribute_set.each { |attribute| result << attribute }
47
+
48
+ expect(result).to eql([parent_attribute, attribute])
44
49
  end
45
50
  end
46
51
 
@@ -48,7 +53,7 @@ describe Virtus::AttributeSet, '#each' do
48
53
  let(:parent_attribute) { Virtus::Attribute.build(String, :name => name) }
49
54
  let(:parent) { described_class.new([ parent_attribute ]) }
50
55
 
51
- it { should equal(object) }
56
+ it { should equal(attribute_set) }
52
57
 
53
58
  it 'yields the expected attributes' do
54
59
  expect { subject }.to change { yields.dup }.
@@ -12,6 +12,6 @@ describe Virtus::AttributeSet, '#[]' do
12
12
  it { should equal(attribute) }
13
13
 
14
14
  it 'allows indexed access to attributes by the string representation of their name' do
15
- object[name.to_s].should equal(attribute)
15
+ expect(object[name.to_s]).to equal(attribute)
16
16
  end
17
17
  end
@@ -42,9 +42,11 @@ describe Virtus::AttributeSet, '#reset' do
42
42
  it { should equal(object) }
43
43
 
44
44
  it 'includes changes from the parent' do
45
- expect { parent << new_attribute; subject }.to change { object.to_set }.
46
- from(Set[ attribute, parent_attribute ]).
47
- to(Set[ attribute, new_attribute ])
45
+ expect(object.to_set).to eql(Set[attribute, parent_attribute])
46
+
47
+ parent << new_attribute
48
+
49
+ expect(subject.to_set).to eql(Set[attribute, new_attribute])
48
50
  end
49
51
  end
50
52
 
@@ -4,14 +4,15 @@ describe Virtus, '#attribute' do
4
4
  let(:name) { :test }
5
5
  let(:options) { {} }
6
6
 
7
- share_examples_for 'a class with boolean attribute' do
7
+ shared_examples_for 'a class with boolean attribute' do
8
8
  subject { Test }
9
9
 
10
10
  let(:object) { subject.new }
11
11
 
12
12
  it 'defines reader and writer' do
13
13
  object.test = true
14
- expect(object.test).to be(true)
14
+
15
+ expect(object).to be_test
15
16
  end
16
17
 
17
18
  it 'defines predicate method' do
@@ -21,7 +22,7 @@ describe Virtus, '#attribute' do
21
22
  end
22
23
  end
23
24
 
24
- share_examples_for 'an object with string attribute' do
25
+ shared_examples_for 'an object with string attribute' do
25
26
  it { should respond_to(:test) }
26
27
  it { should respond_to(:test=) }
27
28
 
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Virtus, '#attributes' do
4
4
 
5
- share_examples_for 'attribute hash' do
5
+ shared_examples_for 'attribute hash' do
6
6
  it 'includes all attributes' do
7
7
  subject.attributes = { :test => 'Hello World', :test_priv => 'Yo' }
8
8
 
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Virtus, '#attributes=' do
4
4
 
5
- share_examples_for 'mass-assignment' do
5
+ shared_examples_for 'mass-assignment' do
6
6
  it 'allows writing known attributes' do
7
7
  subject.attributes = { :test => 'Hello World' }
8
8
 
@@ -1,14 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Virtus, '.model' do
4
- share_examples_for 'a model with constructor' do
4
+ shared_examples_for 'a model with constructor' do
5
5
  it 'accepts attribute hash' do
6
6
  instance = subject.new(:name => 'Jane')
7
7
  expect(instance.name).to eql('Jane')
8
8
  end
9
9
  end
10
10
 
11
- share_examples_for 'a model with mass-assignment' do
11
+ shared_examples_for 'a model with mass-assignment' do
12
12
  let(:attributes) do
13
13
  { :name => 'Jane', :something => nil }
14
14
  end
@@ -22,7 +22,7 @@ describe Virtus, '.model' do
22
22
  end
23
23
  end
24
24
 
25
- share_examples_for 'a model with strict mode turned off' do
25
+ shared_examples_for 'a model with strict mode turned off' do
26
26
  it 'has attributes with strict set to false' do
27
27
  expect(subject.send(:attribute_set)[:name]).to_not be_strict
28
28
  end
@@ -1,14 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Virtus, '.module' do
4
- share_examples_for 'a valid virtus object' do
4
+ shared_examples_for 'a valid virtus object' do
5
5
  it 'reads and writes attribute' do
6
6
  instance.name = 'John'
7
7
  expect(instance.name).to eql('John')
8
8
  end
9
9
  end
10
10
 
11
- share_examples_for 'an object extended with virtus module' do
11
+ shared_examples_for 'an object extended with virtus module' do
12
12
  context 'with default configuration' do
13
13
  subject { Virtus.module }
14
14
 
@@ -114,4 +114,61 @@ describe Virtus, '.module' do
114
114
  end
115
115
  end
116
116
 
117
+ context 'as a peer to another module within a class' do
118
+ subject { Virtus.module }
119
+ let(:other) { Module.new }
120
+
121
+ before do
122
+ other.send(:include, Virtus.module)
123
+ other.attribute :last_name, String, :default => 'Doe'
124
+ other.attribute :something_else
125
+ model.send(:include, mod)
126
+ model.send(:include, other)
127
+ end
128
+
129
+ it 'provides attributes for the model from both modules' do
130
+ expect(model.attribute_set[:name]).to be_kind_of(Virtus::Attribute)
131
+ expect(model.attribute_set[:something]).to be_kind_of(Virtus::Attribute)
132
+ expect(model.attribute_set[:last_name]).to be_kind_of(Virtus::Attribute)
133
+ expect(model.attribute_set[:something_else]).to be_kind_of(Virtus::Attribute)
134
+ end
135
+
136
+ it 'includes the attributes from both modules' do
137
+ expect(model.new.attributes.keys).to eq(
138
+ [:name, :something, :last_name, :something_else]
139
+ )
140
+ end
141
+ end
142
+
143
+ context 'with multiple other modules mixed into it' do
144
+ subject { Virtus.module }
145
+ let(:other) { Module.new }
146
+ let(:yet_another) { Module.new }
147
+
148
+ before do
149
+ other.send(:include, Virtus.module)
150
+ other.attribute :last_name, String, :default => 'Doe'
151
+ other.attribute :something_else
152
+ yet_another.send(:include, Virtus.module)
153
+ yet_another.send(:include, mod)
154
+ yet_another.send(:include, other)
155
+ yet_another.attribute :middle_name, String, :default => 'Foobar'
156
+ model.send(:include, yet_another)
157
+ end
158
+
159
+ it 'provides attributes for the model from all modules' do
160
+ expect(model.attribute_set[:name]).to be_kind_of(Virtus::Attribute)
161
+ expect(model.attribute_set[:something]).to be_kind_of(Virtus::Attribute)
162
+ expect(model.attribute_set[:last_name]).to be_kind_of(Virtus::Attribute)
163
+ expect(model.attribute_set[:something_else]).to be_kind_of(Virtus::Attribute)
164
+ expect(model.attribute_set[:middle_name]).to be_kind_of(Virtus::Attribute)
165
+ end
166
+
167
+ it 'includes the attributes from all modules' do
168
+ expect(model.new.attributes.keys).to eq(
169
+ [:name, :something, :last_name, :something_else, :middle_name]
170
+ )
171
+ end
172
+ end
173
+
117
174
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Virtus::ValueObject do
4
- share_examples_for 'a valid value object' do
4
+ shared_examples_for 'a valid value object' do
5
5
  subject { model.new(attributes) }
6
6
 
7
7
  let(:attributes) { Hash[:id => 1, :name => 'Jane Doe'] }
@@ -43,7 +43,7 @@ describe Virtus::ValueObject do
43
43
  end
44
44
  end
45
45
 
46
- share_examples_for 'a valid value object with mass-assignment turned on' do
46
+ shared_examples_for 'a valid value object with mass-assignment turned on' do
47
47
  subject { model.new }
48
48
 
49
49
  it 'disallows mass-assignment' do
@@ -17,8 +17,8 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = `git ls-files -- {spec}/*`.split("\n")
18
18
  gem.extra_rdoc_files = %w[LICENSE README.md TODO.md]
19
19
 
20
- gem.add_dependency('descendants_tracker', '~> 0.0.3')
21
- gem.add_dependency('equalizer', '~> 0.0.9')
20
+ gem.add_dependency('descendants_tracker', '~> 0.0', '>= 0.0.3')
21
+ gem.add_dependency('equalizer', '~> 0.0', '>= 0.0.9')
22
22
  gem.add_dependency('coercible', '~> 1.0')
23
23
  gem.add_dependency('axiom-types', '~> 0.1')
24
24
  end
metadata CHANGED
@@ -1,78 +1,81 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: virtus
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
5
- prerelease:
4
+ version: 1.0.3
6
5
  platform: ruby
7
6
  authors:
8
7
  - Piotr Solnica
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2014-03-12 00:00:00.000000000 Z
11
+ date: 2014-07-24 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: descendants_tracker
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.0'
20
+ - - ">="
20
21
  - !ruby/object:Gem::Version
21
22
  version: 0.0.3
22
23
  type: :runtime
23
24
  prerelease: false
24
25
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
26
  requirements:
27
- - - ~>
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.0'
30
+ - - ">="
28
31
  - !ruby/object:Gem::Version
29
32
  version: 0.0.3
30
33
  - !ruby/object:Gem::Dependency
31
34
  name: equalizer
32
35
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
36
  requirements:
35
- - - ~>
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.0'
40
+ - - ">="
36
41
  - !ruby/object:Gem::Version
37
42
  version: 0.0.9
38
43
  type: :runtime
39
44
  prerelease: false
40
45
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
46
  requirements:
43
- - - ~>
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.0'
50
+ - - ">="
44
51
  - !ruby/object:Gem::Version
45
52
  version: 0.0.9
46
53
  - !ruby/object:Gem::Dependency
47
54
  name: coercible
48
55
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
56
  requirements:
51
- - - ~>
57
+ - - "~>"
52
58
  - !ruby/object:Gem::Version
53
59
  version: '1.0'
54
60
  type: :runtime
55
61
  prerelease: false
56
62
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
63
  requirements:
59
- - - ~>
64
+ - - "~>"
60
65
  - !ruby/object:Gem::Version
61
66
  version: '1.0'
62
67
  - !ruby/object:Gem::Dependency
63
68
  name: axiom-types
64
69
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
70
  requirements:
67
- - - ~>
71
+ - - "~>"
68
72
  - !ruby/object:Gem::Version
69
73
  version: '0.1'
70
74
  type: :runtime
71
75
  prerelease: false
72
76
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
77
  requirements:
75
- - - ~>
78
+ - - "~>"
76
79
  - !ruby/object:Gem::Version
77
80
  version: '0.1'
78
81
  description: Attributes on Steroids for Plain Old Ruby Objects
@@ -85,13 +88,13 @@ extra_rdoc_files:
85
88
  - README.md
86
89
  - TODO.md
87
90
  files:
88
- - .gitignore
89
- - .pelusa.yml
90
- - .rspec
91
- - .ruby-gemset
92
- - .ruby-version
93
- - .travis.yml
94
- - .yardopts
91
+ - ".gitignore"
92
+ - ".pelusa.yml"
93
+ - ".rspec"
94
+ - ".ruby-gemset"
95
+ - ".ruby-version"
96
+ - ".travis.yml"
97
+ - ".yardopts"
95
98
  - CONTRIBUTING.md
96
99
  - Changelog.md
97
100
  - Gemfile
@@ -209,27 +212,26 @@ files:
209
212
  homepage: https://github.com/solnic/virtus
210
213
  licenses:
211
214
  - MIT
215
+ metadata: {}
212
216
  post_install_message:
213
217
  rdoc_options: []
214
218
  require_paths:
215
219
  - lib
216
220
  required_ruby_version: !ruby/object:Gem::Requirement
217
- none: false
218
221
  requirements:
219
- - - ! '>='
222
+ - - ">="
220
223
  - !ruby/object:Gem::Version
221
224
  version: '0'
222
225
  required_rubygems_version: !ruby/object:Gem::Requirement
223
- none: false
224
226
  requirements:
225
- - - ! '>='
227
+ - - ">="
226
228
  - !ruby/object:Gem::Version
227
229
  version: '0'
228
230
  requirements: []
229
231
  rubyforge_project:
230
- rubygems_version: 1.8.23
232
+ rubygems_version: 2.2.2
231
233
  signing_key:
232
- specification_version: 3
234
+ specification_version: 4
233
235
  summary: Attributes on Steroids for Plain Old Ruby Objects
234
236
  test_files: []
235
237
  has_rdoc: