virtus2 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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