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.
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