virtus 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +4 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE +20 -0
  6. data/README.markdown +83 -0
  7. data/Rakefile +27 -0
  8. data/VERSION +1 -0
  9. data/lib/virtus.rb +61 -0
  10. data/lib/virtus/attributes/array.rb +8 -0
  11. data/lib/virtus/attributes/attribute.rb +214 -0
  12. data/lib/virtus/attributes/boolean.rb +39 -0
  13. data/lib/virtus/attributes/date.rb +44 -0
  14. data/lib/virtus/attributes/date_time.rb +43 -0
  15. data/lib/virtus/attributes/decimal.rb +24 -0
  16. data/lib/virtus/attributes/float.rb +20 -0
  17. data/lib/virtus/attributes/hash.rb +8 -0
  18. data/lib/virtus/attributes/integer.rb +20 -0
  19. data/lib/virtus/attributes/numeric.rb +9 -0
  20. data/lib/virtus/attributes/object.rb +8 -0
  21. data/lib/virtus/attributes/string.rb +11 -0
  22. data/lib/virtus/attributes/time.rb +45 -0
  23. data/lib/virtus/attributes/typecast/numeric.rb +32 -0
  24. data/lib/virtus/attributes/typecast/time.rb +27 -0
  25. data/lib/virtus/class_methods.rb +60 -0
  26. data/lib/virtus/instance_methods.rb +80 -0
  27. data/lib/virtus/support/chainable.rb +15 -0
  28. data/spec/integration/virtus/class_methods/attribute_spec.rb +63 -0
  29. data/spec/integration/virtus/class_methods/attributes_spec.rb +24 -0
  30. data/spec/integration/virtus/class_methods/const_missing_spec.rb +44 -0
  31. data/spec/spec_helper.rb +20 -0
  32. data/spec/unit/shared/attribute.rb +157 -0
  33. data/spec/unit/virtus/attributes/array_spec.rb +9 -0
  34. data/spec/unit/virtus/attributes/attribute_spec.rb +13 -0
  35. data/spec/unit/virtus/attributes/boolean_spec.rb +97 -0
  36. data/spec/unit/virtus/attributes/date_spec.rb +52 -0
  37. data/spec/unit/virtus/attributes/date_time_spec.rb +65 -0
  38. data/spec/unit/virtus/attributes/decimal_spec.rb +98 -0
  39. data/spec/unit/virtus/attributes/float_spec.rb +98 -0
  40. data/spec/unit/virtus/attributes/hash_spec.rb +9 -0
  41. data/spec/unit/virtus/attributes/integer_spec.rb +98 -0
  42. data/spec/unit/virtus/attributes/numeric/class_methods/descendants_spec.rb +15 -0
  43. data/spec/unit/virtus/attributes/object/class_methods/descendants_spec.rb +16 -0
  44. data/spec/unit/virtus/attributes/string_spec.rb +20 -0
  45. data/spec/unit/virtus/attributes/time_spec.rb +71 -0
  46. data/spec/unit/virtus/class_methods/new_spec.rb +41 -0
  47. data/spec/unit/virtus/determine_type_spec.rb +20 -0
  48. data/spec/unit/virtus/instance_methods/attribute_get_spec.rb +22 -0
  49. data/spec/unit/virtus/instance_methods/attribute_set_spec.rb +30 -0
  50. data/spec/unit/virtus/instance_methods/attributes_spec.rb +37 -0
  51. data/virtus.gemspec +95 -0
  52. metadata +131 -0
@@ -0,0 +1,15 @@
1
+ module Virtus
2
+ module Support
3
+ module Chainable
4
+ MODULES = {}
5
+
6
+ def chainable(key, &block)
7
+ begin
8
+ mod = (MODULES[key] ||= Module.new)
9
+ include mod
10
+ mod
11
+ end.module_eval(&block)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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
@@ -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