motion_virtus 1.0.0.beta0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/README.md +445 -0
- data/lib/motion_virtus.rb +13 -0
- data/lib/project/attribute/accessor/builder.rb +69 -0
- data/lib/project/attribute/accessor/lazy_accessor.rb +39 -0
- data/lib/project/attribute/accessor.rb +100 -0
- data/lib/project/attribute/accessor_method.rb +73 -0
- data/lib/project/attribute/array.rb +24 -0
- data/lib/project/attribute/boolean.rb +52 -0
- data/lib/project/attribute/class.rb +23 -0
- data/lib/project/attribute/coercer.rb +43 -0
- data/lib/project/attribute/collection/coercible_writer.rb +83 -0
- data/lib/project/attribute/collection.rb +56 -0
- data/lib/project/attribute/date.rb +36 -0
- data/lib/project/attribute/date_time.rb +38 -0
- data/lib/project/attribute/decimal.rb +23 -0
- data/lib/project/attribute/default_value/from_callable.rb +37 -0
- data/lib/project/attribute/default_value/from_clonable.rb +37 -0
- data/lib/project/attribute/default_value/from_symbol.rb +37 -0
- data/lib/project/attribute/default_value.rb +49 -0
- data/lib/project/attribute/embedded_value/open_struct_coercer.rb +43 -0
- data/lib/project/attribute/embedded_value/struct_coercer.rb +42 -0
- data/lib/project/attribute/embedded_value.rb +69 -0
- data/lib/project/attribute/float.rb +30 -0
- data/lib/project/attribute/hash/coercible_writer.rb +78 -0
- data/lib/project/attribute/hash.rb +66 -0
- data/lib/project/attribute/integer.rb +27 -0
- data/lib/project/attribute/numeric.rb +25 -0
- data/lib/project/attribute/object.rb +13 -0
- data/lib/project/attribute/reader.rb +39 -0
- data/lib/project/attribute/set.rb +22 -0
- data/lib/project/attribute/string.rb +24 -0
- data/lib/project/attribute/symbol.rb +23 -0
- data/lib/project/attribute/time.rb +36 -0
- data/lib/project/attribute/writer/coercible.rb +45 -0
- data/lib/project/attribute/writer.rb +73 -0
- data/lib/project/attribute.rb +292 -0
- data/lib/project/attribute_set.rb +260 -0
- data/lib/project/class_inclusions.rb +41 -0
- data/lib/project/class_methods.rb +102 -0
- data/lib/project/configuration.rb +65 -0
- data/lib/project/const_missing_extensions.rb +16 -0
- data/lib/project/extensions.rb +101 -0
- data/lib/project/instance_methods.rb +165 -0
- data/lib/project/module_builder.rb +92 -0
- data/lib/project/module_extensions.rb +72 -0
- data/lib/project/stubs/date.rb +2 -0
- data/lib/project/stubs/date_time.rb +2 -0
- data/lib/project/stubs/decimal.rb +2 -0
- data/lib/project/stubs/ostruct.rb +149 -0
- data/lib/project/stubs/set.rb +767 -0
- data/lib/project/stubs.rb +5 -0
- data/lib/project/support/equalizer.rb +147 -0
- data/lib/project/support/options.rb +114 -0
- data/lib/project/support/type_lookup.rb +109 -0
- data/lib/project/value_object.rb +139 -0
- data/lib/project/version.rb +3 -0
- data/lib/project/virtus.rb +128 -0
- metadata +158 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
motion_require 'object'
|
2
|
+
|
3
|
+
module Virtus
|
4
|
+
class Attribute
|
5
|
+
|
6
|
+
# EmbeddedValue
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# class Address
|
11
|
+
# include Virtus
|
12
|
+
#
|
13
|
+
# attribute :street, String
|
14
|
+
# attribute :zipcode, String
|
15
|
+
# attribute :city, String
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# class User
|
19
|
+
# include Virtus
|
20
|
+
#
|
21
|
+
# attribute :address, Address
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# user = User.new(:address => {
|
25
|
+
# :street => 'Street 1/2', :zipcode => '12345', :city => 'NYC' })
|
26
|
+
#
|
27
|
+
class EmbeddedValue < Object
|
28
|
+
primitive ::OpenStruct
|
29
|
+
|
30
|
+
# @see Attribute.merge_options
|
31
|
+
#
|
32
|
+
# @return [Hash]
|
33
|
+
# an updated options hash for configuring an EmbeddedValue instance
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
def self.merge_options(type, _options)
|
37
|
+
super.update(:primitive => type)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Determine type based on class
|
41
|
+
#
|
42
|
+
# Virtus::EmbeddedValue.determine_type(Struct) # => Virtus::EmbeddedValue::FromStruct
|
43
|
+
# Virtus::EmbeddedValue.determine_type(VirtusClass) # => Virtus::EmbeddedValue::FromOpenStruct
|
44
|
+
#
|
45
|
+
# @param [Class] klass
|
46
|
+
#
|
47
|
+
# @return [Virtus::Attribute::EmbeddedValue]
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
def self.determine_type(klass)
|
51
|
+
if klass <= Virtus || klass <= OpenStruct || klass <= Struct
|
52
|
+
self
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @api private
|
57
|
+
def self.coercer(type, _options = {})
|
58
|
+
if type <= Virtus || type <= OpenStruct
|
59
|
+
OpenStructCoercer.new(type)
|
60
|
+
elsif type <= Struct
|
61
|
+
StructCoercer.new(type)
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end # class EmbeddedValue
|
68
|
+
end # class Attribute
|
69
|
+
end # module Virtus
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Float
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# class ExchangeRate
|
8
|
+
# include Virtus
|
9
|
+
#
|
10
|
+
# attribute :dollar, Float
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# ExchangeRate.new(:dollar => 2.69)
|
14
|
+
#
|
15
|
+
# # typecasting from a string
|
16
|
+
# ExchangeRate.new(:dollar => '2.69')
|
17
|
+
#
|
18
|
+
# # typecasting from an integer
|
19
|
+
# ExchangeRate.new(:dollar => 2)
|
20
|
+
#
|
21
|
+
# # typecasting from an object which implements #to_f
|
22
|
+
# ExchangeRate.new(:dollar => BigDecimal.new('2.69')
|
23
|
+
#
|
24
|
+
class Float < Numeric
|
25
|
+
primitive ::Float
|
26
|
+
coercion_method :to_float
|
27
|
+
|
28
|
+
end # class Float
|
29
|
+
end # class Attribute
|
30
|
+
end # module Virtus
|
@@ -0,0 +1,78 @@
|
|
1
|
+
motion_require '../hash.rb'
|
2
|
+
|
3
|
+
module Virtus
|
4
|
+
class Attribute
|
5
|
+
class Hash
|
6
|
+
|
7
|
+
# Coercible writer for Hash attributes
|
8
|
+
#
|
9
|
+
class CoercibleWriter < Attribute::Writer::Coercible
|
10
|
+
|
11
|
+
# The type to which keys of this hash will be coerced
|
12
|
+
#
|
13
|
+
# @return [Virtus::Attribute]
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
attr_reader :key_type
|
17
|
+
|
18
|
+
# The type to which values of this hash will be coerced
|
19
|
+
#
|
20
|
+
# @return [Virtus::Attribute]
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
attr_reader :value_type
|
24
|
+
|
25
|
+
# Return writer for hash keys
|
26
|
+
#
|
27
|
+
# @return [Writer::Coercible]
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
attr_reader :key_coercer
|
31
|
+
|
32
|
+
# Return writer for hash values
|
33
|
+
#
|
34
|
+
# @return [Writer::Coercible]
|
35
|
+
#
|
36
|
+
# @api private
|
37
|
+
attr_reader :value_coercer
|
38
|
+
|
39
|
+
# @api private
|
40
|
+
def initialize(name, options)
|
41
|
+
super
|
42
|
+
@key_type = options.fetch(:key_type, ::Object)
|
43
|
+
@value_type = options.fetch(:value_type, ::Object)
|
44
|
+
@key_coercer = Attribute.determine_type(@key_type).coercer(@key_type)
|
45
|
+
@value_coercer = Attribute.determine_type(@value_type).coercer(@value_type)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Coerce a hash with keys and values
|
49
|
+
#
|
50
|
+
# @param [Object] value
|
51
|
+
#
|
52
|
+
# @return [Object]
|
53
|
+
#
|
54
|
+
# @api private
|
55
|
+
def coerce(_value)
|
56
|
+
coerced = super
|
57
|
+
return coerced unless coerced.respond_to?(:each_with_object)
|
58
|
+
coerced.each_with_object(new_hash) do |(key, value), hash|
|
59
|
+
hash[key_coercer.call(key)] = value_coercer.call(value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Return an instance of the hash
|
66
|
+
#
|
67
|
+
# @return [Hash]
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
def new_hash
|
71
|
+
primitive.new
|
72
|
+
end
|
73
|
+
|
74
|
+
end # class CoercibleWriter
|
75
|
+
|
76
|
+
end # class Collection
|
77
|
+
end # class Attribute
|
78
|
+
end # module Virtus
|
@@ -0,0 +1,66 @@
|
|
1
|
+
motion_require 'object'
|
2
|
+
|
3
|
+
module Virtus
|
4
|
+
class Attribute
|
5
|
+
|
6
|
+
# Hash
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# class Post
|
10
|
+
# include Virtus
|
11
|
+
#
|
12
|
+
# attribute :meta, Hash
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# Post.new(:meta => { :tags => %w(foo bar) })
|
16
|
+
#
|
17
|
+
class Hash < Object
|
18
|
+
primitive ::Hash
|
19
|
+
coercion_method :to_hash
|
20
|
+
default primitive.new
|
21
|
+
|
22
|
+
# Handles hashes with [key_type => value_type] syntax
|
23
|
+
#
|
24
|
+
# @param [Class] type
|
25
|
+
#
|
26
|
+
# @param [Hash] options
|
27
|
+
#
|
28
|
+
# @return [Hash]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
def self.merge_options(type, _options)
|
32
|
+
merged_options = super
|
33
|
+
|
34
|
+
if !type.respond_to?(:size)
|
35
|
+
merged_options
|
36
|
+
elsif type.size > 1
|
37
|
+
raise ArgumentError, "more than one [key => value] pair in `#{type.inspect}`"
|
38
|
+
else
|
39
|
+
key_type, value_type = type.first
|
40
|
+
merged_options.merge!(:key_type => key_type, :value_type => value_type)
|
41
|
+
end
|
42
|
+
|
43
|
+
merged_options
|
44
|
+
end
|
45
|
+
|
46
|
+
# @see Virtus::Attribute.coercible_writer_class
|
47
|
+
#
|
48
|
+
# @return [::Class]
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
def self.coercible_writer_class(_type, options)
|
52
|
+
options[:key_type] && options[:value_type] ? CoercibleWriter : super
|
53
|
+
end
|
54
|
+
|
55
|
+
# @see Virtus::Attribute.writer_option_names
|
56
|
+
#
|
57
|
+
# @return [Array<Symbol>]
|
58
|
+
#
|
59
|
+
# @api private
|
60
|
+
def self.writer_option_names
|
61
|
+
super << :key_type << :value_type
|
62
|
+
end
|
63
|
+
|
64
|
+
end # class Hash
|
65
|
+
end # class Attribute
|
66
|
+
end # module Virtus
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Integer
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# class Post
|
8
|
+
# include Virtus
|
9
|
+
#
|
10
|
+
# attribute :read_count, Integer
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Post.new(:read_count => 100)
|
14
|
+
#
|
15
|
+
# # typecasting from a string
|
16
|
+
# Post.new(:read_count => '100')
|
17
|
+
#
|
18
|
+
# # typecasting from an object that implements #to_i
|
19
|
+
# Post.new(:read_count => 100.0)
|
20
|
+
#
|
21
|
+
class Integer < Numeric
|
22
|
+
primitive ::Integer
|
23
|
+
coercion_method :to_integer
|
24
|
+
|
25
|
+
end # class Integer
|
26
|
+
end # class Attribute
|
27
|
+
end # module Virtus
|
@@ -0,0 +1,25 @@
|
|
1
|
+
motion_require 'object'
|
2
|
+
|
3
|
+
module Virtus
|
4
|
+
class Attribute
|
5
|
+
|
6
|
+
# Base class for all numerical attributes
|
7
|
+
class Numeric < Object
|
8
|
+
primitive ::Numeric
|
9
|
+
#accept_options :min, :max
|
10
|
+
|
11
|
+
def self.min(value=Undefined)
|
12
|
+
return @min if value.equal?(Undefined)
|
13
|
+
@min = value
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.max(value=Undefined)
|
18
|
+
return @max if value.equal?(Undefined)
|
19
|
+
@max = value
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
end # class Numeric
|
24
|
+
end # class Attribute
|
25
|
+
end # module Virtus
|
@@ -0,0 +1,13 @@
|
|
1
|
+
motion_require '../attribute.rb'
|
2
|
+
|
3
|
+
module Virtus
|
4
|
+
class Attribute
|
5
|
+
|
6
|
+
# Base class for every attribute
|
7
|
+
class Object < Attribute
|
8
|
+
primitive ::Object
|
9
|
+
coercion_method :to_object
|
10
|
+
|
11
|
+
end # class Object
|
12
|
+
end # class Attribute
|
13
|
+
end # module Virtus
|
@@ -0,0 +1,39 @@
|
|
1
|
+
motion_require 'accessor_method'
|
2
|
+
|
3
|
+
module Virtus
|
4
|
+
class Attribute
|
5
|
+
|
6
|
+
# Reader method object
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Reader < AccessorMethod
|
10
|
+
|
11
|
+
# Returns value of an attribute for the given instance
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# attribute.get(instance) # => value
|
15
|
+
#
|
16
|
+
# @return [Object]
|
17
|
+
# value of an attribute
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def call(instance)
|
21
|
+
instance.instance_variable_get(instance_variable_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Creates an attribute reader method
|
25
|
+
#
|
26
|
+
# @param [Module] mod
|
27
|
+
#
|
28
|
+
# @return [self]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
def define_method(accessor, mod)
|
32
|
+
mod.define_reader_method(accessor, name, visibility)
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
end # class Reader
|
37
|
+
|
38
|
+
end # class Attribute
|
39
|
+
end # module Virtus
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Set
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# class Post
|
8
|
+
# include Virtus
|
9
|
+
#
|
10
|
+
# attribute :tags, Set
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# post = Post.new(:tags => %w(red green blue))
|
14
|
+
#
|
15
|
+
class Set < Collection
|
16
|
+
primitive ::Set
|
17
|
+
coercion_method :to_set
|
18
|
+
default primitive.new
|
19
|
+
|
20
|
+
end # class Set
|
21
|
+
end # class Attribute
|
22
|
+
end # module Virtus
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# String
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# class User
|
8
|
+
# include Virtus
|
9
|
+
#
|
10
|
+
# attribute :name, String
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# User.new(:name => 'John')
|
14
|
+
#
|
15
|
+
# # typecasting from an object which implements #to_s
|
16
|
+
# User.new(:name => :John)
|
17
|
+
#
|
18
|
+
class String < Object
|
19
|
+
primitive ::String
|
20
|
+
coercion_method :to_string
|
21
|
+
|
22
|
+
end # class String
|
23
|
+
end # class Attribute
|
24
|
+
end # module Virtus
|
@@ -0,0 +1,23 @@
|
|
1
|
+
motion_require 'object'
|
2
|
+
|
3
|
+
module Virtus
|
4
|
+
class Attribute
|
5
|
+
|
6
|
+
# Symbol
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# class Product
|
10
|
+
# include Virtus
|
11
|
+
#
|
12
|
+
# attribute :code, Symbol
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# product = Product.new(:code => :red)
|
16
|
+
#
|
17
|
+
class Symbol < Object
|
18
|
+
primitive ::Symbol
|
19
|
+
coercion_method :to_symbol
|
20
|
+
|
21
|
+
end # class Symbol
|
22
|
+
end # class Attribute
|
23
|
+
end # module Virtus
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Time
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# class Post
|
8
|
+
# include Virtus
|
9
|
+
#
|
10
|
+
# attribute :published_at, Time
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Post.new(:published_at => Time.now)
|
14
|
+
#
|
15
|
+
# # typecasting from a string
|
16
|
+
# Post.new(:published_at => '2011/06/09 11:08')
|
17
|
+
#
|
18
|
+
# # typecasting from a hash
|
19
|
+
# Post.new(:published_at => {
|
20
|
+
# :year => 2011,
|
21
|
+
# :month => 6,
|
22
|
+
# :day => 9,
|
23
|
+
# :hour => 11,
|
24
|
+
# :min => 8,
|
25
|
+
# })
|
26
|
+
#
|
27
|
+
# # typecasting from an object which implements #to_time
|
28
|
+
# Post.new(:published_at => DateTime.now)
|
29
|
+
#
|
30
|
+
class Time < Object
|
31
|
+
primitive ::Time
|
32
|
+
coercion_method :to_time
|
33
|
+
|
34
|
+
end # class Time
|
35
|
+
end # class Attribute
|
36
|
+
end # module Virtus
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
class Writer < AccessorMethod
|
4
|
+
|
5
|
+
class Coercible < self
|
6
|
+
|
7
|
+
# Return coercer object used by this writer
|
8
|
+
#
|
9
|
+
# @return [Object]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
attr_reader :coercer
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
def initialize(name, options = {})
|
16
|
+
super
|
17
|
+
@coercer = options.fetch(:coercer)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def call(instance, value)
|
22
|
+
super(instance, coerce(value))
|
23
|
+
end
|
24
|
+
|
25
|
+
# Converts the given value to the primitive type
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# attribute.coerce(value) # => primitive_value
|
29
|
+
#
|
30
|
+
# @param [Object] value
|
31
|
+
# the value
|
32
|
+
#
|
33
|
+
# @return [Object]
|
34
|
+
# nil, original value or value converted to the primitive type
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def coerce(value)
|
38
|
+
coercer.call(value)
|
39
|
+
end
|
40
|
+
|
41
|
+
end # class Coercible
|
42
|
+
|
43
|
+
end # class Writer
|
44
|
+
end # class Attribute
|
45
|
+
end # module Virtus
|
@@ -0,0 +1,73 @@
|
|
1
|
+
motion_require 'accessor_method'
|
2
|
+
|
3
|
+
module Virtus
|
4
|
+
class Attribute
|
5
|
+
|
6
|
+
# Writer method object
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Writer < AccessorMethod
|
10
|
+
|
11
|
+
# Return primitive
|
12
|
+
#
|
13
|
+
# @return [Class]
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
attr_reader :primitive
|
17
|
+
|
18
|
+
# Return default value instance
|
19
|
+
#
|
20
|
+
# @return [DefaultValue]
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
attr_reader :default_value
|
24
|
+
|
25
|
+
# Initialize a writer instance
|
26
|
+
#
|
27
|
+
# @param [#to_sym] name
|
28
|
+
#
|
29
|
+
# @param [Hash] options
|
30
|
+
#
|
31
|
+
# @return [undefined]
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
def initialize(name, options = {})
|
35
|
+
super
|
36
|
+
@name = "#{name}=".to_sym
|
37
|
+
@primitive = options.fetch(:primitive, ::Object)
|
38
|
+
@default_value = DefaultValue.build(options[:default])
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sets instance variable of the attribute
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# attribute.set!(instance, value) # => value
|
45
|
+
#
|
46
|
+
# @return [self]
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
def call(instance, value)
|
50
|
+
instance.instance_variable_set(instance_variable_name, value)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
def set_default_value(instance, attribute)
|
55
|
+
call(instance, default_value.call(instance, attribute))
|
56
|
+
end
|
57
|
+
|
58
|
+
# Creates an attribute writer method
|
59
|
+
#
|
60
|
+
# @param [Module] mod
|
61
|
+
#
|
62
|
+
# @return [self]
|
63
|
+
#
|
64
|
+
# @api private
|
65
|
+
def define_method(accessor, mod)
|
66
|
+
mod.define_writer_method(accessor, name, visibility)
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
end # class Writer
|
71
|
+
|
72
|
+
end # class Attribute
|
73
|
+
end # module Virtus
|