activeentity 0.0.1.beta1
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +42 -0
- data/README.md +145 -0
- data/Rakefile +29 -0
- data/lib/active_entity.rb +73 -0
- data/lib/active_entity/aggregations.rb +276 -0
- data/lib/active_entity/associations.rb +146 -0
- data/lib/active_entity/associations/embedded/association.rb +134 -0
- data/lib/active_entity/associations/embedded/builder/association.rb +100 -0
- data/lib/active_entity/associations/embedded/builder/collection_association.rb +69 -0
- data/lib/active_entity/associations/embedded/builder/embedded_in.rb +38 -0
- data/lib/active_entity/associations/embedded/builder/embeds_many.rb +13 -0
- data/lib/active_entity/associations/embedded/builder/embeds_one.rb +16 -0
- data/lib/active_entity/associations/embedded/builder/singular_association.rb +28 -0
- data/lib/active_entity/associations/embedded/collection_association.rb +188 -0
- data/lib/active_entity/associations/embedded/collection_proxy.rb +310 -0
- data/lib/active_entity/associations/embedded/embedded_in_association.rb +31 -0
- data/lib/active_entity/associations/embedded/embeds_many_association.rb +15 -0
- data/lib/active_entity/associations/embedded/embeds_one_association.rb +19 -0
- data/lib/active_entity/associations/embedded/singular_association.rb +35 -0
- data/lib/active_entity/attribute_assignment.rb +85 -0
- data/lib/active_entity/attribute_decorators.rb +90 -0
- data/lib/active_entity/attribute_methods.rb +330 -0
- data/lib/active_entity/attribute_methods/before_type_cast.rb +78 -0
- data/lib/active_entity/attribute_methods/primary_key.rb +98 -0
- data/lib/active_entity/attribute_methods/query.rb +35 -0
- data/lib/active_entity/attribute_methods/read.rb +47 -0
- data/lib/active_entity/attribute_methods/serialization.rb +90 -0
- data/lib/active_entity/attribute_methods/time_zone_conversion.rb +91 -0
- data/lib/active_entity/attribute_methods/write.rb +63 -0
- data/lib/active_entity/attributes.rb +165 -0
- data/lib/active_entity/base.rb +303 -0
- data/lib/active_entity/coders/json.rb +15 -0
- data/lib/active_entity/coders/yaml_column.rb +50 -0
- data/lib/active_entity/core.rb +281 -0
- data/lib/active_entity/define_callbacks.rb +17 -0
- data/lib/active_entity/enum.rb +234 -0
- data/lib/active_entity/errors.rb +80 -0
- data/lib/active_entity/gem_version.rb +17 -0
- data/lib/active_entity/inheritance.rb +278 -0
- data/lib/active_entity/integration.rb +78 -0
- data/lib/active_entity/locale/en.yml +45 -0
- data/lib/active_entity/model_schema.rb +115 -0
- data/lib/active_entity/nested_attributes.rb +592 -0
- data/lib/active_entity/readonly_attributes.rb +47 -0
- data/lib/active_entity/reflection.rb +441 -0
- data/lib/active_entity/serialization.rb +25 -0
- data/lib/active_entity/store.rb +242 -0
- data/lib/active_entity/translation.rb +24 -0
- data/lib/active_entity/type.rb +73 -0
- data/lib/active_entity/type/date.rb +9 -0
- data/lib/active_entity/type/date_time.rb +9 -0
- data/lib/active_entity/type/decimal_without_scale.rb +15 -0
- data/lib/active_entity/type/hash_lookup_type_map.rb +25 -0
- data/lib/active_entity/type/internal/timezone.rb +17 -0
- data/lib/active_entity/type/json.rb +30 -0
- data/lib/active_entity/type/modifiers/array.rb +72 -0
- data/lib/active_entity/type/registry.rb +92 -0
- data/lib/active_entity/type/serialized.rb +71 -0
- data/lib/active_entity/type/text.rb +11 -0
- data/lib/active_entity/type/time.rb +21 -0
- data/lib/active_entity/type/type_map.rb +62 -0
- data/lib/active_entity/type/unsigned_integer.rb +17 -0
- data/lib/active_entity/validate_embedded_association.rb +305 -0
- data/lib/active_entity/validations.rb +50 -0
- data/lib/active_entity/validations/absence.rb +25 -0
- data/lib/active_entity/validations/associated.rb +60 -0
- data/lib/active_entity/validations/length.rb +26 -0
- data/lib/active_entity/validations/presence.rb +68 -0
- data/lib/active_entity/validations/subset.rb +76 -0
- data/lib/active_entity/validations/uniqueness_in_embedding.rb +99 -0
- data/lib/active_entity/version.rb +10 -0
- data/lib/tasks/active_entity_tasks.rake +6 -0
- metadata +155 -0
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/enumerable"
|
4
|
+
require "active_support/core_ext/string/conversions"
|
5
|
+
require "active_support/core_ext/module/remove_method"
|
6
|
+
require "active_entity/errors"
|
7
|
+
|
8
|
+
module ActiveEntity
|
9
|
+
class AssociationNotFoundError < ConfigurationError #:nodoc:
|
10
|
+
def initialize(record = nil, association_name = nil)
|
11
|
+
if record && association_name
|
12
|
+
super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
|
13
|
+
else
|
14
|
+
super("Association was not found.")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class InverseOfAssociationNotFoundError < ActiveEntityError #:nodoc:
|
20
|
+
def initialize(reflection = nil, associated_class = nil)
|
21
|
+
if reflection
|
22
|
+
super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
|
23
|
+
else
|
24
|
+
super("Could not find the inverse association.")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# See ActiveEntity::Associations::ClassMethods for documentation.
|
30
|
+
module Associations # :nodoc:
|
31
|
+
extend ActiveSupport::Autoload
|
32
|
+
extend ActiveSupport::Concern
|
33
|
+
|
34
|
+
# These classes will be loaded when associations are created.
|
35
|
+
# So there is no need to eager load them.
|
36
|
+
module Embedded
|
37
|
+
extend ActiveSupport::Autoload
|
38
|
+
|
39
|
+
autoload :Association, "active_entity/associations/embedded/association"
|
40
|
+
autoload :SingularAssociation, "active_entity/associations/embedded/singular_association"
|
41
|
+
autoload :CollectionAssociation, "active_entity/associations/embedded/collection_association"
|
42
|
+
autoload :CollectionProxy, "active_entity/associations/embedded/collection_proxy"
|
43
|
+
|
44
|
+
module Builder #:nodoc:
|
45
|
+
autoload :Association, "active_entity/associations/embedded/builder/association"
|
46
|
+
autoload :SingularAssociation, "active_entity/associations/embedded/builder/singular_association"
|
47
|
+
autoload :CollectionAssociation, "active_entity/associations/embedded/builder/collection_association"
|
48
|
+
|
49
|
+
autoload :EmbeddedIn, "active_entity/associations/embedded/builder/embedded_in"
|
50
|
+
autoload :EmbedsOne, "active_entity/associations/embedded/builder/embeds_one"
|
51
|
+
autoload :EmbedsMany, "active_entity/associations/embedded/builder/embeds_many"
|
52
|
+
end
|
53
|
+
|
54
|
+
eager_autoload do
|
55
|
+
autoload :EmbeddedInAssociation
|
56
|
+
autoload :EmbedsOneAssociation
|
57
|
+
autoload :EmbedsManyAssociation
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.eager_load!
|
62
|
+
super
|
63
|
+
Embedded.eager_load!
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the association instance for the given name, instantiating it if it doesn't already exist
|
67
|
+
def association(name) #:nodoc:
|
68
|
+
association = association_instance_get(name)
|
69
|
+
|
70
|
+
if association.nil?
|
71
|
+
unless reflection = self.class._reflect_on_association(name)
|
72
|
+
raise AssociationNotFoundError.new(self, name)
|
73
|
+
end
|
74
|
+
association = reflection.association_class.new(self, reflection)
|
75
|
+
association_instance_set(name, association)
|
76
|
+
end
|
77
|
+
|
78
|
+
association
|
79
|
+
end
|
80
|
+
|
81
|
+
def association_cached?(name) # :nodoc:
|
82
|
+
@association_cache.key?(name)
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize_dup(*) # :nodoc:
|
86
|
+
@association_cache = {}
|
87
|
+
super
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
# Clears out the association cache.
|
92
|
+
def clear_association_cache
|
93
|
+
@association_cache.clear if persisted?
|
94
|
+
end
|
95
|
+
|
96
|
+
def init_internals
|
97
|
+
@association_cache = {}
|
98
|
+
super
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the specified association instance if it exists, +nil+ otherwise.
|
102
|
+
def association_instance_get(name)
|
103
|
+
@association_cache[name]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Set the specified association instance.
|
107
|
+
def association_instance_set(name, association)
|
108
|
+
@association_cache[name] = association
|
109
|
+
end
|
110
|
+
|
111
|
+
module ClassMethods
|
112
|
+
def embedded_in(name, **options)
|
113
|
+
reflection = Embedded::Builder::EmbeddedIn.build(self, name, options)
|
114
|
+
Reflection.add_reflection self, name, reflection
|
115
|
+
end
|
116
|
+
|
117
|
+
def embeds_one(name, **options)
|
118
|
+
reflection = Embedded::Builder::EmbedsOne.build(self, name, options)
|
119
|
+
Reflection.add_reflection self, name, reflection
|
120
|
+
end
|
121
|
+
|
122
|
+
def embeds_many(name, **options)
|
123
|
+
reflection = Embedded::Builder::EmbedsMany.build(self, name, options)
|
124
|
+
Reflection.add_reflection self, name, reflection
|
125
|
+
end
|
126
|
+
|
127
|
+
def association_names
|
128
|
+
@association_names ||=
|
129
|
+
if !abstract_class?
|
130
|
+
reflections.keys.map(&:to_sym)
|
131
|
+
else
|
132
|
+
[]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def embedded_association_names
|
137
|
+
@association_names ||=
|
138
|
+
if !abstract_class?
|
139
|
+
reflections.select { |_, r| r.embedded? }.keys.map(&:to_sym)
|
140
|
+
else
|
141
|
+
[]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/wrap"
|
4
|
+
|
5
|
+
module ActiveEntity
|
6
|
+
module Associations
|
7
|
+
module Embedded
|
8
|
+
# = Active Entity Associations
|
9
|
+
#
|
10
|
+
# This is the root class of all associations ('+ Foo' signifies an included module Foo):
|
11
|
+
#
|
12
|
+
# Association
|
13
|
+
# SingularAssociation
|
14
|
+
# HasOneAssociation + ForeignAssociation
|
15
|
+
# HasOneThroughAssociation + ThroughAssociation
|
16
|
+
# BelongsToAssociation
|
17
|
+
# BelongsToPolymorphicAssociation
|
18
|
+
# CollectionAssociation
|
19
|
+
# HasManyAssociation + ForeignAssociation
|
20
|
+
# HasManyThroughAssociation + ThroughAssociation
|
21
|
+
class Association #:nodoc:
|
22
|
+
attr_reader :owner, :target, :reflection
|
23
|
+
|
24
|
+
delegate :options, to: :reflection
|
25
|
+
|
26
|
+
def initialize(owner, reflection)
|
27
|
+
reflection.check_validity!
|
28
|
+
|
29
|
+
@owner, @reflection = owner, reflection
|
30
|
+
|
31
|
+
@target = nil
|
32
|
+
@inversed = false
|
33
|
+
end
|
34
|
+
|
35
|
+
# Has the \target been already \loaded?
|
36
|
+
def loaded?
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
# Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
|
41
|
+
attr_writer :target
|
42
|
+
|
43
|
+
# Set the inverse association, if possible
|
44
|
+
def set_inverse_instance(record)
|
45
|
+
if inverse = inverse_association_for(record)
|
46
|
+
inverse.inversed_from(owner)
|
47
|
+
end
|
48
|
+
record
|
49
|
+
end
|
50
|
+
|
51
|
+
# Remove the inverse association, if possible
|
52
|
+
def remove_inverse_instance(record)
|
53
|
+
if inverse = inverse_association_for(record)
|
54
|
+
inverse.inversed_from(nil)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def inversed_from(record)
|
59
|
+
self.target = record
|
60
|
+
@inversed = !!record
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the class of the target. belongs_to polymorphic overrides this to look at the
|
64
|
+
# polymorphic_type field on the owner.
|
65
|
+
def klass
|
66
|
+
reflection.klass
|
67
|
+
end
|
68
|
+
|
69
|
+
def extensions
|
70
|
+
reflection.extensions
|
71
|
+
end
|
72
|
+
|
73
|
+
# We can't dump @reflection and @through_reflection since it contains the scope proc
|
74
|
+
def marshal_dump
|
75
|
+
ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
|
76
|
+
[@reflection.name, ivars]
|
77
|
+
end
|
78
|
+
|
79
|
+
def marshal_load(data)
|
80
|
+
reflection_name, ivars = data
|
81
|
+
ivars.each { |name, val| instance_variable_set(name, val) }
|
82
|
+
@reflection = @owner.class._reflect_on_association(reflection_name)
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize_attributes(record, attributes = {}) #:nodoc:
|
86
|
+
record.send(:_assign_attributes, attributes) if attributes.any?
|
87
|
+
set_inverse_instance(record)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Raises ActiveEntity::AssociationTypeMismatch unless +record+ is of
|
93
|
+
# the kind of the class of the associated objects. Meant to be used as
|
94
|
+
# a sanity check when you are about to assign an associated record.
|
95
|
+
def raise_on_type_mismatch!(record)
|
96
|
+
unless record.is_a?(reflection.klass)
|
97
|
+
fresh_class = reflection.class_name.safe_constantize
|
98
|
+
unless fresh_class && record.is_a?(fresh_class)
|
99
|
+
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
|
100
|
+
"got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
|
101
|
+
raise ActiveEntity::AssociationTypeMismatch, message
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def inverse_association_for(record)
|
107
|
+
if invertible_for?(record)
|
108
|
+
record.association(inverse_reflection_for(record).name)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Can be redefined by subclasses, notably polymorphic belongs_to
|
113
|
+
# The record parameter is necessary to support polymorphic inverses as we must check for
|
114
|
+
# the association in the specific class of the record.
|
115
|
+
def inverse_reflection_for(record)
|
116
|
+
reflection.inverse_of
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns true if inverse association on the given record needs to be set.
|
120
|
+
# This method is redefined by subclasses.
|
121
|
+
def invertible_for?(record)
|
122
|
+
inverse_reflection_for(record)
|
123
|
+
end
|
124
|
+
|
125
|
+
def build_record(attributes)
|
126
|
+
reflection.build_association(attributes) do |record|
|
127
|
+
initialize_attributes(record, attributes)
|
128
|
+
yield(record) if block_given?
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This is the parent Association class which defines the variables
|
4
|
+
# used by all associations.
|
5
|
+
#
|
6
|
+
# The hierarchy is defined as follows:
|
7
|
+
# Association
|
8
|
+
# - SingularAssociation
|
9
|
+
# - BelongsToAssociation
|
10
|
+
# - HasOneAssociation
|
11
|
+
# - CollectionAssociation
|
12
|
+
# - HasManyAssociation
|
13
|
+
|
14
|
+
module ActiveEntity::Associations::Embedded::Builder # :nodoc:
|
15
|
+
class Association #:nodoc:
|
16
|
+
class << self
|
17
|
+
attr_accessor :extensions
|
18
|
+
end
|
19
|
+
self.extensions = []
|
20
|
+
|
21
|
+
VALID_OPTIONS = [:class_name, :anonymous_class, :validate] # :nodoc:
|
22
|
+
|
23
|
+
def self.build(model, name, options)
|
24
|
+
if model.dangerous_attribute_method?(name)
|
25
|
+
raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
|
26
|
+
"this will conflict with a method #{name} already defined by Active Entity. " \
|
27
|
+
"Please choose a different association name."
|
28
|
+
end
|
29
|
+
|
30
|
+
reflection = create_reflection model, name, options
|
31
|
+
define_accessors model, reflection
|
32
|
+
define_callbacks model, reflection
|
33
|
+
define_validations model, reflection
|
34
|
+
reflection
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.create_reflection(model, name, options)
|
38
|
+
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
|
39
|
+
|
40
|
+
validate_options(options)
|
41
|
+
|
42
|
+
ActiveEntity::Reflection.create(macro, name, nil, options, model)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.macro
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.valid_options(options)
|
50
|
+
VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.validate_options(options)
|
54
|
+
options.assert_valid_keys(valid_options(options))
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.define_callbacks(model, reflection)
|
58
|
+
Association.extensions.each do |extension|
|
59
|
+
extension.build model, reflection
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Defines the setter and getter methods for the association
|
64
|
+
# class Post < ActiveEntity::Base
|
65
|
+
# has_many :comments
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# Post.first.comments and Post.first.comments= methods are defined by this method...
|
69
|
+
def self.define_accessors(model, reflection)
|
70
|
+
mixin = model.generated_association_methods
|
71
|
+
name = reflection.name
|
72
|
+
define_readers(mixin, name)
|
73
|
+
define_writers(mixin, name)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.define_readers(mixin, name)
|
77
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
78
|
+
def #{name}
|
79
|
+
association(:#{name}).reader
|
80
|
+
end
|
81
|
+
CODE
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.define_writers(mixin, name)
|
85
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
86
|
+
def #{name}=(value)
|
87
|
+
association(:#{name}).writer(value)
|
88
|
+
end
|
89
|
+
CODE
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.define_validations(_model, _reflection)
|
93
|
+
# noop
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.valid_dependent_options
|
97
|
+
raise NotImplementedError
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_entity/associations"
|
4
|
+
|
5
|
+
module ActiveEntity::Associations::Embedded::Builder # :nodoc:
|
6
|
+
class CollectionAssociation < Association #:nodoc:
|
7
|
+
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
|
8
|
+
|
9
|
+
def self.valid_options(options)
|
10
|
+
super + [:before_add, :after_add, :before_remove, :after_remove, :extend]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.define_callbacks(model, reflection)
|
14
|
+
super
|
15
|
+
name = reflection.name
|
16
|
+
options = reflection.options
|
17
|
+
CALLBACKS.each { |callback_name|
|
18
|
+
define_callback(model, callback_name, name, options)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.define_extensions(model, name)
|
23
|
+
if block_given?
|
24
|
+
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
|
25
|
+
extension = Module.new(&Proc.new)
|
26
|
+
model.module_parent.const_set(extension_module_name, extension)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.define_callback(model, callback_name, name, options)
|
31
|
+
full_callback_name = "#{callback_name}_for_#{name}"
|
32
|
+
|
33
|
+
# TODO : why do i need method_defined? I think its because of the inheritance chain
|
34
|
+
model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
|
35
|
+
callbacks = Array(options[callback_name.to_sym]).map do |callback|
|
36
|
+
case callback
|
37
|
+
when Symbol
|
38
|
+
->(_method, owner, record) { owner.send(callback, record) }
|
39
|
+
when Proc
|
40
|
+
->(_method, owner, record) { callback.call(owner, record) }
|
41
|
+
else
|
42
|
+
->(method, owner, record) { callback.send(method, owner, record) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
model.send "#{full_callback_name}=", callbacks
|
46
|
+
end
|
47
|
+
|
48
|
+
# Defines the setter and getter methods for the collection_singular_ids.
|
49
|
+
def self.define_readers(mixin, name)
|
50
|
+
super
|
51
|
+
|
52
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
53
|
+
def #{name.to_s.singularize}_ids
|
54
|
+
association(:#{name}).ids_reader
|
55
|
+
end
|
56
|
+
CODE
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.define_writers(mixin, name)
|
60
|
+
super
|
61
|
+
|
62
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
63
|
+
def #{name.to_s.singularize}_ids=(ids)
|
64
|
+
association(:#{name}).ids_writer(ids)
|
65
|
+
end
|
66
|
+
CODE
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|