stretchy-model 0.6.5 → 0.6.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +2 -1
- data/README.md +28 -10
- data/Rakefile +56 -0
- data/docs/.nojekyll +0 -0
- data/docs/README.md +147 -0
- data/docs/_coverpage.md +14 -0
- data/docs/_sidebar.md +14 -0
- data/docs/examples/_sidebar.md +15 -0
- data/docs/examples/data_analysis.md +216 -0
- data/docs/examples/semantic_search_with_llm.md +83 -0
- data/docs/examples/simple-ingest-pipeline.md +326 -0
- data/docs/guides/_sidebar.md +14 -0
- data/docs/guides/aggregations.md +142 -0
- data/docs/guides/machine-learning.md +154 -0
- data/docs/guides/models.md +372 -0
- data/docs/guides/pipelines.md +151 -0
- data/docs/guides/querying.md +361 -0
- data/docs/guides/quick-start.md +72 -0
- data/docs/guides/scopes.md +125 -0
- data/docs/index.html +113 -0
- data/docs/stretchy.cover.png +0 -0
- data/docs/stretchy.logo.png +0 -0
- data/docs/styles.css +90 -0
- data/lib/stretchy/attributes/transformers/keyword_transformer.rb +41 -35
- data/lib/stretchy/attributes/type/array.rb +24 -1
- data/lib/stretchy/attributes/type/base.rb +6 -2
- data/lib/stretchy/attributes/type/binary.rb +24 -17
- data/lib/stretchy/attributes/type/boolean.rb +29 -22
- data/lib/stretchy/attributes/type/completion.rb +18 -10
- data/lib/stretchy/attributes/type/constant_keyword.rb +35 -26
- data/lib/stretchy/attributes/type/date_time.rb +28 -17
- data/lib/stretchy/attributes/type/dense_vector.rb +46 -49
- data/lib/stretchy/attributes/type/flattened.rb +28 -19
- data/lib/stretchy/attributes/type/geo_point.rb +21 -12
- data/lib/stretchy/attributes/type/geo_shape.rb +21 -12
- data/lib/stretchy/attributes/type/hash.rb +24 -10
- data/lib/stretchy/attributes/type/histogram.rb +25 -0
- data/lib/stretchy/attributes/type/ip.rb +26 -17
- data/lib/stretchy/attributes/type/join.rb +16 -7
- data/lib/stretchy/attributes/type/keyword.rb +21 -26
- data/lib/stretchy/attributes/type/knn_vector.rb +47 -0
- data/lib/stretchy/attributes/type/match_only_text.rb +22 -1
- data/lib/stretchy/attributes/type/nested.rb +16 -11
- data/lib/stretchy/attributes/type/numeric/base.rb +30 -22
- data/lib/stretchy/attributes/type/numeric/byte.rb +20 -0
- data/lib/stretchy/attributes/type/numeric/double.rb +20 -0
- data/lib/stretchy/attributes/type/numeric/float.rb +20 -0
- data/lib/stretchy/attributes/type/numeric/half_float.rb +20 -0
- data/lib/stretchy/attributes/type/numeric/integer.rb +21 -1
- data/lib/stretchy/attributes/type/numeric/long.rb +20 -0
- data/lib/stretchy/attributes/type/numeric/scaled_float.rb +16 -7
- data/lib/stretchy/attributes/type/numeric/short.rb +20 -0
- data/lib/stretchy/attributes/type/numeric/unsigned_long.rb +21 -1
- data/lib/stretchy/attributes/type/percolator.rb +16 -4
- data/lib/stretchy/attributes/type/point.rb +19 -9
- data/lib/stretchy/attributes/type/range/base.rb +24 -1
- data/lib/stretchy/attributes/type/range/date_range.rb +21 -5
- data/lib/stretchy/attributes/type/range/double_range.rb +20 -4
- data/lib/stretchy/attributes/type/range/float_range.rb +21 -5
- data/lib/stretchy/attributes/type/range/integer_range.rb +20 -4
- data/lib/stretchy/attributes/type/range/ip_range.rb +20 -4
- data/lib/stretchy/attributes/type/range/long_range.rb +20 -4
- data/lib/stretchy/attributes/type/rank_feature.rb +16 -6
- data/lib/stretchy/attributes/type/rank_features.rb +16 -9
- data/lib/stretchy/attributes/type/search_as_you_type.rb +28 -18
- data/lib/stretchy/attributes/type/shape.rb +19 -9
- data/lib/stretchy/attributes/type/sparse_vector.rb +25 -21
- data/lib/stretchy/attributes/type/string.rb +42 -1
- data/lib/stretchy/attributes/type/text.rb +53 -28
- data/lib/stretchy/attributes/type/token_count.rb +21 -11
- data/lib/stretchy/attributes/type/version.rb +16 -6
- data/lib/stretchy/attributes/type/wildcard.rb +36 -25
- data/lib/stretchy/attributes.rb +29 -0
- data/lib/stretchy/delegation/gateway_delegation.rb +78 -0
- data/lib/stretchy/index_setting.rb +94 -0
- data/lib/stretchy/indexing/bulk.rb +75 -3
- data/lib/stretchy/model/callbacks.rb +1 -0
- data/lib/stretchy/model/common.rb +157 -0
- data/lib/stretchy/model/persistence.rb +144 -0
- data/lib/stretchy/model/refreshable.rb +26 -0
- data/lib/stretchy/pipeline.rb +2 -1
- data/lib/stretchy/pipelines/processor.rb +38 -36
- data/lib/stretchy/querying.rb +7 -8
- data/lib/stretchy/record.rb +5 -4
- data/lib/stretchy/relation.rb +229 -28
- data/lib/stretchy/relations/aggregation_methods/aggregation.rb +59 -0
- data/lib/stretchy/relations/aggregation_methods/avg.rb +45 -0
- data/lib/stretchy/relations/aggregation_methods/bucket_script.rb +47 -0
- data/lib/stretchy/relations/aggregation_methods/bucket_selector.rb +47 -0
- data/lib/stretchy/relations/aggregation_methods/bucket_sort.rb +47 -0
- data/lib/stretchy/relations/aggregation_methods/cardinality.rb +47 -0
- data/lib/stretchy/relations/aggregation_methods/children.rb +47 -0
- data/lib/stretchy/relations/aggregation_methods/composite.rb +41 -0
- data/lib/stretchy/relations/aggregation_methods/date_histogram.rb +53 -0
- data/lib/stretchy/relations/aggregation_methods/date_range.rb +53 -0
- data/lib/stretchy/relations/aggregation_methods/extended_stats.rb +48 -0
- data/lib/stretchy/relations/aggregation_methods/filter.rb +47 -0
- data/lib/stretchy/relations/aggregation_methods/filters.rb +47 -0
- data/lib/stretchy/relations/aggregation_methods/geo_bounds.rb +40 -0
- data/lib/stretchy/relations/aggregation_methods/geo_centroid.rb +40 -0
- data/lib/stretchy/relations/aggregation_methods/global.rb +39 -0
- data/lib/stretchy/relations/aggregation_methods/histogram.rb +43 -0
- data/lib/stretchy/relations/aggregation_methods/ip_range.rb +41 -0
- data/lib/stretchy/relations/aggregation_methods/max.rb +40 -0
- data/lib/stretchy/relations/aggregation_methods/min.rb +41 -0
- data/lib/stretchy/relations/aggregation_methods/missing.rb +40 -0
- data/lib/stretchy/relations/aggregation_methods/nested.rb +40 -0
- data/lib/stretchy/relations/aggregation_methods/percentile_ranks.rb +45 -0
- data/lib/stretchy/relations/aggregation_methods/percentiles.rb +45 -0
- data/lib/stretchy/relations/aggregation_methods/range.rb +42 -0
- data/lib/stretchy/relations/aggregation_methods/reverse_nested.rb +40 -0
- data/lib/stretchy/relations/aggregation_methods/sampler.rb +40 -0
- data/lib/stretchy/relations/aggregation_methods/scripted_metric.rb +43 -0
- data/lib/stretchy/relations/aggregation_methods/significant_terms.rb +45 -0
- data/lib/stretchy/relations/aggregation_methods/stats.rb +42 -0
- data/lib/stretchy/relations/aggregation_methods/sum.rb +42 -0
- data/lib/stretchy/relations/aggregation_methods/terms.rb +46 -0
- data/lib/stretchy/relations/aggregation_methods/top_hits.rb +42 -0
- data/lib/stretchy/relations/aggregation_methods/top_metrics.rb +44 -0
- data/lib/stretchy/relations/aggregation_methods/value_count.rb +41 -0
- data/lib/stretchy/relations/aggregation_methods/weighted_avg.rb +42 -0
- data/lib/stretchy/relations/aggregation_methods.rb +20 -749
- data/lib/stretchy/relations/finder_methods.rb +2 -18
- data/lib/stretchy/relations/null_relation.rb +55 -0
- data/lib/stretchy/relations/query_builder.rb +82 -36
- data/lib/stretchy/relations/query_methods/bind.rb +19 -0
- data/lib/stretchy/relations/query_methods/extending.rb +29 -0
- data/lib/stretchy/relations/query_methods/fields.rb +70 -0
- data/lib/stretchy/relations/query_methods/filter_query.rb +53 -0
- data/lib/stretchy/relations/query_methods/has_field.rb +40 -0
- data/lib/stretchy/relations/query_methods/highlight.rb +75 -0
- data/lib/stretchy/relations/query_methods/hybrid.rb +60 -0
- data/lib/stretchy/relations/query_methods/ids.rb +40 -0
- data/lib/stretchy/relations/query_methods/match.rb +52 -0
- data/lib/stretchy/relations/query_methods/must_not.rb +54 -0
- data/lib/stretchy/relations/query_methods/neural.rb +58 -0
- data/lib/stretchy/relations/query_methods/neural_sparse.rb +43 -0
- data/lib/stretchy/relations/query_methods/none.rb +21 -0
- data/lib/stretchy/relations/query_methods/or_filter.rb +21 -0
- data/lib/stretchy/relations/query_methods/order.rb +63 -0
- data/lib/stretchy/relations/query_methods/query_string.rb +44 -0
- data/lib/stretchy/relations/query_methods/regexp.rb +61 -0
- data/lib/stretchy/relations/query_methods/should.rb +51 -0
- data/lib/stretchy/relations/query_methods/size.rb +44 -0
- data/lib/stretchy/relations/query_methods/skip_callbacks.rb +47 -0
- data/lib/stretchy/relations/query_methods/source.rb +59 -0
- data/lib/stretchy/relations/query_methods/where.rb +113 -0
- data/lib/stretchy/relations/query_methods.rb +48 -569
- data/lib/stretchy/relations/scoping/default.rb +136 -0
- data/lib/stretchy/relations/scoping/named.rb +70 -0
- data/lib/stretchy/relations/scoping/scope_registry.rb +36 -0
- data/lib/stretchy/relations/scoping.rb +30 -0
- data/lib/stretchy/relations/search_option_methods.rb +2 -0
- data/lib/stretchy/version.rb +1 -1
- data/lib/stretchy.rb +17 -10
- metadata +111 -17
- data/lib/stretchy/common.rb +0 -38
- data/lib/stretchy/null_relation.rb +0 -53
- data/lib/stretchy/persistence.rb +0 -43
- data/lib/stretchy/refreshable.rb +0 -15
- data/lib/stretchy/scoping/default.rb +0 -134
- data/lib/stretchy/scoping/named.rb +0 -68
- data/lib/stretchy/scoping/scope_registry.rb +0 -34
- data/lib/stretchy/scoping.rb +0 -28
data/lib/stretchy/persistence.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
module Stretchy
|
2
|
-
module Persistence
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
class_methods do
|
6
|
-
def create(*args)
|
7
|
-
self.new(*args).save
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
def save
|
12
|
-
run_callbacks :save do
|
13
|
-
if new_record?
|
14
|
-
run_callbacks :create do
|
15
|
-
response = self.class.gateway.save(self.attributes)
|
16
|
-
self.id = response['_id']
|
17
|
-
end
|
18
|
-
else
|
19
|
-
self.class.gateway.save(self.attributes)
|
20
|
-
end
|
21
|
-
self
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def destroy
|
26
|
-
run_callbacks :destroy do
|
27
|
-
delete
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def delete
|
32
|
-
self.class.gateway.delete(self.id)["result"] == 'deleted'
|
33
|
-
end
|
34
|
-
|
35
|
-
def update(*args)
|
36
|
-
run_callbacks :update do
|
37
|
-
self.assign_attributes(*args)
|
38
|
-
self.save
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
end
|
data/lib/stretchy/refreshable.rb
DELETED
@@ -1,134 +0,0 @@
|
|
1
|
-
module Stretchy
|
2
|
-
module Scoping
|
3
|
-
module Default
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
included do
|
7
|
-
# Stores the default scope for the class.
|
8
|
-
class_attribute :default_scopes, instance_writer: false, instance_predicate: false
|
9
|
-
|
10
|
-
self.default_scopes = []
|
11
|
-
end
|
12
|
-
|
13
|
-
module ClassMethods
|
14
|
-
# Returns a scope for the model without the previously set scopes.
|
15
|
-
#
|
16
|
-
# class Post < ActiveRecord::Base
|
17
|
-
# def self.default_scope
|
18
|
-
# where published: true
|
19
|
-
# end
|
20
|
-
# end
|
21
|
-
#
|
22
|
-
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
|
23
|
-
# Post.unscoped.all # Fires "SELECT * FROM posts"
|
24
|
-
# Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts"
|
25
|
-
#
|
26
|
-
# This method also accepts a block. All queries inside the block will
|
27
|
-
# not use the previously set scopes.
|
28
|
-
#
|
29
|
-
# Post.unscoped {
|
30
|
-
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
31
|
-
# }
|
32
|
-
def unscoped
|
33
|
-
block_given? ? relation.scoping { yield } : relation
|
34
|
-
end
|
35
|
-
|
36
|
-
def before_remove_const #:nodoc:
|
37
|
-
self.current_scope = nil
|
38
|
-
end
|
39
|
-
|
40
|
-
protected
|
41
|
-
|
42
|
-
# Use this macro in your model to set a default scope for all operations on
|
43
|
-
# the model.
|
44
|
-
#
|
45
|
-
# class Article < ActiveRecord::Base
|
46
|
-
# default_scope { where(published: true) }
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# Article.all # => SELECT * FROM articles WHERE published = true
|
50
|
-
#
|
51
|
-
# The +default_scope+ is also applied while creating/building a record.
|
52
|
-
# It is not applied while updating a record.
|
53
|
-
#
|
54
|
-
# Article.new.published # => true
|
55
|
-
# Article.create.published # => true
|
56
|
-
#
|
57
|
-
# (You can also pass any object which responds to +call+ to the
|
58
|
-
# +default_scope+ macro, and it will be called when building the
|
59
|
-
# default scope.)
|
60
|
-
#
|
61
|
-
# If you use multiple +default_scope+ declarations in your model then
|
62
|
-
# they will be merged together:
|
63
|
-
#
|
64
|
-
# class Article < ActiveRecord::Base
|
65
|
-
# default_scope { where(published: true) }
|
66
|
-
# default_scope { where(rating: 'G') }
|
67
|
-
# end
|
68
|
-
#
|
69
|
-
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
|
70
|
-
#
|
71
|
-
# This is also the case with inheritance and module includes where the
|
72
|
-
# parent or module defines a +default_scope+ and the child or including
|
73
|
-
# class defines a second one.
|
74
|
-
#
|
75
|
-
# If you need to do more complex things with a default scope, you can
|
76
|
-
# alternatively define it as a class method:
|
77
|
-
#
|
78
|
-
# class Article < ActiveRecord::Base
|
79
|
-
# def self.default_scope
|
80
|
-
# # Should return a scope, you can call 'super' here etc.
|
81
|
-
# end
|
82
|
-
# end
|
83
|
-
def default_scope(scope = nil)
|
84
|
-
scope = Proc.new if block_given?
|
85
|
-
|
86
|
-
if scope.is_a?(Relation) || !scope.respond_to?(:call)
|
87
|
-
raise ArgumentError,
|
88
|
-
"Support for calling #default_scope without a block is removed. For example instead " \
|
89
|
-
"of `default_scope where(color: 'red')`, please use " \
|
90
|
-
"`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
|
91
|
-
"self.default_scope.)"
|
92
|
-
end
|
93
|
-
|
94
|
-
self.default_scopes += [scope]
|
95
|
-
end
|
96
|
-
|
97
|
-
def build_default_scope(base_rel = relation) # :nodoc:
|
98
|
-
if !self.is_a?(method(:default_scope).owner)
|
99
|
-
# The user has defined their own default scope method, so call that
|
100
|
-
evaluate_default_scope { default_scope }
|
101
|
-
elsif default_scopes.any?
|
102
|
-
evaluate_default_scope do
|
103
|
-
default_scopes.inject(base_rel) do |default_scope, scope|
|
104
|
-
default_scope.merge(base_rel.scoping { scope.call })
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def ignore_default_scope? # :nodoc:
|
111
|
-
ScopeRegistry.value_for(:ignore_default_scope, self)
|
112
|
-
end
|
113
|
-
|
114
|
-
def ignore_default_scope=(ignore) # :nodoc:
|
115
|
-
ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
|
116
|
-
end
|
117
|
-
|
118
|
-
# The ignore_default_scope flag is used to prevent an infinite recursion
|
119
|
-
# situation where a default scope references a scope which has a default
|
120
|
-
# scope which references a scope...
|
121
|
-
def evaluate_default_scope # :nodoc:
|
122
|
-
return if ignore_default_scope?
|
123
|
-
|
124
|
-
begin
|
125
|
-
self.ignore_default_scope = true
|
126
|
-
yield
|
127
|
-
ensure
|
128
|
-
self.ignore_default_scope = false
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
@@ -1,68 +0,0 @@
|
|
1
|
-
module Stretchy
|
2
|
-
module Scoping
|
3
|
-
module Named
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
module ClassMethods
|
7
|
-
def all(options={})
|
8
|
-
if current_scope
|
9
|
-
current_scope.clone
|
10
|
-
else
|
11
|
-
default_scoped
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def default_scoped # :nodoc:
|
16
|
-
relation.merge(build_default_scope)
|
17
|
-
end
|
18
|
-
|
19
|
-
# Collects attributes from scopes that should be applied when creating
|
20
|
-
# an AR instance for the particular class this is called on.
|
21
|
-
def scope_attributes # :nodoc:
|
22
|
-
all.scope_for_create
|
23
|
-
end
|
24
|
-
|
25
|
-
# Are there default attributes associated with this scope?
|
26
|
-
def scope_attributes? # :nodoc:
|
27
|
-
current_scope || default_scopes.any?
|
28
|
-
end
|
29
|
-
|
30
|
-
def scope(name, body, &block)
|
31
|
-
if dangerous_class_method?(name)
|
32
|
-
raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
|
33
|
-
"on the model \"#{self.name}\", but there's already defined " \
|
34
|
-
"a class method with the same name."
|
35
|
-
end
|
36
|
-
|
37
|
-
extension = Module.new(&block) if block
|
38
|
-
|
39
|
-
singleton_class.send(:define_method, name) do |*args|
|
40
|
-
scope = all.scoping { body.call(*args) }
|
41
|
-
scope = scope.extending(extension) if extension
|
42
|
-
|
43
|
-
scope || all
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
|
48
|
-
|
49
|
-
private
|
50
|
-
def dangerous_class_method?(method_name)
|
51
|
-
BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Model, self.superclass)
|
52
|
-
end
|
53
|
-
|
54
|
-
def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc
|
55
|
-
if klass.respond_to?(name, true)
|
56
|
-
if superklass.respond_to?(name, true)
|
57
|
-
klass.method(name).owner != superklass.method(name).owner
|
58
|
-
else
|
59
|
-
true
|
60
|
-
end
|
61
|
-
else
|
62
|
-
false
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module Stretchy
|
2
|
-
module Scoping
|
3
|
-
class ScopeRegistry # :nodoc:
|
4
|
-
# extend ActiveSupport::PerThreadRegistry
|
5
|
-
thread_mattr_accessor :registry
|
6
|
-
|
7
|
-
VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
|
8
|
-
|
9
|
-
# def initialize
|
10
|
-
self.registry = Hash.new { |hash, key| hash[key] = {} }
|
11
|
-
# end
|
12
|
-
|
13
|
-
# Obtains the value for a given +scope_name+ and +variable_name+.
|
14
|
-
def self.value_for(scope_type, variable_name)
|
15
|
-
raise_invalid_scope_type!(scope_type)
|
16
|
-
self.registry[scope_type][variable_name]
|
17
|
-
end
|
18
|
-
|
19
|
-
# Sets the +value+ for a given +scope_type+ and +variable_name+.
|
20
|
-
def self.set_value_for(scope_type, variable_name, value)
|
21
|
-
raise_invalid_scope_type!(scope_type)
|
22
|
-
self.registry[scope_type][variable_name] = value
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def raise_invalid_scope_type!(scope_type)
|
28
|
-
if !VALID_SCOPE_TYPES.include?(scope_type)
|
29
|
-
raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
data/lib/stretchy/scoping.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
module Stretchy
|
2
|
-
module Scoping
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
included do
|
6
|
-
include Default
|
7
|
-
include Named
|
8
|
-
end
|
9
|
-
|
10
|
-
module ClassMethods
|
11
|
-
def base_class
|
12
|
-
self
|
13
|
-
end
|
14
|
-
|
15
|
-
def current_scope
|
16
|
-
ScopeRegistry.new.registry[:current_scope][base_class.to_s]
|
17
|
-
# ScopeRegistry.value_for(:current_scope, base_class.to_s)
|
18
|
-
end
|
19
|
-
|
20
|
-
def current_scope=(scope) #:nodoc:
|
21
|
-
ScopeRegistry.new.registry[:current_scope][base_class.to_s] = scope
|
22
|
-
# ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|