virtus2 2.0.1

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 (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +39 -0
  3. data/.rspec +2 -0
  4. data/.yardopts +1 -0
  5. data/CONTRIBUTING.md +18 -0
  6. data/Changelog.md +258 -0
  7. data/Gemfile +10 -0
  8. data/Guardfile +19 -0
  9. data/LICENSE +20 -0
  10. data/README.md +630 -0
  11. data/Rakefile +15 -0
  12. data/TODO.md +6 -0
  13. data/lib/virtus/attribute/accessor.rb +103 -0
  14. data/lib/virtus/attribute/boolean.rb +55 -0
  15. data/lib/virtus/attribute/builder.rb +182 -0
  16. data/lib/virtus/attribute/coercer.rb +45 -0
  17. data/lib/virtus/attribute/coercible.rb +20 -0
  18. data/lib/virtus/attribute/collection.rb +103 -0
  19. data/lib/virtus/attribute/default_value/from_callable.rb +35 -0
  20. data/lib/virtus/attribute/default_value/from_clonable.rb +35 -0
  21. data/lib/virtus/attribute/default_value/from_symbol.rb +35 -0
  22. data/lib/virtus/attribute/default_value.rb +51 -0
  23. data/lib/virtus/attribute/embedded_value.rb +67 -0
  24. data/lib/virtus/attribute/enum.rb +45 -0
  25. data/lib/virtus/attribute/hash.rb +130 -0
  26. data/lib/virtus/attribute/lazy_default.rb +18 -0
  27. data/lib/virtus/attribute/nullify_blank.rb +24 -0
  28. data/lib/virtus/attribute/strict.rb +26 -0
  29. data/lib/virtus/attribute.rb +245 -0
  30. data/lib/virtus/attribute_set.rb +240 -0
  31. data/lib/virtus/builder/hook_context.rb +51 -0
  32. data/lib/virtus/builder.rb +133 -0
  33. data/lib/virtus/class_inclusions.rb +48 -0
  34. data/lib/virtus/class_methods.rb +90 -0
  35. data/lib/virtus/coercer.rb +41 -0
  36. data/lib/virtus/configuration.rb +72 -0
  37. data/lib/virtus/const_missing_extensions.rb +18 -0
  38. data/lib/virtus/extensions.rb +105 -0
  39. data/lib/virtus/instance_methods.rb +218 -0
  40. data/lib/virtus/model.rb +68 -0
  41. data/lib/virtus/module_extensions.rb +88 -0
  42. data/lib/virtus/support/equalizer.rb +128 -0
  43. data/lib/virtus/support/options.rb +113 -0
  44. data/lib/virtus/support/type_lookup.rb +109 -0
  45. data/lib/virtus/value_object.rb +150 -0
  46. data/lib/virtus/version.rb +3 -0
  47. data/lib/virtus.rb +310 -0
  48. data/spec/integration/attributes_attribute_spec.rb +28 -0
  49. data/spec/integration/building_module_spec.rb +90 -0
  50. data/spec/integration/collection_member_coercion_spec.rb +96 -0
  51. data/spec/integration/custom_attributes_spec.rb +42 -0
  52. data/spec/integration/custom_collection_attributes_spec.rb +101 -0
  53. data/spec/integration/default_values_spec.rb +87 -0
  54. data/spec/integration/defining_attributes_spec.rb +86 -0
  55. data/spec/integration/embedded_value_spec.rb +50 -0
  56. data/spec/integration/extending_objects_spec.rb +35 -0
  57. data/spec/integration/hash_attributes_coercion_spec.rb +54 -0
  58. data/spec/integration/inheritance_spec.rb +42 -0
  59. data/spec/integration/injectible_coercers_spec.rb +48 -0
  60. data/spec/integration/mass_assignment_with_accessors_spec.rb +44 -0
  61. data/spec/integration/overriding_virtus_spec.rb +46 -0
  62. data/spec/integration/required_attributes_spec.rb +25 -0
  63. data/spec/integration/struct_as_embedded_value_spec.rb +28 -0
  64. data/spec/integration/using_modules_spec.rb +55 -0
  65. data/spec/integration/value_object_with_custom_constructor_spec.rb +42 -0
  66. data/spec/integration/virtus/instance_level_attributes_spec.rb +23 -0
  67. data/spec/integration/virtus/value_object_spec.rb +99 -0
  68. data/spec/shared/constants_helpers.rb +9 -0
  69. data/spec/shared/freeze_method_behavior.rb +40 -0
  70. data/spec/shared/idempotent_method_behaviour.rb +5 -0
  71. data/spec/shared/options_class_method.rb +19 -0
  72. data/spec/spec_helper.rb +41 -0
  73. data/spec/unit/virtus/attribute/boolean/coerce_spec.rb +43 -0
  74. data/spec/unit/virtus/attribute/boolean/value_coerced_predicate_spec.rb +25 -0
  75. data/spec/unit/virtus/attribute/class_methods/build_spec.rb +180 -0
  76. data/spec/unit/virtus/attribute/class_methods/coerce_spec.rb +32 -0
  77. data/spec/unit/virtus/attribute/coerce_spec.rb +129 -0
  78. data/spec/unit/virtus/attribute/coercible_predicate_spec.rb +20 -0
  79. data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +105 -0
  80. data/spec/unit/virtus/attribute/collection/coerce_spec.rb +74 -0
  81. data/spec/unit/virtus/attribute/collection/value_coerced_predicate_spec.rb +31 -0
  82. data/spec/unit/virtus/attribute/comparison_spec.rb +20 -0
  83. data/spec/unit/virtus/attribute/custom_collection_spec.rb +29 -0
  84. data/spec/unit/virtus/attribute/defined_spec.rb +20 -0
  85. data/spec/unit/virtus/attribute/embedded_value/class_methods/build_spec.rb +70 -0
  86. data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +91 -0
  87. data/spec/unit/virtus/attribute/get_spec.rb +32 -0
  88. data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +106 -0
  89. data/spec/unit/virtus/attribute/hash/coerce_spec.rb +92 -0
  90. data/spec/unit/virtus/attribute/lazy_predicate_spec.rb +20 -0
  91. data/spec/unit/virtus/attribute/rename_spec.rb +16 -0
  92. data/spec/unit/virtus/attribute/required_predicate_spec.rb +19 -0
  93. data/spec/unit/virtus/attribute/set_default_value_spec.rb +107 -0
  94. data/spec/unit/virtus/attribute/set_spec.rb +29 -0
  95. data/spec/unit/virtus/attribute/value_coerced_predicate_spec.rb +19 -0
  96. data/spec/unit/virtus/attribute_set/append_spec.rb +47 -0
  97. data/spec/unit/virtus/attribute_set/define_reader_method_spec.rb +36 -0
  98. data/spec/unit/virtus/attribute_set/define_writer_method_spec.rb +36 -0
  99. data/spec/unit/virtus/attribute_set/each_spec.rb +65 -0
  100. data/spec/unit/virtus/attribute_set/element_reference_spec.rb +17 -0
  101. data/spec/unit/virtus/attribute_set/element_set_spec.rb +64 -0
  102. data/spec/unit/virtus/attribute_set/merge_spec.rb +34 -0
  103. data/spec/unit/virtus/attribute_set/reset_spec.rb +71 -0
  104. data/spec/unit/virtus/attribute_spec.rb +229 -0
  105. data/spec/unit/virtus/attributes_reader_spec.rb +41 -0
  106. data/spec/unit/virtus/attributes_writer_spec.rb +51 -0
  107. data/spec/unit/virtus/class_methods/finalize_spec.rb +67 -0
  108. data/spec/unit/virtus/class_methods/new_spec.rb +39 -0
  109. data/spec/unit/virtus/config_spec.rb +13 -0
  110. data/spec/unit/virtus/element_reader_spec.rb +21 -0
  111. data/spec/unit/virtus/element_writer_spec.rb +19 -0
  112. data/spec/unit/virtus/freeze_spec.rb +41 -0
  113. data/spec/unit/virtus/model_spec.rb +197 -0
  114. data/spec/unit/virtus/module_spec.rb +174 -0
  115. data/spec/unit/virtus/set_default_attributes_spec.rb +32 -0
  116. data/spec/unit/virtus/value_object_spec.rb +138 -0
  117. data/virtus2.gemspec +26 -0
  118. metadata +225 -0
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'custom attributes' do
4
+
5
+ before do
6
+ module Examples
7
+ class NoisyString < Virtus::Attribute
8
+ lazy true
9
+
10
+ def coerce(input)
11
+ input.to_s.upcase
12
+ end
13
+ end
14
+
15
+ class RegularExpression < Virtus::Attribute
16
+ primitive Regexp
17
+ end
18
+
19
+ class User
20
+ include Virtus
21
+
22
+ attribute :name, String
23
+ attribute :scream, NoisyString
24
+ attribute :expression, RegularExpression
25
+ end
26
+ end
27
+ end
28
+
29
+ subject { Examples::User.new }
30
+
31
+ specify 'allows you to define custom attributes' do
32
+ regexp = /awesome/
33
+ subject.expression = regexp
34
+ expect(subject.expression).to eq(regexp)
35
+ end
36
+
37
+ specify 'allows you to define coercion methods' do
38
+ subject.scream = 'welcome'
39
+ expect(subject.scream).to eq('WELCOME')
40
+ end
41
+
42
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'custom collection attributes' do
4
+ let(:library) { Examples::Library.new }
5
+ let(:books) { library.books }
6
+
7
+ before do
8
+ module Examples end
9
+ Examples.const_set 'BookCollection', book_collection_class
10
+
11
+ module Examples
12
+ class Book
13
+ include Virtus
14
+
15
+ attribute :title, String
16
+ end
17
+
18
+ class BookCollectionAttribute < Virtus::Attribute::Collection
19
+ primitive BookCollection
20
+ end
21
+
22
+ class Library
23
+ include Virtus
24
+
25
+ attribute :books, BookCollection[Book]
26
+ end
27
+ end
28
+ end
29
+
30
+ after do
31
+ [:BookCollectionAttribute, :BookCollection, :Book, :Library].each do |const|
32
+ Examples.send(:remove_const, const)
33
+ end
34
+ end
35
+
36
+ shared_examples_for 'a collection' do
37
+ it 'can be used as Virtus attributes' do
38
+ attribute = Examples::Library.attribute_set[:books]
39
+ expect(attribute).to be_kind_of(Examples::BookCollectionAttribute)
40
+ end
41
+
42
+ it 'defaults to an empty collection' do
43
+ books_should_be_an_empty_collection
44
+ end
45
+
46
+ it 'coerces nil' do
47
+ library.books = nil
48
+ books_should_be_an_empty_collection
49
+ end
50
+
51
+ it 'coerces an empty array' do
52
+ library.books = []
53
+ books_should_be_an_empty_collection
54
+ end
55
+
56
+ it 'coerces an array of attribute hashes' do
57
+ library.books = [{ :title => 'Foo' }]
58
+ expect(books).to be_kind_of(Examples::BookCollection)
59
+ end
60
+
61
+ it 'coerces its members' do
62
+ library.books = [{ :title => 'Foo' }]
63
+ expect(books.count).to eq(1)
64
+ expect(books.first).to be_kind_of(Examples::Book)
65
+ end
66
+
67
+ def books_should_be_an_empty_collection
68
+ expect(books).to be_kind_of(Examples::BookCollection)
69
+ expect(books.count).to eq(0)
70
+ end
71
+ end
72
+
73
+ context 'with an array subclass' do
74
+ let(:book_collection_class) { Class.new(Array) }
75
+
76
+ it_behaves_like 'a collection'
77
+ end
78
+
79
+ context 'with an enumerable' do
80
+ require 'forwardable'
81
+
82
+ let(:book_collection_class) {
83
+ Class.new do
84
+ extend Forwardable
85
+ include Enumerable
86
+
87
+ def_delegators :@array, :each, :<<
88
+
89
+ def initialize(*args)
90
+ @array = Array[*args]
91
+ end
92
+
93
+ def self.[](*args)
94
+ new(*args)
95
+ end
96
+ end
97
+ }
98
+
99
+ it_behaves_like 'a collection'
100
+ end
101
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe "default values" do
4
+
5
+ before do
6
+ module Examples
7
+
8
+ class Reference
9
+ include Virtus::ValueObject
10
+
11
+ attribute :ref, String
12
+ end
13
+
14
+ class Page
15
+ include Virtus
16
+
17
+ attribute :title, String
18
+ attribute :slug, String, :default => lambda { |post, attribute| post.title.downcase.gsub(' ', '-') }, :lazy => true
19
+ attribute :view_count, Integer, :default => 0
20
+ attribute :published, Boolean, :default => false, :accessor => :private
21
+ attribute :editor_title, String, :default => :default_editor_title, :lazy => true
22
+ attribute :reference, String, :default => Reference.new
23
+ attribute :revisions, Array
24
+ attribute :index, Hash
25
+ attribute :authors, Set
26
+
27
+ def default_editor_title
28
+ published? ? title : "UNPUBLISHED: #{title}"
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+
35
+ subject { Examples::Page.new }
36
+
37
+ specify 'without a default the value is nil' do
38
+ expect(subject.title).to be_nil
39
+ end
40
+
41
+ specify 'can be supplied with the :default option' do
42
+ expect(subject.view_count).to eq(0)
43
+ end
44
+
45
+ specify "you can pass a 'callable-object' to the :default option" do
46
+ subject.title = 'Example Blog Post'
47
+ expect(subject.slug).to eq('example-blog-post')
48
+ end
49
+
50
+ specify 'you can set defaults for private attributes' do
51
+ subject.title = 'Top Secret'
52
+ expect(subject.editor_title).to eq('UNPUBLISHED: Top Secret')
53
+ end
54
+
55
+ specify 'you can reset attribute to its default' do
56
+ subject.view_count = 10
57
+ expect do
58
+ subject.reset_attribute(:view_count)
59
+ end.to change { subject.view_count }.to(0)
60
+ end
61
+
62
+ context 'a ValueObject' do
63
+ it 'does not duplicate the ValueObject' do
64
+ page1 = Examples::Page.new
65
+ page2 = Examples::Page.new
66
+ expect(page1.reference).to equal(page2.reference)
67
+ end
68
+ end
69
+
70
+ context 'an Array' do
71
+ specify 'without a default the value is an empty Array' do
72
+ expect(subject.revisions).to eql([])
73
+ end
74
+ end
75
+
76
+ context 'a Hash' do
77
+ specify 'without a default the value is an empty Hash' do
78
+ expect(subject.index).to eql({})
79
+ end
80
+ end
81
+
82
+ context 'a Set' do
83
+ specify 'without a default the value is an empty Set' do
84
+ expect(subject.authors).to eql(Set.new)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe "virtus attribute definitions" do
4
+
5
+ before do
6
+ module Examples
7
+ class Person
8
+ include Virtus
9
+
10
+ attribute :name, String
11
+ attribute :age, Integer
12
+ attribute :doctor, Boolean
13
+ attribute :salary, Decimal
14
+ end
15
+
16
+ class Manager < Person
17
+
18
+ end
19
+ end
20
+ end
21
+
22
+ subject(:person) { Examples::Person.new(attributes) }
23
+
24
+ let(:attributes) { {} }
25
+
26
+ specify 'virtus creates accessor methods' do
27
+ person.name = 'Peter'
28
+ expect(person.name).to eq('Peter')
29
+ end
30
+
31
+ specify 'the constructor accepts a hash for mass-assignment' do
32
+ john = Examples::Person.new(:name => 'John', :age => 13)
33
+ expect(john.name).to eq('John')
34
+ expect(john.age).to eq(13)
35
+ end
36
+
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
41
+ end
42
+
43
+ context 'with attributes' do
44
+ let(:attributes) { {:name => 'Jane', :age => 45, :doctor => true, :salary => 4500} }
45
+
46
+ specify "#attributes returns the object's attributes as a hash" do
47
+ expect(person.attributes).to eq(attributes)
48
+ end
49
+
50
+ specify "#to_hash returns the object's attributes as a hash" do
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)
56
+ end
57
+ end
58
+
59
+ context 'inheritance' do
60
+ specify 'inherits all the attributes from the base class' do
61
+ fred = Examples::Manager.new(:name => 'Fred', :age => 29)
62
+ expect(fred.name).to eq('Fred')
63
+ expect(fred.age).to eq(29)
64
+ end
65
+
66
+ specify 'lets you add attributes to the base class at runtime' do
67
+ frank = Examples::Manager.new(:name => 'Frank')
68
+ Examples::Person.attribute :just_added, String
69
+ frank.just_added = 'it works!'
70
+ expect(frank.just_added).to eq('it works!')
71
+ end
72
+
73
+ specify 'lets you add attributes to the subclass at runtime' do
74
+ person_jack = Examples::Person.new(:name => 'Jack')
75
+ manager_frank = Examples::Manager.new(:name => 'Frank')
76
+
77
+ Examples::Manager.attribute :just_added, String
78
+
79
+ manager_frank.just_added = 'awesome!'
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=)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'embedded values' do
4
+ before do
5
+ module Examples
6
+ class City
7
+ include Virtus.model
8
+
9
+ attribute :name, String
10
+ end
11
+
12
+ class Address
13
+ include Virtus.model
14
+
15
+ attribute :street, String
16
+ attribute :zipcode, String
17
+ attribute :city, City
18
+ end
19
+
20
+ class User
21
+ include Virtus.model
22
+
23
+ attribute :name, String
24
+ attribute :address, Address
25
+ end
26
+ end
27
+ end
28
+
29
+ subject { Examples::User.new(:name => 'the guy',
30
+ :address => address_attributes) }
31
+ let(:address_attributes) do
32
+ { :street => 'Street 1/2', :zipcode => '12345', :city => { :name => 'NYC' } }
33
+ end
34
+
35
+ specify '#attributes returns instances of the embedded values' do
36
+ expect(subject.attributes).to eq({
37
+ :name => 'the guy',
38
+ :address => subject.address
39
+ })
40
+ end
41
+
42
+ specify 'allows you to pass a hash for the embedded value' do
43
+ user = Examples::User.new
44
+ user.address = address_attributes
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
+ end
49
+
50
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'I can extend objects' do
4
+ before do
5
+ module Examples
6
+ class User; end
7
+
8
+ class Admin; end
9
+ end
10
+ end
11
+
12
+ specify 'defining attributes on an object' do
13
+ attributes = { :name => 'John', :age => 29 }
14
+
15
+ admin = Examples::Admin.new
16
+ admin.extend(Virtus)
17
+
18
+ admin.attribute :name, String
19
+ admin.attribute :age, Integer
20
+
21
+ admin.name = 'John'
22
+ admin.age = 29
23
+
24
+ expect(admin.name).to eql('John')
25
+ expect(admin.age).to eql(29)
26
+
27
+ expect(admin.attributes).to eql(attributes)
28
+
29
+ new_attributes = { :name => 'Jane', :age => 28 }
30
+ admin.attributes = new_attributes
31
+
32
+ expect(admin.name).to eql('Jane')
33
+ expect(admin.age).to eql(28)
34
+ end
35
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ class Package
5
+ include Virtus
6
+
7
+ attribute :dimensions, Hash[Symbol => Float]
8
+ attribute :meta_info , Hash[String => String]
9
+ end
10
+
11
+
12
+ describe Package do
13
+ let(:instance) do
14
+ described_class.new(
15
+ :dimensions => { 'width' => "2.2", :height => 2, "length" => 4.5 },
16
+ :meta_info => { 'from' => :Me , :to => 'You' }
17
+ )
18
+ end
19
+
20
+ let(:dimensions) { instance.dimensions }
21
+ let(:meta_info) { instance.meta_info }
22
+
23
+ describe '#dimensions' do
24
+ subject { dimensions }
25
+
26
+ it 'has 3 keys' do
27
+ expect(subject.keys.size).to eq(3)
28
+ end
29
+ it { is_expected.to have_key :width }
30
+ it { is_expected.to have_key :height }
31
+ it { is_expected.to have_key :length }
32
+
33
+ it 'should be coerced to [Symbol => Float] format' do
34
+ expect(dimensions[:width]).to be_eql(2.2)
35
+ expect(dimensions[:height]).to be_eql(2.0)
36
+ expect(dimensions[:length]).to be_eql(4.5)
37
+ end
38
+ end
39
+
40
+ describe '#meta_info' do
41
+ subject { meta_info }
42
+
43
+ it 'has 2 keys' do
44
+ expect(subject.keys.size).to eq(2)
45
+ end
46
+ it { is_expected.to have_key 'from' }
47
+ it { is_expected.to have_key 'to' }
48
+
49
+ it 'should be coerced to [String => String] format' do
50
+ expect(meta_info['from']).to eq('Me')
51
+ expect(meta_info['to']).to eq('You')
52
+ end
53
+ end
54
+ end