terrestrial 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -1
  3. data/Gemfile.lock +44 -53
  4. data/README.md +3 -6
  5. data/bin/test +1 -1
  6. data/features/env.rb +12 -2
  7. data/features/example.feature +23 -26
  8. data/lib/terrestrial.rb +31 -0
  9. data/lib/terrestrial/adapters/abstract_adapter.rb +6 -0
  10. data/lib/terrestrial/adapters/memory_adapter.rb +82 -6
  11. data/lib/terrestrial/adapters/sequel_postgres_adapter.rb +191 -0
  12. data/lib/terrestrial/configurations/conventional_association_configuration.rb +65 -35
  13. data/lib/terrestrial/configurations/conventional_configuration.rb +280 -124
  14. data/lib/terrestrial/configurations/mapping_config_options_proxy.rb +97 -0
  15. data/lib/terrestrial/deleted_record.rb +12 -8
  16. data/lib/terrestrial/dirty_map.rb +17 -9
  17. data/lib/terrestrial/functional_pipeline.rb +64 -0
  18. data/lib/terrestrial/inspection_string.rb +6 -1
  19. data/lib/terrestrial/lazy_object_proxy.rb +1 -0
  20. data/lib/terrestrial/many_to_many_association.rb +34 -20
  21. data/lib/terrestrial/many_to_one_association.rb +11 -3
  22. data/lib/terrestrial/one_to_many_association.rb +9 -0
  23. data/lib/terrestrial/public_conveniencies.rb +65 -82
  24. data/lib/terrestrial/record.rb +106 -0
  25. data/lib/terrestrial/relation_mapping.rb +43 -12
  26. data/lib/terrestrial/relational_store.rb +33 -11
  27. data/lib/terrestrial/upsert_record.rb +54 -0
  28. data/lib/terrestrial/version.rb +1 -1
  29. data/spec/automatic_timestamps_spec.rb +339 -0
  30. data/spec/changes_api_spec.rb +81 -0
  31. data/spec/config_override_spec.rb +28 -19
  32. data/spec/custom_serializers_spec.rb +3 -2
  33. data/spec/database_default_fields_spec.rb +213 -0
  34. data/spec/database_generated_id_spec.rb +291 -0
  35. data/spec/database_owned_fields_and_timestamps_spec.rb +200 -0
  36. data/spec/deletion_spec.rb +1 -1
  37. data/spec/error_handling/factory_error_handling_spec.rb +1 -4
  38. data/spec/error_handling/serialization_error_spec.rb +1 -4
  39. data/spec/error_handling/upsert_error_spec.rb +7 -11
  40. data/spec/graph_persistence_spec.rb +52 -18
  41. data/spec/ordered_association_spec.rb +10 -12
  42. data/spec/predefined_queries_spec.rb +14 -12
  43. data/spec/readme_examples_spec.rb +1 -1
  44. data/spec/sequel_query_efficiency_spec.rb +19 -16
  45. data/spec/spec_helper.rb +6 -1
  46. data/spec/support/blog_schema.rb +7 -3
  47. data/spec/support/object_graph_setup.rb +30 -39
  48. data/spec/support/object_store_setup.rb +16 -196
  49. data/spec/support/seed_data_setup.rb +15 -149
  50. data/spec/support/seed_records.rb +141 -0
  51. data/spec/support/sequel_test_support.rb +46 -13
  52. data/spec/terrestrial/abstract_record_spec.rb +138 -106
  53. data/spec/terrestrial/adapters/sequel_postgres_adapter_spec.rb +138 -0
  54. data/spec/terrestrial/deleted_record_spec.rb +0 -27
  55. data/spec/terrestrial/dirty_map_spec.rb +52 -77
  56. data/spec/terrestrial/functional_pipeline_spec.rb +153 -0
  57. data/spec/terrestrial/inspection_string_spec.rb +61 -0
  58. data/spec/terrestrial/upsert_record_spec.rb +29 -0
  59. data/terrestrial.gemspec +7 -8
  60. metadata +43 -40
  61. data/MissingFeatures.md +0 -64
  62. data/lib/terrestrial/abstract_record.rb +0 -99
  63. data/lib/terrestrial/association_loaders.rb +0 -52
  64. data/lib/terrestrial/upserted_record.rb +0 -15
  65. data/spec/terrestrial/public_conveniencies_spec.rb +0 -63
  66. data/spec/terrestrial/upserted_record_spec.rb +0 -59
@@ -0,0 +1,61 @@
1
+ require "spec_helper"
2
+
3
+ require "terrestrial/inspection_string"
4
+
5
+ RSpec.describe Terrestrial::InspectionString do
6
+ class SomeClass
7
+ end
8
+
9
+ describe "#inspect" do
10
+ let(:test_class) { Class.new }
11
+ let(:object) { test_class.new }
12
+
13
+ context "when #inspectable_properties is not implemented or returns `[]`" do
14
+ it "returns the regular inspection string" do
15
+ original_inspection_string = object.inspect
16
+
17
+ object.extend(Terrestrial::InspectionString)
18
+
19
+ expect(object.inspect).to eq(original_inspection_string)
20
+ end
21
+
22
+ context "when class is not anonymous" do
23
+ class TestClass; end
24
+ let(:object) { TestClass.new }
25
+
26
+ it "returns the regular inspection string" do
27
+ original_inspection_string = object.inspect
28
+
29
+ object.extend(Terrestrial::InspectionString)
30
+
31
+ expect(object.inspect).to eq(original_inspection_string)
32
+ end
33
+ end
34
+ end
35
+
36
+ context "when #inspectable_properties returns an array of present instance variables" do
37
+ class TestClass2
38
+ include Terrestrial::InspectionString
39
+
40
+ def initialize
41
+ @one = "one value"
42
+ @two = "two value"
43
+ @three = "three value"
44
+ end
45
+
46
+ def inspectable_properties
47
+ [:one, :two]
48
+ end
49
+ end
50
+ let(:object) { TestClass2.new }
51
+
52
+ it "contains the specified instance variable names and values" do
53
+ expect(object.inspect).to include('one="one value" two="two value"')
54
+ end
55
+
56
+ it "does not contain other instance variable values" do
57
+ expect(object.inspect).not_to include("three")
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ require "terrestrial/upsert_record"
4
+
5
+ RSpec.describe "Terrestrial::UpsertRecord" do
6
+ subject(:record) { Terrestrial::UpsertRecord.new(mapping, object, attributes, depth) }
7
+
8
+ let(:mapping) { double(:mapping) }
9
+
10
+ let(:object) { double(:object) }
11
+ let(:depth) { 1 }
12
+ let(:attributes) {
13
+ {
14
+ id: id,
15
+ name: name,
16
+ }
17
+ }
18
+
19
+ let(:id) { double(:id) }
20
+ let(:name) { double(:name) }
21
+
22
+ describe "#if_upsert" do
23
+ it "invokes the callback" do
24
+ expect { |callback|
25
+ record.if_upsert(&callback)
26
+ }.to yield_with_args(record)
27
+ end
28
+ end
29
+ end
data/terrestrial.gemspec CHANGED
@@ -18,14 +18,13 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.7"
22
- spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_development_dependency "pry", "~> 0.10.1"
24
- spec.add_development_dependency "rspec", "~> 3.1"
25
- spec.add_development_dependency "cucumber", "~> 2.4"
26
- spec.add_development_dependency "pg", "~> 0.17.1"
21
+ spec.add_development_dependency "bundler", "~> 1"
22
+ spec.add_development_dependency "rake", "~> 13.0"
23
+ spec.add_development_dependency "pry", "~> 0.13"
24
+ spec.add_development_dependency "rspec", "~> 3.9"
25
+ spec.add_development_dependency "cucumber", "~> 3.1"
26
+ spec.add_development_dependency "pg", "~> 1.0"
27
27
 
28
- spec.add_dependency "sequel", "~> 4.16"
29
- spec.add_dependency "activesupport", "~> 4.0"
28
+ spec.add_dependency "sequel", "~> 5.0"
30
29
  spec.add_dependency "fetchable", "~> 1.0"
31
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terrestrial
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Best
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-26 00:00:00.000000000 Z
11
+ date: 2021-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,112 +16,98 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.7'
19
+ version: '1'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.7'
26
+ version: '1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.10.1
47
+ version: '0.13'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.10.1
54
+ version: '0.13'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '3.1'
61
+ version: '3.9'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '3.1'
68
+ version: '3.9'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: cucumber
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '2.4'
75
+ version: '3.1'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '2.4'
82
+ version: '3.1'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: pg
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 0.17.1
89
+ version: '1.0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.17.1
96
+ version: '1.0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: sequel
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '4.16'
104
- type: :runtime
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '4.16'
111
- - !ruby/object:Gem::Dependency
112
- name: activesupport
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '4.0'
103
+ version: '5.0'
118
104
  type: :runtime
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
107
  requirements:
122
108
  - - "~>"
123
109
  - !ruby/object:Gem::Version
124
- version: '4.0'
110
+ version: '5.0'
125
111
  - !ruby/object:Gem::Dependency
126
112
  name: fetchable
127
113
  requirement: !ruby/object:Gem::Requirement
@@ -152,7 +138,6 @@ files:
152
138
  - Gemfile
153
139
  - Gemfile.lock
154
140
  - LICENSE.txt
155
- - MissingFeatures.md
156
141
  - README.md
157
142
  - Rakefile
158
143
  - TODO.md
@@ -162,16 +147,18 @@ files:
162
147
  - features/example.feature
163
148
  - features/step_definitions/example_steps.rb
164
149
  - lib/terrestrial.rb
165
- - lib/terrestrial/abstract_record.rb
150
+ - lib/terrestrial/adapters/abstract_adapter.rb
166
151
  - lib/terrestrial/adapters/memory_adapter.rb
167
- - lib/terrestrial/association_loaders.rb
152
+ - lib/terrestrial/adapters/sequel_postgres_adapter.rb
168
153
  - lib/terrestrial/collection_mutability_proxy.rb
169
154
  - lib/terrestrial/configurations/conventional_association_configuration.rb
170
155
  - lib/terrestrial/configurations/conventional_configuration.rb
156
+ - lib/terrestrial/configurations/mapping_config_options_proxy.rb
171
157
  - lib/terrestrial/dataset.rb
172
158
  - lib/terrestrial/deleted_record.rb
173
159
  - lib/terrestrial/dirty_map.rb
174
160
  - lib/terrestrial/error.rb
161
+ - lib/terrestrial/functional_pipeline.rb
175
162
  - lib/terrestrial/graph_loader.rb
176
163
  - lib/terrestrial/graph_serializer.rb
177
164
  - lib/terrestrial/identity_map.rb
@@ -183,15 +170,21 @@ files:
183
170
  - lib/terrestrial/one_to_many_association.rb
184
171
  - lib/terrestrial/public_conveniencies.rb
185
172
  - lib/terrestrial/query_order.rb
173
+ - lib/terrestrial/record.rb
186
174
  - lib/terrestrial/relation_mapping.rb
187
175
  - lib/terrestrial/relational_store.rb
188
176
  - lib/terrestrial/serializer.rb
189
177
  - lib/terrestrial/struct_factory.rb
190
178
  - lib/terrestrial/subset_queries_proxy.rb
191
- - lib/terrestrial/upserted_record.rb
179
+ - lib/terrestrial/upsert_record.rb
192
180
  - lib/terrestrial/version.rb
181
+ - spec/automatic_timestamps_spec.rb
182
+ - spec/changes_api_spec.rb
193
183
  - spec/config_override_spec.rb
194
184
  - spec/custom_serializers_spec.rb
185
+ - spec/database_default_fields_spec.rb
186
+ - spec/database_generated_id_spec.rb
187
+ - spec/database_owned_fields_and_timestamps_spec.rb
195
188
  - spec/deletion_spec.rb
196
189
  - spec/error_handling/factory_error_handling_spec.rb
197
190
  - spec/error_handling/serialization_error_spec.rb
@@ -212,15 +205,18 @@ files:
212
205
  - spec/support/object_graph_setup.rb
213
206
  - spec/support/object_store_setup.rb
214
207
  - spec/support/seed_data_setup.rb
208
+ - spec/support/seed_records.rb
215
209
  - spec/support/sequel_test_support.rb
216
210
  - spec/terrestrial/abstract_record_spec.rb
211
+ - spec/terrestrial/adapters/sequel_postgres_adapter_spec.rb
217
212
  - spec/terrestrial/collection_mutability_proxy_spec.rb
218
213
  - spec/terrestrial/deleted_record_spec.rb
219
214
  - spec/terrestrial/dirty_map_spec.rb
215
+ - spec/terrestrial/functional_pipeline_spec.rb
216
+ - spec/terrestrial/inspection_string_spec.rb
220
217
  - spec/terrestrial/lazy_collection_spec.rb
221
218
  - spec/terrestrial/lazy_object_proxy_spec.rb
222
- - spec/terrestrial/public_conveniencies_spec.rb
223
- - spec/terrestrial/upserted_record_spec.rb
219
+ - spec/terrestrial/upsert_record_spec.rb
224
220
  - terrestrial.gemspec
225
221
  homepage: https://github.com/bestie/terrestrial
226
222
  licenses:
@@ -241,8 +237,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
241
237
  - !ruby/object:Gem::Version
242
238
  version: '0'
243
239
  requirements: []
244
- rubyforge_project:
245
- rubygems_version: 2.5.1
240
+ rubygems_version: 3.1.6
246
241
  signing_key:
247
242
  specification_version: 4
248
243
  summary: A data mapper ORM for Ruby
@@ -250,8 +245,13 @@ test_files:
250
245
  - features/env.rb
251
246
  - features/example.feature
252
247
  - features/step_definitions/example_steps.rb
248
+ - spec/automatic_timestamps_spec.rb
249
+ - spec/changes_api_spec.rb
253
250
  - spec/config_override_spec.rb
254
251
  - spec/custom_serializers_spec.rb
252
+ - spec/database_default_fields_spec.rb
253
+ - spec/database_generated_id_spec.rb
254
+ - spec/database_owned_fields_and_timestamps_spec.rb
255
255
  - spec/deletion_spec.rb
256
256
  - spec/error_handling/factory_error_handling_spec.rb
257
257
  - spec/error_handling/serialization_error_spec.rb
@@ -272,12 +272,15 @@ test_files:
272
272
  - spec/support/object_graph_setup.rb
273
273
  - spec/support/object_store_setup.rb
274
274
  - spec/support/seed_data_setup.rb
275
+ - spec/support/seed_records.rb
275
276
  - spec/support/sequel_test_support.rb
276
277
  - spec/terrestrial/abstract_record_spec.rb
278
+ - spec/terrestrial/adapters/sequel_postgres_adapter_spec.rb
277
279
  - spec/terrestrial/collection_mutability_proxy_spec.rb
278
280
  - spec/terrestrial/deleted_record_spec.rb
279
281
  - spec/terrestrial/dirty_map_spec.rb
282
+ - spec/terrestrial/functional_pipeline_spec.rb
283
+ - spec/terrestrial/inspection_string_spec.rb
280
284
  - spec/terrestrial/lazy_collection_spec.rb
281
285
  - spec/terrestrial/lazy_object_proxy_spec.rb
282
- - spec/terrestrial/public_conveniencies_spec.rb
283
- - spec/terrestrial/upserted_record_spec.rb
286
+ - spec/terrestrial/upsert_record_spec.rb
data/MissingFeatures.md DELETED
@@ -1,64 +0,0 @@
1
- # Missing features
2
-
3
- The following features not included in Terrestrial are omitted purposefully to
4
- keep the library simple and encourage good practices in application code.
5
-
6
- Please open an issue if you feel like any of these features are essential or if
7
- you think you can contribute to a solution please open an issue to discuss.
8
-
9
- ## Coercion
10
-
11
- Database supported types will be returned as expected, `Fixnum`, `DateTime`, `nil` etc.
12
- Should you wish to enhance this data, every row is passed into the mapping's
13
- factory function where you have the opportunity to do arbitrary transformations
14
- before instantiating the domain object.
15
-
16
- \*see note on transforming row data
17
-
18
- ## Validation
19
-
20
- This is the concern of your domain model and/or application boundaries.
21
- Terrestrial allows you to persist any object you wish assuming schema
22
- compatibility.
23
-
24
- ## Database column name aliasing
25
-
26
- While at first glance this is a simple feature, the abstraction starts to leak
27
- when the using the query interface and guaranteeing all queries are substituted
28
- perfectly is beyond the scope of the current version.
29
-
30
- Should you wish to simply pass a column's key with a different parameter name
31
- then you can again lean on the factory function to transform the row's data
32
- before the domain object receives it.
33
-
34
- \*see note on transforming row data
35
-
36
- ## Cascade deletion
37
-
38
- This is chiefly a data concern and is handled by a good database more
39
- efficiently and effectively than any ORM could hope.
40
-
41
- ## Database generated IDs and timestamps
42
-
43
- While database generated values may work, available only after an object is
44
- retrieved, they are not currently supported.
45
-
46
- Data important to your domain should be generated in your application layer.
47
- UUIDs make much more flexible identifiers for domain objects and further enable
48
- decoupling and fast tests.
49
-
50
- Timestamps are useful and important to most applications however if they are
51
- used in your domain they should be pushed from explicitly from application
52
- layer. You should again find this affords you more flexibility and decoupling.
53
-
54
- There is absolutely nothing wrong with data added at time of persistence for
55
- auditing purposes but Terrestrial will make you actively decide whether this
56
- data should be available to the domain and what should be explicitly added.
57
-
58
- \* Transforming row data
59
-
60
- Adding a custom factory method to transform row data before passing it to the
61
- domain layer is highly encouraged. However, ensure that for each custom factory
62
- a serializer function is also supplied that Terrestrial can use to reverse the
63
- operation for persistence.
64
-
@@ -1,99 +0,0 @@
1
- require "forwardable"
2
- require "set"
3
-
4
- module Terrestrial
5
- class AbstractRecord
6
- extend Forwardable
7
- include Comparable
8
- include Enumerable
9
-
10
- def initialize(namespace, identity_fields, attributes = {}, depth = NoDepth)
11
- @namespace = namespace
12
- @identity_fields = identity_fields
13
- @attributes = attributes
14
- @depth = depth
15
- end
16
-
17
- attr_reader :namespace, :identity_fields, :attributes, :depth
18
- private :attributes
19
-
20
- def_delegators :to_h, :fetch
21
-
22
- def if_upsert(&block)
23
- self
24
- end
25
-
26
- def if_delete(&block)
27
- self
28
- end
29
-
30
- def each(&block)
31
- to_h.each(&block)
32
- end
33
-
34
- def keys
35
- attributes.keys
36
- end
37
-
38
- def identity
39
- attributes.select { |k,_v| identity_fields.include?(k) }
40
- end
41
-
42
- def non_identity_attributes
43
- attributes.reject { |k| identity.include?(k) }
44
- end
45
-
46
- def merge(more_data)
47
- new_with_raw_data(attributes.merge(more_data))
48
- end
49
-
50
- def merge!(more_data)
51
- attributes.merge!(more_data)
52
- end
53
-
54
- def reject(&block)
55
- new_with_raw_data(non_identity_attributes.reject(&block).merge(identity))
56
- end
57
-
58
- def to_h
59
- attributes.to_h
60
- end
61
-
62
- def empty?
63
- non_identity_attributes.empty?
64
- end
65
-
66
- def ==(other)
67
- self.class === other &&
68
- [operation, to_h] == [other.operation, other.to_h]
69
- end
70
-
71
- def <=>(other)
72
- depth <=> other.depth
73
- end
74
-
75
- def subset?(other_record)
76
- namespace == other_record.namespace &&
77
- to_set.subset?(other_record.to_set)
78
- end
79
-
80
- protected
81
-
82
- def operation
83
- NoOp
84
- end
85
-
86
- def to_set
87
- Set.new(attributes.to_a)
88
- end
89
-
90
- private
91
-
92
- NoOp = Module.new
93
- NoDepth = Module.new
94
-
95
- def new_with_raw_data(new_raw_data)
96
- self.class.new(namespace, identity_fields, new_raw_data, depth)
97
- end
98
- end
99
- end