spree_core 5.4.0.beta9 → 5.4.0.beta10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/finders/spree/products/find.rb +1 -1
- data/app/jobs/spree/search_provider/index_job.rb +22 -0
- data/app/jobs/spree/search_provider/remove_job.rb +19 -0
- data/app/models/concerns/spree/product_scopes.rb +95 -135
- data/app/models/concerns/spree/search_indexable.rb +83 -0
- data/app/models/concerns/spree/{multi_searchable.rb → searchable.rb} +10 -3
- data/app/models/concerns/spree/user_methods.rb +11 -8
- data/app/models/spree/address.rb +3 -15
- data/app/models/spree/credit_card.rb +1 -0
- data/app/models/spree/line_item.rb +4 -0
- data/app/models/spree/metafield_definition.rb +7 -4
- data/app/models/spree/option_type.rb +3 -0
- data/app/models/spree/option_value.rb +3 -0
- data/app/models/spree/order.rb +27 -9
- data/app/models/spree/order_promotion.rb +1 -1
- data/app/models/spree/product.rb +9 -31
- data/app/models/spree/search_provider/base.rb +81 -0
- data/app/models/spree/search_provider/database.rb +95 -0
- data/app/models/spree/search_provider/meilisearch.rb +302 -0
- data/app/models/spree/search_provider/search_result.rb +5 -0
- data/app/models/spree/taxon.rb +2 -7
- data/app/models/spree/variant.rb +6 -25
- data/app/models/spree/wishlist.rb +1 -0
- data/app/presenters/spree/search_provider/product_presenter.rb +131 -0
- data/app/services/spree/carts/update.rb +5 -4
- data/db/sample_data/payment_methods.rb +2 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +16 -3
- data/lib/tasks/search.rake +15 -0
- metadata +28 -6
- data/app/models/spree/cart_promotion.rb +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 73ed6afd1feda3d20e24a8910273785420d08dfadebf8af34aa90c20b017a0b8
|
|
4
|
+
data.tar.gz: 323a92ea512ad2d37bac8be8b8f878f60dbbc4058ed020875c5740ef02a7481b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 711d0f622de8f398bd3f0e8696c5e12e476d3671a6330781fd5a34a0ecb15cc32f17fcf9d8e1e3028584108a354465ba7721ca990e723077c89dddb9c428fcbb
|
|
7
|
+
data.tar.gz: 3634efec05dc63e7c44a27bcfe0265331224592ee8d88bc1b417446967af051ccd450723344bc287977de3793b95d17ca0fef1235e905aa25e7a787f65325a81
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module SearchProvider
|
|
3
|
+
class IndexJob < Spree::BaseJob
|
|
4
|
+
queue_as Spree.queues.search
|
|
5
|
+
|
|
6
|
+
retry_on StandardError, wait: :polynomially_longer, attempts: 5
|
|
7
|
+
discard_on ActiveRecord::RecordNotFound
|
|
8
|
+
|
|
9
|
+
# @param resource_class [String] e.g. 'Spree::Product'
|
|
10
|
+
# @param resource_id [String] always pass as string for UUID support
|
|
11
|
+
# @param store_id [String] always pass as string for UUID support
|
|
12
|
+
def perform(resource_class, resource_id, store_id)
|
|
13
|
+
resource = resource_class.constantize.find_by(id: resource_id)
|
|
14
|
+
store = Spree::Store.find_by(id: store_id)
|
|
15
|
+
return unless resource && store
|
|
16
|
+
|
|
17
|
+
provider = Spree.search_provider.constantize.new(store)
|
|
18
|
+
provider.index(resource)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module SearchProvider
|
|
3
|
+
class RemoveJob < Spree::BaseJob
|
|
4
|
+
queue_as Spree.queues.search
|
|
5
|
+
|
|
6
|
+
retry_on StandardError, wait: :polynomially_longer, attempts: 5
|
|
7
|
+
|
|
8
|
+
# @param prefixed_id [String] prefixed ID of the document to remove (e.g. 'prod_abc')
|
|
9
|
+
# @param store_id [String] always pass as string for UUID support
|
|
10
|
+
def perform(prefixed_id, store_id)
|
|
11
|
+
store = Spree::Store.find_by(id: store_id)
|
|
12
|
+
return unless store
|
|
13
|
+
|
|
14
|
+
provider = Spree.search_provider.constantize.new(store)
|
|
15
|
+
provider.remove_by_id(prefixed_id)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -3,41 +3,20 @@ module Spree
|
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
|
|
5
5
|
included do
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def self.simple_scopes
|
|
16
|
-
[
|
|
17
|
-
:ascend_by_updated_at,
|
|
18
|
-
:descend_by_updated_at,
|
|
19
|
-
:ascend_by_name,
|
|
20
|
-
:descend_by_name
|
|
21
|
-
]
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def self.add_simple_scopes(scopes)
|
|
25
|
-
scopes.each do |name|
|
|
26
|
-
# We should not define price scopes here, as they require something slightly different
|
|
27
|
-
next if name.to_s.include?('master_price')
|
|
28
|
-
|
|
29
|
-
parts = name.to_s.match(/(.*)_by_(.*)/)
|
|
30
|
-
scope(name.to_s, -> { order(Arel.sql(sanitize_sql("#{Product.quoted_table_name}.#{parts[2]} #{parts[1] == 'ascend' ? 'ASC' : 'DESC'}"))) })
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
add_simple_scopes simple_scopes
|
|
35
|
-
|
|
36
|
-
add_search_scope :ascend_by_master_price do
|
|
6
|
+
scope :ascend_by_updated_at, -> { order("#{Product.quoted_table_name}.updated_at ASC") }
|
|
7
|
+
scope :descend_by_updated_at, -> { order("#{Product.quoted_table_name}.updated_at DESC") }
|
|
8
|
+
scope :ascend_by_name, -> { order("#{Product.quoted_table_name}.name ASC") }
|
|
9
|
+
scope :descend_by_name, -> { order("#{Product.quoted_table_name}.name DESC") }
|
|
10
|
+
|
|
11
|
+
# Deprecated — master variant being removed in 6.0. Use ascend_by_price / descend_by_price instead.
|
|
12
|
+
def self.ascend_by_master_price
|
|
13
|
+
Spree::Deprecation.warn('ascend_by_master_price is deprecated and will be removed in Spree 6.0. Use ascend_by_price instead.')
|
|
37
14
|
order(price_table_name => { amount: :asc })
|
|
38
15
|
end
|
|
39
16
|
|
|
40
|
-
|
|
17
|
+
# Deprecated — master variant being removed in 6.0. Use ascend_by_price / descend_by_price instead.
|
|
18
|
+
def self.descend_by_master_price
|
|
19
|
+
Spree::Deprecation.warn('descend_by_master_price is deprecated and will be removed in Spree 6.0. Use descend_by_price instead.')
|
|
41
20
|
order(price_table_name => { amount: :desc })
|
|
42
21
|
end
|
|
43
22
|
|
|
@@ -47,7 +26,7 @@ module Spree
|
|
|
47
26
|
# Uses Arel::Nodes::As for select expressions so that:
|
|
48
27
|
# - PG allows ORDER BY with DISTINCT (expressions must appear in SELECT list)
|
|
49
28
|
# - Mobility's select_for_count can safely call .right on all select_values
|
|
50
|
-
|
|
29
|
+
scope :ascend_by_price, -> {
|
|
51
30
|
price_agg_sql = Price.non_zero.joins(:variant)
|
|
52
31
|
.select("#{Variant.table_name}.product_id AS product_id, MIN(#{Price.table_name}.amount) AS agg_price")
|
|
53
32
|
.group("#{Variant.table_name}.product_id")
|
|
@@ -59,9 +38,9 @@ module Spree
|
|
|
59
38
|
select("#{Product.table_name}.*").
|
|
60
39
|
select(Arel::Nodes::As.new(price_expr, Arel.sql('min_price'))).
|
|
61
40
|
order(price_expr.asc)
|
|
62
|
-
|
|
41
|
+
}
|
|
63
42
|
|
|
64
|
-
|
|
43
|
+
scope :descend_by_price, -> {
|
|
65
44
|
price_agg_sql = Price.non_zero.joins(:variant)
|
|
66
45
|
.select("#{Variant.table_name}.product_id AS product_id, MAX(#{Price.table_name}.amount) AS agg_price")
|
|
67
46
|
.group("#{Variant.table_name}.product_id")
|
|
@@ -73,17 +52,21 @@ module Spree
|
|
|
73
52
|
select("#{Product.table_name}.*").
|
|
74
53
|
select(Arel::Nodes::As.new(price_expr, Arel.sql('max_price'))).
|
|
75
54
|
order(price_expr.desc)
|
|
76
|
-
|
|
55
|
+
}
|
|
77
56
|
|
|
78
|
-
|
|
57
|
+
scope :price_between, ->(low, high) {
|
|
79
58
|
where(Price.table_name => { amount: low..high })
|
|
80
|
-
|
|
59
|
+
}
|
|
81
60
|
|
|
82
|
-
|
|
61
|
+
# Deprecated — master variant being removed in 6.0. Use price_lte / price_gte instead.
|
|
62
|
+
def self.master_price_lte(price)
|
|
63
|
+
Spree::Deprecation.warn('master_price_lte is deprecated and will be removed in Spree 6.0. Use price_lte instead.')
|
|
83
64
|
where(Price.table_name => { amount: ..price })
|
|
84
65
|
end
|
|
85
66
|
|
|
86
|
-
|
|
67
|
+
# Deprecated — master variant being removed in 6.0. Use price_lte / price_gte instead.
|
|
68
|
+
def self.master_price_gte(price)
|
|
69
|
+
Spree::Deprecation.warn('master_price_gte is deprecated and will be removed in Spree 6.0. Use price_gte instead.')
|
|
87
70
|
where(Price.table_name => { amount: price.. })
|
|
88
71
|
end
|
|
89
72
|
|
|
@@ -106,7 +89,6 @@ module Spree
|
|
|
106
89
|
)
|
|
107
90
|
}
|
|
108
91
|
|
|
109
|
-
# Can't use add_search_scope for this as it needs a default argument
|
|
110
92
|
# Ransack calls with '1' to activate, '0' or nil to skip
|
|
111
93
|
# In Ruby code: in_stock(true) for in-stock, in_stock(false) for out-of-stock
|
|
112
94
|
def self.in_stock(in_stock = true)
|
|
@@ -117,14 +99,13 @@ module Spree
|
|
|
117
99
|
end
|
|
118
100
|
end
|
|
119
101
|
|
|
120
|
-
|
|
102
|
+
scope :price_lte, ->(price) {
|
|
121
103
|
where(Price.table_name => { amount: ..price })
|
|
122
|
-
|
|
104
|
+
}
|
|
123
105
|
|
|
124
|
-
|
|
106
|
+
scope :price_gte, ->(price) {
|
|
125
107
|
where(Price.table_name => { amount: price.. })
|
|
126
|
-
|
|
127
|
-
search_scopes << :in_stock
|
|
108
|
+
}
|
|
128
109
|
|
|
129
110
|
def self.out_of_stock(out_of_stock = true)
|
|
130
111
|
if out_of_stock == '0' || !out_of_stock
|
|
@@ -133,13 +114,12 @@ module Spree
|
|
|
133
114
|
where.not(id: join_variants_and_stock_items.in_stock_or_backorderable_condition)
|
|
134
115
|
end
|
|
135
116
|
end
|
|
136
|
-
search_scopes << :out_of_stock
|
|
137
117
|
|
|
138
|
-
|
|
118
|
+
def self.backorderable
|
|
139
119
|
join_variants_and_stock_items.where(StockItem.table_name => { backorderable: true })
|
|
140
120
|
end
|
|
141
121
|
|
|
142
|
-
|
|
122
|
+
def self.in_stock_or_backorderable
|
|
143
123
|
join_variants_and_stock_items.in_stock_or_backorderable_condition
|
|
144
124
|
end
|
|
145
125
|
|
|
@@ -152,34 +132,22 @@ module Spree
|
|
|
152
132
|
# `:distinct` option as well:
|
|
153
133
|
#
|
|
154
134
|
# Spree::Product.in_taxon(taxon).count(distinct: true)
|
|
155
|
-
|
|
156
|
-
# This is so that the count query is distinct'd:
|
|
157
|
-
#
|
|
158
|
-
# SELECT COUNT(DISTINCT "spree_products"."id") ...
|
|
159
|
-
#
|
|
160
|
-
# vs.
|
|
161
|
-
#
|
|
162
|
-
# SELECT COUNT(*) ...
|
|
163
|
-
add_search_scope :in_taxon do |taxon|
|
|
135
|
+
scope :in_taxon, ->(taxon) {
|
|
164
136
|
joins(:classifications).
|
|
165
137
|
where("#{Classification.table_name}.taxon_id" => taxon.cached_self_and_descendants_ids).distinct
|
|
166
|
-
|
|
138
|
+
}
|
|
167
139
|
|
|
168
140
|
# Alias for in_taxon — public API name
|
|
169
|
-
|
|
170
|
-
in_taxon(category)
|
|
171
|
-
end
|
|
141
|
+
scope :in_category, ->(category) { in_taxon(category) }
|
|
172
142
|
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# Spree::Product.taxons_id_eq([x,y])
|
|
177
|
-
add_search_scope :in_taxons do |*taxons|
|
|
143
|
+
# Deprecated — remove in 6.0. Use in_taxon instead.
|
|
144
|
+
def self.in_taxons(*taxons)
|
|
145
|
+
Spree::Deprecation.warn('in_taxons is deprecated and will be removed in Spree 6.0. Use in_taxon instead.')
|
|
178
146
|
taxons = get_taxons(taxons)
|
|
179
147
|
taxons.first ? prepare_taxon_conditions(taxons) : where(nil)
|
|
180
148
|
end
|
|
181
149
|
|
|
182
|
-
|
|
150
|
+
scope :ascend_by_taxons_min_position, ->(taxon_ids) {
|
|
183
151
|
min_position_sql = "MIN(#{Classification.table_name}.position)"
|
|
184
152
|
|
|
185
153
|
joins(:classifications).
|
|
@@ -187,9 +155,11 @@ module Spree
|
|
|
187
155
|
select("#{Product.table_name}.*", "#{min_position_sql} AS min_taxon_position").
|
|
188
156
|
group("#{Product.table_name}.id").
|
|
189
157
|
order(Arel.sql("#{min_position_sql} ASC"))
|
|
190
|
-
|
|
158
|
+
}
|
|
191
159
|
|
|
192
|
-
|
|
160
|
+
# Deprecated — remove in 6.0. Not used internally.
|
|
161
|
+
def self.with_option(option)
|
|
162
|
+
Spree::Deprecation.warn('with_option is deprecated and will be removed in Spree 6.0.')
|
|
193
163
|
if option.is_a?(OptionType)
|
|
194
164
|
joins(:option_types).where(spree_option_types: { id: option.id })
|
|
195
165
|
elsif option.is_a?(Integer)
|
|
@@ -201,7 +171,7 @@ module Spree
|
|
|
201
171
|
end
|
|
202
172
|
end
|
|
203
173
|
|
|
204
|
-
|
|
174
|
+
scope :with_option_value, ->(option, value) {
|
|
205
175
|
option_type_id = case option
|
|
206
176
|
when OptionType then option.id
|
|
207
177
|
when Integer then option
|
|
@@ -210,89 +180,66 @@ module Spree
|
|
|
210
180
|
OptionType.where(id: option).or(OptionType.where(name: option))&.first&.id
|
|
211
181
|
else
|
|
212
182
|
OptionType.where(name: option)&.first&.id
|
|
213
|
-
OptionType.where(name: option)&.first&.id
|
|
214
183
|
end
|
|
215
184
|
end
|
|
216
185
|
|
|
217
|
-
|
|
186
|
+
next Product.group("#{Spree::Product.table_name}.id").none if option_type_id.blank?
|
|
218
187
|
|
|
219
188
|
group("#{Spree::Product.table_name}.id").
|
|
220
189
|
joins(variants: :option_values).
|
|
221
190
|
where(Spree::OptionValue.table_name => { name: value, option_type_id: option_type_id })
|
|
222
|
-
|
|
191
|
+
}
|
|
223
192
|
|
|
224
193
|
# Filters products by option value IDs (prefix IDs like 'optval_xxx')
|
|
225
194
|
# Accepts an array of option value IDs
|
|
226
|
-
|
|
195
|
+
scope :with_option_value_ids, ->(*ids) {
|
|
227
196
|
ids = ids.flatten.compact
|
|
228
|
-
|
|
197
|
+
next none if ids.empty?
|
|
229
198
|
|
|
230
199
|
# Handle prefixed IDs (optval_xxx) by decoding to actual IDs
|
|
231
200
|
actual_ids = ids.map do |id|
|
|
232
201
|
id.to_s.include?('_') ? OptionValue.decode_prefixed_id(id) : id
|
|
233
202
|
end.compact
|
|
234
203
|
|
|
235
|
-
|
|
204
|
+
next none if actual_ids.empty?
|
|
236
205
|
|
|
237
206
|
joins(variants: :option_values).where(Spree::OptionValue.table_name => { id: actual_ids })
|
|
238
|
-
|
|
207
|
+
}
|
|
239
208
|
|
|
240
|
-
#
|
|
241
|
-
|
|
209
|
+
# Deprecated — remove in 6.0. Not used internally.
|
|
210
|
+
def self.with(value)
|
|
211
|
+
Spree::Deprecation.warn('Product.with is deprecated and will be removed in Spree 6.0.')
|
|
242
212
|
includes(variants: :option_values).
|
|
243
213
|
where("#{OptionValue.table_name}.name = ?", value)
|
|
244
214
|
end
|
|
245
215
|
|
|
246
|
-
#
|
|
247
|
-
|
|
216
|
+
# Deprecated — remove in 6.0. Use .search scope instead.
|
|
217
|
+
def self.in_name(words)
|
|
218
|
+
Spree::Deprecation.warn('in_name is deprecated and will be removed in Spree 6.0. Use .search instead.')
|
|
248
219
|
like_any([:name], prepare_words(words))
|
|
249
220
|
end
|
|
250
221
|
|
|
251
|
-
#
|
|
252
|
-
|
|
222
|
+
# Deprecated — remove in 6.0. Use .search scope instead.
|
|
223
|
+
def self.in_name_or_keywords(words)
|
|
224
|
+
Spree::Deprecation.warn('in_name_or_keywords is deprecated and will be removed in Spree 6.0. Use .search instead.')
|
|
253
225
|
like_any([:name, :meta_keywords], prepare_words(words))
|
|
254
226
|
end
|
|
255
227
|
|
|
256
|
-
#
|
|
257
|
-
|
|
228
|
+
# Deprecated — remove in 6.0. Use .search scope instead.
|
|
229
|
+
def self.in_name_or_description(words)
|
|
230
|
+
Spree::Deprecation.warn('in_name_or_description is deprecated and will be removed in Spree 6.0. Use .search instead.')
|
|
258
231
|
like_any([:name, :description, :meta_description, :meta_keywords], prepare_words(words))
|
|
259
232
|
end
|
|
260
233
|
|
|
261
|
-
#
|
|
262
|
-
|
|
263
|
-
|
|
234
|
+
# Deprecated — remove in 6.0. Use where(id: ids) directly.
|
|
235
|
+
def self.with_ids(*ids)
|
|
236
|
+
Spree::Deprecation.warn('with_ids is deprecated and will be removed in Spree 6.0. Use where(id: ids) instead.')
|
|
264
237
|
where(id: ids)
|
|
265
238
|
end
|
|
266
239
|
|
|
267
|
-
|
|
268
|
-
# times use has put product in cart, not completed orders)
|
|
269
|
-
#
|
|
270
|
-
# there is alternative faster and more elegant solution, it has small drawback though,
|
|
271
|
-
# it doesn stack with other scopes :/
|
|
272
|
-
#
|
|
273
|
-
# joins: "LEFT OUTER JOIN (SELECT line_items.variant_id as vid, COUNT(*) as cnt FROM line_items GROUP BY line_items.variant_id) AS popularity_count ON variants.id = vid",
|
|
274
|
-
# order: 'COALESCE(cnt, 0) DESC'
|
|
275
|
-
add_search_scope :descend_by_popularity do
|
|
276
|
-
joins(:master).
|
|
277
|
-
order(%Q{
|
|
278
|
-
COALESCE((
|
|
279
|
-
SELECT
|
|
280
|
-
COUNT(#{LineItem.quoted_table_name}.id)
|
|
281
|
-
FROM
|
|
282
|
-
#{LineItem.quoted_table_name}
|
|
283
|
-
JOIN
|
|
284
|
-
#{Variant.quoted_table_name} AS popular_variants
|
|
285
|
-
ON
|
|
286
|
-
popular_variants.id = #{LineItem.quoted_table_name}.variant_id
|
|
287
|
-
WHERE
|
|
288
|
-
popular_variants.product_id = #{Product.quoted_table_name}.id
|
|
289
|
-
), 0) DESC
|
|
290
|
-
})
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
add_search_scope :not_deleted do
|
|
240
|
+
scope :not_deleted, -> {
|
|
294
241
|
where("#{Product.quoted_table_name}.deleted_at IS NULL or #{Product.quoted_table_name}.deleted_at >= ?", Time.zone.now)
|
|
295
|
-
|
|
242
|
+
}
|
|
296
243
|
|
|
297
244
|
def self.not_discontinued(only_not_discontinued = true)
|
|
298
245
|
if only_not_discontinued != '0' && only_not_discontinued
|
|
@@ -301,7 +248,6 @@ module Spree
|
|
|
301
248
|
all
|
|
302
249
|
end
|
|
303
250
|
end
|
|
304
|
-
search_scopes << :not_discontinued
|
|
305
251
|
|
|
306
252
|
def self.with_currency(currency)
|
|
307
253
|
joins(variants_including_master: :prices).
|
|
@@ -309,9 +255,7 @@ module Spree
|
|
|
309
255
|
where.not(Price.table_name => { amount: nil }).
|
|
310
256
|
distinct
|
|
311
257
|
end
|
|
312
|
-
search_scopes << :with_currency
|
|
313
258
|
|
|
314
|
-
# Can't use add_search_scope for this as it needs a default argument
|
|
315
259
|
def self.available(available_on = nil, currency = nil)
|
|
316
260
|
scope = not_discontinued.where(status: 'active')
|
|
317
261
|
if available_on
|
|
@@ -326,21 +270,22 @@ module Spree
|
|
|
326
270
|
|
|
327
271
|
scope
|
|
328
272
|
end
|
|
329
|
-
search_scopes << :available
|
|
330
273
|
|
|
331
274
|
def self.active(currency = nil)
|
|
332
275
|
available(nil, currency)
|
|
333
276
|
end
|
|
334
|
-
search_scopes << :active
|
|
335
277
|
|
|
278
|
+
# Deprecated — remove in 6.0. Use active(currency).in_taxon(taxon) directly.
|
|
336
279
|
def self.for_filters(currency, taxon: nil)
|
|
280
|
+
Spree::Deprecation.warn('for_filters is deprecated and will be removed in Spree 6.0. Use active(currency).in_taxon(taxon) instead.')
|
|
337
281
|
scope = active(currency)
|
|
338
282
|
scope = scope.in_taxon(taxon) if taxon.present?
|
|
339
283
|
scope
|
|
340
284
|
end
|
|
341
|
-
search_scopes << :for_filters
|
|
342
285
|
|
|
286
|
+
# Deprecated — remove in 6.0. Not used internally.
|
|
343
287
|
def self.for_user(user = nil)
|
|
288
|
+
Spree::Deprecation.warn('for_user is deprecated and will be removed in Spree 6.0.')
|
|
344
289
|
if user.try(:has_spree_role?, 'admin')
|
|
345
290
|
with_deleted
|
|
346
291
|
else
|
|
@@ -348,7 +293,9 @@ module Spree
|
|
|
348
293
|
end
|
|
349
294
|
end
|
|
350
295
|
|
|
351
|
-
|
|
296
|
+
# Deprecated — remove in 6.0. Not used internally.
|
|
297
|
+
def self.taxons_name_eq(name)
|
|
298
|
+
Spree::Deprecation.warn('taxons_name_eq is deprecated and will be removed in Spree 6.0.')
|
|
352
299
|
group('spree_products.id').joins(:taxons).where(Taxon.arel_table[:name].eq(name))
|
|
353
300
|
end
|
|
354
301
|
|
|
@@ -357,7 +304,7 @@ module Spree
|
|
|
357
304
|
#
|
|
358
305
|
# Uses Arel::Nodes::As so that ORDER BY expressions appear in SELECT
|
|
359
306
|
# and work with DISTINCT (same pattern as the price sorting scopes).
|
|
360
|
-
|
|
307
|
+
scope :by_best_selling, ->(order_direction = :desc) {
|
|
361
308
|
sp_table = StoreProduct.table_name
|
|
362
309
|
units_expr = Arel.sql("COALESCE(#{sp_table}.units_sold_count, 0)")
|
|
363
310
|
revenue_expr = Arel.sql("COALESCE(#{sp_table}.revenue, 0)")
|
|
@@ -369,19 +316,32 @@ module Spree
|
|
|
369
316
|
select(Arel::Nodes::As.new(revenue_expr, Arel.sql('best_selling_revenue'))).
|
|
370
317
|
order(units_expr.send(order_dir)).
|
|
371
318
|
order(revenue_expr.send(order_dir))
|
|
372
|
-
|
|
319
|
+
}
|
|
373
320
|
|
|
374
|
-
# .
|
|
375
|
-
|
|
376
|
-
|
|
321
|
+
# Deprecated — remove in 6.0. Use by_best_selling instead.
|
|
322
|
+
def self.descend_by_popularity
|
|
323
|
+
Spree::Deprecation.warn('descend_by_popularity is deprecated and will be removed in Spree 6.0. Use by_best_selling instead.')
|
|
324
|
+
joins(:master).
|
|
325
|
+
order(%Q{
|
|
326
|
+
COALESCE((
|
|
327
|
+
SELECT
|
|
328
|
+
COUNT(#{LineItem.quoted_table_name}.id)
|
|
329
|
+
FROM
|
|
330
|
+
#{LineItem.quoted_table_name}
|
|
331
|
+
JOIN
|
|
332
|
+
#{Variant.quoted_table_name} AS popular_variants
|
|
333
|
+
ON
|
|
334
|
+
popular_variants.id = #{LineItem.quoted_table_name}.variant_id
|
|
335
|
+
WHERE
|
|
336
|
+
popular_variants.product_id = #{Product.quoted_table_name}.id
|
|
337
|
+
), 0) DESC
|
|
338
|
+
})
|
|
339
|
+
end
|
|
377
340
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
i18n { name.lower.matches("%#{query.downcase}%") }
|
|
382
|
-
end
|
|
341
|
+
# .search_by_name — simple ILIKE on product name
|
|
342
|
+
def self.search_by_name(query)
|
|
343
|
+
i18n { name.lower.matches("%#{query.downcase}%") }
|
|
383
344
|
end
|
|
384
|
-
search_scopes << :search_by_name
|
|
385
345
|
|
|
386
346
|
def self.price_table_name
|
|
387
347
|
Price.quoted_table_name
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module SearchIndexable
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
after_commit :enqueue_search_index, on: [:create, :update]
|
|
7
|
+
after_commit :enqueue_search_removal, on: :destroy
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Index this record synchronously (inline, no job).
|
|
11
|
+
# Useful for bulk imports, rake tasks, or when you need immediate indexing.
|
|
12
|
+
def add_to_search_index
|
|
13
|
+
return unless search_indexing_enabled?
|
|
14
|
+
|
|
15
|
+
store_ids_for_indexing.each do |store_id|
|
|
16
|
+
store = Spree::Store.find_by(id: store_id)
|
|
17
|
+
next unless store
|
|
18
|
+
|
|
19
|
+
provider = Spree.search_provider.constantize.new(store)
|
|
20
|
+
provider.index(self)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns the hash that would be sent to the search provider for indexing.
|
|
25
|
+
# Useful for debugging and previewing what gets indexed.
|
|
26
|
+
#
|
|
27
|
+
# product.search_presentation # uses Spree::Current.store
|
|
28
|
+
# product.search_presentation(store) # explicit store
|
|
29
|
+
# => { id: 1, name: "Shirt", price_USD: 19.99, ... }
|
|
30
|
+
def search_presentation(store = nil)
|
|
31
|
+
store ||= Spree::Current.store
|
|
32
|
+
Spree::SearchProvider::ProductPresenter.new(self, store).call
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Remove this record from search index synchronously (inline, no job).
|
|
36
|
+
def remove_from_search_index
|
|
37
|
+
return unless search_indexing_enabled?
|
|
38
|
+
|
|
39
|
+
store_ids_for_indexing.each do |store_id|
|
|
40
|
+
store = Spree::Store.find_by(id: store_id)
|
|
41
|
+
next unless store
|
|
42
|
+
|
|
43
|
+
provider = Spree.search_provider.constantize.new(store)
|
|
44
|
+
provider.remove(self)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def enqueue_search_index
|
|
51
|
+
return unless search_indexing_enabled?
|
|
52
|
+
|
|
53
|
+
store_ids_for_indexing.each do |store_id|
|
|
54
|
+
Spree::SearchProvider::IndexJob.perform_later(self.class.name, id.to_s, store_id.to_s)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def enqueue_search_removal
|
|
59
|
+
return unless search_indexing_enabled?
|
|
60
|
+
|
|
61
|
+
pid = prefixed_id
|
|
62
|
+
store_ids_for_indexing.each do |store_id|
|
|
63
|
+
Spree::SearchProvider::RemoveJob.perform_later(pid, store_id.to_s)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def search_indexing_enabled?
|
|
68
|
+
Spree.search_provider.constantize.indexing_required?
|
|
69
|
+
rescue NameError
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def store_ids_for_indexing
|
|
74
|
+
if respond_to?(:store_ids)
|
|
75
|
+
store_ids
|
|
76
|
+
elsif respond_to?(:store_id)
|
|
77
|
+
[store_id].compact
|
|
78
|
+
else
|
|
79
|
+
Spree::Store.pluck(:id)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
module Spree
|
|
2
|
-
module
|
|
2
|
+
module Searchable
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
|
|
5
5
|
included do
|
|
6
|
-
def self.
|
|
6
|
+
def self.sanitize_query_for_search(query)
|
|
7
7
|
ActiveRecord::Base.sanitize_sql_like(query.to_s.downcase.strip)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def self.
|
|
10
|
+
def self.search_condition(model_class, attribute, query)
|
|
11
11
|
encrypted_attributes = model_class.encrypted_attributes.presence || []
|
|
12
12
|
|
|
13
13
|
if encrypted_attributes.include?(attribute.to_sym)
|
|
@@ -16,6 +16,13 @@ module Spree
|
|
|
16
16
|
model_class.arel_table[attribute.to_sym].lower.matches("%#{query}%")
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
|
+
|
|
20
|
+
# Backward compatibility aliases — remove in Spree 6.0
|
|
21
|
+
def self.sanitize_query_for_multi_search(query) = sanitize_query_for_search(query)
|
|
22
|
+
def self.multi_search_condition(model_class, attribute, query) = search_condition(model_class, attribute, query)
|
|
19
23
|
end
|
|
20
24
|
end
|
|
25
|
+
|
|
26
|
+
# Backward compatibility alias — remove in Spree 6.0
|
|
27
|
+
MultiSearchable = Searchable
|
|
21
28
|
end
|
|
@@ -9,7 +9,7 @@ module Spree
|
|
|
9
9
|
include Spree::UserReporting
|
|
10
10
|
include Spree::UserRoles
|
|
11
11
|
include Spree::RansackableAttributes
|
|
12
|
-
include Spree::
|
|
12
|
+
include Spree::Searchable
|
|
13
13
|
include Spree::Publishable
|
|
14
14
|
|
|
15
15
|
included do
|
|
@@ -74,28 +74,31 @@ module Spree
|
|
|
74
74
|
find_by_token_for!(:password_reset, token)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
def self.
|
|
78
|
-
sanitized_query =
|
|
77
|
+
def self.search(query)
|
|
78
|
+
sanitized_query = sanitize_query_for_search(query)
|
|
79
79
|
return none if query.blank?
|
|
80
80
|
|
|
81
81
|
name_conditions = []
|
|
82
82
|
|
|
83
|
-
name_conditions <<
|
|
84
|
-
name_conditions <<
|
|
83
|
+
name_conditions << search_condition(self, :first_name, sanitized_query)
|
|
84
|
+
name_conditions << search_condition(self, :last_name, sanitized_query)
|
|
85
85
|
|
|
86
86
|
full_name = NameOfPerson::PersonName.full(sanitized_query)
|
|
87
87
|
|
|
88
88
|
if full_name.first.present? && full_name.last.present?
|
|
89
|
-
name_conditions <<
|
|
90
|
-
name_conditions <<
|
|
89
|
+
name_conditions << search_condition(self, :first_name, full_name.first)
|
|
90
|
+
name_conditions << search_condition(self, :last_name, full_name.last)
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
where(arel_table[:email].lower.eq(query.downcase)).or(where(name_conditions.reduce(:or)))
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
+
# Backward compatibility alias — remove in Spree 6.0
|
|
97
|
+
def self.multi_search(query) = search(query)
|
|
98
|
+
|
|
96
99
|
self.whitelisted_ransackable_associations = %w[bill_address ship_address addresses tags spree_roles]
|
|
97
100
|
self.whitelisted_ransackable_attributes = %w[id email first_name last_name accepts_email_marketing]
|
|
98
|
-
self.whitelisted_ransackable_scopes = %w[multi_search]
|
|
101
|
+
self.whitelisted_ransackable_scopes = %w[search multi_search]
|
|
99
102
|
|
|
100
103
|
def self.with_email(query)
|
|
101
104
|
where("#{table_name}.email LIKE ?", "%#{query}%")
|