stretchy-model 0.6.5 → 0.7.0
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 +15 -0
- data/docs/examples/_sidebar.md +15 -0
- data/docs/examples/data_analysis.md +216 -0
- data/docs/examples/neural_search_with_llm.md +381 -0
- data/docs/examples/simple-ingest-pipeline.md +326 -0
- data/docs/guides/_sidebar.md +15 -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/elasticsearch/api/actions/connector/check_in.rb +64 -0
- data/lib/elasticsearch/api/actions/connector/delete.rb +64 -0
- data/lib/elasticsearch/api/actions/connector/get.rb +64 -0
- data/lib/elasticsearch/api/actions/connector/last_sync.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/list.rb +60 -0
- data/lib/elasticsearch/api/actions/connector/post.rb +57 -0
- data/lib/elasticsearch/api/actions/connector/put.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/update_api_key_id.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/update_configuration.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/update_error.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/update_filtering.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/update_index_name.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/update_name.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/update_native.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/update_pipeline.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/update_scheduling.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/update_service_type.rb +66 -0
- data/lib/elasticsearch/api/actions/connector/update_status.rb +66 -0
- data/lib/elasticsearch/api/namespace/connector.rb +36 -0
- data/lib/opensearch/api/actions/machine_learning/connector/delete.rb +42 -0
- data/lib/opensearch/api/actions/machine_learning/connector/get.rb +42 -0
- data/lib/opensearch/api/actions/machine_learning/connector/list.rb +38 -0
- data/lib/opensearch/api/actions/machine_learning/connector/post.rb +35 -0
- data/lib/opensearch/api/actions/machine_learning/connector/put.rb +44 -0
- data/lib/opensearch/api/actions/machine_learning/models/predict.rb +32 -0
- data/lib/opensearch/api/namespace/connector.rb +19 -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/machine_learning/connector.rb +130 -0
- data/lib/stretchy/machine_learning/errors.rb +25 -0
- data/lib/stretchy/machine_learning/model.rb +162 -109
- data/lib/stretchy/machine_learning/registry.rb +19 -0
- 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/open_search_compatibility.rb +2 -0
- data/lib/stretchy/pipeline.rb +2 -1
- data/lib/stretchy/pipelines/processor.rb +40 -36
- data/lib/stretchy/querying.rb +7 -8
- data/lib/stretchy/rails/railtie.rb +11 -0
- data/lib/stretchy/rails/tasks/connector/create.rake +32 -0
- data/lib/stretchy/rails/tasks/connector/delete.rake +27 -0
- data/lib/stretchy/rails/tasks/connector/status.rake +31 -0
- data/lib/stretchy/rails/tasks/connector/update.rake +32 -0
- data/lib/stretchy/rails/tasks/index/create.rake +28 -0
- data/lib/stretchy/rails/tasks/index/delete.rake +27 -0
- data/lib/stretchy/rails/tasks/index/status.rake +23 -0
- data/lib/stretchy/rails/tasks/ml/delete.rake +25 -0
- data/lib/stretchy/rails/tasks/ml/deploy.rake +78 -0
- data/lib/stretchy/rails/tasks/ml/status.rake +31 -0
- data/lib/stretchy/rails/tasks/pipeline/create.rake +27 -0
- data/lib/stretchy/rails/tasks/pipeline/delete.rake +26 -0
- data/lib/stretchy/rails/tasks/pipeline/status.rake +25 -0
- data/lib/stretchy/rails/tasks/status.rake +15 -0
- data/lib/stretchy/rails/tasks/stretchy.rake +42 -0
- 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 +24 -10
- metadata +170 -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
@@ -0,0 +1,361 @@
|
|
1
|
+
# Querying
|
2
|
+
|
3
|
+
## Adding Query Conditions
|
4
|
+
|
5
|
+
### Must
|
6
|
+
>[!TIP|style:flat|label:where]
|
7
|
+
> `.where` is aliased as `.must` and is provided for familiarity. They are interchangeable in functionality.
|
8
|
+
|
9
|
+
The `.where` method is used to filter the documents that should be returned from a search. It adds conditions to the `must` clause of the query, which means that only documents that match all of the conditions will be returned.
|
10
|
+
|
11
|
+
The `.where` method is flexible and can accept multiple conditions and different types of expressions. The `.where` method returns a new `Stretchy::Relation` object, so you can chain other methods onto it to further refine your search. Here are some examples of how it can be used:
|
12
|
+
|
13
|
+
You can pass one or more key-value pairs to `.where` to search for documents where specific fields have specific values. For example, `Model.where(color: 'blue', title: 'Candy')` will return only documents where the `color` field is 'blue' and the `title` field is 'Candy'.
|
14
|
+
```ruby
|
15
|
+
Model.where(color: 'blue', :title: "Candy")
|
16
|
+
```
|
17
|
+
|
18
|
+
>[!INFO|label:Default Behavior|style:flat]
|
19
|
+
> The default behavior is to create a term query for each argument against keyword fields.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
Model.must(file_name: 'rb', content: '.where')
|
23
|
+
```
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
{
|
27
|
+
"query" => {
|
28
|
+
"bool" => {
|
29
|
+
"must" => [
|
30
|
+
{
|
31
|
+
"term" => {
|
32
|
+
"file_name.keyword" => "rb"
|
33
|
+
}
|
34
|
+
},
|
35
|
+
{
|
36
|
+
"term" => {
|
37
|
+
"content.keyword" => ".where"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
]
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
```
|
45
|
+
|
46
|
+
For `match` behavior the query can be modified like so:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
Model.must(match: {file_name: '*.rb', content: '.where'})
|
50
|
+
#or Model.where(match: {file_name: '*.rb', content: '.where'})
|
51
|
+
```
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
{
|
55
|
+
"query" => {
|
56
|
+
"bool" => {
|
57
|
+
"must" => [
|
58
|
+
{
|
59
|
+
"match" => {
|
60
|
+
"file_name" => "*.rb"
|
61
|
+
}
|
62
|
+
},
|
63
|
+
{
|
64
|
+
"match" => {
|
65
|
+
"content" => ".where"
|
66
|
+
}
|
67
|
+
}
|
68
|
+
]
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
```
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
##### Ranges
|
77
|
+
You can use ranges to search for documents where a field's value falls within a certain range. For example,
|
78
|
+
```ruby
|
79
|
+
Model.where(date: 2.days.ago...1.day.ago)
|
80
|
+
|
81
|
+
Model.where(age: 18..30)
|
82
|
+
|
83
|
+
Model.where(price: {gte: 100, lt: 140})
|
84
|
+
```
|
85
|
+
|
86
|
+
##### Regex
|
87
|
+
You can use regular expressions to search for documents where a field's value matches a certain pattern.
|
88
|
+
```ruby
|
89
|
+
Model.where(name: /br.*n/i)
|
90
|
+
```
|
91
|
+
|
92
|
+
##### Terms
|
93
|
+
You can use an array of values to search for documents where a field's value is in the array.
|
94
|
+
```ruby
|
95
|
+
Model.where(name: ['Candy', 'Lilly'])
|
96
|
+
```
|
97
|
+
|
98
|
+
### Should
|
99
|
+
The `.should` method is used to add conditions to the should clause of the query. This is different from the `.where` and `.filter_query` methods, which add conditions to the `must` and `filter` clauses, respectively.
|
100
|
+
|
101
|
+
Conditions in the `should` clause are optional: a document can match any number of `should` conditions, including none of them. However, each `should` condition that a document matches increases its relevance score, so documents that match more `should` conditions are ranked higher in the results.
|
102
|
+
|
103
|
+
This makes `.should` useful for conditions that should increase the relevance of documents, but should not exclude documents that don't meet them. For example, you might use `.should` to give a higher ranking to documents that contain certain keywords or that were published recently.
|
104
|
+
|
105
|
+
Here's an example of how you might use the `.should` method:
|
106
|
+
```ruby
|
107
|
+
Model.should(title: 'Important').should(published_at: { gte: '2022-01-01' })
|
108
|
+
```
|
109
|
+
In this example, the `.should` method is used to increase the relevance of documents where the `title` field contains 'Important' and where the `published_at` field is on or after '2022-01-01'. Documents that don't meet these conditions are still included in the results, but they are ranked lower.
|
110
|
+
|
111
|
+
The `.should` method returns a new `Stretchy::Relation` object, so you can chain other methods onto it to further refine your search.
|
112
|
+
|
113
|
+
### Negation
|
114
|
+
The `.where_not` (aliased as `.must_not`) method is used to add conditions to the `must_not` clause of the query. This is different from the `.where` and `.filter_query` methods, which add conditions to the `must` and `filter` clauses, respectively.
|
115
|
+
|
116
|
+
Conditions in the `must_not` clause are used to exclude documents from the results. A document will be excluded if it matches any of the `must_not` conditions. This makes `.where_not` (or `.must_not`) useful for conditions that should exclude documents from the results.
|
117
|
+
|
118
|
+
Here's an example of how you might use the `.where_not` (or `.must_not`) method:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
Model.where_not(status: 'deleted')
|
122
|
+
```
|
123
|
+
In this example, the `.where_not` method is used to exclude documents where the `status` field is 'deleted'. Documents that meet this condition are not included in the results, regardless of whether they match any other conditions.
|
124
|
+
|
125
|
+
The `.where_not` method returns a new `Stretchy::Relation` object, so you can chain other methods onto it to further refine your search.
|
126
|
+
|
127
|
+
## Filter Query
|
128
|
+
|
129
|
+
The `.filter_query` method is used to add conditions to the filter clause of the query. This is similar to the .where method, but with an important difference: while the .where method affects both the matching of documents and the calculation of relevance scores, the `.filter_query` method only affects matching and does not affect scoring.
|
130
|
+
|
131
|
+
This makes `.filter_query` useful for conditions that should exclude documents from the results, but should not affect how the remaining documents are ranked. For example, you might use `.filter_query` to exclude documents that are marked as 'deleted' or that fall outside a certain date range.
|
132
|
+
|
133
|
+
In this example, the `.filter_query` method is used to exclude documents where the `deleted` field is `true` and where the `published_at` field is before '2022-01-01'. The remaining documents are ranked based on their relevance to the query, without considering the `deleted` and `published_at` fields.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
Model.filter_query(:term, deleted: false, color: 'green')
|
137
|
+
```
|
138
|
+
|
139
|
+
|
140
|
+
In this example, the `.filter_query` is used to generate a `range` filter and include documents where `author.age` is between 18 and 30.
|
141
|
+
```ruby
|
142
|
+
Model.filter_query(:range, 'author.age': {gte: 18, lte: 30})
|
143
|
+
```
|
144
|
+
|
145
|
+
|
146
|
+
The `.filter_query` method returns a new `Stretchy::Relation` object, so you can chain other methods onto it to further refine your search.
|
147
|
+
|
148
|
+
## Sorting
|
149
|
+
|
150
|
+
Sorting in Stretchy is done using the `.order` (aliased as `.sort`) method. This method allows you to specify one or more fields to sort the search results by. You can also specify the sort order for each field: ascending (`asc`) or descending (`desc`).
|
151
|
+
|
152
|
+
Here's an example of how you might use the `.order` method:
|
153
|
+
`.order`
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
Model.order(title: 'asc')
|
157
|
+
```
|
158
|
+
|
159
|
+
In this example, the search results will be sorted by the `title` field in ascending order. Documents with a lower `title` value will appear before documents with a higher `title` value.
|
160
|
+
|
161
|
+
You can also sort by multiple fields. For example:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
Model.order(title: 'asc', published_at: 'desc')
|
165
|
+
```
|
166
|
+
|
167
|
+
In this example, the search results will first be sorted by the `title` field in ascending order. If two documents have the same `title`, they will be sorted by the `published_at` field in descending order. This means that among the documents with the same title, the ones that were published more recently will appear first.
|
168
|
+
|
169
|
+
### Sorting Defaults
|
170
|
+
In Stretchy, the default_sort_key is the field that is used for sorting when no other sort order is specified. By default, this is set to `:created_at`, which means that if you don't specify a sort order, the search results will be sorted by the `created_at` field.
|
171
|
+
|
172
|
+
You can change the `default_sort_key` for a model by using the `default_sort_key` class method in your model definition. Here's an example:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
class Model < StretchyModel
|
176
|
+
default_sort_key :new_sort_field
|
177
|
+
end
|
178
|
+
```
|
179
|
+
In this example, the `default_sort_key` for the `Model` class is set to `:new_sort_field`. This means that if you don't specify a sort order when querying the `Model` class, the search results will be sorted by the `new_sort_field` field.
|
180
|
+
|
181
|
+
Please note that you should replace `Model` with the name of your actual model, and `new_sort_field` with the name of the field that you want to use as the default sort key.
|
182
|
+
|
183
|
+
## Query String
|
184
|
+
|
185
|
+
The .query_string method is used to add a query string to the search. This method allows you to use Elasticsearch's [Query String syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax) to define complex search conditions.
|
186
|
+
|
187
|
+
The Query String syntax includes a series of terms and operators. Terms can be single words or phrases. Operators include `AND`, `OR`, and `NOT`, among others. Field names can be included in the query string to search for specific values in specific fields. For example, `"eye_color: green"` will match documents where the `eye_color` field is 'green'. The default operator between terms is OR.
|
188
|
+
|
189
|
+
Here's an example of how you might use the `.query_string` method:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
Model.query_string("((big cat) OR (domestic cat)) AND NOT panther eye_color: green")
|
193
|
+
```
|
194
|
+
|
195
|
+
In this example, the search results will include documents that match the query string. This means that they must either contain the phrase 'big cat' or the phrase 'domestic cat', they must not contain the word 'panther', and they must have 'green' as the value of the `eye_color` field.
|
196
|
+
|
197
|
+
The `.query_string` method returns a new `Stretchy::Relation` object, so you can chain other methods onto it to further refine your search.
|
198
|
+
|
199
|
+
### Changing the Default Operator
|
200
|
+
|
201
|
+
By default, the operator between terms in the query string is `OR`. This means that if a document matches any of the terms, it will be included in the search results. If you want to change this so that a document must match all of the terms to be included in the search results, you can set the `default_operator` option to `"AND"`.
|
202
|
+
|
203
|
+
Here's an example:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
Model.search_options(default_operator: "AND").query_string("(big cat) (domestic cat)")
|
207
|
+
```
|
208
|
+
In this example, the search results will include only documents that contain both 'big cat' and 'domestic cat'. If a document contains only one of these phrases, it will not be included in the search results.
|
209
|
+
|
210
|
+
## Highlighting
|
211
|
+
|
212
|
+
The `.highlight` method is used to highlight search matches in the fields of the returned documents. This can be useful to show where the search terms appear in the results.
|
213
|
+
|
214
|
+
The `.highlight` method accepts a list of field names to highlight. When a field is highlighted, Elasticsearch will return the original field value with the matching search terms wrapped in `<em> ` tags.
|
215
|
+
|
216
|
+
Here's an example of how you might use the `.highlight` method:
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
Model.query_string("big cat").highlight(:title, :description)
|
220
|
+
```
|
221
|
+
|
222
|
+
In this example, the search results will include documents that contain 'big cat' in either the `title` or `description` field. The matching phrases in these fields will be highlighted.
|
223
|
+
|
224
|
+
The `.highlight` method returns a new `Stretchy::Relation` object, so you can chain other methods onto it to further refine your search.
|
225
|
+
|
226
|
+
Please note that highlighting requires extra processing by Elasticsearch, so it can slow down your search queries. You should use it sparingly and only when necessary.
|
227
|
+
|
228
|
+
## Source Filtering
|
229
|
+
The `.source` method is used to control which fields of the source document are returned in the search results. This can be useful to reduce the amount of data that is returned, especially for large documents.
|
230
|
+
|
231
|
+
The `.source` method accepts a list of field names to include in the results. Only the specified fields will be returned.
|
232
|
+
|
233
|
+
Here's an example of how you might use the `.source` method:
|
234
|
+
```ruby
|
235
|
+
Model.source(:title, :description)
|
236
|
+
```
|
237
|
+
|
238
|
+
In this example, the search results will include only the `title` and `description` fields of the matching documents.
|
239
|
+
|
240
|
+
## Field Selection
|
241
|
+
The `.fields` method is similar to the `.source` method, but it allows you to retrieve fields that are not included in the source document. This can be useful to retrieve fields that are computed at query time, such as script fields or doc values fields.
|
242
|
+
|
243
|
+
The `.fields` method accepts a list of field names to retrieve.
|
244
|
+
|
245
|
+
Here's an example of how you might use the `.fields` method:
|
246
|
+
|
247
|
+
```ruby
|
248
|
+
Model.fields(:computed_field, :doc_values_field)
|
249
|
+
```
|
250
|
+
In this example, the search results will include the `computed_field` and `doc_values_field` fields of the matching documents, even if these fields are not included in the source document.
|
251
|
+
|
252
|
+
Both the `.source` and `.fields` methods return a new `Stretchy::Relation` object, so you can chain other methods onto them to further refine your search.
|
253
|
+
|
254
|
+
## Search Options
|
255
|
+
|
256
|
+
The `.search_options` method is used to specify options that control how the search is executed. These options are passed directly to Elasticsearch and can include any of the parameters that [Elasticsearch's Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html#search-search-api-path-params) accepts.
|
257
|
+
|
258
|
+
Here's an example of how you might use the `.search_options` method:
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
Model.search_options(routing: 'user1', preference: '_local')
|
262
|
+
```
|
263
|
+
In this example, the `routing` option is set to 'user1', which means that the search will be routed to the shard that contains documents with 'user1' as the routing value. The `preference` option is set to '_local', which means that the search will prefer to be executed on the local shard if possible.
|
264
|
+
|
265
|
+
The `.search_options` method returns a new `Stretchy::Relation` object, so you can chain other methods onto it to further refine your search.
|
266
|
+
|
267
|
+
|
268
|
+
### Routing
|
269
|
+
|
270
|
+
The `.routing` method is a convenient way to specify the routing value for your search queries. Routing in Elasticsearch is a mechanism to control which shard a document is indexed into. When executing a search query, if a routing value is provided, Elasticsearch will only search the shards that match the routing value. This can significantly speed up search performance by reducing the number of shards that need to be searched.
|
271
|
+
|
272
|
+
Here's an example of how you might use the `.routing` method:
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
Model.routing('user1')
|
276
|
+
```
|
277
|
+
|
278
|
+
In this example, the search will be routed to the shard that contains documents with 'user1' as the routing value. This means that only the shard with 'user1' will be searched, which can make the search faster if there are many shards and only a few of them contain documents with 'user1'.
|
279
|
+
|
280
|
+
The `.routing` method returns a new `Stretchy::Relation` object, so you can chain other methods onto it to further refine your search.
|
281
|
+
|
282
|
+
### Query Safety
|
283
|
+
|
284
|
+
Stretchy provides a mechanism to add requirements for queries that act as a circuit breaker. This can be helpful in preventing rogue queries that could potentially harm the performance of your Elasticsearch cluster.
|
285
|
+
|
286
|
+
The `query_must_have` method is used to specify these requirements. This method takes three arguments:
|
287
|
+
|
288
|
+
* The first argument is the name of the requirement.
|
289
|
+
* The second argument is the location where the requirement should be checked. This can be `:search_option`, `:query`, `:filter`, or `:must_not`.
|
290
|
+
* The third argument is a Proc that is used to validate the requirement. This Proc takes two arguments: the options that were passed to the search method, and the values that were passed to the `query_must_have` method. The Proc should return true if the requirement is met, and false otherwise.
|
291
|
+
Here's an example of how you might use the `query_must_have` method:
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
class Model < Stretchy::Model
|
295
|
+
query_must_have :routing, in: :search_option, validate_with: Proc.new { |options, values| options.include? :routing }
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
In this example, a requirement is added that the `:routing` option must be included in the search options. If a search is performed on the `Model` class without including the `:routing` option, an error will be raised.
|
300
|
+
|
301
|
+
This can be useful to ensure that all searches are routed to the correct shard, which can improve search performance. It can also help to prevent rogue queries that don't include the `:routing` option and could potentially search all shards, which could harm performance.
|
302
|
+
|
303
|
+
#### Skipping Callbacks
|
304
|
+
The `.skip_callbacks` method is used to skip the enforcement of certain `query_must_have` requirements for the immediate query. This can be useful in certain situations where you need to bypass the usual requirements, but it should be used with caution, as it can potentially lead to rogue queries.
|
305
|
+
|
306
|
+
The `.skip_callbacks` method accepts a list of requirement names to skip.
|
307
|
+
|
308
|
+
Here's an example of how you might use the `.skip_callbacks` method:
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
Model.skip_callbacks(:routing).where(title: 'goats')
|
312
|
+
```
|
313
|
+
In this example, the `:routing` requirement is skipped for the immediate query. This means that even though the `Model` class has a `query_must_have` requirement for `:routing`, this requirement will not be enforced for this query. The query will be executed even if the `:routing` option is not included.
|
314
|
+
|
315
|
+
Please note that skipping callbacks can potentially harm performance and lead to unexpected results, so it should be used sparingly and only when necessary.
|
316
|
+
|
317
|
+
## Vector Search
|
318
|
+
>[!NOTE|style:flat|label:Compatability ]
|
319
|
+
> Some of these features only work with Opensearch 2.12+ or require an Elasticsearch license to access Machine Learning features.
|
320
|
+
|
321
|
+
Before using neural search, you must set up a [Machine Learning](guides/machine-learning?id=machine-learning) model.
|
322
|
+
|
323
|
+
|
324
|
+
### neural
|
325
|
+
Neural search transforms text into vectors and facilitates vector search both at ingestion time and at search time. During ingestion, neural search transforms document text into vector embeddings and indexes both the text and its vector embeddings in a vector index. When you use a neural query during search, neural search converts the query text into vector embeddings, uses vector search to compare the query and document embeddings, and returns the closest results.
|
326
|
+
|
327
|
+
Before you ingest documents into an index, documents are passed through a machine learning (ML) model, which generates vector embeddings for the document fields. When you send a search request, the query text or image is also passed through the ML model, which generates the corresponding vector embeddings. Then neural search performs a vector search on the embeddings and returns matching documents.
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
Model.neural(vector_embedding: "soft cats", k: 5)
|
331
|
+
```
|
332
|
+
|
333
|
+
##### Multimodal
|
334
|
+
|
335
|
+
By supplying a hash with `query_image` and/or `query_text` to the embedding field you can perform multimodal neural search:
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
Model.neural(vector_embedding: {
|
339
|
+
query_text: 'Leather coat',
|
340
|
+
query_image: "iDs0djslsdSUAfx11..."
|
341
|
+
}, k: 5)
|
342
|
+
```
|
343
|
+
### neural_sparse
|
344
|
+
|
345
|
+
```ruby
|
346
|
+
Model.neural_sparse(body_embedding: "A funny thing about...")
|
347
|
+
```
|
348
|
+
|
349
|
+
### hybrid
|
350
|
+
|
351
|
+
Hybrid search combines keyword and neural search to improve search relevance. To implement hybrid search, you need to set up a search pipeline that runs at search time.
|
352
|
+
|
353
|
+
```ruby
|
354
|
+
Modeo.hybrid(neural: [
|
355
|
+
{passage_embedding: 'really soft cats', model_id: '1234', k: 2}
|
356
|
+
],
|
357
|
+
query: [
|
358
|
+
{term: {status: :active}}
|
359
|
+
]
|
360
|
+
)
|
361
|
+
```
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Quick Start
|
2
|
+
|
3
|
+
## Prerequisites
|
4
|
+
|
5
|
+
- Ruby and Rails installed on your machine
|
6
|
+
|
7
|
+
>[!TIP|style:flat|label:Single Node Clusters]
|
8
|
+
> These docker commands will run a single-node cluster suitable for local development.
|
9
|
+
|
10
|
+
|
11
|
+
#### Choose Elasticsearch or OpenSearch
|
12
|
+
|
13
|
+
__Start Elasticsearch:__
|
14
|
+
|
15
|
+
```sh
|
16
|
+
docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:8.12.0
|
17
|
+
```
|
18
|
+
__Start Opensearch:__
|
19
|
+
```sh
|
20
|
+
docker run -d -p 9200:9200 -e "discovery.type=single-node" opensearchproject/opensearch:2.12.0
|
21
|
+
```
|
22
|
+
|
23
|
+
## Create a New Rails Application
|
24
|
+
|
25
|
+
```sh
|
26
|
+
rails new fantastic_thing
|
27
|
+
cd fantastic_thing
|
28
|
+
```
|
29
|
+
|
30
|
+
## Add stretchy-model
|
31
|
+
```sh
|
32
|
+
bundle add stretchy-model
|
33
|
+
```
|
34
|
+
|
35
|
+
> [!INFO|style:flat|label:OpenSearch]
|
36
|
+
> If using Opensearch be sure to add it to your Gemfile:
|
37
|
+
>
|
38
|
+
```sh
|
39
|
+
bundle add opensearch-ruby
|
40
|
+
```
|
41
|
+
|
42
|
+
## Configure Stretchy
|
43
|
+
#### Add credentials
|
44
|
+
```sh
|
45
|
+
rails credentials:edit
|
46
|
+
```
|
47
|
+
|
48
|
+
```yaml
|
49
|
+
elasticsearch:
|
50
|
+
url: localhost:9200
|
51
|
+
|
52
|
+
# if using opensearch
|
53
|
+
# opensearch:
|
54
|
+
# host: https://localhost:9200
|
55
|
+
# user: admin
|
56
|
+
# password: admin
|
57
|
+
# transport_options:
|
58
|
+
# ssl:
|
59
|
+
# verify: false
|
60
|
+
```
|
61
|
+
|
62
|
+
|
63
|
+
#### Add an initializer
|
64
|
+
|
65
|
+
Create an initializer file *config/initializers/stretchy.rb* and add the following:
|
66
|
+
```ruby
|
67
|
+
Stretchy.configure do |config|
|
68
|
+
config.client = Elasticsearch::Client.new Rails.application.credentials.elasticsearch
|
69
|
+
# or if using Opensearch
|
70
|
+
# config.client = Openseaerch::Client.new Rails.application.credentials.opensearch
|
71
|
+
end
|
72
|
+
```
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# Scopes
|
2
|
+
|
3
|
+
In Stretchy, `scope` is a method that allows you to define commonly used queries that can be referenced as method calls on the model. Scopes are a way to encapsulate complex queries into simple, meaningful methods. This can make your code more maintainable and your queries more readable.
|
4
|
+
|
5
|
+
Here's an example of how you might define a scope:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class Model < StretchyModel
|
9
|
+
scope :published, -> { where(published: true) }
|
10
|
+
end
|
11
|
+
```
|
12
|
+
|
13
|
+
In this example, a scope named `published` is defined. This scope includes all documents where the `published` field is `true`.
|
14
|
+
|
15
|
+
You can use this scope in your queries like this:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
Model.published
|
19
|
+
```
|
20
|
+
|
21
|
+
This will return all published documents.
|
22
|
+
|
23
|
+
Scopes are chainable, meaning you can use multiple scopes in a single query:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
Model.published.recent
|
27
|
+
```
|
28
|
+
|
29
|
+
In this example, the `published` and `recent` scopes are used together. This will return all documents that are both published and recent.
|
30
|
+
|
31
|
+
Scopes can also take arguments, allowing you to create more dynamic queries:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class Model < Stretchy::Model
|
35
|
+
scope :with_title, ->(title) { where(title: title) }
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
In this example, the `with_title` scope takes a `title` argument and includes all documents where the `title` field matches the given title. You can use this scope like this:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
Model.with_title('My Title')
|
43
|
+
```
|
44
|
+
|
45
|
+
This will return all documents with the title 'My Title'.
|
46
|
+
|
47
|
+
### With Aggregations
|
48
|
+
|
49
|
+
Using scopes with aggregations is a powerful way to add complex aggregations and use them throughout your code.
|
50
|
+
|
51
|
+
Aggregations in Elasticsearch provide a way to group and summarize your data in various ways, such as counting the number of documents that match certain criteria, calculating averages, sums, or other statistics, creating histograms or other types of data visualizations, and more.
|
52
|
+
|
53
|
+
Here's an example of how you might define a scope that includes an aggregation:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class Model < StretchyModel
|
57
|
+
scope :published, -> { where(published: true) }
|
58
|
+
scope :average_rating_agg, -> { aggregation(:average_rating, avg: { field: :rating }) }
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
> [!TIP|label:Naming Conventions]
|
63
|
+
> _Implementing a naming convention for aggregation scopes can help make your code more readable and maintainable. By appending `_agg` to the name of aggregation scopes, you can easily distinguish them from other scopes._
|
64
|
+
|
65
|
+
In this example, a scope named `average_rating_agg` is defined. This scope includes an aggregation that calculates the average value of the `rating` field.
|
66
|
+
|
67
|
+
You can use this scope in your queries like this:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
Model.published.average_rating_agg
|
71
|
+
```
|
72
|
+
|
73
|
+
This will return the average rating of published documents.
|
74
|
+
|
75
|
+
Just like other scopes, aggregation scopes are chainable and can take arguments, allowing you to create dynamic aggregation queries. For example, you could define a scope that calculates the average rating for a specific category:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class Model < StretchyModel
|
79
|
+
scope :average_rating_agg_for, ->(category) {
|
80
|
+
where(category: category).aggregation(:average_rating, avg: { field: :rating })
|
81
|
+
}
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
You can use this scope like this:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
Model.average_rating_agg_for('Books')
|
89
|
+
```
|
90
|
+
|
91
|
+
This will return the average rating of all documents in the 'Books' category.
|
92
|
+
|
93
|
+
### Shared Scopes
|
94
|
+
|
95
|
+
Stretchy has built-in shared scopes available to all StretchyModel models.
|
96
|
+
|
97
|
+
#### Between Scope
|
98
|
+
The `between` scope is used to filter documents based on a range of values in a specific field. By default, this field is created_at, but you can specify a different field if needed.
|
99
|
+
|
100
|
+
Here's an example of how you might use the `between` scope:
|
101
|
+
```ruby
|
102
|
+
Model.between(1.day.ago..Time.now)
|
103
|
+
```
|
104
|
+
|
105
|
+
In this example, the search results will include only documents that were created in the last day.
|
106
|
+
|
107
|
+
Here's how to specify a different field:
|
108
|
+
```ruby
|
109
|
+
Model.between(15..20, :price)
|
110
|
+
```
|
111
|
+
|
112
|
+
This will return documents with a price between 15 and 20.
|
113
|
+
|
114
|
+
#### Using Time-Based Indices Scope
|
115
|
+
The `using_time_based_indices` scope is used to search across multiple indices based on a time range. This can significantly reduce the load on the Elasticsearch cluster when there are many indices for a given model.
|
116
|
+
|
117
|
+
Here's an example of how you might use the `using_time_based_indices` scope:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
Model.using_time_based_indices(2.months.ago..1.day.ago)
|
121
|
+
```
|
122
|
+
|
123
|
+
In this example, the search will be performed across all indices for the Model class that were created in the last two months.
|
124
|
+
|
125
|
+
These scopes are available to all `StretchyModel` models, making it easy to perform common types of queries.
|
data/docs/index.html
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<title>stretchy documentation</title>
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
7
|
+
<meta name="description" content="Description">
|
8
|
+
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
10
|
+
<meta property="og:title" content="stretchy-model | Elasticsearch Models for Rails" />
|
11
|
+
<meta property="og:site_name" content="stretchy-model"/>
|
12
|
+
<meta property="og:description" content="Unlock insights in your structured data!" />
|
13
|
+
<meta property="og:type" content="website" />
|
14
|
+
<meta property="fb:app_id" content="330382707086328" />
|
15
|
+
<meta property="og:image" content="http://theablefew.com/img/stretchy-share.png" />
|
16
|
+
<meta property="og:url" content="https://theablefew.github.io/stretchy/#/" />
|
17
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
|
18
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/v5-font-face.min.css" integrity="sha512-DG+gORwHSOHlIRwrUl2peOlG9vcxDg8qnbI1WkCfttaERikRSgrRoDeDa1PK4uZD24IJwAeKb6TuQk+/15b66A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
19
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
20
|
+
<link rel="stylesheet" href="./styles.css">
|
21
|
+
</head>
|
22
|
+
<body>
|
23
|
+
<div id="app"></div>
|
24
|
+
<script>
|
25
|
+
window.$docsify = {
|
26
|
+
name: 'stretchy-model',
|
27
|
+
repo: 'theablefew/stretchy',
|
28
|
+
coverpage: true,
|
29
|
+
base_path: 'doc/',
|
30
|
+
loadSidebar: true,
|
31
|
+
subMaxLevel: 2,
|
32
|
+
copyCode: {
|
33
|
+
copyIcon: '<span class="fa fa-copy"></span>',
|
34
|
+
copyIconClass: 'fa fa-copy'
|
35
|
+
}
|
36
|
+
}
|
37
|
+
</script>
|
38
|
+
<!-- Docsify v4 -->
|
39
|
+
<script src="https://cdn.jsdelivr.net/npm/docsify@4"></script>
|
40
|
+
<script src="https://cdn.jsdelivr.net/npm/docsify-prism@latest/dist/docsify-prism.min.js"></script>
|
41
|
+
<script src="https://unpkg.com/docsify-plugin-flexible-alerts"></script>
|
42
|
+
|
43
|
+
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.x/plugins/autoloader/prism-autoloader.min.js"></script>
|
44
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/js/fontawesome.min.js" integrity="sha512-C8qHv0HOaf4yoA7ISuuCTrsPX8qjolYTZyoFRKNA9dFKnxgzIHnYTOJhXQIt6zwpIFzCrRzUBuVgtC4e5K1nhA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
45
|
+
<script type="module">
|
46
|
+
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
|
47
|
+
mermaid.initialize({ startOnLoad: true });
|
48
|
+
window.mermaid = mermaid;
|
49
|
+
</script>
|
50
|
+
<script src="//unpkg.com/docsify-mermaid@2.0.1/dist/docsify-mermaid.js"></script>
|
51
|
+
<script type="text/javascript">
|
52
|
+
window.onload = function() {
|
53
|
+
const copyButtonLabel = "Copy Code";
|
54
|
+
|
55
|
+
// define your function as a separate function so you can call it multiple times
|
56
|
+
function addCopyButtons() {
|
57
|
+
let blocks = document.querySelectorAll("pre");
|
58
|
+
|
59
|
+
blocks.forEach((block) => {
|
60
|
+
// only add button if browser supports Clipboard API and it doesn't already have a copy button
|
61
|
+
if (navigator.clipboard && !block.querySelector(".clipboard-copy")) {
|
62
|
+
let button = document.createElement("div");
|
63
|
+
button.classList.add("clipboard-copy");
|
64
|
+
|
65
|
+
let icon = document.createElement("i");
|
66
|
+
icon.classList.add("fa-solid", "fa-copy", "fa-lg");
|
67
|
+
button.append(icon);
|
68
|
+
let copyText = document.createElement("div");
|
69
|
+
copyText.classList.add("copy-text");
|
70
|
+
copyText.innerText = "Copied!";
|
71
|
+
button.appendChild(copyText);
|
72
|
+
block.appendChild(button);
|
73
|
+
|
74
|
+
button.addEventListener("click", async () => {
|
75
|
+
await copyCode(block);
|
76
|
+
});
|
77
|
+
}
|
78
|
+
});
|
79
|
+
}
|
80
|
+
|
81
|
+
// call your function initially to add copy buttons to existing content
|
82
|
+
addCopyButtons();
|
83
|
+
|
84
|
+
// create a MutationObserver to call your function when new content is added
|
85
|
+
let observer = new MutationObserver(addCopyButtons);
|
86
|
+
observer.observe(document.body, {childList: true, subtree: true});
|
87
|
+
}
|
88
|
+
|
89
|
+
async function copyCode(block) {
|
90
|
+
let code = block.querySelector("code");
|
91
|
+
let text = code.innerText;
|
92
|
+
|
93
|
+
await navigator.clipboard.writeText(text);
|
94
|
+
|
95
|
+
// change button text to indicate success
|
96
|
+
let button = block.querySelector(".clipboard-copy");
|
97
|
+
let icon = button.querySelector("i");
|
98
|
+
icon.classList.remove("fa-copy");
|
99
|
+
icon.classList.add("fa-check");
|
100
|
+
|
101
|
+
let copyText = button.querySelector(".copy-text");
|
102
|
+
copyText.classList.toggle("show");
|
103
|
+
button.appendChild(copyText);
|
104
|
+
|
105
|
+
setTimeout(() => {
|
106
|
+
icon.classList.remove("fa-check");
|
107
|
+
icon.classList.add("fa-copy");
|
108
|
+
copyText.classList.toggle("show");
|
109
|
+
}, 2000);
|
110
|
+
}
|
111
|
+
</script>
|
112
|
+
</body>
|
113
|
+
</html>
|
Binary file
|
Binary file
|