tenacity 0.3.0 → 0.4.0
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/EXTEND.rdoc +9 -13
- data/Rakefile +6 -0
- data/history.txt +17 -0
- data/lib/tenacity.rb +13 -9
- data/lib/tenacity/associate_proxy.rb +56 -0
- data/lib/tenacity/associates_proxy.rb +5 -3
- data/lib/tenacity/association.rb +81 -9
- data/lib/tenacity/associations/belongs_to.rb +28 -16
- data/lib/tenacity/associations/has_many.rb +113 -53
- data/lib/tenacity/associations/has_one.rb +33 -14
- data/lib/tenacity/class_methods.rb +117 -9
- data/lib/tenacity/errors.rb +9 -0
- data/lib/tenacity/instance_methods.rb +40 -3
- data/lib/tenacity/orm_ext/activerecord.rb +114 -95
- data/lib/tenacity/orm_ext/couchrest.rb +132 -113
- data/lib/tenacity/orm_ext/datamapper.rb +138 -112
- data/lib/tenacity/orm_ext/helpers.rb +36 -0
- data/lib/tenacity/orm_ext/mongo_mapper.rb +102 -84
- data/lib/tenacity/orm_ext/mongoid.rb +106 -87
- data/lib/tenacity/orm_ext/sequel.rb +137 -110
- data/lib/tenacity/version.rb +1 -1
- data/tenacity.gemspec +2 -1
- data/test/association_features/belongs_to_test.rb +46 -1
- data/test/association_features/has_many_test.rb +187 -2
- data/test/association_features/has_one_test.rb +57 -2
- data/test/associations/belongs_to_test.rb +41 -7
- data/test/associations/has_many_test.rb +59 -10
- data/test/associations/has_one_test.rb +57 -2
- data/test/fixtures/active_record_car.rb +5 -4
- data/test/fixtures/active_record_climate_control_unit.rb +1 -0
- data/test/fixtures/active_record_engine.rb +1 -0
- data/test/fixtures/active_record_has_many_target.rb +7 -0
- data/test/fixtures/active_record_has_one_target.rb +7 -0
- data/test/fixtures/active_record_object.rb +19 -0
- data/test/fixtures/couch_rest_has_many_target.rb +7 -0
- data/test/fixtures/couch_rest_has_one_target.rb +7 -0
- data/test/fixtures/couch_rest_object.rb +14 -0
- data/test/fixtures/data_mapper_has_many_target.rb +23 -3
- data/test/fixtures/data_mapper_has_one_target.rb +23 -3
- data/test/fixtures/data_mapper_object.rb +14 -0
- data/test/fixtures/mongo_mapper_air_filter.rb +6 -0
- data/test/fixtures/mongo_mapper_alternator.rb +7 -0
- data/test/fixtures/mongo_mapper_autosave_false_has_many_target.rb +8 -0
- data/test/fixtures/mongo_mapper_autosave_false_has_one_target.rb +8 -0
- data/test/fixtures/mongo_mapper_autosave_true_has_many_target.rb +8 -0
- data/test/fixtures/mongo_mapper_autosave_true_has_one_target.rb +8 -0
- data/test/fixtures/mongo_mapper_circuit_board.rb +6 -0
- data/test/fixtures/mongo_mapper_dashboard.rb +5 -2
- data/test/fixtures/mongo_mapper_has_many_target.rb +7 -0
- data/test/fixtures/mongo_mapper_has_one_target.rb +7 -0
- data/test/fixtures/mongo_mapper_object.rb +14 -0
- data/test/fixtures/mongo_mapper_wheel.rb +2 -0
- data/test/fixtures/mongo_mapper_windows.rb +6 -0
- data/test/fixtures/mongoid_has_many_target.rb +7 -0
- data/test/fixtures/mongoid_has_one_target.rb +7 -0
- data/test/fixtures/mongoid_object.rb +14 -0
- data/test/fixtures/sequel_has_many_target.rb +7 -0
- data/test/fixtures/sequel_has_one_target.rb +7 -0
- data/test/fixtures/sequel_object.rb +14 -0
- data/test/helpers/active_record_test_helper.rb +87 -8
- data/test/helpers/couch_rest_test_helper.rb +7 -0
- data/test/helpers/data_mapper_test_helper.rb +41 -3
- data/test/helpers/sequel_test_helper.rb +65 -9
- data/test/orm_ext/activerecord_test.rb +1 -1
- data/test/orm_ext/datamapper_test.rb +33 -24
- data/test/orm_ext/mongo_mapper_test.rb +6 -6
- data/test/orm_ext/mongoid_test.rb +6 -6
- data/test/orm_ext/sequel_test.rb +1 -1
- data/test/test_helper.rb +18 -9
- metadata +119 -55
@@ -1,140 +1,159 @@
|
|
1
1
|
module Tenacity
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
model.
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
2
|
+
module OrmExt
|
3
|
+
# Tenacity relationships on CouchRest objects require no special keys
|
4
|
+
# defined on the object. Tenacity will define the keys that it needs
|
5
|
+
# to support the relationships. Take the following class for example:
|
6
|
+
#
|
7
|
+
# class Car < CouchRest::ExtendedDocument
|
8
|
+
# include Tenacity
|
9
|
+
#
|
10
|
+
# t_has_many :wheels
|
11
|
+
# t_has_one :dashboard
|
12
|
+
# t_belongs_to :driver
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# == t_belongs_to
|
16
|
+
#
|
17
|
+
# The +t_belongs_to+ association will define a property named after the association.
|
18
|
+
# The example above will create a property named <tt>:driver_id</tt>
|
19
|
+
#
|
20
|
+
#
|
21
|
+
# == t_has_one
|
22
|
+
#
|
23
|
+
# The +t_has_one+ association will not define any new properties on the object, since
|
24
|
+
# the associated object holds the foreign key.
|
25
|
+
#
|
26
|
+
#
|
27
|
+
# == t_has_many
|
28
|
+
#
|
29
|
+
# The +t_has_many+ association will define a property named after the association.
|
30
|
+
# The example above will create a property named <tt>:wheels_ids</tt>
|
31
|
+
#
|
32
|
+
module CouchRest
|
33
|
+
|
34
|
+
def self.setup(model) #:nodoc:
|
35
|
+
begin
|
36
|
+
require 'couchrest_model'
|
37
|
+
if model.ancestors.include?(::CouchRest::Model::Base)
|
38
|
+
model.send :include, CouchRest::InstanceMethods
|
39
|
+
model.extend CouchRest::ClassMethods
|
40
|
+
end
|
41
|
+
rescue LoadError
|
42
|
+
# CouchRest::Model not available
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
begin
|
46
|
+
require 'couchrest_extended_document'
|
47
|
+
if model.ancestors.include?(::CouchRest::ExtendedDocument)
|
48
|
+
model.send :include, CouchRest::InstanceMethods
|
49
|
+
model.extend CouchRest::ClassMethods
|
50
|
+
end
|
51
|
+
rescue LoadError
|
52
|
+
# CouchRest::ExtendedDocument not available
|
49
53
|
end
|
50
|
-
rescue LoadError
|
51
|
-
# CouchRest::ExtendedDocument not available
|
52
|
-
end
|
53
54
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
55
|
+
# For pre 1.0 versions of couchrest
|
56
|
+
begin
|
57
|
+
require 'couchrest'
|
58
|
+
if model.ancestors.include?(::CouchRest::ExtendedDocument)
|
59
|
+
model.send :include, CouchRest::InstanceMethods
|
60
|
+
model.extend CouchRest::ClassMethods
|
61
|
+
end
|
62
|
+
rescue LoadError
|
63
|
+
rescue NameError
|
64
|
+
# CouchRest::ExtendedDocument not available
|
60
65
|
end
|
61
|
-
rescue LoadError
|
62
|
-
rescue NameError
|
63
|
-
# CouchRest::ExtendedDocument not available
|
64
66
|
end
|
65
|
-
end
|
66
67
|
|
67
|
-
|
68
|
-
|
69
|
-
(id.nil? || id.strip == "") ? nil : get(id)
|
70
|
-
end
|
68
|
+
module ClassMethods #:nodoc:
|
69
|
+
include Tenacity::OrmExt::Helpers
|
71
70
|
|
72
|
-
|
73
|
-
|
71
|
+
def _t_id_type
|
72
|
+
String
|
73
|
+
end
|
74
74
|
|
75
|
-
|
76
|
-
|
77
|
-
result['rows'].each do |row|
|
78
|
-
docs << (row['doc'].nil? ? nil : create_from_database(row['doc']))
|
75
|
+
def _t_find(id)
|
76
|
+
(id.nil? || id.strip == "") ? nil : get(_t_serialize(id))
|
79
77
|
end
|
80
|
-
docs.reject { |doc| doc.nil? }
|
81
|
-
end
|
82
78
|
|
83
|
-
|
84
|
-
|
85
|
-
|
79
|
+
def _t_find_bulk(ids)
|
80
|
+
return [] if ids.nil? || ids.empty?
|
81
|
+
ids = [ids] unless ids.class == Array
|
86
82
|
|
87
|
-
|
88
|
-
|
89
|
-
|
83
|
+
docs = []
|
84
|
+
result = database.get_bulk(_t_serialize_ids(ids))
|
85
|
+
result['rows'].each do |row|
|
86
|
+
docs << (row['doc'].nil? ? nil : create_from_database(row['doc']))
|
87
|
+
end
|
88
|
+
docs.reject { |doc| doc.nil? }
|
89
|
+
end
|
90
90
|
|
91
|
-
|
92
|
-
|
93
|
-
property association.foreign_keys_property, :type => [String]
|
94
|
-
view_by association.foreign_keys_property
|
95
|
-
after_save { |record| record.class._t_save_associates(record, association) if record.class.respond_to?(:_t_save_associates) }
|
91
|
+
def _t_find_first_by_associate(property, id)
|
92
|
+
self.send("by_#{property}", :key => _t_serialize(id)).first
|
96
93
|
end
|
97
|
-
end
|
98
94
|
|
99
|
-
|
100
|
-
|
101
|
-
unless self.respond_to?(property_name)
|
102
|
-
property property_name, :type => String
|
103
|
-
view_by property_name
|
104
|
-
before_save { |record| _t_stringify_belongs_to_value(record, association) if self.respond_to?(:_t_stringify_belongs_to_value) }
|
95
|
+
def _t_find_all_by_associate(property, id)
|
96
|
+
self.send("by_#{property}", :key => _t_serialize(id))
|
105
97
|
end
|
106
|
-
end
|
107
98
|
|
108
|
-
|
109
|
-
|
110
|
-
if run_callbacks
|
111
|
-
docs.each { |doc| doc.destroy }
|
112
|
-
else
|
113
|
-
docs.each { |doc| database.delete_doc(doc) }
|
99
|
+
def _t_initialize_tenacity
|
100
|
+
after_save { |record| record._t_save_autosave_associations }
|
114
101
|
end
|
115
|
-
end
|
116
|
-
end
|
117
102
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
new_doc = database.get(self.id)
|
122
|
-
self.clear
|
123
|
-
new_doc.each { |k,v| self[k] = new_doc[k] }
|
124
|
-
end
|
103
|
+
def _t_initialize_has_one_association(association)
|
104
|
+
before_destroy { |record| record._t_cleanup_has_one_association(association) }
|
105
|
+
end
|
125
106
|
|
126
|
-
|
127
|
-
|
128
|
-
|
107
|
+
def _t_initialize_has_many_association(association)
|
108
|
+
unless self.respond_to?(association.foreign_keys_property)
|
109
|
+
property association.foreign_keys_property, :type => [id_class_for(association)]
|
110
|
+
view_by association.foreign_keys_property
|
111
|
+
after_save { |record| record.class._t_save_associates(record, association) if record.class.respond_to?(:_t_save_associates) }
|
112
|
+
after_destroy { |record| record._t_cleanup_has_many_association(association) }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def _t_initialize_belongs_to_association(association)
|
117
|
+
property_name = association.foreign_key
|
118
|
+
unless self.respond_to?(property_name)
|
119
|
+
property property_name, :type => id_class_for(association)
|
120
|
+
property association.polymorphic_type, :type => String if association.polymorphic?
|
121
|
+
view_by property_name
|
122
|
+
after_destroy { |record| record._t_cleanup_belongs_to_association(association) }
|
123
|
+
end
|
124
|
+
end
|
129
125
|
|
130
|
-
|
131
|
-
|
126
|
+
def _t_delete(ids, run_callbacks=true)
|
127
|
+
docs = _t_find_bulk(ids)
|
128
|
+
if run_callbacks
|
129
|
+
docs.each { |doc| doc.destroy }
|
130
|
+
else
|
131
|
+
docs.each { |doc| database.delete_doc(doc) }
|
132
|
+
end
|
133
|
+
end
|
132
134
|
end
|
133
135
|
|
134
|
-
|
135
|
-
|
136
|
+
module InstanceMethods #:nodoc:
|
137
|
+
def _t_reload
|
138
|
+
return if self.id.nil?
|
139
|
+
new_doc = database.get(self.id)
|
140
|
+
self.clear
|
141
|
+
new_doc.each { |k,v| self[k] = new_doc[k] }
|
142
|
+
end
|
143
|
+
|
144
|
+
def _t_associate_many(association, associate_ids)
|
145
|
+
self.send(association.foreign_keys_property + '=', associate_ids)
|
146
|
+
end
|
147
|
+
|
148
|
+
def _t_get_associate_ids(association)
|
149
|
+
self.send(association.foreign_keys_property) || []
|
150
|
+
end
|
151
|
+
|
152
|
+
def _t_clear_associates(association)
|
153
|
+
self.send(association.foreign_keys_property + '=', [])
|
154
|
+
end
|
136
155
|
end
|
137
|
-
end
|
138
156
|
|
157
|
+
end
|
139
158
|
end
|
140
159
|
end
|
@@ -1,139 +1,165 @@
|
|
1
1
|
module Tenacity
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
model.
|
49
|
-
|
2
|
+
module OrmExt
|
3
|
+
# Tenacity relationships on DataMapper objects require that certain columns
|
4
|
+
# exist on the associated table, and that join tables exist for one-to-many
|
5
|
+
# relationships. Take the following class for example:
|
6
|
+
#
|
7
|
+
# class Car
|
8
|
+
# include DataMapper::Resource
|
9
|
+
# include Tenacity
|
10
|
+
#
|
11
|
+
# property :id, Serial
|
12
|
+
# property :driver_id, String
|
13
|
+
#
|
14
|
+
# t_has_many :wheels
|
15
|
+
# t_has_one :dashboard
|
16
|
+
# t_belongs_to :driver
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
#
|
20
|
+
# == t_belongs_to
|
21
|
+
#
|
22
|
+
# The +t_belongs_to+ association requires that a property exist in the table
|
23
|
+
# to hold the id of the assoicated object.
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# == t_has_one
|
27
|
+
#
|
28
|
+
# The +t_has_one+ association requires no special column in the table, since
|
29
|
+
# the associated object holds the foreign key.
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# == t_has_many
|
33
|
+
#
|
34
|
+
# The +t_has_many+ association requires that a join table exist to store the
|
35
|
+
# associations. The name of the join table follows ActiveRecord conventions.
|
36
|
+
# The name of the join table in this example would be cars_wheels, since cars
|
37
|
+
# comes before wheels when shorted alphabetically.
|
38
|
+
#
|
39
|
+
# create_table :car_wheels do
|
40
|
+
# column :car_id, Integer
|
41
|
+
# column :wheel_id, String
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
module DataMapper
|
45
|
+
|
46
|
+
def self.setup(model)
|
47
|
+
require 'datamapper'
|
48
|
+
if model.included_modules.include?(::DataMapper::Resource)
|
49
|
+
model.send :include, DataMapper::InstanceMethods
|
50
|
+
model.extend DataMapper::ClassMethods
|
51
|
+
end
|
52
|
+
rescue LoadError
|
53
|
+
# DataMapper not available
|
50
54
|
end
|
51
|
-
rescue LoadError
|
52
|
-
# DataMapper not available
|
53
|
-
end
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
get(id)
|
58
|
-
end
|
56
|
+
module ClassMethods #:nodoc:
|
57
|
+
include Tenacity::OrmExt::Helpers
|
59
58
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
59
|
+
def _t_id_type
|
60
|
+
Integer
|
61
|
+
end
|
64
62
|
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
def _t_find(id)
|
64
|
+
get(_t_serialize(id))
|
65
|
+
end
|
68
66
|
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
def _t_find_bulk(ids)
|
68
|
+
return [] if ids.nil? || ids == []
|
69
|
+
all(:id => _t_serialize_ids(ids))
|
70
|
+
end
|
72
71
|
|
73
|
-
|
74
|
-
|
75
|
-
record.class._t_save_associates(record, association)
|
72
|
+
def _t_find_first_by_associate(property, id)
|
73
|
+
first(property => _t_serialize(id))
|
76
74
|
end
|
77
|
-
end
|
78
75
|
|
79
|
-
|
80
|
-
|
81
|
-
record.class._t_stringify_belongs_to_value(record, association)
|
76
|
+
def _t_find_all_by_associate(property, id)
|
77
|
+
all(property => _t_serialize(id))
|
82
78
|
end
|
83
|
-
end
|
84
79
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
else
|
90
|
-
objects.each { |object| object.destroy! }
|
80
|
+
def _t_initialize_tenacity
|
81
|
+
after :save do |record|
|
82
|
+
record._t_save_autosave_associations
|
83
|
+
end
|
91
84
|
end
|
92
|
-
end
|
93
|
-
end
|
94
85
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
def save
|
100
|
-
if clean?
|
101
|
-
taint!; super; reload
|
102
|
-
else
|
103
|
-
super
|
86
|
+
def _t_initialize_has_one_association(association)
|
87
|
+
after :destroy do |record|
|
88
|
+
record._t_cleanup_has_one_association(association)
|
89
|
+
end
|
104
90
|
end
|
105
|
-
end
|
106
91
|
|
107
|
-
|
108
|
-
|
109
|
-
|
92
|
+
def _t_initialize_has_many_association(association)
|
93
|
+
after :save do |record|
|
94
|
+
record.class._t_save_associates(record, association)
|
95
|
+
end
|
110
96
|
|
111
|
-
|
112
|
-
|
113
|
-
|
97
|
+
after :destroy do |record|
|
98
|
+
record._t_cleanup_has_many_association(association)
|
99
|
+
end
|
100
|
+
end
|
114
101
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
associate_ids.each do |associate_id|
|
119
|
-
self.repository.adapter.execute("insert into #{association.join_table} (#{association.association_key}, #{association.association_foreign_key}) values (#{self.id}, '#{associate_id}')")
|
102
|
+
def _t_initialize_belongs_to_association(association)
|
103
|
+
after :destroy do |record|
|
104
|
+
record._t_cleanup_belongs_to_association(association)
|
120
105
|
end
|
121
106
|
end
|
122
|
-
end
|
123
107
|
|
124
|
-
|
125
|
-
|
126
|
-
|
108
|
+
def _t_delete(ids, run_callbacks=true)
|
109
|
+
objects = _t_find_bulk(ids)
|
110
|
+
if run_callbacks
|
111
|
+
objects.each { |object| object.destroy }
|
112
|
+
else
|
113
|
+
objects.each { |object| object.destroy! }
|
114
|
+
end
|
115
|
+
end
|
127
116
|
end
|
128
117
|
|
129
|
-
|
118
|
+
module InstanceMethods #:nodoc:
|
119
|
+
include Tenacity::OrmExt::Helpers
|
120
|
+
|
121
|
+
# DataMapper will not issue callbacks unless at least one of the properties is dirty.
|
122
|
+
# So, taint the object by marking one of the attributes as dirty, save the object,
|
123
|
+
# and resture the persisted_state by reloading the object from the database.
|
124
|
+
def save
|
125
|
+
if clean?
|
126
|
+
taint!; super; reload
|
127
|
+
else
|
128
|
+
super
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def _t_reload
|
133
|
+
reload
|
134
|
+
end
|
135
|
+
|
136
|
+
def _t_clear_associates(association)
|
137
|
+
self.repository.adapter.execute("delete from #{association.join_table} where #{association.association_key} = #{_t_serialize_id_for_sql(self.id)}")
|
138
|
+
end
|
130
139
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
140
|
+
def _t_associate_many(association, associate_ids)
|
141
|
+
self.transaction do
|
142
|
+
_t_clear_associates(association)
|
143
|
+
associate_ids.each do |associate_id|
|
144
|
+
self.repository.adapter.execute("insert into #{association.join_table} (#{association.association_key}, #{association.association_foreign_key}) values (#{_t_serialize_id_for_sql(self.id)}, #{_t_serialize_id_for_sql(associate_id)})")
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def _t_get_associate_ids(association)
|
150
|
+
return [] if self.id.nil?
|
151
|
+
self.repository.adapter.select("select #{association.association_foreign_key} from #{association.join_table} where #{association.association_key} = #{_t_serialize_id_for_sql(self.id)}")
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def taint!
|
157
|
+
property = properties.first.name
|
158
|
+
self.persisted_state = ::DataMapper::Resource::State::Dirty.new(self) unless self.persisted_state.kind_of?(::DataMapper::Resource::State::Dirty)
|
159
|
+
self.persisted_state.original_attributes[properties[property]] = self.send(property)
|
160
|
+
end
|
135
161
|
end
|
136
|
-
end
|
137
162
|
|
163
|
+
end
|
138
164
|
end
|
139
165
|
end
|