virtus 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +9 -2
- data/.yardopts +1 -0
- data/History.md +51 -0
- data/{README.markdown → README.md} +63 -7
- data/TODO +2 -4
- data/VERSION +1 -1
- data/config/flay.yml +2 -2
- data/config/flog.yml +1 -1
- data/config/roodi.yml +5 -6
- data/config/site.reek +3 -3
- data/examples/custom_coercion_spec.rb +50 -0
- data/examples/default_values_spec.rb +21 -0
- data/lib/virtus.rb +21 -6
- data/lib/virtus/attribute.rb +113 -253
- data/lib/virtus/attribute/array.rb +6 -3
- data/lib/virtus/attribute/boolean.rb +9 -28
- data/lib/virtus/attribute/date.rb +9 -12
- data/lib/virtus/attribute/date_time.rb +10 -12
- data/lib/virtus/attribute/decimal.rb +4 -11
- data/lib/virtus/attribute/float.rb +4 -11
- data/lib/virtus/attribute/hash.rb +5 -3
- data/lib/virtus/attribute/integer.rb +4 -11
- data/lib/virtus/attribute/numeric.rb +1 -0
- data/lib/virtus/attribute/object.rb +1 -0
- data/lib/virtus/attribute/string.rb +4 -11
- data/lib/virtus/attribute/time.rb +9 -16
- data/lib/virtus/class_methods.rb +42 -7
- data/lib/virtus/coercion.rb +32 -0
- data/lib/virtus/coercion/date.rb +26 -0
- data/lib/virtus/coercion/date_time.rb +26 -0
- data/lib/virtus/coercion/decimal.rb +40 -0
- data/lib/virtus/coercion/false_class.rb +24 -0
- data/lib/virtus/coercion/float.rb +24 -0
- data/lib/virtus/coercion/hash.rb +82 -0
- data/lib/virtus/coercion/integer.rb +60 -0
- data/lib/virtus/coercion/numeric.rb +66 -0
- data/lib/virtus/coercion/object.rb +25 -0
- data/lib/virtus/coercion/string.rb +155 -0
- data/lib/virtus/coercion/symbol.rb +24 -0
- data/lib/virtus/coercion/time.rb +26 -0
- data/lib/virtus/coercion/time_coercions.rb +85 -0
- data/lib/virtus/coercion/true_class.rb +24 -0
- data/lib/virtus/instance_methods.rb +7 -0
- data/lib/virtus/support/descendants_tracker.rb +1 -1
- data/lib/virtus/support/options.rb +114 -0
- data/lib/virtus/support/type_lookup.rb +95 -0
- data/spec/integration/virtus/attributes/attribute/{typecast_spec.rb → set_spec.rb} +7 -7
- data/spec/unit/shared/attribute.rb +3 -3
- data/spec/unit/shared/attribute/accept_options.rb +0 -18
- data/spec/unit/shared/attribute/accepted_options.rb +0 -6
- data/spec/unit/shared/attribute/get.rb +32 -17
- data/spec/unit/shared/attribute/inspect.rb +7 -0
- data/spec/unit/shared/attribute/primitive.rb +15 -0
- data/spec/unit/shared/attribute/set.rb +16 -21
- data/spec/unit/virtus/attribute/array_spec.rb +18 -3
- data/spec/unit/virtus/attribute/boolean_spec.rb +8 -6
- data/spec/unit/virtus/attribute/date_spec.rb +8 -6
- data/spec/unit/virtus/attribute/date_time_spec.rb +8 -6
- data/spec/unit/virtus/attribute/decimal_spec.rb +18 -6
- data/spec/unit/virtus/attribute/float_spec.rb +19 -7
- data/spec/unit/virtus/attribute/hash_spec.rb +5 -3
- data/spec/unit/virtus/attribute/integer_spec.rb +10 -8
- data/spec/unit/virtus/attribute/string_spec.rb +10 -8
- data/spec/unit/virtus/attribute/time_spec.rb +8 -6
- data/spec/unit/virtus/class_methods/attributes_spec.rb +11 -0
- data/spec/unit/virtus/coercion/class_name_reference_spec.rb +17 -0
- data/spec/unit/virtus/coercion/date/class_methods/to_datetime_spec.rb +30 -0
- data/spec/unit/virtus/coercion/date/class_methods/to_string_spec.rb +12 -0
- data/spec/unit/virtus/coercion/date/class_methods/to_time_spec.rb +12 -0
- data/spec/unit/virtus/coercion/date_time/class_methods/to_date_spec.rb +30 -0
- data/spec/unit/virtus/coercion/date_time/class_methods/to_string_spec.rb +12 -0
- data/spec/unit/virtus/coercion/date_time/class_methods/to_time_spec.rb +30 -0
- data/spec/unit/virtus/coercion/decimal/class_methods/to_float_spec.rb +12 -0
- data/spec/unit/virtus/coercion/decimal/class_methods/to_integer_spec.rb +12 -0
- data/spec/unit/virtus/coercion/decimal/class_methods/to_string_spec.rb +12 -0
- data/spec/unit/virtus/coercion/false_class/class_methods/to_string_spec.rb +12 -0
- data/spec/unit/virtus/coercion/float/class_methods/to_decimal_spec.rb +12 -0
- data/spec/unit/virtus/coercion/float/class_methods/to_integer_spec.rb +12 -0
- data/spec/unit/virtus/coercion/float/class_methods/to_string_spec.rb +12 -0
- data/spec/unit/virtus/coercion/hash/class_methods/to_array_spec.rb +12 -0
- data/spec/unit/virtus/coercion/hash/class_methods/to_date_spec.rb +31 -0
- data/spec/unit/virtus/coercion/hash/class_methods/to_datetime_spec.rb +31 -0
- data/spec/unit/virtus/coercion/hash/class_methods/to_time_spec.rb +31 -0
- data/spec/unit/virtus/coercion/integer/class_methods/to_boolean_spec.rb +25 -0
- data/spec/unit/virtus/coercion/integer/class_methods/to_decimal_spec.rb +12 -0
- data/spec/unit/virtus/coercion/integer/class_methods/to_float_spec.rb +12 -0
- data/spec/unit/virtus/coercion/integer/class_methods/to_string_spec.rb +12 -0
- data/spec/unit/virtus/coercion/object/class_methods/method_missing_spec.rb +33 -0
- data/spec/unit/virtus/coercion/string/class_methods/to_boolean_spec.rb +29 -0
- data/spec/unit/virtus/coercion/string/class_methods/to_date_spec.rb +23 -0
- data/spec/unit/virtus/coercion/string/class_methods/to_datetime_spec.rb +50 -0
- data/spec/unit/virtus/coercion/string/class_methods/to_decimal_spec.rb +23 -0
- data/spec/unit/virtus/coercion/string/class_methods/to_float_spec.rb +21 -0
- data/spec/unit/virtus/coercion/string/class_methods/to_integer_spec.rb +21 -0
- data/spec/unit/virtus/coercion/string/class_methods/to_time_spec.rb +50 -0
- data/spec/unit/virtus/coercion/symbol/class_methods/to_string_spec.rb +12 -0
- data/spec/unit/virtus/coercion/true_class/class_methods/to_string_spec.rb +12 -0
- data/spec/unit/virtus/instance_methods/attributes_spec.rb +7 -0
- data/spec/unit/virtus/options/accept_options_spec.rb +38 -0
- data/spec/unit/virtus/options/accepted_options_spec.rb +21 -0
- data/spec/unit/virtus/options/options_spec.rb +11 -0
- data/spec/unit/virtus/type_lookup/determine_type_spec.rb +68 -0
- data/spec/unit/virtus/type_lookup/primitive_spec.rb +9 -0
- data/virtus.gemspec +70 -17
- metadata +78 -27
- data/History.txt +0 -38
- data/lib/virtus/typecast/boolean.rb +0 -29
- data/lib/virtus/typecast/numeric.rb +0 -87
- data/lib/virtus/typecast/string.rb +0 -24
- data/lib/virtus/typecast/time.rb +0 -192
- data/spec/unit/shared/attribute/complex.rb +0 -15
- data/spec/unit/shared/attribute/options.rb +0 -7
- data/spec/unit/virtus/attribute/attribute_spec.rb +0 -12
data/.travis.yml
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
-
|
1
|
+
bundler_args: --without guard metrics
|
2
|
+
script: "bundle exec rake spec"
|
2
3
|
rvm:
|
3
4
|
- 1.8.7
|
4
5
|
- 1.9.2
|
5
|
-
- jruby
|
6
6
|
- rbx
|
7
|
+
- rbx-2.0
|
8
|
+
- ree
|
9
|
+
- jruby
|
10
|
+
- ruby-head
|
11
|
+
notifications:
|
12
|
+
email:
|
13
|
+
- piotr.solnica@gmail.com
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- README.md History.md LICENSE
|
data/History.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# v0.0.6 to-be-released
|
2
|
+
|
3
|
+
* [BREAKING CHANGE] Moved Virtus.determine_type to a shared module Virtus::TypeLookup (dkubb)
|
4
|
+
* [BREAKING CHANGE] Attribute#typecast_to_primitive has been replaced by Attribute#coerce (solnic)
|
5
|
+
* [BREAKING CHANGE] Attribute#typecast logic was moved to Attribute#set which is now a public method (solnic)
|
6
|
+
* [feature] Added support for default values (solnic)
|
7
|
+
* [general] Added custom inspect for Attribute classes (solnic)
|
8
|
+
* [general] Added backports as a development dependency (dkubb)
|
9
|
+
* [changed] Options API has been extracted from Attribute to a support module Virtus::Options (solnic)
|
10
|
+
* [changed] Typecast classes have been replace by a new hierarchy of Coercion classes like Coercion::String, Coercion::Integer etc. (solnic)
|
11
|
+
* [changed] Attribute#get, #get!, #set, #set! & #coerce are now part of the public API (solnic)
|
12
|
+
|
13
|
+
[Compare v0.0.5..master](https://github.com/solnic/virtus/compare/v0.0.5...master)
|
14
|
+
|
15
|
+
# v0.0.5 2011-07-10
|
16
|
+
|
17
|
+
* [bugfix] Fixed DescendantsTracker + ActiveSupport collision (dkubb)
|
18
|
+
|
19
|
+
[Compare v0.0.4..v0.0.5](https://github.com/solnic/virtus/compare/v0.0.4...v0.0.5)
|
20
|
+
|
21
|
+
# v0.0.4 2011-07-08
|
22
|
+
|
23
|
+
* [BREAKING CHANGE] attributes hash has been replaced by a specialized class AttributeSet (dkubb)
|
24
|
+
* [BREAKING CHANGE] Virtus::ClassMethods.attribute returns self instead of a created attribute (solnic)
|
25
|
+
* [changed] descendants tracking has been extracted into DescendantsTracker module (dkubb)
|
26
|
+
* [changed] Instance #primitive? method has been replaced by class utility method Virtus::Attribute.primitive? (solnic)
|
27
|
+
* [changed] Virtus::Attribute::String#typecast_to_primitive delegates to Virtus::Typecast::String.call (solnic)
|
28
|
+
|
29
|
+
[Compare v0.0.3..v0.0.4](https://github.com/solnic/virtus/compare/v0.0.3...v0.0.4)
|
30
|
+
|
31
|
+
# v0.0.3 2011-06-09
|
32
|
+
|
33
|
+
* [BREAKING CHANGE] Attribute classes were moved to Virtus::Attribute namespace (solnic)
|
34
|
+
* [BREAKING CHANGE] Attribute instance no longer holds the reference to a model (solnic)
|
35
|
+
* [BREAKING CHANGE] #typecast no longer receives an instance of a model (override #set which calls #typecast if you need that) (solnic)
|
36
|
+
* [changed] Adding reader/writer methods was moved from the attribute constructor to Virtus::ClassMethods.attribute (solnic)
|
37
|
+
* [changed] Typecast logic has been moved into separate classes (see Virtus::Typecast) (solnic)
|
38
|
+
* [added] Virtus::Attribute::DateTime#typecast supports objects which implement #to_datetime (solnic)
|
39
|
+
* [general] Internals have been cleaned up, simplified and properly documented (solnic)
|
40
|
+
|
41
|
+
[Compare v0.0.2..v0.0.3](https://github.com/solnic/virtus/compare/v0.0.2...v0.0.3)
|
42
|
+
|
43
|
+
# v0.0.2 2011-06-06
|
44
|
+
|
45
|
+
* [bugfix] Fixed #typecast in custom attribute classes (solnic)
|
46
|
+
|
47
|
+
[Compare v0.0.1..v0.0.2](https://github.com/solnic/virtus/compare/v0.0.1...v0.0.2)
|
48
|
+
|
49
|
+
# v0.0.1 2011-06-04
|
50
|
+
|
51
|
+
First public release :)
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
This is a partial extraction of the DataMapper [Property
|
4
4
|
API](http://rubydoc.info/github/datamapper/dm-core/master/DataMapper/Property)
|
5
|
-
with various modifications.
|
6
|
-
attributes on a model
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
with various modifications and improvements. The goal is to provide a common API
|
6
|
+
for defining attributes on a model so all ORMs/ODMs could use it instead of
|
7
|
+
reinventing the wheel all over again. It is also suitable for any other
|
8
|
+
usecase where you need to extend your ruby objects with attributes that require
|
9
|
+
data type coercions.
|
10
10
|
|
11
11
|
## Installation
|
12
12
|
|
@@ -33,13 +33,63 @@ attributes that require typecasting and/or validations.
|
|
33
33
|
# hash of attributes
|
34
34
|
user.attributes # => { :name => "Piotr" }
|
35
35
|
|
36
|
-
# automatic
|
36
|
+
# automatic coercion
|
37
37
|
user.age = '28'
|
38
38
|
user.age # => 28
|
39
39
|
|
40
40
|
user.birthday = 'November 18th, 1983'
|
41
41
|
user.birthday # => #<DateTime: 1983-11-18T00:00:00+00:00 (4891313/2,0/1,2299161)>
|
42
42
|
|
43
|
+
## Default values
|
44
|
+
|
45
|
+
require 'virtus'
|
46
|
+
|
47
|
+
class Page
|
48
|
+
include Virtus
|
49
|
+
|
50
|
+
attribute :title, String
|
51
|
+
attribute :slug, String, :default => lambda { |post, attribute| post.title.downcase.gsub(' ', '-') }
|
52
|
+
attribute :view_count, Integer, :default => 0
|
53
|
+
end
|
54
|
+
|
55
|
+
page = Page.new(:title => 'Virtus Is Awesome')
|
56
|
+
page.slug # => 'virtus-is-awesome'
|
57
|
+
page.view_count # => 0
|
58
|
+
|
59
|
+
## Coercions
|
60
|
+
|
61
|
+
Virtus comes with a builtin coercion library. It's super easy to add your own
|
62
|
+
coercion classes. Take a look:
|
63
|
+
|
64
|
+
require 'virtus'
|
65
|
+
require 'digest/md5'
|
66
|
+
|
67
|
+
class MD5 < Virtus::Attribute::Object
|
68
|
+
primitive String
|
69
|
+
coercion_method :to_md5
|
70
|
+
end
|
71
|
+
|
72
|
+
module Virtus
|
73
|
+
class Coercion
|
74
|
+
class String < Virtus::Coercion::Object
|
75
|
+
def self.to_md5(value)
|
76
|
+
Digest::MD5.hexdigest(value)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class User
|
83
|
+
include Virtus
|
84
|
+
|
85
|
+
attribute :name, String
|
86
|
+
attribute :password, MD5
|
87
|
+
end
|
88
|
+
|
89
|
+
user = User.new(:name => 'Piotr', :password => 'foobar')
|
90
|
+
user.name # => 'Piotr'
|
91
|
+
user.password # => '3858f62230ac3c915f300c664312c63f'
|
92
|
+
|
43
93
|
## Custom Attributes
|
44
94
|
|
45
95
|
require 'virtus'
|
@@ -50,7 +100,7 @@ attributes that require typecasting and/or validations.
|
|
50
100
|
class JSON < Virtus::Attribute::Object
|
51
101
|
primitive Hash
|
52
102
|
|
53
|
-
def
|
103
|
+
def coerce(value)
|
54
104
|
::JSON.parse(value)
|
55
105
|
end
|
56
106
|
end
|
@@ -68,6 +118,12 @@ attributes that require typecasting and/or validations.
|
|
68
118
|
user.info = '{"email":"john@domain.com"}'
|
69
119
|
user.info # => {"email"=>"john@domain.com"}
|
70
120
|
|
121
|
+
## Contributors
|
122
|
+
|
123
|
+
* Dan Kubb ([dkubb](https://github.com/dkubb))
|
124
|
+
* Chris Corbyn ([d11wtq](https://github.com/d11wtq))
|
125
|
+
* Emmanuel Gomez ([emmanuel](https://github.com/emmanuel))
|
126
|
+
|
71
127
|
## Note on Patches/Pull Requests
|
72
128
|
|
73
129
|
* Fork the project.
|
data/TODO
CHANGED
@@ -1,4 +1,2 @@
|
|
1
|
-
*
|
2
|
-
* Add
|
3
|
-
* Add support for default values
|
4
|
-
* Add better inspection of attribute instances
|
1
|
+
* Make #to_time #to_date and #to_datetime work on Ruby 1.8.7 instead of typecasting to string and parsing the value
|
2
|
+
* Add support for defining attributes on Modules
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.6
|
data/config/flay.yml
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
---
|
2
|
-
threshold:
|
3
|
-
total_score:
|
2
|
+
threshold: 20
|
3
|
+
total_score: 268
|
data/config/flog.yml
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
---
|
2
|
-
threshold:
|
2
|
+
threshold: 15.9
|
data/config/roodi.yml
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
---
|
2
|
-
AbcMetricMethodCheck: { score:
|
2
|
+
AbcMetricMethodCheck: { score: 8.1 }
|
3
3
|
AssignmentInConditionalCheck: { }
|
4
4
|
CaseMissingElseCheck: { }
|
5
|
-
ClassLineCountCheck: { line_count:
|
5
|
+
ClassLineCountCheck: { line_count: 309 }
|
6
6
|
ClassNameCheck: { pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/ }
|
7
7
|
ClassVariableCheck: { }
|
8
8
|
CyclomaticComplexityBlockCheck: { complexity: 2 }
|
9
|
-
CyclomaticComplexityMethodCheck: { complexity:
|
9
|
+
CyclomaticComplexityMethodCheck: { complexity: 4 }
|
10
10
|
EmptyRescueBodyCheck: { }
|
11
11
|
ForLoopCheck: { }
|
12
|
-
|
13
|
-
MethodLineCountCheck: { line_count: 19 }
|
12
|
+
MethodLineCountCheck: { line_count: 9 }
|
14
13
|
MethodNameCheck: { pattern: !ruby/regexp /\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|<<|[+*&|-])\z/ }
|
15
|
-
ModuleLineCountCheck: { line_count:
|
14
|
+
ModuleLineCountCheck: { line_count: 315 }
|
16
15
|
ModuleNameCheck: { pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/ }
|
17
16
|
# TODO: decrease parameter_count to 2 or less
|
18
17
|
ParameterNumberCheck: { parameter_count: 3 }
|
data/config/site.reek
CHANGED
@@ -11,7 +11,7 @@ LargeClass:
|
|
11
11
|
max_methods: 11
|
12
12
|
exclude: []
|
13
13
|
enabled: true
|
14
|
-
max_instance_variables:
|
14
|
+
max_instance_variables: 7
|
15
15
|
UncommunicativeMethodName:
|
16
16
|
accept: []
|
17
17
|
exclude: []
|
@@ -21,12 +21,12 @@ UncommunicativeMethodName:
|
|
21
21
|
- !ruby/regexp /[0-9]$/
|
22
22
|
- !ruby/regexp /[A-Z]/
|
23
23
|
LongParameterList:
|
24
|
-
max_params: 3
|
24
|
+
max_params: 3
|
25
25
|
exclude: []
|
26
26
|
enabled: true
|
27
27
|
overrides: {}
|
28
28
|
FeatureEnvy:
|
29
|
-
exclude: []
|
29
|
+
exclude: [Virtus::Coercion::TimeCoercions#to_string, Virtus::Coercion::TimeCoercions#coerce_with_method]
|
30
30
|
enabled: true
|
31
31
|
ClassVariable:
|
32
32
|
exclude: []
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
class MD5 < Virtus::Attribute::Object
|
4
|
+
primitive String
|
5
|
+
coercion_method :to_md5
|
6
|
+
end
|
7
|
+
|
8
|
+
module Virtus
|
9
|
+
class Coercion
|
10
|
+
class String < Virtus::Coercion::Object
|
11
|
+
def self.to_md5(value)
|
12
|
+
Digest::MD5.hexdigest(value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class User
|
19
|
+
include Virtus
|
20
|
+
|
21
|
+
attribute :name, String
|
22
|
+
attribute :password, MD5
|
23
|
+
end
|
24
|
+
|
25
|
+
describe User do
|
26
|
+
it { should respond_to(:name) }
|
27
|
+
it { should respond_to(:name=) }
|
28
|
+
it { should respond_to(:password) }
|
29
|
+
it { should respond_to(:password=) }
|
30
|
+
|
31
|
+
describe '#name=' do
|
32
|
+
let(:value) { 'Piotr' }
|
33
|
+
|
34
|
+
before do
|
35
|
+
subject.name = value
|
36
|
+
end
|
37
|
+
|
38
|
+
its(:name) { should == value }
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#password=' do
|
42
|
+
let(:value) { 'foobar' }
|
43
|
+
|
44
|
+
before do
|
45
|
+
subject.password = value
|
46
|
+
end
|
47
|
+
|
48
|
+
its(:password) { should == Virtus::Coercion::String.to_md5(value) }
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
class Page
|
4
|
+
include Virtus
|
5
|
+
|
6
|
+
attribute :title, String
|
7
|
+
attribute :slug, String, :default => lambda { |post, attribute| post.title.downcase.gsub(' ', '-') }
|
8
|
+
attribute :view_count, Integer, :default => 0
|
9
|
+
end
|
10
|
+
|
11
|
+
describe Page do
|
12
|
+
describe '#slug' do
|
13
|
+
before { subject.title = 'Virtus Is Awesome' }
|
14
|
+
|
15
|
+
its(:slug) { should eql('virtus-is-awesome') }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#views_count' do
|
19
|
+
its(:view_count) { should eql(0) }
|
20
|
+
end
|
21
|
+
end
|
data/lib/virtus.rb
CHANGED
@@ -13,28 +13,43 @@ module Virtus
|
|
13
13
|
#
|
14
14
|
# @param [Class] descendant
|
15
15
|
#
|
16
|
-
# @return [
|
16
|
+
# @return [undefined]
|
17
17
|
#
|
18
18
|
# @api private
|
19
19
|
def self.included(descendant)
|
20
|
-
|
20
|
+
super
|
21
21
|
descendant.extend(ClassMethods)
|
22
22
|
descendant.send(:include, InstanceMethods)
|
23
23
|
end
|
24
24
|
|
25
|
+
private_class_method :included
|
26
|
+
|
25
27
|
end # module Virtus
|
26
28
|
|
27
29
|
require 'virtus/support/descendants_tracker'
|
30
|
+
require 'virtus/support/type_lookup'
|
31
|
+
require 'virtus/support/options'
|
28
32
|
|
29
33
|
require 'virtus/class_methods'
|
30
34
|
require 'virtus/instance_methods'
|
31
35
|
|
32
36
|
require 'virtus/attribute_set'
|
33
37
|
|
34
|
-
require 'virtus/
|
35
|
-
require 'virtus/
|
36
|
-
require 'virtus/
|
37
|
-
require 'virtus/
|
38
|
+
require 'virtus/coercion'
|
39
|
+
require 'virtus/coercion/object'
|
40
|
+
require 'virtus/coercion/numeric'
|
41
|
+
require 'virtus/coercion/integer'
|
42
|
+
require 'virtus/coercion/float'
|
43
|
+
require 'virtus/coercion/decimal'
|
44
|
+
require 'virtus/coercion/false_class'
|
45
|
+
require 'virtus/coercion/true_class'
|
46
|
+
require 'virtus/coercion/hash'
|
47
|
+
require 'virtus/coercion/time_coercions'
|
48
|
+
require 'virtus/coercion/date'
|
49
|
+
require 'virtus/coercion/date_time'
|
50
|
+
require 'virtus/coercion/time'
|
51
|
+
require 'virtus/coercion/string'
|
52
|
+
require 'virtus/coercion/symbol'
|
38
53
|
|
39
54
|
require 'virtus/attribute'
|
40
55
|
require 'virtus/attribute/object'
|
data/lib/virtus/attribute.rb
CHANGED
@@ -5,185 +5,8 @@ module Virtus
|
|
5
5
|
# @abstract
|
6
6
|
class Attribute
|
7
7
|
extend DescendantsTracker
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
# @example
|
12
|
-
# Attribute.determine_type('String') # => Virtus::Attribute::String
|
13
|
-
#
|
14
|
-
# @param [Class, #to_s] class_or_name
|
15
|
-
# name of a class or a class itself
|
16
|
-
#
|
17
|
-
# @return [Class]
|
18
|
-
# one of the Virtus::Attribute::Object descendants
|
19
|
-
#
|
20
|
-
# @return [nil]
|
21
|
-
# nil if the type cannot be determined by the class_or_name
|
22
|
-
#
|
23
|
-
# @api public
|
24
|
-
def self.determine_type(class_or_name)
|
25
|
-
# first match on the Attribute singleton class first, then match
|
26
|
-
# any class, finally fallback to matching on the string
|
27
|
-
case class_or_name
|
28
|
-
when Attribute::Object.singleton_class then determine_type_from_attribute(class_or_name)
|
29
|
-
when Class then determine_type_from_primitive(class_or_name)
|
30
|
-
else
|
31
|
-
determine_type_from_string(class_or_name.to_s)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Return the Attribute class given an Attribute descendant
|
36
|
-
#
|
37
|
-
# @param [Class<Attribute>] attribute
|
38
|
-
#
|
39
|
-
# @return [Class<Attribute>]
|
40
|
-
#
|
41
|
-
# @api private
|
42
|
-
def self.determine_type_from_attribute(attribute)
|
43
|
-
attribute
|
44
|
-
end
|
45
|
-
|
46
|
-
private_class_method :determine_type_from_attribute
|
47
|
-
|
48
|
-
# Return the Attribute class given a primitive
|
49
|
-
#
|
50
|
-
# @param [Class] primitive
|
51
|
-
#
|
52
|
-
# @return [Class<Attribute>]
|
53
|
-
#
|
54
|
-
# @return [nil]
|
55
|
-
# nil if the type cannot be determined by the primitive
|
56
|
-
#
|
57
|
-
# @api private
|
58
|
-
def self.determine_type_from_primitive(primitive)
|
59
|
-
descendants.detect { |descendant| primitive <= descendant.primitive }
|
60
|
-
end
|
61
|
-
|
62
|
-
private_class_method :determine_type_from_primitive
|
63
|
-
|
64
|
-
# Return the Attribute class given a string
|
65
|
-
#
|
66
|
-
# @param [String] string
|
67
|
-
#
|
68
|
-
# @return [Class<Attribute>]
|
69
|
-
#
|
70
|
-
# @return [nil]
|
71
|
-
# nil if the type cannot be determined by the string
|
72
|
-
#
|
73
|
-
# @api private
|
74
|
-
def self.determine_type_from_string(string)
|
75
|
-
const_get(string) if const_defined?(string)
|
76
|
-
end
|
77
|
-
|
78
|
-
private_class_method :determine_type_from_string
|
79
|
-
|
80
|
-
# Returns default options hash for a given attribute class
|
81
|
-
#
|
82
|
-
# @example
|
83
|
-
# Virtus::Attribute::String.options
|
84
|
-
# # => {:primitive => String, :complex => false}
|
85
|
-
#
|
86
|
-
# @return [Hash]
|
87
|
-
# a hash of default option values
|
88
|
-
#
|
89
|
-
# @api public
|
90
|
-
def self.options
|
91
|
-
options = {}
|
92
|
-
accepted_options.each do |option_name|
|
93
|
-
option_value = send(option_name)
|
94
|
-
options[option_name] = option_value unless option_value.nil?
|
95
|
-
end
|
96
|
-
options
|
97
|
-
end
|
98
|
-
|
99
|
-
# Returns an array of valid options
|
100
|
-
#
|
101
|
-
# @example
|
102
|
-
# Virtus::Attribute::String.accepted_options
|
103
|
-
# # => [:primitive, :complex, :accessor, :reader, :writer]
|
104
|
-
#
|
105
|
-
# @return [Array]
|
106
|
-
# the array of valid option names
|
107
|
-
#
|
108
|
-
# @api public
|
109
|
-
def self.accepted_options
|
110
|
-
@accepted_options ||= []
|
111
|
-
end
|
112
|
-
|
113
|
-
# Defines which options are valid for a given attribute class
|
114
|
-
#
|
115
|
-
# @example
|
116
|
-
# class MyAttribute < Virtus::Attribute::Object
|
117
|
-
# accept_options :foo, :bar
|
118
|
-
# end
|
119
|
-
#
|
120
|
-
# @return [Array]
|
121
|
-
# All accepted options
|
122
|
-
#
|
123
|
-
# @api public
|
124
|
-
def self.accept_options(*new_options)
|
125
|
-
add_accepted_options(new_options)
|
126
|
-
new_options.each { |option| add_option_method(option) }
|
127
|
-
descendants.each { |descendant| descendant.add_accepted_options(new_options) }
|
128
|
-
self
|
129
|
-
end
|
130
|
-
|
131
|
-
# Adds a reader/writer method for the give option name
|
132
|
-
#
|
133
|
-
# @return [undefined]
|
134
|
-
#
|
135
|
-
# @api private
|
136
|
-
def self.add_option_method(option)
|
137
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
138
|
-
def self.#{option}(value = Undefined) # def self.primitive(value = Undefined)
|
139
|
-
return @#{option} if value.equal?(Undefined) # return @primitive if value.equal?(Undefined)
|
140
|
-
@#{option} = value # @primitive = value
|
141
|
-
end # end
|
142
|
-
RUBY
|
143
|
-
end
|
144
|
-
|
145
|
-
private_class_method :add_option_method
|
146
|
-
|
147
|
-
# Sets default options
|
148
|
-
#
|
149
|
-
# @param [#to_hash] new_options
|
150
|
-
# options to be set
|
151
|
-
#
|
152
|
-
# @return [self]
|
153
|
-
#
|
154
|
-
# @api private
|
155
|
-
def self.set_options(new_options)
|
156
|
-
new_options.to_hash.each do |option_name, option_value|
|
157
|
-
send(option_name, option_value)
|
158
|
-
end
|
159
|
-
self
|
160
|
-
end
|
161
|
-
|
162
|
-
# Adds new options that an attribute class can accept
|
163
|
-
#
|
164
|
-
# @param [#to_ary] new_options
|
165
|
-
# new options to be added
|
166
|
-
#
|
167
|
-
# @return [self]
|
168
|
-
#
|
169
|
-
# @api private
|
170
|
-
def self.add_accepted_options(new_options)
|
171
|
-
accepted_options.concat(new_options.to_ary)
|
172
|
-
self
|
173
|
-
end
|
174
|
-
|
175
|
-
# Adds descendant to descendants array and inherits default options
|
176
|
-
#
|
177
|
-
# @param [Class] descendant
|
178
|
-
#
|
179
|
-
# @return [self]
|
180
|
-
#
|
181
|
-
# @api private
|
182
|
-
def self.inherited(descendant)
|
183
|
-
super
|
184
|
-
descendant.add_accepted_options(accepted_options).set_options(options)
|
185
|
-
self
|
186
|
-
end
|
8
|
+
extend TypeLookup
|
9
|
+
extend Options
|
187
10
|
|
188
11
|
# Returns if the given value's class is an attribute's primitive
|
189
12
|
#
|
@@ -235,9 +58,24 @@ module Virtus
|
|
235
58
|
# @api private
|
236
59
|
attr_reader :writer_visibility
|
237
60
|
|
61
|
+
# Returns method name that should be used for coerceing
|
62
|
+
#
|
63
|
+
# @return [Symbol]
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
attr_reader :coercion_method
|
67
|
+
|
68
|
+
# Returns default value
|
69
|
+
#
|
70
|
+
# @return [Object]
|
71
|
+
#
|
72
|
+
# @api private
|
73
|
+
attr_reader :default
|
74
|
+
|
238
75
|
DEFAULT_ACCESSOR = :public
|
239
76
|
|
240
|
-
OPTIONS = [ :primitive, :
|
77
|
+
OPTIONS = [ :primitive, :accessor, :reader,
|
78
|
+
:writer, :coercion_method, :default ].freeze
|
241
79
|
|
242
80
|
accept_options *OPTIONS
|
243
81
|
|
@@ -257,140 +95,146 @@ module Virtus
|
|
257
95
|
@options = self.class.options.merge(options.to_hash).freeze
|
258
96
|
|
259
97
|
@instance_variable_name = "@#{@name}".freeze
|
260
|
-
@
|
98
|
+
@coercion_method = @options.fetch(:coercion_method)
|
261
99
|
|
100
|
+
set_default
|
262
101
|
set_visibility
|
263
102
|
end
|
264
103
|
|
265
|
-
# Returns
|
104
|
+
# Returns a concise string representation of the attribute instance
|
266
105
|
#
|
267
106
|
# @example
|
268
|
-
# Virtus::Attribute::String.
|
269
|
-
# Virtus::Attribute::
|
107
|
+
# attribute = Virtus::Attribute::String.new(:name)
|
108
|
+
# attribute.inspect # => #<Virtus::Attribute::String @name=:name>
|
270
109
|
#
|
271
|
-
# @return [
|
110
|
+
# @return [String]
|
272
111
|
#
|
273
112
|
# @api public
|
274
|
-
def
|
275
|
-
@
|
276
|
-
end
|
277
|
-
|
278
|
-
# Converts the given value to the primitive type
|
279
|
-
#
|
280
|
-
# @param [Object] value
|
281
|
-
# the value
|
282
|
-
#
|
283
|
-
# @return [Object]
|
284
|
-
# nil, original value or value converted to the primitive type
|
285
|
-
#
|
286
|
-
# @api private
|
287
|
-
def typecast(value)
|
288
|
-
if value.nil? || self.class.primitive?(value)
|
289
|
-
value
|
290
|
-
else
|
291
|
-
typecast_to_primitive(value)
|
292
|
-
end
|
113
|
+
def inspect
|
114
|
+
"#<#{self.class.name} @name=#{name.inspect}>"
|
293
115
|
end
|
294
116
|
|
295
|
-
#
|
117
|
+
# Returns value of an attribute for the given instance
|
296
118
|
#
|
297
|
-
#
|
119
|
+
# Sets the default value if an ivar is not set and default
|
120
|
+
# value is configured
|
298
121
|
#
|
299
|
-
# @
|
300
|
-
|
301
|
-
value
|
302
|
-
end
|
303
|
-
|
304
|
-
# Returns value of an attribute for the given instance
|
122
|
+
# @example
|
123
|
+
# attribute.get(instance) # => value
|
305
124
|
#
|
306
125
|
# @return [Object]
|
307
126
|
# value of an attribute
|
308
127
|
#
|
309
|
-
# @api
|
128
|
+
# @api public
|
310
129
|
def get(instance)
|
311
|
-
|
130
|
+
if instance.instance_variable_defined?(instance_variable_name)
|
131
|
+
get!(instance)
|
132
|
+
else
|
133
|
+
set_default_value(instance)
|
134
|
+
end
|
312
135
|
end
|
313
136
|
|
314
137
|
# Returns the instance variable of the attribute
|
315
138
|
#
|
139
|
+
# @example
|
140
|
+
# attribute.get!(instance) # => value
|
141
|
+
#
|
316
142
|
# @return [Object]
|
317
143
|
# value of an attribute
|
318
144
|
#
|
319
|
-
# @api
|
145
|
+
# @api public
|
320
146
|
def get!(instance)
|
321
147
|
instance.instance_variable_get(instance_variable_name)
|
322
148
|
end
|
323
149
|
|
324
150
|
# Sets the value on the instance
|
325
151
|
#
|
326
|
-
# @
|
327
|
-
# value
|
152
|
+
# @example
|
153
|
+
# attribute.set(instance, value) # => value
|
328
154
|
#
|
329
|
-
# @
|
155
|
+
# @return [self]
|
156
|
+
#
|
157
|
+
# @api public
|
330
158
|
def set(instance, value)
|
331
|
-
set!(instance,
|
159
|
+
set!(instance, coerce(value))
|
332
160
|
end
|
333
161
|
|
334
162
|
# Sets instance variable of the attribute
|
335
163
|
#
|
336
|
-
# @
|
337
|
-
# value
|
164
|
+
# @example
|
165
|
+
# attribute.set!(instance, value) # => value
|
338
166
|
#
|
339
|
-
# @
|
167
|
+
# @return [self]
|
168
|
+
#
|
169
|
+
# @api public
|
340
170
|
def set!(instance, value)
|
341
171
|
instance.instance_variable_set(instance_variable_name, value)
|
342
172
|
end
|
343
173
|
|
174
|
+
# Converts the given value to the primitive type
|
175
|
+
#
|
176
|
+
# @example
|
177
|
+
# attribute.coerce(value) # => primitive_value
|
178
|
+
#
|
179
|
+
# @param [Object] value
|
180
|
+
# the value
|
181
|
+
#
|
182
|
+
# @return [Object]
|
183
|
+
# nil, original value or value converted to the primitive type
|
184
|
+
#
|
185
|
+
# @api public
|
186
|
+
def coerce(value)
|
187
|
+
Coercion[value.class].send(coercion_method, value)
|
188
|
+
end
|
189
|
+
|
344
190
|
# Creates an attribute reader method
|
345
191
|
#
|
192
|
+
# @param [Module] mod
|
193
|
+
#
|
346
194
|
# @return [self]
|
347
195
|
#
|
348
196
|
# @api private
|
349
|
-
def
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
def #{method_name} # def name
|
356
|
-
return #{instance_variable_name} if defined?(#{instance_variable_name}) # return @name if defined?(@name)
|
357
|
-
attribute = self.class.attributes[#{method_name.inspect}] # attribute = self.class.attributes[:name]
|
358
|
-
#{instance_variable_name} = attribute ? attribute.get(self) : nil # @name = attribute ? attribute.get(self) : nil
|
359
|
-
end # end
|
360
|
-
end # end
|
361
|
-
include AttributeMethods # include AttributeMethods
|
362
|
-
RUBY
|
363
|
-
|
364
|
-
model.send(reader_visibility, method_name)
|
197
|
+
def define_reader_method(mod)
|
198
|
+
reader_method_name = name
|
199
|
+
attribute = self
|
200
|
+
|
201
|
+
mod.send(:define_method, reader_method_name) { attribute.get(self) }
|
202
|
+
mod.send(reader_visibility, reader_method_name)
|
365
203
|
|
366
204
|
self
|
367
205
|
end
|
368
206
|
|
369
207
|
# Creates an attribute writer method
|
370
208
|
#
|
209
|
+
# @param [Module] mod
|
210
|
+
#
|
371
211
|
# @return [self]
|
372
212
|
#
|
373
213
|
# @api private
|
374
|
-
def
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
379
|
-
module AttributeMethods # module AttributeMethods
|
380
|
-
def #{method_name}(value) # def name=(value)
|
381
|
-
self.class.attributes[#{name.inspect}].set(self, value) # self.class.attributes[:name].set(self, value)
|
382
|
-
end # end
|
383
|
-
end # end
|
384
|
-
include AttributeMethods # include AttributeMethods
|
385
|
-
RUBY
|
214
|
+
def define_writer_method(mod)
|
215
|
+
writer_method_name = "#{name}="
|
216
|
+
attribute = self
|
386
217
|
|
387
|
-
|
218
|
+
mod.send(:define_method, writer_method_name) { |value| attribute.set(self, value) }
|
219
|
+
mod.send(writer_visibility, writer_method_name)
|
388
220
|
|
389
221
|
self
|
390
222
|
end
|
391
223
|
|
392
224
|
private
|
393
225
|
|
226
|
+
# Sets a default value
|
227
|
+
#
|
228
|
+
# @param [Object]
|
229
|
+
#
|
230
|
+
# @return [Object]
|
231
|
+
# default value that was set
|
232
|
+
#
|
233
|
+
# @api private
|
234
|
+
def set_default_value(instance)
|
235
|
+
set!(instance, default.kind_of?(Proc) ? default.call(instance, self) : default)
|
236
|
+
end
|
237
|
+
|
394
238
|
# Sets visibility of reader/write methods based on the options hash
|
395
239
|
#
|
396
240
|
# @return [undefined]
|
@@ -398,8 +242,24 @@ module Virtus
|
|
398
242
|
# @api private
|
399
243
|
def set_visibility
|
400
244
|
default_accessor = @options.fetch(:accessor, self.class::DEFAULT_ACCESSOR)
|
401
|
-
@reader_visibility = @options.fetch(:reader,
|
402
|
-
@writer_visibility = @options.fetch(:writer,
|
245
|
+
@reader_visibility = @options.fetch(:reader, default_accessor)
|
246
|
+
@writer_visibility = @options.fetch(:writer, default_accessor)
|
247
|
+
end
|
248
|
+
|
249
|
+
# Sets default value option
|
250
|
+
#
|
251
|
+
# @return [Object]
|
252
|
+
#
|
253
|
+
# @api private
|
254
|
+
def set_default
|
255
|
+
default = @options[:default]
|
256
|
+
|
257
|
+
@default = case default
|
258
|
+
when Proc, ::NilClass, ::TrueClass, ::FalseClass, ::Numeric, ::Symbol
|
259
|
+
default
|
260
|
+
else
|
261
|
+
default.dup
|
262
|
+
end
|
403
263
|
end
|
404
264
|
|
405
265
|
end # class Attribute
|