virtus 1.0.0.beta8 → 1.0.0.rc1

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.
@@ -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 }
@@ -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
 
@@ -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
@@ -18,9 +18,8 @@ module Virtus
18
18
  object.instance_eval do
19
19
  extend Methods
20
20
  extend InstanceMethods
21
- extend InstanceMethods::Constructor
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
- AttributeSet.create(descendant)
33
- descendant.instance_eval do
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 = Object, options = {})
63
+ def attribute(name, type = nil, options = {})
67
64
  assert_valid_name(name)
68
- attribute_set << Attribute.build(type, merge_options(name, options))
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
- # Merge default options
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 [Hash]
97
+ # @return [Set]
108
98
  #
109
99
  # @api private
110
- def merge_options(name, options)
111
- { :name => name }.merge(options)
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 # Methods
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
- attribute_set.set_default(self, attribute) if attribute
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) if Class === descendant
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
@@ -25,7 +25,7 @@ module Virtus
25
25
  # @return [self]
26
26
  #
27
27
  # @api private
28
- def attribute(name, type, options = {})
28
+ def attribute(name, type = nil, options = {})
29
29
  @attribute_definitions << [name, type, options]
30
30
  self
31
31
  end
@@ -34,7 +34,7 @@ module Virtus
34
34
  Virtus.warn "Virtus::ValueObject is deprecated and will be removed in 1.0.0 #{caller.first}"
35
35
 
36
36
  base.instance_eval do
37
- include ::Virtus
37
+ include Virtus
38
38
  include InstanceMethods
39
39
  extend ClassMethods
40
40
  extend AllowedWriterMethods
@@ -1,3 +1,3 @@
1
1
  module Virtus
2
- VERSION = '1.0.0.beta8'
2
+ VERSION = '1.0.0.rc1'
3
3
  end
@@ -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
@@ -12,7 +12,7 @@ if ENV['COVERAGE'] == 'true'
12
12
  add_filter 'config/'
13
13
  add_filter 'spec'
14
14
  add_filter '.bundle'
15
- minimum_coverage 99
15
+ minimum_coverage 98
16
16
  end
17
17
  end
18
18
 
@@ -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(:finalize => false)
21
+ include Virtus.model
22
22
 
23
23
  attribute :title, String
24
24
  end
25
25
 
26
26
  class Address
27
- include Virtus.model(:finalize => false)
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
- subject { Class.new }
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 = 'Jane'
7
- expect(instance.name).to eql('Jane')
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