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.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/README.md +28 -10
  4. data/Rakefile +56 -0
  5. data/docs/.nojekyll +0 -0
  6. data/docs/README.md +147 -0
  7. data/docs/_coverpage.md +14 -0
  8. data/docs/_sidebar.md +15 -0
  9. data/docs/examples/_sidebar.md +15 -0
  10. data/docs/examples/data_analysis.md +216 -0
  11. data/docs/examples/neural_search_with_llm.md +381 -0
  12. data/docs/examples/simple-ingest-pipeline.md +326 -0
  13. data/docs/guides/_sidebar.md +15 -0
  14. data/docs/guides/aggregations.md +142 -0
  15. data/docs/guides/machine-learning.md +154 -0
  16. data/docs/guides/models.md +372 -0
  17. data/docs/guides/pipelines.md +151 -0
  18. data/docs/guides/querying.md +361 -0
  19. data/docs/guides/quick-start.md +72 -0
  20. data/docs/guides/scopes.md +125 -0
  21. data/docs/index.html +113 -0
  22. data/docs/stretchy.cover.png +0 -0
  23. data/docs/stretchy.logo.png +0 -0
  24. data/docs/styles.css +90 -0
  25. data/lib/elasticsearch/api/actions/connector/check_in.rb +64 -0
  26. data/lib/elasticsearch/api/actions/connector/delete.rb +64 -0
  27. data/lib/elasticsearch/api/actions/connector/get.rb +64 -0
  28. data/lib/elasticsearch/api/actions/connector/last_sync.rb +66 -0
  29. data/lib/elasticsearch/api/actions/connector/list.rb +60 -0
  30. data/lib/elasticsearch/api/actions/connector/post.rb +57 -0
  31. data/lib/elasticsearch/api/actions/connector/put.rb +66 -0
  32. data/lib/elasticsearch/api/actions/connector/update_api_key_id.rb +66 -0
  33. data/lib/elasticsearch/api/actions/connector/update_configuration.rb +66 -0
  34. data/lib/elasticsearch/api/actions/connector/update_error.rb +66 -0
  35. data/lib/elasticsearch/api/actions/connector/update_filtering.rb +66 -0
  36. data/lib/elasticsearch/api/actions/connector/update_index_name.rb +66 -0
  37. data/lib/elasticsearch/api/actions/connector/update_name.rb +66 -0
  38. data/lib/elasticsearch/api/actions/connector/update_native.rb +66 -0
  39. data/lib/elasticsearch/api/actions/connector/update_pipeline.rb +66 -0
  40. data/lib/elasticsearch/api/actions/connector/update_scheduling.rb +66 -0
  41. data/lib/elasticsearch/api/actions/connector/update_service_type.rb +66 -0
  42. data/lib/elasticsearch/api/actions/connector/update_status.rb +66 -0
  43. data/lib/elasticsearch/api/namespace/connector.rb +36 -0
  44. data/lib/opensearch/api/actions/machine_learning/connector/delete.rb +42 -0
  45. data/lib/opensearch/api/actions/machine_learning/connector/get.rb +42 -0
  46. data/lib/opensearch/api/actions/machine_learning/connector/list.rb +38 -0
  47. data/lib/opensearch/api/actions/machine_learning/connector/post.rb +35 -0
  48. data/lib/opensearch/api/actions/machine_learning/connector/put.rb +44 -0
  49. data/lib/opensearch/api/actions/machine_learning/models/predict.rb +32 -0
  50. data/lib/opensearch/api/namespace/connector.rb +19 -0
  51. data/lib/stretchy/attributes/transformers/keyword_transformer.rb +41 -35
  52. data/lib/stretchy/attributes/type/array.rb +24 -1
  53. data/lib/stretchy/attributes/type/base.rb +6 -2
  54. data/lib/stretchy/attributes/type/binary.rb +24 -17
  55. data/lib/stretchy/attributes/type/boolean.rb +29 -22
  56. data/lib/stretchy/attributes/type/completion.rb +18 -10
  57. data/lib/stretchy/attributes/type/constant_keyword.rb +35 -26
  58. data/lib/stretchy/attributes/type/date_time.rb +28 -17
  59. data/lib/stretchy/attributes/type/dense_vector.rb +46 -49
  60. data/lib/stretchy/attributes/type/flattened.rb +28 -19
  61. data/lib/stretchy/attributes/type/geo_point.rb +21 -12
  62. data/lib/stretchy/attributes/type/geo_shape.rb +21 -12
  63. data/lib/stretchy/attributes/type/hash.rb +24 -10
  64. data/lib/stretchy/attributes/type/histogram.rb +25 -0
  65. data/lib/stretchy/attributes/type/ip.rb +26 -17
  66. data/lib/stretchy/attributes/type/join.rb +16 -7
  67. data/lib/stretchy/attributes/type/keyword.rb +21 -26
  68. data/lib/stretchy/attributes/type/knn_vector.rb +47 -0
  69. data/lib/stretchy/attributes/type/match_only_text.rb +22 -1
  70. data/lib/stretchy/attributes/type/nested.rb +16 -11
  71. data/lib/stretchy/attributes/type/numeric/base.rb +30 -22
  72. data/lib/stretchy/attributes/type/numeric/byte.rb +20 -0
  73. data/lib/stretchy/attributes/type/numeric/double.rb +20 -0
  74. data/lib/stretchy/attributes/type/numeric/float.rb +20 -0
  75. data/lib/stretchy/attributes/type/numeric/half_float.rb +20 -0
  76. data/lib/stretchy/attributes/type/numeric/integer.rb +21 -1
  77. data/lib/stretchy/attributes/type/numeric/long.rb +20 -0
  78. data/lib/stretchy/attributes/type/numeric/scaled_float.rb +16 -7
  79. data/lib/stretchy/attributes/type/numeric/short.rb +20 -0
  80. data/lib/stretchy/attributes/type/numeric/unsigned_long.rb +21 -1
  81. data/lib/stretchy/attributes/type/percolator.rb +16 -4
  82. data/lib/stretchy/attributes/type/point.rb +19 -9
  83. data/lib/stretchy/attributes/type/range/base.rb +24 -1
  84. data/lib/stretchy/attributes/type/range/date_range.rb +21 -5
  85. data/lib/stretchy/attributes/type/range/double_range.rb +20 -4
  86. data/lib/stretchy/attributes/type/range/float_range.rb +21 -5
  87. data/lib/stretchy/attributes/type/range/integer_range.rb +20 -4
  88. data/lib/stretchy/attributes/type/range/ip_range.rb +20 -4
  89. data/lib/stretchy/attributes/type/range/long_range.rb +20 -4
  90. data/lib/stretchy/attributes/type/rank_feature.rb +16 -6
  91. data/lib/stretchy/attributes/type/rank_features.rb +16 -9
  92. data/lib/stretchy/attributes/type/search_as_you_type.rb +28 -18
  93. data/lib/stretchy/attributes/type/shape.rb +19 -9
  94. data/lib/stretchy/attributes/type/sparse_vector.rb +25 -21
  95. data/lib/stretchy/attributes/type/string.rb +42 -1
  96. data/lib/stretchy/attributes/type/text.rb +53 -28
  97. data/lib/stretchy/attributes/type/token_count.rb +21 -11
  98. data/lib/stretchy/attributes/type/version.rb +16 -6
  99. data/lib/stretchy/attributes/type/wildcard.rb +36 -25
  100. data/lib/stretchy/attributes.rb +29 -0
  101. data/lib/stretchy/delegation/gateway_delegation.rb +78 -0
  102. data/lib/stretchy/index_setting.rb +94 -0
  103. data/lib/stretchy/indexing/bulk.rb +75 -3
  104. data/lib/stretchy/machine_learning/connector.rb +130 -0
  105. data/lib/stretchy/machine_learning/errors.rb +25 -0
  106. data/lib/stretchy/machine_learning/model.rb +162 -109
  107. data/lib/stretchy/machine_learning/registry.rb +19 -0
  108. data/lib/stretchy/model/callbacks.rb +1 -0
  109. data/lib/stretchy/model/common.rb +157 -0
  110. data/lib/stretchy/model/persistence.rb +144 -0
  111. data/lib/stretchy/model/refreshable.rb +26 -0
  112. data/lib/stretchy/open_search_compatibility.rb +2 -0
  113. data/lib/stretchy/pipeline.rb +2 -1
  114. data/lib/stretchy/pipelines/processor.rb +40 -36
  115. data/lib/stretchy/querying.rb +7 -8
  116. data/lib/stretchy/rails/railtie.rb +11 -0
  117. data/lib/stretchy/rails/tasks/connector/create.rake +32 -0
  118. data/lib/stretchy/rails/tasks/connector/delete.rake +27 -0
  119. data/lib/stretchy/rails/tasks/connector/status.rake +31 -0
  120. data/lib/stretchy/rails/tasks/connector/update.rake +32 -0
  121. data/lib/stretchy/rails/tasks/index/create.rake +28 -0
  122. data/lib/stretchy/rails/tasks/index/delete.rake +27 -0
  123. data/lib/stretchy/rails/tasks/index/status.rake +23 -0
  124. data/lib/stretchy/rails/tasks/ml/delete.rake +25 -0
  125. data/lib/stretchy/rails/tasks/ml/deploy.rake +78 -0
  126. data/lib/stretchy/rails/tasks/ml/status.rake +31 -0
  127. data/lib/stretchy/rails/tasks/pipeline/create.rake +27 -0
  128. data/lib/stretchy/rails/tasks/pipeline/delete.rake +26 -0
  129. data/lib/stretchy/rails/tasks/pipeline/status.rake +25 -0
  130. data/lib/stretchy/rails/tasks/status.rake +15 -0
  131. data/lib/stretchy/rails/tasks/stretchy.rake +42 -0
  132. data/lib/stretchy/record.rb +5 -4
  133. data/lib/stretchy/relation.rb +229 -28
  134. data/lib/stretchy/relations/aggregation_methods/aggregation.rb +59 -0
  135. data/lib/stretchy/relations/aggregation_methods/avg.rb +45 -0
  136. data/lib/stretchy/relations/aggregation_methods/bucket_script.rb +47 -0
  137. data/lib/stretchy/relations/aggregation_methods/bucket_selector.rb +47 -0
  138. data/lib/stretchy/relations/aggregation_methods/bucket_sort.rb +47 -0
  139. data/lib/stretchy/relations/aggregation_methods/cardinality.rb +47 -0
  140. data/lib/stretchy/relations/aggregation_methods/children.rb +47 -0
  141. data/lib/stretchy/relations/aggregation_methods/composite.rb +41 -0
  142. data/lib/stretchy/relations/aggregation_methods/date_histogram.rb +53 -0
  143. data/lib/stretchy/relations/aggregation_methods/date_range.rb +53 -0
  144. data/lib/stretchy/relations/aggregation_methods/extended_stats.rb +48 -0
  145. data/lib/stretchy/relations/aggregation_methods/filter.rb +47 -0
  146. data/lib/stretchy/relations/aggregation_methods/filters.rb +47 -0
  147. data/lib/stretchy/relations/aggregation_methods/geo_bounds.rb +40 -0
  148. data/lib/stretchy/relations/aggregation_methods/geo_centroid.rb +40 -0
  149. data/lib/stretchy/relations/aggregation_methods/global.rb +39 -0
  150. data/lib/stretchy/relations/aggregation_methods/histogram.rb +43 -0
  151. data/lib/stretchy/relations/aggregation_methods/ip_range.rb +41 -0
  152. data/lib/stretchy/relations/aggregation_methods/max.rb +40 -0
  153. data/lib/stretchy/relations/aggregation_methods/min.rb +41 -0
  154. data/lib/stretchy/relations/aggregation_methods/missing.rb +40 -0
  155. data/lib/stretchy/relations/aggregation_methods/nested.rb +40 -0
  156. data/lib/stretchy/relations/aggregation_methods/percentile_ranks.rb +45 -0
  157. data/lib/stretchy/relations/aggregation_methods/percentiles.rb +45 -0
  158. data/lib/stretchy/relations/aggregation_methods/range.rb +42 -0
  159. data/lib/stretchy/relations/aggregation_methods/reverse_nested.rb +40 -0
  160. data/lib/stretchy/relations/aggregation_methods/sampler.rb +40 -0
  161. data/lib/stretchy/relations/aggregation_methods/scripted_metric.rb +43 -0
  162. data/lib/stretchy/relations/aggregation_methods/significant_terms.rb +45 -0
  163. data/lib/stretchy/relations/aggregation_methods/stats.rb +42 -0
  164. data/lib/stretchy/relations/aggregation_methods/sum.rb +42 -0
  165. data/lib/stretchy/relations/aggregation_methods/terms.rb +46 -0
  166. data/lib/stretchy/relations/aggregation_methods/top_hits.rb +42 -0
  167. data/lib/stretchy/relations/aggregation_methods/top_metrics.rb +44 -0
  168. data/lib/stretchy/relations/aggregation_methods/value_count.rb +41 -0
  169. data/lib/stretchy/relations/aggregation_methods/weighted_avg.rb +42 -0
  170. data/lib/stretchy/relations/aggregation_methods.rb +20 -749
  171. data/lib/stretchy/relations/finder_methods.rb +2 -18
  172. data/lib/stretchy/relations/null_relation.rb +55 -0
  173. data/lib/stretchy/relations/query_builder.rb +82 -36
  174. data/lib/stretchy/relations/query_methods/bind.rb +19 -0
  175. data/lib/stretchy/relations/query_methods/extending.rb +29 -0
  176. data/lib/stretchy/relations/query_methods/fields.rb +70 -0
  177. data/lib/stretchy/relations/query_methods/filter_query.rb +53 -0
  178. data/lib/stretchy/relations/query_methods/has_field.rb +40 -0
  179. data/lib/stretchy/relations/query_methods/highlight.rb +75 -0
  180. data/lib/stretchy/relations/query_methods/hybrid.rb +60 -0
  181. data/lib/stretchy/relations/query_methods/ids.rb +40 -0
  182. data/lib/stretchy/relations/query_methods/match.rb +52 -0
  183. data/lib/stretchy/relations/query_methods/must_not.rb +54 -0
  184. data/lib/stretchy/relations/query_methods/neural.rb +58 -0
  185. data/lib/stretchy/relations/query_methods/neural_sparse.rb +43 -0
  186. data/lib/stretchy/relations/query_methods/none.rb +21 -0
  187. data/lib/stretchy/relations/query_methods/or_filter.rb +21 -0
  188. data/lib/stretchy/relations/query_methods/order.rb +63 -0
  189. data/lib/stretchy/relations/query_methods/query_string.rb +44 -0
  190. data/lib/stretchy/relations/query_methods/regexp.rb +61 -0
  191. data/lib/stretchy/relations/query_methods/should.rb +51 -0
  192. data/lib/stretchy/relations/query_methods/size.rb +44 -0
  193. data/lib/stretchy/relations/query_methods/skip_callbacks.rb +47 -0
  194. data/lib/stretchy/relations/query_methods/source.rb +59 -0
  195. data/lib/stretchy/relations/query_methods/where.rb +113 -0
  196. data/lib/stretchy/relations/query_methods.rb +48 -569
  197. data/lib/stretchy/relations/scoping/default.rb +136 -0
  198. data/lib/stretchy/relations/scoping/named.rb +70 -0
  199. data/lib/stretchy/relations/scoping/scope_registry.rb +36 -0
  200. data/lib/stretchy/relations/scoping.rb +30 -0
  201. data/lib/stretchy/relations/search_option_methods.rb +2 -0
  202. data/lib/stretchy/version.rb +1 -1
  203. data/lib/stretchy.rb +24 -10
  204. metadata +170 -17
  205. data/lib/stretchy/common.rb +0 -38
  206. data/lib/stretchy/null_relation.rb +0 -53
  207. data/lib/stretchy/persistence.rb +0 -43
  208. data/lib/stretchy/refreshable.rb +0 -15
  209. data/lib/stretchy/scoping/default.rb +0 -134
  210. data/lib/stretchy/scoping/named.rb +0 -68
  211. data/lib/stretchy/scoping/scope_registry.rb +0 -34
  212. 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