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,103 @@
1
+ require 'mr/factory'
2
+ require 'mr/factory/config'
3
+ require 'mr/factory/model_stack'
4
+ require 'mr/factory/record_factory'
5
+ require 'mr/fake_record'
6
+
7
+ module MR; end
8
+ module MR::Factory
9
+
10
+ class ModelFactory
11
+
12
+ attr_reader :model_class, :record_class, :config
13
+
14
+ def initialize(model_class, record_class, &block)
15
+ @model_class = model_class
16
+ @record_class = record_class
17
+ @config = Config.new(@record_class)
18
+ self.instance_eval(&block) if block
19
+
20
+ @record_factory = MR::Factory::RecordFactory.new(record_class)
21
+ end
22
+
23
+ def model(args = nil)
24
+ @model_class.new(@record_factory.record).tap do |model|
25
+ self.config.apply_args(model, args || {})
26
+ end
27
+ end
28
+ alias :instance :model
29
+
30
+ def saved_model(args = nil)
31
+ model = self.model(args).tap(&:save)
32
+ model.record.reset_save_called if model.record.kind_of?(MR::FakeRecord)
33
+ model
34
+ end
35
+ alias :saved_instance :saved_model
36
+
37
+ def stack(args = nil)
38
+ MR::Factory::ModelStack.new(self.model(args), self.config)
39
+ end
40
+ alias :instance_stack :stack
41
+
42
+ def stack_model(args = nil)
43
+ self.stack(args).model
44
+ end
45
+
46
+ def saved_stack(args = nil)
47
+ self.stack(args).tap(&:create)
48
+ end
49
+
50
+ def saved_stack_model(args = nil)
51
+ self.saved_stack(args).model
52
+ end
53
+
54
+ def saved_dependencies_stack(args = nil)
55
+ self.stack(args).tap(&:create_deps)
56
+ end
57
+ alias :saved_deps_stack :saved_dependencies_stack
58
+
59
+ def saved_dependencies_stack_model(args = nil)
60
+ self.saved_deps_stack(args).model
61
+ end
62
+ alias :saved_deps_stack_model :saved_dependencies_stack_model
63
+
64
+ private
65
+
66
+ def default_association(association_name, factory, options = nil)
67
+ self.config.add_association_factory(association_name, factory, options)
68
+ rescue NoAssociationError => exception
69
+ raise exception.class, exception.message, caller
70
+ end
71
+
72
+ def default_args(&block)
73
+ self.config.set_default_args(&block)
74
+ end
75
+
76
+ class Config
77
+ include MR::Factory::WithAssociationsConfig
78
+
79
+ def build_associated_model(association_name, record_class)
80
+ self.factory_for(association_name, record_class).model
81
+ end
82
+
83
+ def build_associated_record(association_name, record_class)
84
+ self.build_associated_model(association_name, record_class).record
85
+ end
86
+
87
+ def ar_association_for(model, name)
88
+ if (reflection = model.record_class.reflect_on_association(name))
89
+ model.record.association(reflection.name)
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def build_factory_for_record_class(record_class)
96
+ MR::Factory::ModelFactory.new(record_class.model_class, record_class)
97
+ end
98
+ end
99
+
100
+ end
101
+
102
+
103
+ end
@@ -0,0 +1,28 @@
1
+ require 'mr/factory/record_factory'
2
+ require 'mr/factory/record_stack'
3
+
4
+ module MR; end
5
+ module MR::Factory
6
+
7
+ class ModelStack
8
+
9
+ attr_reader :model
10
+
11
+ def initialize(model, factory_config)
12
+ @model = model
13
+ @record_stack = MR::Factory::RecordStack.for_record(model.record, factory_config)
14
+ end
15
+
16
+ def factory_config; @record_stack.factory_config; end
17
+
18
+ def create; @record_stack.create; end
19
+ def destroy; @record_stack.destroy; end
20
+
21
+ def create_dependencies; @record_stack.create_dependencies; end
22
+ alias :create_deps :create_dependencies
23
+
24
+ def destroy_dependencies; @record_stack.destroy_dependencies; end
25
+ alias :destroy_deps :destroy_dependencies
26
+ end
27
+
28
+ end
@@ -0,0 +1,104 @@
1
+ require 'mr/factory'
2
+ require 'mr/factory/config'
3
+ require 'mr/read_model'
4
+
5
+ module MR; end
6
+ module MR::Factory
7
+
8
+ class ReadModelFactory
9
+
10
+ attr_reader :read_model_class, :config
11
+
12
+ def initialize(read_model_class, &block)
13
+ unless read_model_class < MR::ReadModelStruct
14
+ raise ArgumentError, "takes a read model or read model struct"
15
+ end
16
+ @read_model_class = read_model_class
17
+ @config = Config.new(read_model_class)
18
+ self.instance_eval(&block) if block
19
+ end
20
+
21
+ def read_model_data(args = nil)
22
+ Data.new.tap do |read_model_data|
23
+ self.config.apply_args(read_model_data, args || {})
24
+ end
25
+ end
26
+
27
+ def read_model(args = nil)
28
+ @read_model_class.new(self.read_model_data(args))
29
+ end
30
+ alias :instance :read_model
31
+
32
+ private
33
+
34
+ def default_args(&block)
35
+ self.config.set_default_args(&block)
36
+ end
37
+
38
+ class Config
39
+ include MR::Factory::Config
40
+
41
+ def read_model_class
42
+ self.object_class
43
+ end
44
+
45
+ private
46
+
47
+ def apply_default_args(data)
48
+ field_defaults.each do |field_name, field_type|
49
+ data[field_name] = MR::Factory.send(field_type)
50
+ end
51
+ json_struct_list_defaults.each do |field_name, factory|
52
+ data[field_name] = Factory.integer(3).times.map{ factory.read_model_data }
53
+ end
54
+ json_struct_obj_defaults.each do |field_name, factory|
55
+ data[field_name] = factory.read_model_data
56
+ end
57
+ super(data)
58
+ end
59
+
60
+ def field_defaults
61
+ @field_defaults ||= self.read_model_class.fields.inject({}) do |h, field|
62
+ h.merge!(field.name.to_s => field.type)
63
+ end
64
+ end
65
+
66
+ def json_struct_list_defaults
67
+ @json_struct_list_defaults ||= build_defaults_for_json_struct_fields(
68
+ self.read_model_class.json_struct_lists
69
+ )
70
+ end
71
+
72
+ def json_struct_obj_defaults
73
+ @json_struct_obj_defaults ||= build_defaults_for_json_struct_fields(
74
+ self.read_model_class.json_struct_objs
75
+ )
76
+ end
77
+
78
+ def build_defaults_for_json_struct_fields(fields)
79
+ fields.inject({}) do |h, field|
80
+ factory = MR::Factory::ReadModelFactory.new(field.struct_class)
81
+ h.merge!(field.name.to_s => factory)
82
+ end
83
+ end
84
+ end
85
+
86
+ class Data
87
+ def initialize; @hash = {}; end
88
+
89
+ def [](key); @hash[key]; end
90
+ def []=(key, value); @hash[key] = value; end
91
+
92
+ def method_missing(method, *args, &block)
93
+ method_string = method.to_s
94
+ if method_string =~ /=\z/ && args.size == 1
95
+ @hash[method_string.gsub('=', '')] = args.first
96
+ else
97
+ super
98
+ end
99
+ end
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,130 @@
1
+ require 'mr/factory'
2
+ require 'mr/factory/config'
3
+ require 'mr/factory/record_stack'
4
+ require 'mr/fake_record'
5
+
6
+ module MR; end
7
+ module MR::Factory
8
+
9
+ class RecordFactory
10
+
11
+ attr_reader :record_class, :config
12
+
13
+ def initialize(record_class, &block)
14
+ @record_class = record_class
15
+ @config = Config.new(record_class)
16
+ self.instance_eval(&block) if block
17
+ end
18
+
19
+ def record(args = nil)
20
+ @record_class.new.tap do |record|
21
+ self.config.apply_args(record, args || {})
22
+ end
23
+ end
24
+ alias :instance :record
25
+
26
+ def saved_record(args = nil)
27
+ record = self.record(args).tap(&:save!)
28
+ record.reset_save_called if record.kind_of?(MR::FakeRecord)
29
+ record
30
+ end
31
+ alias :saved_instance :saved_record
32
+
33
+ def stack(args = nil)
34
+ MR::Factory::RecordStack.for_record(self.record(args), self.config)
35
+ end
36
+ alias :instance_stack :stack
37
+
38
+ def stack_record(args = nil)
39
+ self.stack(args).record
40
+ end
41
+
42
+ def saved_stack(args = nil)
43
+ self.stack(args).tap(&:create)
44
+ end
45
+
46
+ def saved_stack_record(args = nil)
47
+ self.saved_stack(args).record
48
+ end
49
+
50
+ def saved_dependencies_stack(args = nil)
51
+ self.stack(args).tap(&:create_deps)
52
+ end
53
+ alias :saved_deps_stack :saved_dependencies_stack
54
+
55
+ def saved_dependencies_stack_record(args = nil)
56
+ self.saved_deps_stack(args).record
57
+ end
58
+ alias :saved_deps_stack_record :saved_dependencies_stack_record
59
+
60
+ private
61
+
62
+ def default_association(association_name, factory, options = nil)
63
+ self.config.add_association_factory(association_name, factory, options)
64
+ rescue NoAssociationError => exception
65
+ raise exception.class, exception.message, caller
66
+ end
67
+
68
+ def default_args(&block)
69
+ self.config.set_default_args(&block)
70
+ end
71
+
72
+ class Config
73
+ include MR::Factory::WithAssociationsConfig
74
+
75
+ def build_associated_record(association_name, record_class)
76
+ self.factory_for(association_name, record_class).record
77
+ end
78
+
79
+ def ar_association_for(record, name)
80
+ if (reflection = record.class.reflect_on_association(name))
81
+ record.association(reflection.name)
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def apply_default_args(record)
88
+ column_defaults.each do |column_name, column_type|
89
+ record.send("#{column_name}=", MR::Factory.send(column_type))
90
+ end
91
+ super(record)
92
+ end
93
+
94
+ def build_factory_for_record_class(record_class)
95
+ MR::Factory::RecordFactory.new(record_class)
96
+ end
97
+
98
+ def column_defaults
99
+ @column_defaults ||= begin
100
+ association_column_names = association_column_names(self.record_class)
101
+ self.record_class.columns.inject({}) do |args, column|
102
+ column_type = column.type || column.sql_type
103
+ if should_default_column?(column, association_column_names, column_type)
104
+ args[column.name.to_s] = column_type
105
+ end
106
+ args
107
+ end
108
+ end
109
+ end
110
+
111
+ def should_default_column?(column, association_column_names, column_type)
112
+ !column.primary &&
113
+ !association_column_names.include?(column.name) &&
114
+ !column_type.nil? &&
115
+ MR::Factory.respond_to?(column_type)
116
+ end
117
+
118
+ def association_column_names(record_class)
119
+ names = []
120
+ record_class.reflect_on_all_associations(:belongs_to).each do |reflection|
121
+ names << reflection.foreign_type if reflection.options[:polymorphic]
122
+ names << reflection.foreign_key
123
+ end
124
+ names
125
+ end
126
+ end
127
+
128
+ end
129
+
130
+ end
@@ -0,0 +1,219 @@
1
+ require 'mr/factory'
2
+ require 'mr/fake_record'
3
+
4
+ module MR; end
5
+ module MR::Factory
6
+
7
+ class RecordStack
8
+
9
+ def self.for_record(record, factory_config)
10
+ self.new(RecordData.new(record), factory_config)
11
+ end
12
+
13
+ attr_reader :record_data, :factory_config, :record_lookup
14
+ attr_reader :record_stacks
15
+
16
+ def initialize(record_data, factory_config, record_lookup = nil)
17
+ @record_data = record_data
18
+ @factory_config = factory_config
19
+ @record_lookup = record_lookup || build_lookup(@record_data)
20
+
21
+ @record_stacks = @record_data.association_datas.map do |association_data|
22
+ next unless association_data.required?(factory_config)
23
+
24
+ associated_record_data = association_data.get_record_data(
25
+ @factory_config,
26
+ @record_lookup
27
+ )
28
+ associated_factory_config = @factory_config.factory_config_for(
29
+ association_data.name,
30
+ associated_record_data.record_class
31
+ )
32
+ @record_lookup[associated_record_data.record_class] ||= associated_record_data
33
+ @record_data.set_association(association_data.name, associated_record_data)
34
+ MR::Factory::RecordStack.new(
35
+ associated_record_data,
36
+ associated_factory_config,
37
+ @record_lookup
38
+ )
39
+ end.compact
40
+ end
41
+
42
+ def record
43
+ self.record_data.record
44
+ end
45
+
46
+ def create
47
+ self.create_dependencies
48
+ self.record_data.create_record
49
+ true
50
+ end
51
+
52
+ def destroy
53
+ self.record_data.destroy_record
54
+ self.destroy_dependencies
55
+ true
56
+ end
57
+
58
+ def create_dependencies
59
+ self.record_stacks.each(&:create)
60
+ self.record_data.refresh_record_associations
61
+ true
62
+ end
63
+ alias :create_deps :create_dependencies
64
+
65
+ def destroy_dependencies
66
+ self.record_stacks.each(&:destroy)
67
+ true
68
+ end
69
+ alias :destroy_deps :destroy_dependencies
70
+
71
+ private
72
+
73
+ def build_lookup(record_data)
74
+ load_preset_associations_into_lookup({}, record_data)
75
+ end
76
+
77
+ def load_preset_associations_into_lookup(lookup, record_data)
78
+ record_data.association_datas.select(&:preset?).each do |association_data|
79
+ preset_record_data = association_data.preset_record_data
80
+ lookup[preset_record_data.record_class] ||= preset_record_data
81
+ load_preset_associations_into_lookup(lookup, preset_record_data)
82
+ end
83
+ lookup
84
+ end
85
+
86
+ class RecordData
87
+
88
+ attr_reader :record, :association_datas
89
+
90
+ def initialize(record)
91
+ @record = record
92
+ @association_datas = build_association_datas(@record)
93
+ end
94
+
95
+ def record_class
96
+ @record.class
97
+ end
98
+
99
+ def set_association(name, record_data)
100
+ @record.send("#{name}=", record_data.record)
101
+ end
102
+
103
+ def create_record
104
+ if @record.new_record?
105
+ @record.save!
106
+ @record.reset_save_called if @record.kind_of?(MR::FakeRecord)
107
+ end
108
+ end
109
+
110
+ def destroy_record
111
+ @record.destroy unless @record.destroyed?
112
+ end
113
+
114
+ # ensures records have their associations foreign type/key attributes set
115
+ # by re-setting the association to its current value
116
+ def refresh_record_associations
117
+ @association_datas.each do |association_data|
118
+ associated_record = @record.send(association_data.name)
119
+ @record.send("#{association_data.name}=", associated_record)
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def build_association_datas(record)
126
+ record.class.reflect_on_all_associations(:belongs_to).map do |reflection|
127
+ AssociationData.new(record.association(reflection.name))
128
+ end.compact.sort
129
+ end
130
+
131
+ end
132
+
133
+ class AssociationData
134
+
135
+ attr_reader :association, :preset_record_data
136
+
137
+ def initialize(association)
138
+ @association = association
139
+
140
+ preset_record = association.owner.send("#{self.name}")
141
+ @preset_record_data = RecordData.new(preset_record) if preset_record
142
+ end
143
+
144
+ def name
145
+ self.association.reflection.name
146
+ end
147
+
148
+ def record_class
149
+ self.association.klass
150
+ end
151
+
152
+ def preset?
153
+ !self.preset_record_data.nil?
154
+ end
155
+
156
+ def required?(factory_config)
157
+ self.preset? ||
158
+ factory_config.force_in_stack_association?(self.name) ||
159
+ columns_required?(self.association)
160
+ end
161
+
162
+ def get_record_data(factory_config, lookup)
163
+ if self.preset?
164
+ self.preset_record_data
165
+ elsif (lookup_record_data = get_lookup_record_data(factory_config, lookup))
166
+ lookup_record_data
167
+ else
168
+ RecordData.new(build_record(factory_config))
169
+ end
170
+ end
171
+
172
+ def <=>(other)
173
+ if other.kind_of?(self.class)
174
+ self.name.to_s <=> other.name.to_s
175
+ else
176
+ super
177
+ end
178
+ end
179
+
180
+ private
181
+
182
+ # get the intersection of the record classes and use a random match to
183
+ # get a record data from the lookup; if their are no matching record
184
+ # classes the intersection returns an empty array which will return a
185
+ # `nil` value from the lookup
186
+ def get_lookup_record_data(factory_config, lookup)
187
+ record_classes = lookup_record_classes(factory_config) & lookup.keys
188
+ lookup[record_classes.sample]
189
+ end
190
+
191
+ def lookup_record_classes(factory_config)
192
+ if self.record_class
193
+ [self.record_class]
194
+ else
195
+ factory_config.record_classes_for(self.name)
196
+ end
197
+ end
198
+
199
+ def build_record(factory_config)
200
+ factory_config.build_associated_record(self.name, self.record_class)
201
+ rescue NoRecordClassError
202
+ raise NoRecordClassError.for_association(self.association)
203
+ end
204
+
205
+ def columns_required?(association)
206
+ column_required?(association.owner, association.reflection.foreign_key) ||
207
+ column_required?(association.owner, association.reflection.foreign_type)
208
+ end
209
+
210
+ def column_required?(record, column_name)
211
+ column = record.column_for_attribute(column_name)
212
+ !(column.nil? || column.null)
213
+ end
214
+
215
+ end
216
+
217
+ end
218
+
219
+ end