Class: Spree::Taxon

Inherits:
Object
  • Object
show all
Extended by:
FriendlyId
Includes:
MemoizedData, Metadata, Metafields, SingleStoreResource, TranslatableResource, TranslatableResourceSlug
Defined in:
app/models/spree/taxon.rb

Direct Known Subclasses

Category

Constant Summary collapse

RULES_MATCH_POLICIES =
%w[all any].freeze
SORT_ORDERS =
[
  'manual',
  'best_selling',
  'price asc',
  'price desc',
  'available_on desc',
  'available_on asc',
  'name asc',
  'name desc'
].freeze
MEMOIZED_METHODS =
%w[cached_self_and_descendants_ids].freeze
TRANSLATABLE_FIELDS =

Translations

%i[name pretty_name description permalink].freeze
RICH_TEXT_TRANSLATABLE_FIELDS =
%i[description].freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Metadata

#metadata, #metadata=, #public_metadata=

Methods included from TranslatableResource

#get_field_with_locale, #translatable_store, #upsert_translations

Class Method Details

.recalculate_products_count(taxon_ids) ⇒ Object

Recomputes the stored, descendant-inclusive products_count for the given taxons AND all of their ancestors — the nodes whose inclusive count can shift when a classification changes. Counts unique products classified under each node or its descendants (a product reachable through several nodes is counted once per ancestor). Call after any classification change.

Parameters:

  • taxon_ids (Array<Integer>)

    taxons whose branch counts changed



220
221
222
223
224
225
226
227
228
229
230
231
# File 'app/models/spree/taxon.rb', line 220

def self.recalculate_products_count(taxon_ids)
  changed = unscoped.where(id: Array(taxon_ids).compact.uniq)

  # The affected nodes are each changed taxon plus its ancestors.
  affected = changed.flat_map { |taxon| taxon.self_and_ancestors.to_a }.uniq(&:id)

  affected.each do |taxon|
    count = Spree::Classification.where(taxon_id: taxon.self_and_descendants.select(:id))
                                 .distinct.count(:product_id)
    taxon.update_column(:products_count, count) if taxon.products_count != count
  end
end

.search_by_name(query) ⇒ Object

Search



133
134
135
# File 'app/models/spree/taxon.rb', line 133

def self.search_by_name(query)
  i18n { name.lower.matches("%#{query.downcase}%") }
end

Instance Method Details

#active_products_with_descendantsObject



202
203
204
205
206
207
208
209
210
211
# File 'app/models/spree/taxon.rb', line 202

def active_products_with_descendants
  @active_products_with_descendants ||= store.products
                                             .joins(:classifications)
                                             .active
                                             .where(
                                               Spree::Classification.table_name => {
                                                 taxon_id: descendants.ids + [id]
                                               }
                                             )
end

#cached_self_and_descendants_idsObject



420
421
422
423
424
# File 'app/models/spree/taxon.rb', line 420

def cached_self_and_descendants_ids
  @cached_self_and_descendants_ids ||= Rails.cache.fetch("#{cache_key_with_version}/descendant-ids") do
    self_and_descendants.ids
  end
end

#child_index=(idx) ⇒ Object

awesome_nested_set sorts by :lft and :rgt. This call re-inserts the child node so that its resulting position matches the observable 0-indexed position. ** Note ** no :position column needed - a_n_s doesn't handle the reordering if you bring your own :order_column.

See #3390 for background.



432
433
434
# File 'app/models/spree/taxon.rb', line 432

def child_index=(idx)
  move_to_child_with_index(parent, idx.to_i) unless new_record?
end

#generate_pretty_nameObject



382
383
384
# File 'app/models/spree/taxon.rb', line 382

def generate_pretty_name
  [parent&.pretty_name, name].compact.join(' -> ')
end

#generate_slugObject



386
387
388
389
390
391
392
393
394
# File 'app/models/spree/taxon.rb', line 386

def generate_slug
  if parent.present?
    [parent.permalink, (permalink.blank? ? name.to_url : permalink.split('/').last.to_url)].join('/')
  elsif permalink.blank?
    name.to_url
  else
    permalink.to_url
  end
end

#manual?Boolean

Returns:



182
183
184
# File 'app/models/spree/taxon.rb', line 182

def manual?
  !automatic?
end

#manual_sort_order?Boolean

Returns:



198
199
200
# File 'app/models/spree/taxon.rb', line 198

def manual_sort_order?
  sort_order == 'manual'
end

#products_matching_rules(opts = {}) ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'app/models/spree/taxon.rb', line 233

def products_matching_rules(opts = {})
  return Spree::Product.none if manual? || rules.empty?

  storefront = opts[:storefront] || false
  currency = opts[:currency] || store.default_currency

  all_products = store.products.not_archived

  products_matcher_cache_key = [
    'products_matching_rules',
    cache_key_with_version,
    storefront,
    currency,
    all_products.cache_key_with_version
  ]

  all_products = all_products.active(currency: currency) if storefront

  any_rules_match_policy = rules_match_policy == 'any'
  products = any_rules_match_policy ? Spree::Product.none : all_products

  rules.each do |rule|
    if any_rules_match_policy
      product_ids = rule.apply(all_products).ids
      # it's safer to use this approach with ids as it will not break if the rule is not a simple where clause
      # and we will avoid `ArgumentError (Relation passed to #or must be structurally compatible. Incompatible values: [:group, :order, :joins, :readonly])` error
      products = products.or(all_products.where(id: product_ids)) if product_ids.any?
    else
      products = rule.apply(products)
    end
  end

  products
end


404
405
406
407
408
409
410
# File 'app/models/spree/taxon.rb', line 404

def regenerate_pretty_name_and_permalink
  Mobility.with_locale(nil) do
    update_columns(pretty_name: generate_pretty_name, permalink: generate_slug, updated_at: Time.current)
  end

  children.reload.each(&:regenerate_pretty_name_and_permalink_as_child)
end


412
413
414
415
416
417
418
# File 'app/models/spree/taxon.rb', line 412

def regenerate_pretty_name_and_permalink_as_child
  Mobility.with_locale(nil) do
    update_columns(pretty_name: generate_pretty_name, permalink: generate_slug, updated_at: Time.current)
  end

  children.reload.each(&:regenerate_pretty_name_and_permalink_as_child)
end

#regenerate_taxon_products(only_once: false) ⇒ Object

we need to create a new taxon product (classification) record for each product that matches the rules so we can later use them for product filtering and so on if we want to fire the service once during object lifecycle - pass only_once: true



271
272
273
274
275
276
# File 'app/models/spree/taxon.rb', line 271

def regenerate_taxon_products(only_once: false)
  return unless marked_for_regenerate_taxon_products?

  Spree::Taxons::RegenerateProducts.call(taxon: self)
  self.marked_for_regenerate_taxon_products = false if !frozen? && only_once
end

#requires_taxonomy?Boolean

Whether a taxonomy is mandatory. Legacy Spree::Taxon requires one; Spree::Category overrides this to false (it is owned via store_id).

Returns:



194
195
196
# File 'app/models/spree/taxon.rb', line 194

def requires_taxonomy?
  true
end

#seo_titleObject

Return meta_title if set otherwise generates from taxon name



374
375
376
# File 'app/models/spree/taxon.rb', line 374

def seo_title
  meta_title.blank? ? name : meta_title
end


396
397
398
399
400
401
402
# File 'app/models/spree/taxon.rb', line 396

def set_permalink
  if Spree.use_translations?
    translations.each(&:set_permalink)
  else
    self.permalink = generate_slug
  end
end

#set_pretty_nameObject



378
379
380
# File 'app/models/spree/taxon.rb', line 378

def set_pretty_name
  self.pretty_name = generate_pretty_name
end

#slugObject



278
279
280
# File 'app/models/spree/taxon.rb', line 278

def slug
  permalink
end

#slug=(value) ⇒ Object



282
283
284
# File 'app/models/spree/taxon.rb', line 282

def slug=(value)
  self.permalink = value
end

#storeObject

The owning store. Prefers the direct store_id; falls back to the taxonomy's store for legacy rows not yet backfilled.



188
189
190
# File 'app/models/spree/taxon.rb', line 188

def store
  super || taxonomy&.store
end