deferring 0.0.1
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 +18 -0
- data/.rspec +2 -0
- data/Appraisals +15 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +236 -0
- data/Rakefile +11 -0
- data/deferring.gemspec +31 -0
- data/gemfiles/rails_30.gemfile +7 -0
- data/gemfiles/rails_30.gemfile.lock +51 -0
- data/gemfiles/rails_32.gemfile +7 -0
- data/gemfiles/rails_32.gemfile.lock +53 -0
- data/gemfiles/rails_4.gemfile +7 -0
- data/gemfiles/rails_4.gemfile.lock +58 -0
- data/gemfiles/rails_40.gemfile +7 -0
- data/gemfiles/rails_40.gemfile.lock +59 -0
- data/gemfiles/rails_41.gemfile +7 -0
- data/gemfiles/rails_41.gemfile.lock +58 -0
- data/lib/deferring.rb +150 -0
- data/lib/deferring/deferred_association.rb +187 -0
- data/lib/deferring/version.rb +5 -0
- data/spec/lib/deferring_spec.rb +536 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/active_record.rb +28 -0
- data/spec/support/models.rb +67 -0
- data/spec/support/rails_versions.rb +13 -0
- metadata +175 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
raincheck (0.0.1)
|
5
|
+
activerecord (> 3.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.1.0)
|
11
|
+
activesupport (= 4.1.0)
|
12
|
+
builder (~> 3.1)
|
13
|
+
activerecord (4.1.0)
|
14
|
+
activemodel (= 4.1.0)
|
15
|
+
activesupport (= 4.1.0)
|
16
|
+
arel (~> 5.0.0)
|
17
|
+
activesupport (4.1.0)
|
18
|
+
i18n (~> 0.6, >= 0.6.9)
|
19
|
+
json (~> 1.7, >= 1.7.7)
|
20
|
+
minitest (~> 5.1)
|
21
|
+
thread_safe (~> 0.1)
|
22
|
+
tzinfo (~> 1.1)
|
23
|
+
appraisal (1.0.0)
|
24
|
+
bundler
|
25
|
+
rake
|
26
|
+
thor (>= 0.14.0)
|
27
|
+
arel (5.0.1.20140414130214)
|
28
|
+
builder (3.2.2)
|
29
|
+
diff-lcs (1.2.5)
|
30
|
+
i18n (0.6.9)
|
31
|
+
json (1.8.1)
|
32
|
+
minitest (5.3.3)
|
33
|
+
rake (10.3.1)
|
34
|
+
rspec (2.14.1)
|
35
|
+
rspec-core (~> 2.14.0)
|
36
|
+
rspec-expectations (~> 2.14.0)
|
37
|
+
rspec-mocks (~> 2.14.0)
|
38
|
+
rspec-core (2.14.8)
|
39
|
+
rspec-expectations (2.14.5)
|
40
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
41
|
+
rspec-mocks (2.14.6)
|
42
|
+
sqlite3 (1.3.9)
|
43
|
+
thor (0.19.1)
|
44
|
+
thread_safe (0.3.3)
|
45
|
+
tzinfo (1.1.0)
|
46
|
+
thread_safe (~> 0.1)
|
47
|
+
|
48
|
+
PLATFORMS
|
49
|
+
ruby
|
50
|
+
|
51
|
+
DEPENDENCIES
|
52
|
+
activerecord (= 4.1.0)
|
53
|
+
appraisal
|
54
|
+
bundler (~> 1.3)
|
55
|
+
raincheck!
|
56
|
+
rake
|
57
|
+
rspec
|
58
|
+
sqlite3
|
@@ -0,0 +1,59 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
deferring (0.0.1)
|
5
|
+
activerecord (> 3.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.0.4)
|
11
|
+
activesupport (= 4.0.4)
|
12
|
+
builder (~> 3.1.0)
|
13
|
+
activerecord (4.0.4)
|
14
|
+
activemodel (= 4.0.4)
|
15
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
16
|
+
activesupport (= 4.0.4)
|
17
|
+
arel (~> 4.0.0)
|
18
|
+
activerecord-deprecated_finders (1.0.3)
|
19
|
+
activesupport (4.0.4)
|
20
|
+
i18n (~> 0.6, >= 0.6.9)
|
21
|
+
minitest (~> 4.2)
|
22
|
+
multi_json (~> 1.3)
|
23
|
+
thread_safe (~> 0.1)
|
24
|
+
tzinfo (~> 0.3.37)
|
25
|
+
appraisal (1.0.0)
|
26
|
+
bundler
|
27
|
+
rake
|
28
|
+
thor (>= 0.14.0)
|
29
|
+
arel (4.0.2)
|
30
|
+
builder (3.1.4)
|
31
|
+
diff-lcs (1.2.5)
|
32
|
+
i18n (0.6.9)
|
33
|
+
minitest (4.7.5)
|
34
|
+
multi_json (1.9.2)
|
35
|
+
rake (10.3.1)
|
36
|
+
rspec (2.14.1)
|
37
|
+
rspec-core (~> 2.14.0)
|
38
|
+
rspec-expectations (~> 2.14.0)
|
39
|
+
rspec-mocks (~> 2.14.0)
|
40
|
+
rspec-core (2.14.8)
|
41
|
+
rspec-expectations (2.14.5)
|
42
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
43
|
+
rspec-mocks (2.14.6)
|
44
|
+
sqlite3 (1.3.9)
|
45
|
+
thor (0.19.1)
|
46
|
+
thread_safe (0.3.3)
|
47
|
+
tzinfo (0.3.39)
|
48
|
+
|
49
|
+
PLATFORMS
|
50
|
+
ruby
|
51
|
+
|
52
|
+
DEPENDENCIES
|
53
|
+
activerecord (= 4.0.4)
|
54
|
+
appraisal
|
55
|
+
bundler (~> 1.3)
|
56
|
+
deferring!
|
57
|
+
rake
|
58
|
+
rspec
|
59
|
+
sqlite3
|
@@ -0,0 +1,58 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
deferring (0.0.1)
|
5
|
+
activerecord (> 3.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.1.0)
|
11
|
+
activesupport (= 4.1.0)
|
12
|
+
builder (~> 3.1)
|
13
|
+
activerecord (4.1.0)
|
14
|
+
activemodel (= 4.1.0)
|
15
|
+
activesupport (= 4.1.0)
|
16
|
+
arel (~> 5.0.0)
|
17
|
+
activesupport (4.1.0)
|
18
|
+
i18n (~> 0.6, >= 0.6.9)
|
19
|
+
json (~> 1.7, >= 1.7.7)
|
20
|
+
minitest (~> 5.1)
|
21
|
+
thread_safe (~> 0.1)
|
22
|
+
tzinfo (~> 1.1)
|
23
|
+
appraisal (1.0.0)
|
24
|
+
bundler
|
25
|
+
rake
|
26
|
+
thor (>= 0.14.0)
|
27
|
+
arel (5.0.1.20140414130214)
|
28
|
+
builder (3.2.2)
|
29
|
+
diff-lcs (1.2.5)
|
30
|
+
i18n (0.6.9)
|
31
|
+
json (1.8.1)
|
32
|
+
minitest (5.3.3)
|
33
|
+
rake (10.3.1)
|
34
|
+
rspec (2.14.1)
|
35
|
+
rspec-core (~> 2.14.0)
|
36
|
+
rspec-expectations (~> 2.14.0)
|
37
|
+
rspec-mocks (~> 2.14.0)
|
38
|
+
rspec-core (2.14.8)
|
39
|
+
rspec-expectations (2.14.5)
|
40
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
41
|
+
rspec-mocks (2.14.6)
|
42
|
+
sqlite3 (1.3.9)
|
43
|
+
thor (0.19.1)
|
44
|
+
thread_safe (0.3.3)
|
45
|
+
tzinfo (1.1.0)
|
46
|
+
thread_safe (~> 0.1)
|
47
|
+
|
48
|
+
PLATFORMS
|
49
|
+
ruby
|
50
|
+
|
51
|
+
DEPENDENCIES
|
52
|
+
activerecord (= 4.1.0)
|
53
|
+
appraisal
|
54
|
+
bundler (~> 1.3)
|
55
|
+
deferring!
|
56
|
+
rake
|
57
|
+
rspec
|
58
|
+
sqlite3
|
data/lib/deferring.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'deferring/version'
|
4
|
+
require 'deferring/deferred_association'
|
5
|
+
|
6
|
+
module Deferring
|
7
|
+
# Creates a wrapper around `has_and_belongs_to_many`. A normal habtm
|
8
|
+
# association is created, but this association is wrapped in a
|
9
|
+
# DeferredAssociation. The accessor methods of the original association are
|
10
|
+
# replaced with ones that will defer saving the association until the parent
|
11
|
+
# object has been saved.
|
12
|
+
def deferred_has_and_belongs_to_many(*args)
|
13
|
+
has_and_belongs_to_many(*args)
|
14
|
+
generate_deferred_association_methods(args.first.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates a wrapper around `has_many`. A normal has many association is
|
18
|
+
# created, but this association is wrapped in a DeferredAssociation. The
|
19
|
+
# accessor methods of the original association are replaced with ones that
|
20
|
+
# will defer saving the association until the parent object has been saved.
|
21
|
+
def deferred_has_many(*args)
|
22
|
+
has_many(*args)
|
23
|
+
generate_deferred_association_methods(args.first.to_s)
|
24
|
+
end
|
25
|
+
|
26
|
+
def deferred_accepts_nested_attributes_for(*args)
|
27
|
+
accepts_nested_attributes_for(*args)
|
28
|
+
association_name = args.first.to_s
|
29
|
+
|
30
|
+
# teams_attributes=
|
31
|
+
define_method :"#{association_name}_attributes=" do |records|
|
32
|
+
find_or_create_deferred_association(association_name)
|
33
|
+
|
34
|
+
# Remove the records that are to be destroyed from the ids that are to be
|
35
|
+
# assigned to the DeferredAssociation instance.
|
36
|
+
records.reject! { |record| record[:_destroy] }
|
37
|
+
|
38
|
+
klass = self.class.reflect_on_association(:"#{association_name}").klass
|
39
|
+
objects = klass.find(records.map { |record| record[:id] })
|
40
|
+
send(:"deferred_#{association_name}").objects = objects
|
41
|
+
end
|
42
|
+
|
43
|
+
generate_find_or_create_deferred_association_method
|
44
|
+
end
|
45
|
+
|
46
|
+
def generate_deferred_association_methods(association_name)
|
47
|
+
# Store the original accessor methods of the association.
|
48
|
+
alias_method :"original_#{association_name}", :"#{association_name}"
|
49
|
+
alias_method :"original_#{association_name}=", :"#{association_name}="
|
50
|
+
|
51
|
+
# Accessor for our own association.
|
52
|
+
attr_accessor :"deferred_#{association_name}"
|
53
|
+
|
54
|
+
# before/afer remove callbacks
|
55
|
+
define_callbacks :"deferred_#{association_name}_save", scope: [:kind, :name]
|
56
|
+
define_callbacks :"deferred_#{association_name.singularize}_remove", scope: [:kind, :name]
|
57
|
+
define_callbacks :"deferred_#{association_name.singularize}_add", scope: [:kind, :name]
|
58
|
+
|
59
|
+
# collection
|
60
|
+
#
|
61
|
+
# Returns an array of all the associated objects. An empty array is returned
|
62
|
+
# if none are found.
|
63
|
+
# TODO: add force_reload argument?
|
64
|
+
define_method :"#{association_name}" do
|
65
|
+
find_or_create_deferred_association(association_name)
|
66
|
+
send(:"deferred_#{association_name}")
|
67
|
+
end
|
68
|
+
|
69
|
+
# collection=objects
|
70
|
+
#
|
71
|
+
# Replaces the collection's content by deleting and adding objects as
|
72
|
+
# appropriate.
|
73
|
+
define_method :"#{association_name}=" do |objects|
|
74
|
+
find_or_create_deferred_association(association_name)
|
75
|
+
send(:"deferred_#{association_name}").objects = objects
|
76
|
+
end
|
77
|
+
|
78
|
+
# collection_singular_ids=
|
79
|
+
#
|
80
|
+
# Replace the collection by the objects identified by the primary keys in
|
81
|
+
# ids.
|
82
|
+
define_method :"#{association_name.singularize}_ids=" do |ids|
|
83
|
+
find_or_create_deferred_association(association_name)
|
84
|
+
|
85
|
+
klass = self.class.reflect_on_association(:"#{association_name}").klass
|
86
|
+
objects = klass.find(ids.reject(&:blank?))
|
87
|
+
send(:"deferred_#{association_name}").objects = objects
|
88
|
+
end
|
89
|
+
|
90
|
+
# collection_singular_ids
|
91
|
+
#
|
92
|
+
# Returns an array of the associated objects' ids.
|
93
|
+
define_method :"#{association_name.singularize}_ids" do
|
94
|
+
find_or_create_deferred_association(association_name)
|
95
|
+
send(:"deferred_#{association_name}").ids
|
96
|
+
end
|
97
|
+
|
98
|
+
# collection_singalur_checked
|
99
|
+
attr_accessor :"#{association_name}_checked"
|
100
|
+
# collection_singalur_checked=
|
101
|
+
define_method(:"#{association_name}_checked=") do |ids|
|
102
|
+
send(:"#{association_name.singularize}_ids=", ids.split(','))
|
103
|
+
end
|
104
|
+
|
105
|
+
# the save after the parent object has been saved
|
106
|
+
after_save :"perform_deferred_#{association_name}_save!"
|
107
|
+
define_method :"perform_deferred_#{association_name}_save!" do
|
108
|
+
run_callbacks :"deferred_#{association_name}_save" do
|
109
|
+
find_or_create_deferred_association(association_name)
|
110
|
+
|
111
|
+
# Send the objects of our delegated association to the original
|
112
|
+
# association and store the result.
|
113
|
+
send(:"original_#{association_name}=", send(:"deferred_#{association_name}").objects)
|
114
|
+
send(:"deferred_#{association_name}").objects.each do |record|
|
115
|
+
record.run_callbacks :commit
|
116
|
+
end
|
117
|
+
|
118
|
+
# Store the new value of the association into our delegated association.
|
119
|
+
send(
|
120
|
+
:"deferred_#{association_name}=",
|
121
|
+
DeferredAssociation.new(send(:"original_#{association_name}"), self, association_name))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
define_method :"reload_with_deferred_#{association_name}" do |*args|
|
126
|
+
find_or_create_deferred_association(association_name)
|
127
|
+
|
128
|
+
send(:"reload_without_deferred_#{association_name}", *args).tap do
|
129
|
+
send(
|
130
|
+
:"deferred_#{association_name}=",
|
131
|
+
DeferredAssociation.new(send(:"original_#{association_name}"), self, association_name))
|
132
|
+
end
|
133
|
+
end
|
134
|
+
alias_method_chain :reload, :"deferred_#{association_name}"
|
135
|
+
|
136
|
+
generate_find_or_create_deferred_association_method
|
137
|
+
end
|
138
|
+
|
139
|
+
def generate_find_or_create_deferred_association_method
|
140
|
+
define_method :find_or_create_deferred_association do |name|
|
141
|
+
if send(:"deferred_#{name}").nil?
|
142
|
+
send(
|
143
|
+
:"deferred_#{name}=",
|
144
|
+
DeferredAssociation.new(send(:"original_#{name}"), self, name))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
ActiveRecord::Base.send(:extend, Deferring)
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'delegate'
|
4
|
+
|
5
|
+
module Deferring
|
6
|
+
class DeferredAssociation < SimpleDelegator
|
7
|
+
# TODO: Write tests for enumerable.
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
attr_reader :load_state
|
11
|
+
|
12
|
+
def initialize(original_association, obj, name)
|
13
|
+
super(original_association)
|
14
|
+
@name = name
|
15
|
+
@obj = obj
|
16
|
+
@load_state = :ghost
|
17
|
+
end
|
18
|
+
|
19
|
+
alias_method :original_association, :__getobj__
|
20
|
+
|
21
|
+
delegate :to_s, :to_a, :inspect, :==, # methods undefined by SimpleDelegator
|
22
|
+
:is_a?, :as_json,
|
23
|
+
|
24
|
+
:[], :clear, :reject, :reject!, :flatten, :flatten!, :sort!,
|
25
|
+
:empty?, :size, :length, # methods on Array
|
26
|
+
|
27
|
+
to: :objects
|
28
|
+
|
29
|
+
def each(&block)
|
30
|
+
objects.each(&block)
|
31
|
+
end
|
32
|
+
|
33
|
+
# TODO: Add explanation about :first/:last loaded? problem.
|
34
|
+
[:first, :last].each do |method|
|
35
|
+
define_method method do
|
36
|
+
unless objects_loaded?
|
37
|
+
original_association.send(method)
|
38
|
+
else
|
39
|
+
objects.send(method)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def find(*args)
|
45
|
+
original_association.find(*args)
|
46
|
+
end
|
47
|
+
|
48
|
+
def select(value = Proc.new)
|
49
|
+
if block_given?
|
50
|
+
objects.select { |*block_args| value.call(*block_args) }
|
51
|
+
else
|
52
|
+
original_association.select(value)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Rails 3.0 specific, not needed anymore for Rails 3.0+
|
57
|
+
def set_inverse_instance(associated_record, parent_record)
|
58
|
+
original_association.__send__(:set_inverse_instance, associated_record, parent_record)
|
59
|
+
end
|
60
|
+
|
61
|
+
def association
|
62
|
+
load_objects
|
63
|
+
original_association
|
64
|
+
end
|
65
|
+
|
66
|
+
def objects
|
67
|
+
load_objects
|
68
|
+
@objects
|
69
|
+
end
|
70
|
+
|
71
|
+
def original_objects
|
72
|
+
load_objects
|
73
|
+
@original_objects
|
74
|
+
end
|
75
|
+
|
76
|
+
def objects=(records)
|
77
|
+
@objects = records
|
78
|
+
@original_objects = original_association.to_a.clone
|
79
|
+
objects_loaded!
|
80
|
+
|
81
|
+
pending_deletes.each do |record|
|
82
|
+
# TODO: I don't like the fact that we know something about @obj in here.
|
83
|
+
# Refactor to remove that (some kind of notification), it looks
|
84
|
+
# terrible this way ;(
|
85
|
+
@obj.instance_variable_set(:"@deferred_#{@name.singularize}_remove", record)
|
86
|
+
@obj.run_callbacks :"deferred_#{@name.singularize}_remove"
|
87
|
+
@obj.send(:remove_instance_variable, :"@deferred_#{@name.singularize}_remove")
|
88
|
+
end
|
89
|
+
|
90
|
+
pending_creates.each do |record|
|
91
|
+
@obj.instance_variable_set(:"@deferred_#{@name.singularize}_add", record)
|
92
|
+
@obj.run_callbacks :"deferred_#{@name.singularize}_add"
|
93
|
+
@obj.send(:remove_instance_variable, :"@deferred_#{@name.singularize}_add")
|
94
|
+
end
|
95
|
+
|
96
|
+
@objects
|
97
|
+
end
|
98
|
+
|
99
|
+
def ids
|
100
|
+
objects.map(&:id)
|
101
|
+
end
|
102
|
+
|
103
|
+
def <<(records)
|
104
|
+
# TODO: Do we want to prevent including the same object twice? Not sure,
|
105
|
+
# but it will probably be filtered after saving and retrieving as well.
|
106
|
+
Array(records).flatten.uniq.each do |record|
|
107
|
+
@obj.instance_variable_set(:"@deferred_#{@name.singularize}_add", record)
|
108
|
+
@obj.run_callbacks :"deferred_#{@name.singularize}_add" do
|
109
|
+
objects << record
|
110
|
+
end
|
111
|
+
@obj.send(:remove_instance_variable, :"@deferred_#{@name.singularize}_add")
|
112
|
+
end
|
113
|
+
objects
|
114
|
+
end
|
115
|
+
alias_method :push, :<<
|
116
|
+
alias_method :concat, :<<
|
117
|
+
alias_method :append, :<<
|
118
|
+
|
119
|
+
def delete(records)
|
120
|
+
Array(records).flatten.uniq.each do |record|
|
121
|
+
@obj.instance_variable_set(:"@deferred_#{@name.singularize}_remove", record)
|
122
|
+
@obj.run_callbacks :"deferred_#{@name.singularize}_remove" do
|
123
|
+
objects.delete(record)
|
124
|
+
end
|
125
|
+
@obj.send(:remove_instance_variable, :"@deferred_#{@name.singularize}_remove")
|
126
|
+
end
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
def build(*args, &block)
|
131
|
+
association.build(*args, &block).tap do |result|
|
132
|
+
objects.push(result)
|
133
|
+
association.reload
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def create!(*args, &block)
|
138
|
+
association.create!(*args, &block).tap do |result|
|
139
|
+
@load_state = :ghost
|
140
|
+
load_objects
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def reload
|
145
|
+
original_association.reload
|
146
|
+
@load_state = :ghost
|
147
|
+
self
|
148
|
+
end
|
149
|
+
alias_method :reset, :reload
|
150
|
+
|
151
|
+
# Returns the associated records to which links will be created after saving
|
152
|
+
# the parent of the association.
|
153
|
+
def pending_creates
|
154
|
+
return [] unless objects_loaded?
|
155
|
+
objects - original_objects
|
156
|
+
end
|
157
|
+
alias_method :links, :pending_creates
|
158
|
+
|
159
|
+
# Returns the associated records to which the links will be deleted after
|
160
|
+
# saving the parent of the assocation.
|
161
|
+
def pending_deletes
|
162
|
+
# TODO: Write test for it.
|
163
|
+
return [] unless objects_loaded?
|
164
|
+
original_objects - objects
|
165
|
+
end
|
166
|
+
alias_method :unlinks, :pending_deletes
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
def load_objects
|
171
|
+
return if objects_loaded?
|
172
|
+
|
173
|
+
@objects = original_association.to_a.clone
|
174
|
+
@original_objects = @objects.clone.freeze
|
175
|
+
objects_loaded!
|
176
|
+
end
|
177
|
+
|
178
|
+
def objects_loaded?
|
179
|
+
@load_state == :loaded
|
180
|
+
end
|
181
|
+
|
182
|
+
def objects_loaded!
|
183
|
+
@load_state = :loaded
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|