virtus 0.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.
- data/.gitignore +4 -0
- data/.rvmrc +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +7 -0
- data/LICENSE +20 -0
- data/README.markdown +83 -0
- data/Rakefile +27 -0
- data/VERSION +1 -0
- data/lib/virtus.rb +61 -0
- data/lib/virtus/attributes/array.rb +8 -0
- data/lib/virtus/attributes/attribute.rb +214 -0
- data/lib/virtus/attributes/boolean.rb +39 -0
- data/lib/virtus/attributes/date.rb +44 -0
- data/lib/virtus/attributes/date_time.rb +43 -0
- data/lib/virtus/attributes/decimal.rb +24 -0
- data/lib/virtus/attributes/float.rb +20 -0
- data/lib/virtus/attributes/hash.rb +8 -0
- data/lib/virtus/attributes/integer.rb +20 -0
- data/lib/virtus/attributes/numeric.rb +9 -0
- data/lib/virtus/attributes/object.rb +8 -0
- data/lib/virtus/attributes/string.rb +11 -0
- data/lib/virtus/attributes/time.rb +45 -0
- data/lib/virtus/attributes/typecast/numeric.rb +32 -0
- data/lib/virtus/attributes/typecast/time.rb +27 -0
- data/lib/virtus/class_methods.rb +60 -0
- data/lib/virtus/instance_methods.rb +80 -0
- data/lib/virtus/support/chainable.rb +15 -0
- data/spec/integration/virtus/class_methods/attribute_spec.rb +63 -0
- data/spec/integration/virtus/class_methods/attributes_spec.rb +24 -0
- data/spec/integration/virtus/class_methods/const_missing_spec.rb +44 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/unit/shared/attribute.rb +157 -0
- data/spec/unit/virtus/attributes/array_spec.rb +9 -0
- data/spec/unit/virtus/attributes/attribute_spec.rb +13 -0
- data/spec/unit/virtus/attributes/boolean_spec.rb +97 -0
- data/spec/unit/virtus/attributes/date_spec.rb +52 -0
- data/spec/unit/virtus/attributes/date_time_spec.rb +65 -0
- data/spec/unit/virtus/attributes/decimal_spec.rb +98 -0
- data/spec/unit/virtus/attributes/float_spec.rb +98 -0
- data/spec/unit/virtus/attributes/hash_spec.rb +9 -0
- data/spec/unit/virtus/attributes/integer_spec.rb +98 -0
- data/spec/unit/virtus/attributes/numeric/class_methods/descendants_spec.rb +15 -0
- data/spec/unit/virtus/attributes/object/class_methods/descendants_spec.rb +16 -0
- data/spec/unit/virtus/attributes/string_spec.rb +20 -0
- data/spec/unit/virtus/attributes/time_spec.rb +71 -0
- data/spec/unit/virtus/class_methods/new_spec.rb +41 -0
- data/spec/unit/virtus/determine_type_spec.rb +20 -0
- data/spec/unit/virtus/instance_methods/attribute_get_spec.rb +22 -0
- data/spec/unit/virtus/instance_methods/attribute_set_spec.rb +30 -0
- data/spec/unit/virtus/instance_methods/attributes_spec.rb +37 -0
- data/virtus.gemspec +95 -0
- metadata +131 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::ClassMethods, '.attribute' do
|
4
|
+
let(:described_class) do
|
5
|
+
Class.new { include Virtus }
|
6
|
+
end
|
7
|
+
|
8
|
+
it { described_class.should respond_to(:attribute) }
|
9
|
+
|
10
|
+
describe ".attribute" do
|
11
|
+
before do
|
12
|
+
described_class.attribute(:name, String)
|
13
|
+
described_class.attribute(:email, String, :accessor => :private)
|
14
|
+
described_class.attribute(:address, String, :accessor => :protected)
|
15
|
+
described_class.attribute(:age, Integer, :reader => :private)
|
16
|
+
described_class.attribute(:bday, Date, :writer => :protected)
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:public_instance_methods) { described_class.public_instance_methods.map { |method| method.to_s } }
|
20
|
+
let(:protected_instance_methods) { described_class.protected_instance_methods.map { |method| method.to_s } }
|
21
|
+
let(:private_instance_methods) { described_class.private_instance_methods.map { |method| method.to_s } }
|
22
|
+
|
23
|
+
it "should create an attribute" do
|
24
|
+
described_class.attributes.should have_key(:name)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should create an attribute of a correct type" do
|
28
|
+
described_class.attributes[:name].should be_instance_of(Virtus::Attributes::String)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "creates attribute writer" do
|
32
|
+
public_instance_methods.should include('name=')
|
33
|
+
end
|
34
|
+
|
35
|
+
it "creates attribute reader" do
|
36
|
+
public_instance_methods.should include('name')
|
37
|
+
end
|
38
|
+
|
39
|
+
it "creates attribute private reader when :accessor => :private" do
|
40
|
+
private_instance_methods.should include('email')
|
41
|
+
end
|
42
|
+
|
43
|
+
it "creates attribute private writer when :accessor => :private" do
|
44
|
+
private_instance_methods.should include('email=')
|
45
|
+
end
|
46
|
+
|
47
|
+
it "creates attribute protected reader when :accessor => :protected" do
|
48
|
+
protected_instance_methods.should include('address')
|
49
|
+
end
|
50
|
+
|
51
|
+
it "creates attribute protected writer when :accessor => :protected" do
|
52
|
+
protected_instance_methods.should include('address=')
|
53
|
+
end
|
54
|
+
|
55
|
+
it "creates attribute private reader when :reader => :private" do
|
56
|
+
private_instance_methods.should include('age')
|
57
|
+
end
|
58
|
+
|
59
|
+
it "creates attribute protected writer when :writer => :protected" do
|
60
|
+
protected_instance_methods.should include('bday=')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::ClassMethods, '.attributes' do
|
4
|
+
let(:described_class) do
|
5
|
+
Class.new { include Virtus }
|
6
|
+
end
|
7
|
+
|
8
|
+
it { described_class.should respond_to(:attributes) }
|
9
|
+
|
10
|
+
describe ".attributes" do
|
11
|
+
before do
|
12
|
+
described_class.attribute(:name, String)
|
13
|
+
described_class.attribute(:age, Integer)
|
14
|
+
end
|
15
|
+
|
16
|
+
subject { described_class.attributes }
|
17
|
+
|
18
|
+
it "returns an attributes hash" do
|
19
|
+
subject.should == {
|
20
|
+
:name => described_class.attributes[:name],
|
21
|
+
:age => described_class.attributes[:age] }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::ClassMethods, '.const_missing' do
|
4
|
+
after do
|
5
|
+
Object.send(:remove_const, :User)
|
6
|
+
end
|
7
|
+
|
8
|
+
context "with an existing attribute constant which doesn't exist in the global ns" do
|
9
|
+
before do
|
10
|
+
class User
|
11
|
+
include Virtus
|
12
|
+
attribute :name, String
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should create attribute of the correct type" do
|
17
|
+
User.attributes[:name].should be_instance_of(Virtus::Attributes::String)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "with an existing attribute constant which doesn't exist in the global ns" do
|
22
|
+
before do
|
23
|
+
class User
|
24
|
+
include Virtus
|
25
|
+
attribute :admin, Boolean
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should create attribute of the correct type" do
|
30
|
+
User.attributes[:admin].should be_instance_of(Virtus::Attributes::Boolean)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with an unknown constant" do
|
35
|
+
it "should raise NameError" do
|
36
|
+
expect {
|
37
|
+
class User
|
38
|
+
include Virtus
|
39
|
+
attribute :not_gonna_work, NoSuchThing
|
40
|
+
end
|
41
|
+
}.to raise_error(NameError)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rspec'
|
4
|
+
|
5
|
+
if ENV['SPEC_COV'] && RUBY_VERSION >= '1.9.2'
|
6
|
+
require 'simplecov'
|
7
|
+
|
8
|
+
SimpleCov.start do
|
9
|
+
add_filter "/spec/"
|
10
|
+
add_group "lib", "lib"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'virtus'
|
15
|
+
|
16
|
+
ENV['TZ'] = 'UTC'
|
17
|
+
|
18
|
+
SPEC_ROOT = Pathname(__FILE__).dirname.expand_path
|
19
|
+
|
20
|
+
Pathname.glob((SPEC_ROOT + '**/shared/**/*.rb').to_s).each { |file| require file }
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# TODO: split this into separate files
|
2
|
+
|
3
|
+
shared_examples_for "Attribute" do
|
4
|
+
def attribute_name
|
5
|
+
raise "+attribute_name+ should be defined"
|
6
|
+
end
|
7
|
+
|
8
|
+
before :all do
|
9
|
+
Object.send(:remove_const, :SubAttribute) if Object.const_defined?(:SubAttribute)
|
10
|
+
Object.send(:remove_const, :User) if Object.const_defined?(:User)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:sub_attribute) { class SubAttribute < described_class; end; SubAttribute }
|
14
|
+
|
15
|
+
let(:model) do
|
16
|
+
Class.new { include Virtus }
|
17
|
+
end
|
18
|
+
|
19
|
+
describe ".options" do
|
20
|
+
subject { described_class.options }
|
21
|
+
it { should be_instance_of(Hash) }
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".accepted_options" do
|
25
|
+
it "returns an array of accepted options" do
|
26
|
+
described_class.accepted_options.should be_instance_of(Array)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "accepts base options" do
|
30
|
+
described_class.accepted_options.should include(*Virtus::Attributes::Attribute::OPTIONS)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe ".accept_options" do
|
35
|
+
let(:new_option) { :width }
|
36
|
+
|
37
|
+
before :all do
|
38
|
+
described_class.accepted_options.should_not include(new_option)
|
39
|
+
described_class.accept_options(new_option)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "sets new accepted options on itself" do
|
43
|
+
described_class.accepted_options.should include(new_option)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "sets new accepted option on its descendants" do
|
47
|
+
sub_attribute.accepted_options.should include(new_option)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "creates option accessors" do
|
51
|
+
described_class.should respond_to(new_option)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "creates option accessors on descendants" do
|
55
|
+
sub_attribute.should respond_to(new_option)
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with default option value' do
|
59
|
+
let(:option) { :height }
|
60
|
+
let(:value) { 10 }
|
61
|
+
|
62
|
+
before :all do
|
63
|
+
sub_attribute.accept_options(option)
|
64
|
+
sub_attribute.height(value)
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when new attribute is created" do
|
68
|
+
subject { sub_attribute.new(attribute_name, model) }
|
69
|
+
|
70
|
+
it "sets the default value" do
|
71
|
+
subject.options[option].should == value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when new attribute is created and overrides option's default value" do
|
76
|
+
let(:new_value) { 11 }
|
77
|
+
|
78
|
+
subject { sub_attribute.new(attribute_name, model, option => new_value) }
|
79
|
+
|
80
|
+
it "sets the new value" do
|
81
|
+
subject.options[option].should == new_value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "#set" do
|
88
|
+
let(:attribute) { model.attribute(attribute_name, described_class) }
|
89
|
+
let(:object) { model.new }
|
90
|
+
|
91
|
+
context "with nil" do
|
92
|
+
subject { attribute.set(object, nil) }
|
93
|
+
|
94
|
+
it "doesn't set the ivar" do
|
95
|
+
subject
|
96
|
+
object.instance_variable_defined?(attribute.instance_variable_name).should be(false)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "returns nil" do
|
100
|
+
subject.should be(nil)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "with a primitive value" do
|
105
|
+
before { attribute.set(object, attribute_value) }
|
106
|
+
|
107
|
+
it "sets the value in an ivar" do
|
108
|
+
object.instance_variable_get(attribute.instance_variable_name).should == attribute_value
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "with a non-primitive value" do
|
113
|
+
before { attribute.set(object, attribute_value_other) }
|
114
|
+
|
115
|
+
it "sets the value in an ivar converted to the primitive type" do
|
116
|
+
object.instance_variable_get(attribute.instance_variable_name).should be_kind_of(described_class.primitive)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#get" do
|
122
|
+
let(:attribute) { model.attribute(attribute_name, described_class) }
|
123
|
+
let(:object) { model.new }
|
124
|
+
|
125
|
+
context "when a non-nil value is set" do
|
126
|
+
before { attribute.set(object, attribute_value) }
|
127
|
+
|
128
|
+
subject { attribute.get(object) }
|
129
|
+
|
130
|
+
it { should == attribute_value }
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when nil is set" do
|
134
|
+
before { attribute.set(object, nil) }
|
135
|
+
|
136
|
+
subject { attribute.get(object) }
|
137
|
+
|
138
|
+
it { should be(nil) }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "#complex" do
|
143
|
+
let(:attribute) { model.attribute(attribute_name, described_class, :complex => complex) }
|
144
|
+
|
145
|
+
subject { attribute.complex? }
|
146
|
+
|
147
|
+
context "when set to true" do
|
148
|
+
let(:complex) { true }
|
149
|
+
it { should be(true) }
|
150
|
+
end
|
151
|
+
|
152
|
+
context "when set to false" do
|
153
|
+
let(:complex) { false }
|
154
|
+
it { should be(false) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attributes::Array do
|
4
|
+
it_should_behave_like 'Attribute' do
|
5
|
+
let(:attribute_name) { :colors }
|
6
|
+
let(:attribute_value) { [ 'red', 'green', 'blue' ] }
|
7
|
+
let(:attribute_value_other) { [ 'orange', 'yellow', 'gray' ] }
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attributes::Attribute do
|
4
|
+
describe '#typecast_to_primitive' do
|
5
|
+
let(:model) { Class.new { include Virtus } }
|
6
|
+
let(:attribute) { Virtus::Attributes::Attribute.new(:name, model) }
|
7
|
+
let(:value) { 'value' }
|
8
|
+
|
9
|
+
it "returns original value" do
|
10
|
+
attribute.typecast_to_primitive(value, model).should == value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attributes::Boolean do
|
4
|
+
it_should_behave_like 'Attribute' do
|
5
|
+
let(:attribute_name) { :is_admin }
|
6
|
+
let(:attribute_value) { true }
|
7
|
+
let(:attribute_value_other) { '1' }
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "accessor names" do
|
11
|
+
let(:model) do
|
12
|
+
Class.new do
|
13
|
+
include Virtus
|
14
|
+
|
15
|
+
attribute :is_admin, Virtus::Attributes::Boolean
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:object) { model.new(:is_admin => true) }
|
20
|
+
|
21
|
+
it "uses standard boolean reader naming conventions" do
|
22
|
+
object.is_admin?.should be_true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#typecast' do
|
27
|
+
let(:model) { Class.new { include Virtus } }
|
28
|
+
let(:attribute) { model.attribute(:is_admin, Virtus::Attributes::Boolean) }
|
29
|
+
|
30
|
+
subject { attribute.typecast(value) }
|
31
|
+
|
32
|
+
context "with 1" do
|
33
|
+
let(:value) { 1 }
|
34
|
+
it { should be(true) }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with '1'" do
|
38
|
+
let(:value) { '1' }
|
39
|
+
it { should be(true) }
|
40
|
+
end
|
41
|
+
|
42
|
+
context "with 'true'" do
|
43
|
+
let(:value) { 'true' }
|
44
|
+
it { should be(true) }
|
45
|
+
end
|
46
|
+
|
47
|
+
context "with 'TRUE'" do
|
48
|
+
let(:value) { 'TRUE' }
|
49
|
+
it { should be(true) }
|
50
|
+
end
|
51
|
+
|
52
|
+
context "with 't'" do
|
53
|
+
let(:value) { 't' }
|
54
|
+
it { should be(true) }
|
55
|
+
end
|
56
|
+
|
57
|
+
context "with 'T'" do
|
58
|
+
let(:value) { 'T' }
|
59
|
+
it { should be(true) }
|
60
|
+
end
|
61
|
+
|
62
|
+
context "with 0" do
|
63
|
+
let(:value) { 0 }
|
64
|
+
it { should be(false) }
|
65
|
+
end
|
66
|
+
|
67
|
+
context "with '0'" do
|
68
|
+
let(:value) { '0' }
|
69
|
+
it { should be(false) }
|
70
|
+
end
|
71
|
+
|
72
|
+
context "with 'false'" do
|
73
|
+
let(:value) { 'false' }
|
74
|
+
it { should be(false) }
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with 'FALSE'" do
|
78
|
+
let(:value) { 'FALSE' }
|
79
|
+
it { should be(false) }
|
80
|
+
end
|
81
|
+
|
82
|
+
context "with 'f'" do
|
83
|
+
let(:value) { 'f' }
|
84
|
+
it { should be(false) }
|
85
|
+
end
|
86
|
+
|
87
|
+
context "with 'F'" do
|
88
|
+
let(:value) { 'F' }
|
89
|
+
it { should be(false) }
|
90
|
+
end
|
91
|
+
|
92
|
+
context "with 'Foo'" do
|
93
|
+
let(:value) { 'Foo' }
|
94
|
+
it { should == value }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|