mr 0.35.2

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.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +22 -0
  5. data/README.md +29 -0
  6. data/bench/all.rb +4 -0
  7. data/bench/factory.rb +68 -0
  8. data/bench/fake_record.rb +174 -0
  9. data/bench/model.rb +201 -0
  10. data/bench/read_model.rb +191 -0
  11. data/bench/results/factory.txt +21 -0
  12. data/bench/results/fake_record.txt +37 -0
  13. data/bench/results/model.txt +44 -0
  14. data/bench/results/read_model.txt +46 -0
  15. data/bench/setup.rb +132 -0
  16. data/lib/mr.rb +11 -0
  17. data/lib/mr/after_commit.rb +49 -0
  18. data/lib/mr/after_commit/fake_record.rb +39 -0
  19. data/lib/mr/after_commit/record.rb +48 -0
  20. data/lib/mr/after_commit/record_procs_methods.rb +82 -0
  21. data/lib/mr/factory.rb +82 -0
  22. data/lib/mr/factory/config.rb +240 -0
  23. data/lib/mr/factory/model_factory.rb +103 -0
  24. data/lib/mr/factory/model_stack.rb +28 -0
  25. data/lib/mr/factory/read_model_factory.rb +104 -0
  26. data/lib/mr/factory/record_factory.rb +130 -0
  27. data/lib/mr/factory/record_stack.rb +219 -0
  28. data/lib/mr/fake_query.rb +53 -0
  29. data/lib/mr/fake_record.rb +58 -0
  30. data/lib/mr/fake_record/associations.rb +257 -0
  31. data/lib/mr/fake_record/attributes.rb +168 -0
  32. data/lib/mr/fake_record/persistence.rb +116 -0
  33. data/lib/mr/json_field.rb +180 -0
  34. data/lib/mr/json_field/fake_record.rb +31 -0
  35. data/lib/mr/json_field/record.rb +38 -0
  36. data/lib/mr/model.rb +67 -0
  37. data/lib/mr/model/associations.rb +161 -0
  38. data/lib/mr/model/configuration.rb +67 -0
  39. data/lib/mr/model/fields.rb +177 -0
  40. data/lib/mr/model/persistence.rb +79 -0
  41. data/lib/mr/query.rb +126 -0
  42. data/lib/mr/read_model.rb +83 -0
  43. data/lib/mr/read_model/data.rb +38 -0
  44. data/lib/mr/read_model/fields.rb +218 -0
  45. data/lib/mr/read_model/query_expression.rb +188 -0
  46. data/lib/mr/read_model/querying.rb +214 -0
  47. data/lib/mr/read_model/set_querying.rb +82 -0
  48. data/lib/mr/read_model/subquery.rb +98 -0
  49. data/lib/mr/record.rb +35 -0
  50. data/lib/mr/test_helpers.rb +229 -0
  51. data/lib/mr/type_converter.rb +85 -0
  52. data/lib/mr/version.rb +3 -0
  53. data/log/.gitkeep +0 -0
  54. data/mr.gemspec +29 -0
  55. data/test/helper.rb +21 -0
  56. data/test/support/db.rb +10 -0
  57. data/test/support/factory.rb +13 -0
  58. data/test/support/factory/area.rb +6 -0
  59. data/test/support/factory/comment.rb +14 -0
  60. data/test/support/factory/image.rb +6 -0
  61. data/test/support/factory/user.rb +6 -0
  62. data/test/support/models/area.rb +58 -0
  63. data/test/support/models/comment.rb +60 -0
  64. data/test/support/models/image.rb +53 -0
  65. data/test/support/models/user.rb +96 -0
  66. data/test/support/read_model/querying.rb +150 -0
  67. data/test/support/read_models/comment_with_user_data.rb +27 -0
  68. data/test/support/read_models/set_data.rb +49 -0
  69. data/test/support/read_models/subquery_data.rb +41 -0
  70. data/test/support/read_models/user_with_area_data.rb +15 -0
  71. data/test/support/schema.rb +39 -0
  72. data/test/support/setup_test_db.rb +10 -0
  73. data/test/system/factory/model_factory_tests.rb +87 -0
  74. data/test/system/factory/model_stack_tests.rb +30 -0
  75. data/test/system/factory/record_factory_tests.rb +84 -0
  76. data/test/system/factory/record_stack_tests.rb +51 -0
  77. data/test/system/factory_tests.rb +32 -0
  78. data/test/system/read_model_tests.rb +199 -0
  79. data/test/system/with_model_tests.rb +275 -0
  80. data/test/unit/after_commit/fake_record_tests.rb +110 -0
  81. data/test/unit/after_commit/record_procs_methods_tests.rb +177 -0
  82. data/test/unit/after_commit/record_tests.rb +134 -0
  83. data/test/unit/after_commit_tests.rb +113 -0
  84. data/test/unit/factory/config_tests.rb +651 -0
  85. data/test/unit/factory/model_factory_tests.rb +473 -0
  86. data/test/unit/factory/model_stack_tests.rb +97 -0
  87. data/test/unit/factory/read_model_factory_tests.rb +195 -0
  88. data/test/unit/factory/record_factory_tests.rb +446 -0
  89. data/test/unit/factory/record_stack_tests.rb +549 -0
  90. data/test/unit/factory_tests.rb +213 -0
  91. data/test/unit/fake_query_tests.rb +137 -0
  92. data/test/unit/fake_record/associations_tests.rb +585 -0
  93. data/test/unit/fake_record/attributes_tests.rb +265 -0
  94. data/test/unit/fake_record/persistence_tests.rb +239 -0
  95. data/test/unit/fake_record_tests.rb +106 -0
  96. data/test/unit/json_field/fake_record_tests.rb +75 -0
  97. data/test/unit/json_field/record_tests.rb +80 -0
  98. data/test/unit/json_field_tests.rb +302 -0
  99. data/test/unit/model/associations_tests.rb +346 -0
  100. data/test/unit/model/configuration_tests.rb +92 -0
  101. data/test/unit/model/fields_tests.rb +278 -0
  102. data/test/unit/model/persistence_tests.rb +114 -0
  103. data/test/unit/model_tests.rb +137 -0
  104. data/test/unit/query_tests.rb +300 -0
  105. data/test/unit/read_model/data_tests.rb +56 -0
  106. data/test/unit/read_model/fields_tests.rb +416 -0
  107. data/test/unit/read_model/query_expression_tests.rb +381 -0
  108. data/test/unit/read_model/querying_tests.rb +613 -0
  109. data/test/unit/read_model/set_querying_tests.rb +149 -0
  110. data/test/unit/read_model/subquery_tests.rb +242 -0
  111. data/test/unit/read_model_tests.rb +187 -0
  112. data/test/unit/record_tests.rb +45 -0
  113. data/test/unit/test_helpers_tests.rb +431 -0
  114. data/test/unit/type_converter_tests.rb +207 -0
  115. metadata +285 -0
@@ -0,0 +1,11 @@
1
+ require 'active_record'
2
+
3
+ require 'mr/version'
4
+ require 'mr/model'
5
+ require 'mr/query'
6
+ require 'mr/read_model'
7
+ require 'mr/record'
8
+
9
+ module MR
10
+
11
+ end
@@ -0,0 +1,49 @@
1
+ require 'much-plugin'
2
+
3
+ require 'mr/after_commit/fake_record'
4
+ require 'mr/after_commit/record'
5
+ require 'mr/after_commit/record_procs_methods'
6
+ require 'mr/model'
7
+
8
+ module MR
9
+
10
+ module AfterCommit
11
+ include MuchPlugin
12
+
13
+ # demeter these constants so they are available from the `MR::AfterCommit`
14
+ # namespace
15
+ VALID_CALLBACK_TYPES = RecordProcsMethods::VALID_CALLBACK_TYPES
16
+ DEFAULT_CALLBACK_TYPE = RecordProcsMethods::DEFAULT_CALLBACK_TYPE
17
+
18
+ plugin_included do
19
+ include MR::Model
20
+ include InstanceMethods
21
+ end
22
+
23
+ module InstanceMethods
24
+
25
+ def after_commit_procs(*args)
26
+ self.record.after_commit_procs(*args)
27
+ end
28
+
29
+ def after_commit(on = nil, &block)
30
+ self.record.add_after_commit_proc(on, &block)
31
+ end
32
+
33
+ def prepend_after_commit(on = nil, &block)
34
+ self.record.prepend_after_commit_proc(on, &block)
35
+ end
36
+
37
+ def clear_after_commit_procs(*args)
38
+ self.record.clear_after_commit_procs(*args)
39
+ end
40
+
41
+ def called_after_commit_procs(*args)
42
+ self.record.called_after_commit_procs(*args)
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,39 @@
1
+ require 'much-plugin'
2
+
3
+ require 'mr/after_commit/record_procs_methods'
4
+ require 'mr/fake_record'
5
+
6
+ module MR; end
7
+ module MR::AfterCommit
8
+
9
+ module FakeRecord
10
+ include MuchPlugin
11
+
12
+ plugin_included do
13
+ include MR::FakeRecord
14
+ include RecordProcsMethods
15
+ include InstanceMethods
16
+ end
17
+
18
+ module InstanceMethods
19
+
20
+ def save!
21
+ is_new = self.new_record?
22
+ super.tap do
23
+ callback_type = is_new ? :create : :update
24
+ mr_after_commit_call_procs(callback_type)
25
+ mr_after_commit_call_procs(:save)
26
+ end
27
+ end
28
+
29
+ def destroy
30
+ super.tap do
31
+ mr_after_commit_call_procs(:destroy)
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,48 @@
1
+ require 'much-plugin'
2
+
3
+ require 'mr/after_commit/record_procs_methods'
4
+ require 'mr/record'
5
+
6
+ module MR; end
7
+ module MR::AfterCommit
8
+
9
+ module Record
10
+ include MuchPlugin
11
+
12
+ plugin_included do
13
+ include MR::Record
14
+ include RecordProcsMethods
15
+ include InstanceMethods
16
+
17
+ after_commit :mr_after_commit_call_procs_for_create, :on => :create
18
+ after_commit :mr_after_commit_call_procs_for_update, :on => :update
19
+ after_commit :mr_after_commit_call_procs_for_destroy, :on => :destroy
20
+ end
21
+
22
+ module InstanceMethods
23
+
24
+ private
25
+
26
+ # ActiveRecord runs `after_save` after `after_create`, so run the save
27
+ # procs after the create procs
28
+ def mr_after_commit_call_procs_for_create
29
+ mr_after_commit_call_procs(:create)
30
+ mr_after_commit_call_procs(:save)
31
+ end
32
+
33
+ # ActiveRecord runs `after_save` after `after_update`, so run the save
34
+ # procs after the update procs
35
+ def mr_after_commit_call_procs_for_update
36
+ mr_after_commit_call_procs(:update)
37
+ mr_after_commit_call_procs(:save)
38
+ end
39
+
40
+ def mr_after_commit_call_procs_for_destroy
41
+ mr_after_commit_call_procs(:destroy)
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,82 @@
1
+ module MR; end
2
+ module MR::AfterCommit
3
+
4
+ module RecordProcsMethods
5
+
6
+ VALID_CALLBACK_TYPES = [
7
+ :create,
8
+ :update,
9
+ :save,
10
+ :destroy
11
+ ].freeze
12
+
13
+ DEFAULT_CALLBACK_TYPE = :save.freeze
14
+
15
+ # these methods are used by the `Record` and `FakeRecord` mixins
16
+
17
+ def after_commit_procs(*keys)
18
+ if keys.empty?
19
+ mr_after_commit_procs_hash.values.flatten
20
+ else
21
+ keys.map{ |k| mr_after_commit_procs_hash[k.to_sym] }.flatten
22
+ end
23
+ end
24
+
25
+ def add_after_commit_proc(callback_type = nil, &block)
26
+ callback_type ||= DEFAULT_CALLBACK_TYPE
27
+ mr_after_commit_procs_hash[callback_type.to_sym] << block
28
+ end
29
+
30
+ def prepend_after_commit_proc(callback_type = nil, &block)
31
+ callback_type ||= DEFAULT_CALLBACK_TYPE
32
+ mr_after_commit_procs_hash[callback_type.to_sym].unshift(block)
33
+ end
34
+
35
+ def clear_after_commit_procs(*keys)
36
+ if keys.empty?
37
+ mr_after_commit_procs_hash.clear
38
+ else
39
+ keys.map{ |k| mr_after_commit_procs_hash.delete(k.to_sym) }
40
+ end
41
+ end
42
+
43
+ def called_after_commit_procs(*keys)
44
+ if keys.empty?
45
+ mr_after_commit_called_procs_hash.values.flatten
46
+ else
47
+ keys.map{ |k| mr_after_commit_called_procs_hash[k.to_sym] }.flatten
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def mr_after_commit_call_procs(callback_type)
54
+ procs = self.after_commit_procs(callback_type)
55
+ procs.each(&:call)
56
+ mr_after_commit_called_procs_hash[callback_type] += procs
57
+ self.clear_after_commit_procs(callback_type)
58
+ end
59
+
60
+ def mr_after_commit_procs_hash
61
+ @mr_after_commit_procs_hash ||= MR::AfterCommit::CallbackProcsHash.new
62
+ end
63
+
64
+ def mr_after_commit_called_procs_hash
65
+ @mr_after_commit_called_procs_hash ||= MR::AfterCommit::CallbackProcsHash.new
66
+ end
67
+
68
+ end
69
+
70
+ module CallbackProcsHash
71
+ def self.new
72
+ Hash.new do |h, k|
73
+ if !VALID_CALLBACK_TYPES.include?(k)
74
+ raise ArgumentError, "#{k.inspect} is not a valid callback " \
75
+ "type, use: #{VALID_CALLBACK_TYPES.join(', ')}"
76
+ end
77
+ h[k] = []
78
+ end
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,82 @@
1
+ require 'assert/factory'
2
+ require 'thread'
3
+ require 'mr/factory/model_factory'
4
+ require 'mr/factory/read_model_factory'
5
+ require 'mr/factory/record_factory'
6
+ require 'mr/type_converter'
7
+
8
+ module MR
9
+
10
+ module Factory
11
+ extend Assert::Factory
12
+ extend self
13
+
14
+ def new(object_class, *args, &block)
15
+ if object_class < MR::Model
16
+ ModelFactory.new(object_class, *args, &block)
17
+ elsif object_class < MR::ReadModelStruct
18
+ ReadModelFactory.new(object_class, *args, &block)
19
+ elsif object_class < MR::Record
20
+ RecordFactory.new(object_class, *args, &block)
21
+ else
22
+ raise ArgumentError, "takes a MR::Model, MR::Record, or MR::ReadModel"
23
+ end
24
+ end
25
+
26
+ def primary_key(identifier = nil)
27
+ identifier ||= 'MR::Factory'
28
+ @primary_keys ||= {}
29
+ @primary_keys[identifier.to_s] ||= PrimaryKeyProvider.new
30
+ self.type_cast(@primary_keys[identifier.to_s].next, :primary_key)
31
+ end
32
+
33
+ def decimal(max = nil)
34
+ self.type_cast(Assert::Factory::Random.float(max), :decimal)
35
+ end
36
+
37
+ def timestamp
38
+ self.datetime
39
+ end
40
+
41
+ def type_converter
42
+ @type_converter ||= MR::TypeConverter.new
43
+ end
44
+
45
+ class PrimaryKeyProvider
46
+ attr_reader :mutex, :current
47
+ def initialize
48
+ @current = 0
49
+ @mutex = Mutex.new
50
+ end
51
+ def next
52
+ @mutex.synchronize{ @current += 1 }
53
+ end
54
+ end
55
+
56
+ # used by factories and stacks to say they can't determine a record class for
57
+ # an association (usually a polymorphic association doesn't have its foreign
58
+ # type attribute set)
59
+ class NoRecordClassError < RuntimeError
60
+ def self.for_association(ar_association)
61
+ owner_record_class = ar_association.owner.class
62
+ association_name = ar_association.reflection.name
63
+ message = "can't build '#{association_name}' association on " \
64
+ "#{owner_record_class}"
65
+ if ar_association.reflection.options[:polymorphic]
66
+ foreign_type_attribute = ar_association.reflection.foreign_type
67
+ message += " -- try manually setting it, building it via a stack " \
68
+ "if you've configured default associations, or setting " \
69
+ "its '#{foreign_type_attribute}' attribute"
70
+ else
71
+ message += " -- try manually setting it or building it via a stack " \
72
+ "if you've configured default associations"
73
+ end
74
+ self.new(message)
75
+ end
76
+ end
77
+
78
+ NoAssociationError = Class.new(ArgumentError)
79
+
80
+ end
81
+
82
+ end
@@ -0,0 +1,240 @@
1
+ require 'much-plugin'
2
+
3
+ module MR; end
4
+ module MR::Factory
5
+
6
+ module Config
7
+ include MuchPlugin
8
+
9
+ plugin_included do
10
+ include InstanceMethods
11
+ end
12
+
13
+ module InstanceMethods
14
+
15
+ attr_reader :object_class
16
+
17
+ def initialize(object_class)
18
+ @object_class = object_class
19
+ end
20
+
21
+ # make the methods lazy-eval'd so factories can be built without paying
22
+ # the performance of them building out their config, this makes it less
23
+ # costly to require real and fake factories together in a test suite
24
+ # (i.e. we don't want to split our real and fake factories into separate
25
+ # files)
26
+
27
+ def apply_args(object, args)
28
+ apply_default_args(object)
29
+ apply_args_from_hash(object, args)
30
+ true
31
+ end
32
+
33
+ def set_default_args(&block)
34
+ @default_args_proc = block
35
+ end
36
+
37
+ private
38
+
39
+ def object_builder_class; ObjectBuilder; end
40
+
41
+ def apply_default_args(object)
42
+ apply_args_from_proc(object, &@default_args_proc) if @default_args_proc
43
+ end
44
+
45
+ def apply_args_from_hash(object, args_hash)
46
+ object_builder_class.apply_hash(object, self, args_hash)
47
+ end
48
+
49
+ def apply_args_from_proc(object, &args_proc)
50
+ object_builder_class.apply_proc(object, self, &args_proc)
51
+ end
52
+
53
+ end
54
+
55
+ class ObjectBuilder
56
+
57
+ def self.apply_hash(object, factory_config, hash)
58
+ self.new(object, factory_config).tap do |builder|
59
+ hash.each{ |k, v| builder.set(k, v) }
60
+ end
61
+ end
62
+
63
+ def self.apply_proc(object, factory_config, &proc)
64
+ self.new(object, factory_config).tap do |builder|
65
+ builder.instance_eval(&proc)
66
+ end
67
+ end
68
+
69
+ def initialize(object, factory_config)
70
+ @__mr_ob_object = object
71
+ @__mr_ob_factory_config = factory_config
72
+ end
73
+
74
+ def set(name, value)
75
+ __mr_ob_set_attribute(@__mr_ob_object, name, value)
76
+ rescue ArgumentError => exception
77
+ exception.set_backtrace(caller)
78
+ raise exception
79
+ end
80
+
81
+ private
82
+
83
+ def __mr_ob_set_attribute(object, name, value)
84
+ object.send("#{name}=", value)
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ module WithAssociationsConfig
91
+ include MuchPlugin
92
+
93
+ plugin_included do
94
+ include MR::Factory::Config
95
+ include InstanceMethods
96
+ end
97
+
98
+ module InstanceMethods
99
+
100
+ def record_class
101
+ self.object_class
102
+ end
103
+
104
+ def force_in_stack_association_names
105
+ @force_in_stack_association_names ||= []
106
+ end
107
+
108
+ def force_in_stack_association?(association_name)
109
+ self.force_in_stack_association_names.include?(association_name)
110
+ end
111
+
112
+ def default_factories
113
+ @default_factories ||= Hash.new do |h, record_class|
114
+ # raise a no record class error if passed a `nil` record class, this
115
+ # can happen when a polymorphic association is passed to `factory_for`
116
+ # and its foreign type attribute isn't set
117
+ raise NoRecordClassError if record_class.nil?
118
+ h[record_class] = build_factory_for_record_class(record_class)
119
+ end
120
+ end
121
+
122
+ def association_factories
123
+ @association_factories ||= {}
124
+ end
125
+
126
+ def factories_for(association_name, record_class)
127
+ key = AssociationFactoriesKey.new(association_name, record_class)
128
+ self.association_factories[key] || [self.default_factories[record_class]]
129
+ end
130
+
131
+ def factory_for(association_name, record_class)
132
+ self.factories_for(association_name, record_class).sample
133
+ end
134
+
135
+ def factory_config_for(association_name, record_class)
136
+ self.factory_for(association_name, record_class).config
137
+ end
138
+
139
+ def record_classes_for(association_name)
140
+ self.factories_for(association_name, nil).map(&:record_class)
141
+ rescue NoRecordClassError
142
+ []
143
+ end
144
+
145
+ def build_associated_record(association_name, record_class)
146
+ raise NotImplementedError
147
+ end
148
+
149
+ def add_association_factory(association_name, factory, options = nil)
150
+ reflection = self.record_class.reflect_on_association(association_name)
151
+ if reflection.nil?
152
+ raise NoAssociationError, "there is no #{association_name.inspect} " \
153
+ "association for #{self.record_class.inspect}"
154
+ end
155
+
156
+ options ||= {}
157
+ if options[:force_in_stack]
158
+ (self.force_in_stack_association_names << association_name).uniq!
159
+ end
160
+
161
+ add_default_factory(factory.record_class, factory)
162
+
163
+ # for polymorphic associations add a lookup with no associated record
164
+ # class, this will allow a polymorphic association without their foreign
165
+ # type attribute set to use the configured factories for the association
166
+ if reflection.options[:polymorphic]
167
+ key = AssociationFactoriesKey.new(association_name)
168
+ add_association_factory_by_key(key, factory)
169
+ end
170
+
171
+ key = AssociationFactoriesKey.new(association_name, factory.record_class)
172
+ add_association_factory_by_key(key, factory)
173
+ end
174
+
175
+ def ar_association_for(object, name)
176
+ raise NotImplementedError
177
+ end
178
+
179
+ private
180
+
181
+ def object_builder_class; WithAssociationsConfig::ObjectBuilder; end
182
+
183
+ def build_factory_for_record_class(record_class)
184
+ raise NotImplementedError
185
+ end
186
+
187
+ def add_association_factory_by_key(key, factory)
188
+ self.association_factories[key] = [] if !self.association_factories.key?(key)
189
+ self.association_factories[key] << factory
190
+ end
191
+
192
+ def add_default_factory(record_class, factory)
193
+ if !self.default_factories.key?(record_class)
194
+ self.default_factories[record_class] = factory
195
+ end
196
+ end
197
+
198
+ end
199
+
200
+ class ObjectBuilder < MR::Factory::Config::ObjectBuilder
201
+
202
+ def set(name, value)
203
+ if value.kind_of?(Hash) && (association = __mr_ob_get_association(name))
204
+ __mr_ob_set_association_from_hash(association, name, value)
205
+ else
206
+ super(name, value)
207
+ end
208
+ rescue ArgumentError, NoRecordClassError => exception
209
+ exception.set_backtrace(caller)
210
+ raise exception
211
+ end
212
+
213
+ private
214
+
215
+ def __mr_ob_get_association(name)
216
+ @__mr_ob_factory_config.ar_association_for(@__mr_ob_object, name)
217
+ end
218
+
219
+ def __mr_ob_set_association_from_hash(association, name, value)
220
+ factory = begin
221
+ @__mr_ob_factory_config.factory_for(name, association.klass)
222
+ rescue NoRecordClassError
223
+ raise NoRecordClassError.for_association(association)
224
+ end
225
+ associated_object = @__mr_ob_object.send(name) || factory.instance
226
+ self.class.apply_hash(associated_object, factory.config, value)
227
+ __mr_ob_set_attribute(@__mr_ob_object, name, associated_object)
228
+ end
229
+
230
+ end
231
+
232
+ module AssociationFactoriesKey
233
+ def self.new(association_name, associated_record_class = nil)
234
+ [association_name, associated_record_class].compact.join("-")
235
+ end
236
+ end
237
+
238
+ end
239
+
240
+ end