locomotive-mongoid-tree 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +192 -0
- data/Rakefile +31 -0
- data/lib/mongoid/tree.rb +318 -0
- data/lib/mongoid/tree/ordering.rb +195 -0
- data/lib/mongoid/tree/traversal.rb +119 -0
- data/spec/mongoid/tree/ordering_spec.rb +328 -0
- data/spec/mongoid/tree/traversal_spec.rb +175 -0
- data/spec/mongoid/tree_spec.rb +382 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/macros/tree_macros.rb +44 -0
- data/spec/support/models/node.rb +28 -0
- metadata +125 -0
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Benedikt Deicke
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
= mongoid-tree http://travis-ci.org/benedikt/mongoid-tree.png
|
2
|
+
|
3
|
+
A tree structure for Mongoid documents using the materialized path pattern
|
4
|
+
|
5
|
+
== Requirements
|
6
|
+
|
7
|
+
* mongoid (~> 2.0)
|
8
|
+
|
9
|
+
|
10
|
+
== Install
|
11
|
+
|
12
|
+
To install mongoid_tree, simply add it to your Gemfile:
|
13
|
+
|
14
|
+
gem 'mongoid-tree', :require => 'mongoid/tree'
|
15
|
+
|
16
|
+
In order to get the latest development version of mongoid-tree:
|
17
|
+
|
18
|
+
gem 'mongoid-tree', :git => 'git://github.com/benedikt/mongoid-tree', :require => 'mongoid/tree'
|
19
|
+
|
20
|
+
You might want to remove the <tt>:require => 'mongoid/tree'</tt> option and explicitly <tt>require 'mongoid/tree'</tt> where needed and finally run
|
21
|
+
|
22
|
+
bundle install
|
23
|
+
|
24
|
+
|
25
|
+
== Usage
|
26
|
+
|
27
|
+
Read the API documentation at http://benedikt.github.com/mongoid-tree and take a look at the Mongoid::Tree module
|
28
|
+
|
29
|
+
class Node
|
30
|
+
include Mongoid::Document
|
31
|
+
include Mongoid::Tree
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
=== Utility methods
|
36
|
+
|
37
|
+
There are several utility methods that help getting to other related documents in the tree:
|
38
|
+
|
39
|
+
Node.root
|
40
|
+
Node.roots
|
41
|
+
Node.leaves
|
42
|
+
|
43
|
+
node.root
|
44
|
+
node.parent
|
45
|
+
node.children
|
46
|
+
node.ancestors
|
47
|
+
node.ancestors_and_self
|
48
|
+
node.descendants
|
49
|
+
node.descendants_and_self
|
50
|
+
node.siblings
|
51
|
+
node.siblings_and_self
|
52
|
+
node.leaves
|
53
|
+
|
54
|
+
In addition it's possible to check certain aspects of the document's position in the tree:
|
55
|
+
|
56
|
+
node.root?
|
57
|
+
node.leaf?
|
58
|
+
node.depth
|
59
|
+
node.ancestor_of?(other)
|
60
|
+
node.descendant_of?(other)
|
61
|
+
node.sibling_of?(other)
|
62
|
+
|
63
|
+
See Mongoid::Tree for more information on these methods.
|
64
|
+
|
65
|
+
|
66
|
+
=== Ordering
|
67
|
+
|
68
|
+
Mongoid::Tree doesn't order children by default. To enable ordering of tree nodes include the Mongoid::Tree::Ordering module. This will add a <tt>position</tt> field to your document and provide additional utility methods:
|
69
|
+
|
70
|
+
node.lower_siblings
|
71
|
+
node.higher_siblings
|
72
|
+
node.first_sibling_in_list
|
73
|
+
node.last_sibling_in_list
|
74
|
+
|
75
|
+
node.move_up
|
76
|
+
node.move_down
|
77
|
+
node.move_to_top
|
78
|
+
node.move_to_bottom
|
79
|
+
node.move_above(other)
|
80
|
+
node.move_below(other)
|
81
|
+
|
82
|
+
node.at_top?
|
83
|
+
node.at_bottom?
|
84
|
+
|
85
|
+
Example:
|
86
|
+
|
87
|
+
class Node
|
88
|
+
include Mongoid::Document
|
89
|
+
include Mongoid::Tree
|
90
|
+
include Mongoid::Tree::Ordering
|
91
|
+
end
|
92
|
+
|
93
|
+
See Mongoid::Tree::Ordering for more information on these methods.
|
94
|
+
|
95
|
+
=== Traversal
|
96
|
+
|
97
|
+
It's possible to traverse the tree using different traversal methods using the Mongoid::Tree::Traversal module.
|
98
|
+
|
99
|
+
Example:
|
100
|
+
|
101
|
+
class Node
|
102
|
+
include Mongoid::Document
|
103
|
+
include Mongoid::Tree
|
104
|
+
include Mongoid::Tree::Traversal
|
105
|
+
end
|
106
|
+
|
107
|
+
node.traverse(:breadth_first) do |n|
|
108
|
+
# Do something with Node n
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
=== Destroying
|
113
|
+
|
114
|
+
Mongoid::Tree does not handle destroying of nodes by default. However it provides several strategies that help you to deal with children of deleted documents. You can simply add them as <tt>before_destroy</tt> callbacks.
|
115
|
+
|
116
|
+
Available strategies are:
|
117
|
+
|
118
|
+
* :nullify_children -- Sets the children's parent_id to null
|
119
|
+
* :move_children_to_parent -- Moves the children to the current document's parent
|
120
|
+
* :destroy_children -- Destroys all children by calling their #destroy method (invokes callbacks)
|
121
|
+
* :delete_descendants -- Deletes all descendants using a database query (doesn't invoke callbacks)
|
122
|
+
|
123
|
+
Example:
|
124
|
+
|
125
|
+
class Node
|
126
|
+
include Mongoid::Document
|
127
|
+
include Mongoid::Tree
|
128
|
+
|
129
|
+
before_destroy :nullify_children
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
=== Callbacks
|
134
|
+
|
135
|
+
There are two callbacks that are called before and after the rearranging process. This enables you to do additional computations after the documents position in the tree is updated. See Mongoid::Tree for details.
|
136
|
+
|
137
|
+
Example:
|
138
|
+
|
139
|
+
class Page
|
140
|
+
include Mongoid::Document
|
141
|
+
include Mongoid::Tree
|
142
|
+
|
143
|
+
after_rearrange :rebuild_path
|
144
|
+
|
145
|
+
field :slug
|
146
|
+
field :path
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def rebuild_path
|
151
|
+
self.path = self.ancestors_and_self.collect(&:slug).join('/')
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
=== Validations
|
157
|
+
|
158
|
+
Mongoid::Tree currently does not validate the document's children or parent associations by default. To explicitly enable validation for children and parent documents it's required to add a <tt>validates_associated</tt> validation.
|
159
|
+
|
160
|
+
Example
|
161
|
+
|
162
|
+
class Node
|
163
|
+
include Mongoid::Document
|
164
|
+
include Mongoid::Tree
|
165
|
+
|
166
|
+
validates_associated :parent, :children
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
== Build Status
|
171
|
+
|
172
|
+
mongoid-tree is on {Travis}[http://travis-ci.org/benedikt/mongoid-tree] running the specs on Ruby 1.8.7, Ruby 1.9.2 and Ruby Enterprise Edition.
|
173
|
+
|
174
|
+
|
175
|
+
== Known issues
|
176
|
+
|
177
|
+
See https://github.com/benedikt/mongoid-tree/issues
|
178
|
+
|
179
|
+
|
180
|
+
== Repository
|
181
|
+
|
182
|
+
See https://github.com/benedikt/mongoid-tree and feel free to fork it!
|
183
|
+
|
184
|
+
|
185
|
+
== Contributors
|
186
|
+
|
187
|
+
See a list of all contributors at https://github.com/benedikt/mongoid-tree/contributors. Thanks a lot everyone!
|
188
|
+
|
189
|
+
|
190
|
+
== Copyright
|
191
|
+
|
192
|
+
Copyright (c) 2010-2011 Benedikt Deicke. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
require 'rdoc/task'
|
3
|
+
|
4
|
+
spec = Gem::Specification.load("locomotive-mongoid-tree.gemspec")
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
task :default => :spec
|
9
|
+
|
10
|
+
RDoc::Task.new do |rdoc|
|
11
|
+
rdoc.rdoc_dir = 'doc'
|
12
|
+
rdoc.title = "#{spec.name} #{spec.version}"
|
13
|
+
rdoc.options += spec.rdoc_options
|
14
|
+
rdoc.rdoc_files.include(spec.extra_rdoc_files)
|
15
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Build the .gem file"
|
19
|
+
task :build do
|
20
|
+
system "gem build #{spec.name}.gemspec"
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Push the .gem file to rubygems.org"
|
24
|
+
task :release => :build do
|
25
|
+
system "gem push #{spec.name}-#{spec.version}.gem"
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "Open an irb session"
|
29
|
+
task :console do
|
30
|
+
sh "irb -rubygems -I lib -r ./spec/spec_helper.rb"
|
31
|
+
end
|
data/lib/mongoid/tree.rb
ADDED
@@ -0,0 +1,318 @@
|
|
1
|
+
module Mongoid # :nodoc:
|
2
|
+
##
|
3
|
+
# = Mongoid::Tree
|
4
|
+
#
|
5
|
+
# This module extends any Mongoid document with tree functionality.
|
6
|
+
#
|
7
|
+
# == Usage
|
8
|
+
#
|
9
|
+
# Simply include the module in any Mongoid document:
|
10
|
+
#
|
11
|
+
# class Node
|
12
|
+
# include Mongoid::Document
|
13
|
+
# include Mongoid::Tree
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# === Using the tree structure
|
17
|
+
#
|
18
|
+
# Each document references many children. You can access them using the <tt>#children</tt> method.
|
19
|
+
#
|
20
|
+
# node = Node.create
|
21
|
+
# node.children.create
|
22
|
+
# node.children.count # => 1
|
23
|
+
#
|
24
|
+
# Every document references one parent (unless it's a root document).
|
25
|
+
#
|
26
|
+
# node = Node.create
|
27
|
+
# node.parent # => nil
|
28
|
+
# node.children.create
|
29
|
+
# node.children.first.parent # => node
|
30
|
+
#
|
31
|
+
# === Destroying
|
32
|
+
#
|
33
|
+
# Mongoid::Tree does not handle destroying of nodes by default. However it provides
|
34
|
+
# several strategies that help you to deal with children of deleted documents. You can
|
35
|
+
# simply add them as <tt>before_destroy</tt> callbacks.
|
36
|
+
#
|
37
|
+
# Available strategies are:
|
38
|
+
#
|
39
|
+
# * :nullify_children -- Sets the children's parent_id to null
|
40
|
+
# * :move_children_to_parent -- Moves the children to the current document's parent
|
41
|
+
# * :destroy_children -- Destroys all children by calling their #destroy method (invokes callbacks)
|
42
|
+
# * :delete_descendants -- Deletes all descendants using a database query (doesn't invoke callbacks)
|
43
|
+
#
|
44
|
+
# Example:
|
45
|
+
#
|
46
|
+
# class Node
|
47
|
+
# include Mongoid::Document
|
48
|
+
# include Mongoid::Tree
|
49
|
+
#
|
50
|
+
# before_destroy :nullify_children
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# === Callbacks
|
54
|
+
#
|
55
|
+
# Mongoid::Tree offers callbacks for its rearranging process. This enables you to
|
56
|
+
# rebuild certain fields when the document was moved in the tree. Rearranging happens
|
57
|
+
# before the document is validated. This gives you a chance to validate your additional
|
58
|
+
# changes done in your callbacks. See ActiveModel::Callbacks and ActiveSupport::Callbacks
|
59
|
+
# for further details on callbacks.
|
60
|
+
#
|
61
|
+
# Example:
|
62
|
+
#
|
63
|
+
# class Page
|
64
|
+
# include Mongoid::Document
|
65
|
+
# include Mongoid::Tree
|
66
|
+
#
|
67
|
+
# after_rearrange :rebuild_path
|
68
|
+
#
|
69
|
+
# field :slug
|
70
|
+
# field :path
|
71
|
+
#
|
72
|
+
# private
|
73
|
+
#
|
74
|
+
# def rebuild_path
|
75
|
+
# self.path = self.ancestors_and_self.collect(&:slug).join('/')
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
module Tree
|
80
|
+
extend ActiveSupport::Concern
|
81
|
+
|
82
|
+
autoload :Ordering, 'mongoid/tree/ordering'
|
83
|
+
autoload :Traversal, 'mongoid/tree/traversal'
|
84
|
+
|
85
|
+
included do
|
86
|
+
references_many :children, :class_name => self.name, :foreign_key => :parent_id, :inverse_of => :parent, :autosave => true, :validate => false
|
87
|
+
|
88
|
+
referenced_in :parent, :class_name => self.name, :inverse_of => :children, :index => true, :validate => false
|
89
|
+
|
90
|
+
field :parent_ids, :type => Array, :default => []
|
91
|
+
index :parent_ids
|
92
|
+
|
93
|
+
set_callback :save, :after, :rearrange_children, :if => :rearrange_children?
|
94
|
+
set_callback :validation, :before do
|
95
|
+
run_callbacks(:rearrange) { rearrange }
|
96
|
+
end
|
97
|
+
|
98
|
+
validate :position_in_tree
|
99
|
+
|
100
|
+
define_model_callbacks :rearrange, :only => [:before, :after]
|
101
|
+
|
102
|
+
class_eval "def base_class; ::#{self.name}; end"
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# :singleton-method: root
|
107
|
+
# Returns the first root document
|
108
|
+
|
109
|
+
##
|
110
|
+
# :singleton-method: roots
|
111
|
+
# Returns all root documents
|
112
|
+
|
113
|
+
##
|
114
|
+
# :singleton-method: leaves
|
115
|
+
# Returns all leaves (be careful, currently involves two queries)
|
116
|
+
|
117
|
+
##
|
118
|
+
# This module includes those methods documented above
|
119
|
+
module ClassMethods # :nodoc:
|
120
|
+
|
121
|
+
def root
|
122
|
+
first(:conditions => { :parent_id => nil })
|
123
|
+
end
|
124
|
+
|
125
|
+
def roots
|
126
|
+
where(:parent_id => nil)
|
127
|
+
end
|
128
|
+
|
129
|
+
def leaves
|
130
|
+
where(:_id.nin => only(:parent_id).collect(&:parent_id))
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
##
|
136
|
+
# :singleton-method: before_rearrange
|
137
|
+
# Sets a callback that is called before the document is rearranged
|
138
|
+
# (Generated by ActiveSupport)
|
139
|
+
|
140
|
+
##
|
141
|
+
# :singleton-method: after_rearrange
|
142
|
+
# Sets a callback that is called after the document is rearranged
|
143
|
+
# (Generated by ActiveSupport)
|
144
|
+
|
145
|
+
##
|
146
|
+
# :method: children
|
147
|
+
# Returns a list of the document's children. It's a <tt>references_many</tt> association.
|
148
|
+
# (Generated by Mongoid)
|
149
|
+
|
150
|
+
##
|
151
|
+
# :method: parent
|
152
|
+
# Returns the document's parent (unless it's a root document). It's a <tt>referenced_in</tt> association.
|
153
|
+
# (Generated by Mongoid)
|
154
|
+
|
155
|
+
##
|
156
|
+
# :method: parent=
|
157
|
+
#call-seq:
|
158
|
+
# parent= document
|
159
|
+
#
|
160
|
+
# Sets this documents parent document.
|
161
|
+
# (Generated by Mongoid)
|
162
|
+
|
163
|
+
##
|
164
|
+
# :method: parent_ids
|
165
|
+
# Returns a list of the document's parent_ids, starting with the root node.
|
166
|
+
# (Generated by Mongoid)
|
167
|
+
|
168
|
+
##
|
169
|
+
# Is this document a root node (has no parent)?
|
170
|
+
def root?
|
171
|
+
parent_id.nil?
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Is this document a leaf node (has no children)?
|
176
|
+
def leaf?
|
177
|
+
children.empty?
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Returns the depth of this document (number of ancestors)
|
182
|
+
def depth
|
183
|
+
parent_ids.count
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# Returns this document's root node
|
188
|
+
def root
|
189
|
+
if parent_ids.present?
|
190
|
+
return base_class.find(parent_ids.first)
|
191
|
+
else
|
192
|
+
return self.root? ? self : self.parent.root
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
##
|
197
|
+
# Returns a chainable criteria for this document's ancestors
|
198
|
+
def ancestors
|
199
|
+
base_class.where(:_id.in => parent_ids)
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Returns an array of this document's ancestors and itself
|
204
|
+
def ancestors_and_self
|
205
|
+
ancestors + [self]
|
206
|
+
end
|
207
|
+
|
208
|
+
##
|
209
|
+
# Is this document an ancestor of the other document?
|
210
|
+
def ancestor_of?(other)
|
211
|
+
other.parent_ids.include?(self.id)
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# Returns a chainable criteria for this document's descendants
|
216
|
+
def descendants
|
217
|
+
base_class.where(:parent_ids => self.id)
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Returns and array of this document's descendants and itself
|
222
|
+
def descendants_and_self
|
223
|
+
[self] + descendants
|
224
|
+
end
|
225
|
+
|
226
|
+
##
|
227
|
+
# Is this document a descendant of the other document?
|
228
|
+
def descendant_of?(other)
|
229
|
+
self.parent_ids.include?(other.id)
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# Returns this document's siblings
|
234
|
+
def siblings
|
235
|
+
siblings_and_self.excludes(:id => self.id)
|
236
|
+
end
|
237
|
+
|
238
|
+
##
|
239
|
+
# Returns this document's siblings and itself
|
240
|
+
def siblings_and_self
|
241
|
+
base_class.where(:parent_id => self.parent_id)
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# Is this document a sibling of the other document?
|
246
|
+
def sibling_of?(other)
|
247
|
+
self.parent_id == other.parent_id
|
248
|
+
end
|
249
|
+
|
250
|
+
##
|
251
|
+
# Returns all leaves of this document (be careful, currently involves two queries)
|
252
|
+
def leaves
|
253
|
+
base_class.where(:_id.nin => base_class.only(:parent_id).collect(&:parent_id)).and(:parent_ids => self.id)
|
254
|
+
end
|
255
|
+
|
256
|
+
##
|
257
|
+
# Forces rearranging of all children after next save
|
258
|
+
def rearrange_children!
|
259
|
+
@rearrange_children = true
|
260
|
+
end
|
261
|
+
|
262
|
+
##
|
263
|
+
# Will the children be rearranged after next save?
|
264
|
+
def rearrange_children?
|
265
|
+
!!@rearrange_children
|
266
|
+
end
|
267
|
+
|
268
|
+
##
|
269
|
+
# Nullifies all children's parent_id
|
270
|
+
def nullify_children
|
271
|
+
children.each do |c|
|
272
|
+
c.parent = c.parent_id = nil
|
273
|
+
c.save
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
##
|
278
|
+
# Moves all children to this document's parent
|
279
|
+
def move_children_to_parent
|
280
|
+
children.each do |c|
|
281
|
+
c.parent_id = self.parent_id
|
282
|
+
c.save
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
##
|
287
|
+
# Deletes all descendants using the database (doesn't invoke callbacks)
|
288
|
+
def delete_descendants
|
289
|
+
base_class.delete_all(:conditions => { :parent_ids => self.id })
|
290
|
+
end
|
291
|
+
|
292
|
+
##
|
293
|
+
# Destroys all children by calling their #destroy method (does invoke callbacks)
|
294
|
+
def destroy_children
|
295
|
+
children.destroy_all
|
296
|
+
end
|
297
|
+
|
298
|
+
private
|
299
|
+
def rearrange
|
300
|
+
if self.parent_id
|
301
|
+
self.parent_ids = parent.parent_ids + [self.parent_id]
|
302
|
+
else
|
303
|
+
self.parent_ids = []
|
304
|
+
end
|
305
|
+
|
306
|
+
rearrange_children! if self.parent_ids_changed?
|
307
|
+
end
|
308
|
+
|
309
|
+
def rearrange_children
|
310
|
+
@rearrange_children = false
|
311
|
+
self.children.each { |c| c.save }
|
312
|
+
end
|
313
|
+
|
314
|
+
def position_in_tree
|
315
|
+
errors.add(:parent_id, :invalid) if self.parent_ids.include?(self.id)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|