virtus 1.0.0.beta8 → 1.0.0.rc1

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