virtus 0.0.2 → 0.0.3
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 +3 -0
- data/.rvmrc +1 -1
- data/Gemfile +20 -1
- data/History.txt +21 -0
- data/README.markdown +2 -2
- data/Rakefile +1 -2
- data/VERSION +1 -1
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/roodi.yml +18 -0
- data/config/site.reek +91 -0
- data/config/yardstick.yml +2 -0
- data/lib/virtus.rb +51 -45
- data/lib/virtus/attribute.rb +301 -0
- data/lib/virtus/attribute/array.rb +17 -0
- data/lib/virtus/attribute/boolean.rb +60 -0
- data/lib/virtus/attribute/date.rb +35 -0
- data/lib/virtus/attribute/date_time.rb +34 -0
- data/lib/virtus/attribute/decimal.rb +24 -0
- data/lib/virtus/attribute/float.rb +33 -0
- data/lib/virtus/attribute/hash.rb +18 -0
- data/lib/virtus/attribute/integer.rb +30 -0
- data/lib/virtus/{attributes → attribute}/numeric.rb +2 -3
- data/lib/virtus/{attributes → attribute}/object.rb +2 -1
- data/lib/virtus/attribute/string.rb +31 -0
- data/lib/virtus/attribute/time.rb +34 -0
- data/lib/virtus/class_methods.rb +25 -8
- data/lib/virtus/instance_methods.rb +48 -9
- data/lib/virtus/support/chainable.rb +4 -6
- data/lib/virtus/typecast/boolean.rb +27 -0
- data/lib/virtus/typecast/numeric.rb +82 -0
- data/lib/virtus/typecast/time.rb +162 -0
- data/spec/integration/virtus/attributes/attribute/typecast_spec.rb +4 -4
- data/spec/integration/virtus/class_methods/attribute_spec.rb +1 -1
- data/spec/integration/virtus/class_methods/attributes_spec.rb +3 -2
- data/spec/integration/virtus/class_methods/const_missing_spec.rb +2 -2
- data/spec/rcov.opts +6 -0
- data/spec/spec_helper.rb +0 -9
- data/spec/unit/shared/attribute.rb +8 -8
- data/spec/unit/virtus/{attributes → attribute}/array_spec.rb +1 -1
- data/spec/unit/virtus/attribute/attribute_spec.rb +12 -0
- data/spec/unit/virtus/{attributes → attribute}/boolean_spec.rb +4 -4
- data/spec/unit/virtus/{attributes → attribute}/date_spec.rb +13 -7
- data/spec/unit/virtus/{attributes → attribute}/date_time_spec.rb +31 -10
- data/spec/unit/virtus/{attributes → attribute}/decimal_spec.rb +18 -18
- data/spec/unit/virtus/{attributes → attribute}/float_spec.rb +18 -18
- data/spec/unit/virtus/{attributes → attribute}/hash_spec.rb +1 -1
- data/spec/unit/virtus/{attributes → attribute}/integer_spec.rb +18 -18
- data/spec/unit/virtus/attribute/numeric/class_methods/descendants_spec.rb +15 -0
- data/spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb +16 -0
- data/spec/unit/virtus/{attributes → attribute}/string_spec.rb +2 -2
- data/spec/unit/virtus/{attributes → attribute}/time_spec.rb +19 -9
- data/spec/unit/virtus/class_methods/new_spec.rb +7 -7
- data/spec/unit/virtus/determine_type_spec.rb +4 -4
- data/spec/unit/virtus/instance_methods/attribute_get_spec.rb +1 -1
- data/spec/unit/virtus/instance_methods/attribute_set_spec.rb +2 -2
- data/spec/unit/virtus/instance_methods/attributes_spec.rb +2 -2
- data/tasks/metrics/ci.rake +7 -0
- data/tasks/metrics/flay.rake +41 -0
- data/tasks/metrics/flog.rake +43 -0
- data/tasks/metrics/heckle.rake +261 -0
- data/tasks/metrics/metric_fu.rake +29 -0
- data/tasks/metrics/reek.rake +9 -0
- data/tasks/metrics/roodi.rake +15 -0
- data/tasks/metrics/yardstick.rake +23 -0
- data/tasks/spec.rake +26 -0
- data/tasks/yard.rake +9 -0
- data/virtus.gemspec +48 -33
- metadata +51 -41
- data/lib/virtus/attributes/array.rb +0 -8
- data/lib/virtus/attributes/attribute.rb +0 -214
- data/lib/virtus/attributes/boolean.rb +0 -39
- data/lib/virtus/attributes/date.rb +0 -44
- data/lib/virtus/attributes/date_time.rb +0 -43
- data/lib/virtus/attributes/decimal.rb +0 -24
- data/lib/virtus/attributes/float.rb +0 -20
- data/lib/virtus/attributes/hash.rb +0 -8
- data/lib/virtus/attributes/integer.rb +0 -20
- data/lib/virtus/attributes/string.rb +0 -11
- data/lib/virtus/attributes/time.rb +0 -45
- data/lib/virtus/attributes/typecast/numeric.rb +0 -32
- data/lib/virtus/attributes/typecast/time.rb +0 -27
- data/spec/unit/virtus/attributes/attribute_spec.rb +0 -13
- data/spec/unit/virtus/attributes/numeric/class_methods/descendants_spec.rb +0 -15
- data/spec/unit/virtus/attributes/object/class_methods/descendants_spec.rb +0 -16
data/.gitignore
CHANGED
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm --create
|
1
|
+
rvm use @$(basename `pwd`) --create
|
data/Gemfile
CHANGED
@@ -1,7 +1,26 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
|
+
group :virtus do
|
4
|
+
gem 'virtus', File.read('VERSION'), :path => File.dirname(__FILE__)
|
5
|
+
end
|
6
|
+
|
3
7
|
group :development do
|
4
8
|
gem "jeweler", "~> 1.5.2"
|
5
9
|
gem "rspec", "~> 2.6.0"
|
6
|
-
|
10
|
+
end
|
11
|
+
|
12
|
+
platforms :mri_18 do
|
13
|
+
group :metrics do
|
14
|
+
gem 'flay', '~> 1.4.2'
|
15
|
+
gem 'flog', '~> 2.5.1'
|
16
|
+
gem 'heckle', '~> 1.4.3'
|
17
|
+
gem 'json', '~> 1.5.1'
|
18
|
+
gem 'metric_fu', '~> 2.1.1'
|
19
|
+
gem 'mspec', '~> 1.5.17'
|
20
|
+
gem 'rcov', '~> 0.9.9'
|
21
|
+
gem 'reek', '~> 1.2.8', :git => 'git://github.com/dkubb/reek.git'
|
22
|
+
gem 'roodi', '~> 2.1.0'
|
23
|
+
gem 'ruby2ruby', '= 1.2.2'
|
24
|
+
gem 'yardstick', '~> 0.4.0'
|
25
|
+
end
|
7
26
|
end
|
data/History.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
=== v0.0.3 2011-06-09
|
2
|
+
|
3
|
+
* [BREAKING CHANGE] Attribute classes were moved to Virtus::Attribute namespace
|
4
|
+
* [BREAKING CHANGE] Attribute instance no longer holds the reference to a model
|
5
|
+
* [BREAKING CHANGE] #typecast no longer receives an instance of a model (override #set which calls #typecast if you need that)
|
6
|
+
* [changed] Adding reader/writer methods was moved from the attribute constructor to Virtus::ClassMethods.attribute
|
7
|
+
* [changed] Typecast logic has been moved into separate classes (see Virtus::Typecast)
|
8
|
+
* [added] Virtus::Attribute::DateTime#typecast supports objects which implement #to_datetime
|
9
|
+
* [general] Internals have been cleaned up, simplified and properly documented
|
10
|
+
|
11
|
+
Details: https://github.com/solnic/virtus/compare/v0.0.2...v0.0.3
|
12
|
+
|
13
|
+
=== v0.0.2 2011-06-06
|
14
|
+
|
15
|
+
* [bugfix] Fixed #typecast in custom attribute classes
|
16
|
+
|
17
|
+
Details: https://github.com/solnic/virtus/compare/v0.0.1...v0.0.2
|
18
|
+
|
19
|
+
=== v0.0.1 2011-06-04
|
20
|
+
|
21
|
+
First public release :)
|
data/README.markdown
CHANGED
@@ -47,10 +47,10 @@ attributes that require typecasting and/or validations.
|
|
47
47
|
|
48
48
|
module MyApp
|
49
49
|
module Attributes
|
50
|
-
class JSON < Virtus::
|
50
|
+
class JSON < Virtus::Attribute::Object
|
51
51
|
primitive Hash
|
52
52
|
|
53
|
-
def typecast(value
|
53
|
+
def typecast(value)
|
54
54
|
::JSON.parse(value)
|
55
55
|
end
|
56
56
|
end
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/config/flay.yml
ADDED
data/config/flog.yml
ADDED
data/config/roodi.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
AbcMetricMethodCheck: { score: 11.8 }
|
3
|
+
AssignmentInConditionalCheck: { }
|
4
|
+
CaseMissingElseCheck: { }
|
5
|
+
ClassLineCountCheck: { line_count: 390 }
|
6
|
+
ClassNameCheck: { pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/ }
|
7
|
+
ClassVariableCheck: { }
|
8
|
+
CyclomaticComplexityBlockCheck: { complexity: 2 }
|
9
|
+
CyclomaticComplexityMethodCheck: { complexity: 5 }
|
10
|
+
EmptyRescueBodyCheck: { }
|
11
|
+
ForLoopCheck: { }
|
12
|
+
# TODO: decrease line_count to 5 to 10
|
13
|
+
MethodLineCountCheck: { line_count: 20 }
|
14
|
+
MethodNameCheck: { pattern: !ruby/regexp /\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|[+*&|-])\z/ }
|
15
|
+
ModuleLineCountCheck: { line_count: 392 }
|
16
|
+
ModuleNameCheck: { pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/ }
|
17
|
+
# TODO: decrease parameter_count to 2 or less
|
18
|
+
ParameterNumberCheck: { parameter_count: 3 }
|
data/config/site.reek
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
---
|
2
|
+
UncommunicativeParameterName:
|
3
|
+
accept: []
|
4
|
+
exclude: []
|
5
|
+
enabled: true
|
6
|
+
reject:
|
7
|
+
- !ruby/regexp /^.$/
|
8
|
+
- !ruby/regexp /[0-9]$/
|
9
|
+
- !ruby/regexp /[A-Z]/
|
10
|
+
LargeClass:
|
11
|
+
max_methods: 15 # TODO: decrease max_methods to 10-15 or less
|
12
|
+
exclude: []
|
13
|
+
enabled: true
|
14
|
+
max_instance_variables: 5
|
15
|
+
UncommunicativeMethodName:
|
16
|
+
accept: []
|
17
|
+
exclude: []
|
18
|
+
enabled: true
|
19
|
+
reject:
|
20
|
+
- !ruby/regexp /^[a-z]$/
|
21
|
+
- !ruby/regexp /[0-9]$/
|
22
|
+
- !ruby/regexp /[A-Z]/
|
23
|
+
LongParameterList:
|
24
|
+
max_params: 3 # TODO: decrease max_params to 2
|
25
|
+
exclude: []
|
26
|
+
enabled: true
|
27
|
+
overrides: {}
|
28
|
+
FeatureEnvy:
|
29
|
+
exclude: []
|
30
|
+
enabled: true
|
31
|
+
ClassVariable:
|
32
|
+
exclude: []
|
33
|
+
enabled: true
|
34
|
+
BooleanParameter:
|
35
|
+
exclude: []
|
36
|
+
enabled: true
|
37
|
+
IrresponsibleModule:
|
38
|
+
exclude: []
|
39
|
+
enabled: true
|
40
|
+
UncommunicativeModuleName:
|
41
|
+
accept: []
|
42
|
+
exclude: []
|
43
|
+
enabled: true
|
44
|
+
reject:
|
45
|
+
- !ruby/regexp /^.$/
|
46
|
+
- !ruby/regexp /[0-9]$/
|
47
|
+
NestedIterators:
|
48
|
+
ignore_iterators: []
|
49
|
+
exclude: []
|
50
|
+
enabled: true
|
51
|
+
max_allowed_nesting: 1
|
52
|
+
LongMethod:
|
53
|
+
max_statements: 7 # TODO: decrease max_statements to 5 or less
|
54
|
+
exclude: []
|
55
|
+
enabled: true
|
56
|
+
Duplication:
|
57
|
+
allow_calls: []
|
58
|
+
exclude: []
|
59
|
+
enabled: true
|
60
|
+
max_calls: 1
|
61
|
+
UtilityFunction:
|
62
|
+
max_helper_calls: 1
|
63
|
+
exclude: []
|
64
|
+
enabled: true
|
65
|
+
Attribute:
|
66
|
+
exclude: []
|
67
|
+
enabled: false
|
68
|
+
UncommunicativeVariableName:
|
69
|
+
accept: []
|
70
|
+
exclude: []
|
71
|
+
enabled: true
|
72
|
+
reject:
|
73
|
+
- !ruby/regexp /^.$/
|
74
|
+
- !ruby/regexp /[0-9]$/
|
75
|
+
- !ruby/regexp /[A-Z]/
|
76
|
+
SimulatedPolymorphism:
|
77
|
+
exclude: []
|
78
|
+
enabled: true
|
79
|
+
max_ifs: 1
|
80
|
+
DataClump:
|
81
|
+
exclude: []
|
82
|
+
enabled: true
|
83
|
+
max_copies: 1
|
84
|
+
min_clump_size: 2
|
85
|
+
ControlCouple:
|
86
|
+
exclude: []
|
87
|
+
enabled: true
|
88
|
+
LongYieldList:
|
89
|
+
max_params: 2
|
90
|
+
exclude: []
|
91
|
+
enabled: true
|
data/lib/virtus.rb
CHANGED
@@ -5,57 +5,63 @@ require 'time'
|
|
5
5
|
require 'bigdecimal'
|
6
6
|
require 'bigdecimal/util'
|
7
7
|
|
8
|
+
# Base module which adds Attribute API to your classes
|
8
9
|
module Virtus
|
10
|
+
# Represents an undefined parameter used by auto-generated option methods
|
9
11
|
module Undefined; end
|
10
12
|
|
11
|
-
class
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
13
|
+
# Extends base class with Attributes and Chainable modules
|
14
|
+
#
|
15
|
+
# @param [Class] base
|
16
|
+
#
|
17
|
+
# @return [Class]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
def self.included(base)
|
21
|
+
base.extend(ClassMethods)
|
22
|
+
base.send(:include, InstanceMethods)
|
23
|
+
base.extend(Support::Chainable)
|
24
|
+
end
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
26
|
+
# Returns a Virtus::Attributes::Object sub-class based on a name or class
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# Virtus.determine_type('String') # => Virtus::Attribute::String
|
30
|
+
#
|
31
|
+
# @param [Class,String] class_or_name
|
32
|
+
# name of a class or a class itself
|
33
|
+
#
|
34
|
+
# @return [Class]
|
35
|
+
# one of the Virtus::Attributes::Object sub-class
|
36
|
+
#
|
37
|
+
# @api semipublic
|
38
|
+
def self.determine_type(class_or_name)
|
39
|
+
if class_or_name.is_a?(Class) && class_or_name < Attribute::Object
|
40
|
+
class_or_name
|
41
|
+
elsif Attribute.const_defined?(name = class_or_name.to_s)
|
42
|
+
Attribute.const_get(name)
|
38
43
|
end
|
39
44
|
end
|
40
45
|
end
|
41
46
|
|
42
|
-
|
47
|
+
require 'virtus/support/chainable'
|
48
|
+
require 'virtus/class_methods'
|
49
|
+
require 'virtus/instance_methods'
|
50
|
+
|
51
|
+
require 'virtus/typecast/boolean'
|
52
|
+
require 'virtus/typecast/numeric'
|
53
|
+
require 'virtus/typecast/time'
|
43
54
|
|
44
|
-
require
|
45
|
-
require
|
46
|
-
require
|
47
|
-
require
|
48
|
-
require
|
49
|
-
require
|
50
|
-
require
|
51
|
-
require
|
52
|
-
require
|
53
|
-
require
|
54
|
-
require
|
55
|
-
require
|
56
|
-
require
|
57
|
-
require dir + 'virtus/attributes/float'
|
58
|
-
require dir + 'virtus/attributes/hash'
|
59
|
-
require dir + 'virtus/attributes/integer'
|
60
|
-
require dir + 'virtus/attributes/string'
|
61
|
-
require dir + 'virtus/attributes/time'
|
55
|
+
require 'virtus/attribute'
|
56
|
+
require 'virtus/attribute/object'
|
57
|
+
require 'virtus/attribute/array'
|
58
|
+
require 'virtus/attribute/boolean'
|
59
|
+
require 'virtus/attribute/date'
|
60
|
+
require 'virtus/attribute/date_time'
|
61
|
+
require 'virtus/attribute/numeric'
|
62
|
+
require 'virtus/attribute/decimal'
|
63
|
+
require 'virtus/attribute/float'
|
64
|
+
require 'virtus/attribute/hash'
|
65
|
+
require 'virtus/attribute/integer'
|
66
|
+
require 'virtus/attribute/string'
|
67
|
+
require 'virtus/attribute/time'
|
@@ -0,0 +1,301 @@
|
|
1
|
+
module Virtus
|
2
|
+
# Abstract class implementing base API for attribute types
|
3
|
+
#
|
4
|
+
# @abstract
|
5
|
+
class Attribute
|
6
|
+
# Returns default options hash for a given attribute class
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# Virtus::Attribute::String.options
|
10
|
+
# # => {:primitive => String, :complex => false}
|
11
|
+
#
|
12
|
+
# @return [Hash]
|
13
|
+
# a hash of default option values
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
def self.options
|
17
|
+
options = {}
|
18
|
+
accepted_options.each do |method|
|
19
|
+
value = send(method)
|
20
|
+
options[method] = value unless value.nil?
|
21
|
+
end
|
22
|
+
options
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns an array of valid options
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# Virtus::Attribute::String.accepted_options
|
29
|
+
# # => [:primitive, :complex, :accessor, :reader, :writer]
|
30
|
+
#
|
31
|
+
# @return [Array]
|
32
|
+
# the array of valid option names
|
33
|
+
#
|
34
|
+
# @api public
|
35
|
+
def self.accepted_options
|
36
|
+
@accepted_options ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
# Defines which options are valid for a given attribute class
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# class MyAttribute < Virtus::Attribute::Object
|
43
|
+
# accept_options :foo, :bar
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# @return [Array]
|
47
|
+
# All accepted options
|
48
|
+
#
|
49
|
+
# @api public
|
50
|
+
def self.accept_options(*args)
|
51
|
+
accepted_options.concat(args)
|
52
|
+
|
53
|
+
# create methods for each new option
|
54
|
+
args.each { |option| add_option_method(option) }
|
55
|
+
|
56
|
+
# add new options to all descendants
|
57
|
+
descendants.each { |descendant| descendant.accepted_options.concat(args) }
|
58
|
+
|
59
|
+
accepted_options
|
60
|
+
end
|
61
|
+
|
62
|
+
# Adds a reader/writer method for the give option name
|
63
|
+
#
|
64
|
+
# @return [NilClass]
|
65
|
+
#
|
66
|
+
# @api private
|
67
|
+
def self.add_option_method(option)
|
68
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
69
|
+
def self.#{option}(value = Undefined) # def self.unique(value = Undefined)
|
70
|
+
return @#{option} if value.equal?(Undefined) # return @unique if value.equal?(Undefined)
|
71
|
+
@#{option} = value # @unique = value
|
72
|
+
end # end
|
73
|
+
RUBY
|
74
|
+
end
|
75
|
+
private_class_method :add_option_method
|
76
|
+
|
77
|
+
# Returns all the descendant classes
|
78
|
+
#
|
79
|
+
# @example
|
80
|
+
# Virtus::Attribute::Numeric.descendants
|
81
|
+
# # => [Virtus::Attribute::Decimal, Virtus::Attribute::Float, Virtus::Attribute::Integer]
|
82
|
+
#
|
83
|
+
# @return [Array]
|
84
|
+
# the array of descendants
|
85
|
+
#
|
86
|
+
# @api public
|
87
|
+
def self.descendants
|
88
|
+
@descendants ||= []
|
89
|
+
end
|
90
|
+
|
91
|
+
# Adds descendant to descendants array and inherits default options
|
92
|
+
#
|
93
|
+
# @param [Class]
|
94
|
+
#
|
95
|
+
# @return [Class]
|
96
|
+
#
|
97
|
+
# @api private
|
98
|
+
def self.inherited(descendant)
|
99
|
+
descendants << descendant
|
100
|
+
descendant.accepted_options.concat(accepted_options)
|
101
|
+
options.each { |key, value| descendant.send(key, value) }
|
102
|
+
descendant
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns name of the attribute
|
106
|
+
#
|
107
|
+
# @example
|
108
|
+
# User.attributes[:age].name # => :age
|
109
|
+
#
|
110
|
+
# @return [Symbol]
|
111
|
+
#
|
112
|
+
# @api public
|
113
|
+
attr_reader :name
|
114
|
+
|
115
|
+
# Returns primitive class of the attribute
|
116
|
+
#
|
117
|
+
# @return [Class]
|
118
|
+
#
|
119
|
+
# @api private
|
120
|
+
attr_reader :primitive
|
121
|
+
|
122
|
+
# Returns options hash for the attribute
|
123
|
+
#
|
124
|
+
# @return [Hash]
|
125
|
+
#
|
126
|
+
# @api private
|
127
|
+
attr_reader :options
|
128
|
+
|
129
|
+
# Returns instance variable name of the attribute
|
130
|
+
#
|
131
|
+
# @return [String]
|
132
|
+
#
|
133
|
+
# @api private
|
134
|
+
attr_reader :instance_variable_name
|
135
|
+
|
136
|
+
# Returns reader visibility
|
137
|
+
#
|
138
|
+
# @return [Symbol]
|
139
|
+
#
|
140
|
+
# @api private
|
141
|
+
attr_reader :reader_visibility
|
142
|
+
|
143
|
+
|
144
|
+
# Returns write visibility
|
145
|
+
#
|
146
|
+
# @return [Symbol]
|
147
|
+
#
|
148
|
+
# @api private
|
149
|
+
attr_reader :writer_visibility
|
150
|
+
|
151
|
+
DEFAULT_ACCESSOR = :public.freeze
|
152
|
+
|
153
|
+
OPTIONS = [ :primitive, :complex, :accessor, :reader, :writer ].freeze
|
154
|
+
|
155
|
+
accept_options *OPTIONS
|
156
|
+
|
157
|
+
# Initializes an attribute instance
|
158
|
+
#
|
159
|
+
# @param [Symbol] name
|
160
|
+
# the name of an attribute
|
161
|
+
#
|
162
|
+
# @param [Hash] options
|
163
|
+
# hash of extra options which overrides defaults set on an attribute class
|
164
|
+
#
|
165
|
+
# @api private
|
166
|
+
def initialize(name, options = {})
|
167
|
+
@name = name
|
168
|
+
@options = self.class.options.merge(options).freeze
|
169
|
+
|
170
|
+
@primitive = @options[:primitive]
|
171
|
+
|
172
|
+
@instance_variable_name = "@#{@name}".freeze
|
173
|
+
|
174
|
+
default_accessor = @options.fetch(:accessor, DEFAULT_ACCESSOR)
|
175
|
+
@reader_visibility = @options.fetch(:reader, default_accessor)
|
176
|
+
@writer_visibility = @options.fetch(:writer, default_accessor)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns if an attribute is a complex one
|
180
|
+
#
|
181
|
+
# @example
|
182
|
+
# Virtus::Attribute::String.complex? # => false
|
183
|
+
# Virtus::Attribute::Array.complex? # => true
|
184
|
+
#
|
185
|
+
# @return [TrueClass, FalseClass]
|
186
|
+
#
|
187
|
+
# @api semipublic
|
188
|
+
def complex?
|
189
|
+
options[:complex]
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns if the given value's class is an attribute's primitive
|
193
|
+
#
|
194
|
+
# @return [TrueClass, FalseClass]
|
195
|
+
#
|
196
|
+
# @api private
|
197
|
+
def primitive?(value)
|
198
|
+
value.kind_of?(primitive)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Converts the given value to the primitive type
|
202
|
+
#
|
203
|
+
# @param [Object] value
|
204
|
+
# the value
|
205
|
+
#
|
206
|
+
# @return [Object]
|
207
|
+
# nil, original value or value converted to the primitive type
|
208
|
+
#
|
209
|
+
# @api private
|
210
|
+
def typecast(value)
|
211
|
+
if value.nil? || primitive?(value)
|
212
|
+
value
|
213
|
+
else
|
214
|
+
typecast_to_primitive(value)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Converts the given value to the primitive type
|
219
|
+
#
|
220
|
+
# @api private
|
221
|
+
def typecast_to_primitive(value)
|
222
|
+
value
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns value of an attribute for the given instance
|
226
|
+
#
|
227
|
+
# @return [Object]
|
228
|
+
# value of an attribute
|
229
|
+
#
|
230
|
+
# @api private
|
231
|
+
def get(instance)
|
232
|
+
get!(instance)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Returns the instance variable of the attribute
|
236
|
+
#
|
237
|
+
# @return [Object]
|
238
|
+
# value of an attribute
|
239
|
+
#
|
240
|
+
# @api private
|
241
|
+
def get!(instance)
|
242
|
+
instance.instance_variable_get(instance_variable_name)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Sets the value on the instance
|
246
|
+
#
|
247
|
+
# @return [Object]
|
248
|
+
# value of an attribute
|
249
|
+
#
|
250
|
+
# @api private
|
251
|
+
def set(instance, value)
|
252
|
+
set!(instance, typecast(value)) unless value.nil?
|
253
|
+
end
|
254
|
+
|
255
|
+
# Sets instance variable of the attribute
|
256
|
+
#
|
257
|
+
# @return [Object]
|
258
|
+
# value of an attribute
|
259
|
+
#
|
260
|
+
# @api private
|
261
|
+
def set!(instance, value)
|
262
|
+
instance.instance_variable_set(instance_variable_name, value)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Creates an attribute reader method
|
266
|
+
#
|
267
|
+
# @return [NilClass]
|
268
|
+
#
|
269
|
+
# @api private
|
270
|
+
def add_reader_method(model)
|
271
|
+
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
272
|
+
chainable(:attribute) do
|
273
|
+
#{reader_visibility}
|
274
|
+
|
275
|
+
def #{name}
|
276
|
+
return #{instance_variable_name} if defined?(#{instance_variable_name})
|
277
|
+
attribute = self.class.attributes[#{name.inspect}]
|
278
|
+
#{instance_variable_name} = attribute ? attribute.get(self) : nil
|
279
|
+
end
|
280
|
+
end
|
281
|
+
RUBY
|
282
|
+
end
|
283
|
+
|
284
|
+
# Creates an attribute writer method
|
285
|
+
#
|
286
|
+
# @return [NilClass]
|
287
|
+
#
|
288
|
+
# @api private
|
289
|
+
def add_writer_method(model)
|
290
|
+
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
291
|
+
chainable(:attribute) do
|
292
|
+
#{writer_visibility}
|
293
|
+
|
294
|
+
def #{name}=(value)
|
295
|
+
self.class.attributes[#{name.inspect}].set(self, value)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
RUBY
|
299
|
+
end
|
300
|
+
end # Attribute
|
301
|
+
end # Virtus
|