Class: Item::ArticleRetriever

Inherits:
BaseService show all
Defined in:
app/services/item/article_retriever.rb

Overview

Retrieves all the articles associated with a particular item.

Defined Under Namespace

Classes: Result

Constant Summary collapse

ARTICLE_TYPE_TO_STI =
{
  'faq' => 'ArticleFaq',
  'technical' => 'ArticleTechnical',
  'training' => 'ArticleTraining',
  'procedure' => 'ArticleProcedure',
  'blog_post' => 'Post'
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from BaseService

#log_debug, #log_error, #log_info, #log_warning, #logger, #options, #tagged_logger

Constructor Details

#initialize(article_types:, sales: nil, support: nil, add_vote_data: false) ⇒ ArticleRetriever

==== Initialization options

  • +:article_types+ - required, filter by STI type. Accepts old enum names (:faq, :technical, etc.)
  • +:sales+ - true/false or nil for any
  • +:support+ - true/false or nil for any
  • +:add_vote_data+ - true/false. If true, custom columns vote_count and positive_votes will be added


26
27
28
29
30
31
32
33
# File 'app/services/item/article_retriever.rb', line 26

def initialize(article_types:, sales: nil, support: nil, add_vote_data: false)
  raise "Invalid article types, must be a non-empty array." unless article_types.present? && article_types.is_a?(Array)

  @sti_types = article_types.map { |at| ARTICLE_TYPE_TO_STI[at.to_s] || at.to_s }.compact
  @sales = sales
  @support = support
  @add_vote_data = add_vote_data
end

Instance Attribute Details

#add_vote_dataObject (readonly)

Returns the value of attribute add_vote_data.



17
18
19
# File 'app/services/item/article_retriever.rb', line 17

def add_vote_data
  @add_vote_data
end

#salesObject (readonly)

Returns the value of attribute sales.



17
18
19
# File 'app/services/item/article_retriever.rb', line 17

def sales
  @sales
end

#sti_typesObject (readonly)

Returns the value of attribute sti_types.



17
18
19
# File 'app/services/item/article_retriever.rb', line 17

def sti_types
  @sti_types
end

#supportObject (readonly)

Returns the value of attribute support.



17
18
19
# File 'app/services/item/article_retriever.rb', line 17

def support
  @support
end

Instance Method Details

#process(item: nil, product_line: nil, product_category: nil, tags: nil) ⇒ Object

Retrieves all product specifications available to a given item

==== Parameters

  • +item+ - The item for which you want to retrieve articles

==== Returns

A Result object with

  • +articles+ - an array of articles


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'app/services/item/article_retriever.rb', line 46

def process(item: nil, product_line: nil, product_category: nil, tags: nil)
  articles = []
  article_scope = Article.all
  article_scope = article_scope.where(type: sti_types) if sti_types.present?
  article_scope = article_scope.where(sales: sales) unless sales.nil?
  article_scope = article_scope.where(support: support) unless support.nil?
  article_scope = article_scope.with_votes if add_vote_data
  # `tagged_with` called on the Article base class generates taggable_type = 'Article',
  # but taggings are stored using the actual STI class name (e.g. 'ArticleFaq', 'Post').
  # Build a correlated EXISTS subquery with the correct taggable_type(s) explicitly.
  if tags.present?
    tag_names = Array(tags).flatten.filter_map(&:presence).map { |t| t.to_s.strip.downcase }.uniq
    taggable_types = sti_types.present? ? sti_types : [Article.base_class.name]
    tag_subquery = Tagging
      .where(Tagging[:taggable_id].eq(Article.arel_table[:id]))
      .where(taggable_type: taggable_types)
      .joins(:tag)
      .where(Tag[:name].lower.in(tag_names))
      .select('1')
    article_scope = article_scope.where("EXISTS (#{tag_subquery.to_sql})")
  end
  # Preload tags for efficient rendering with filter badges
  # Use preload (not includes) to avoid GROUP BY conflicts with with_votes scope
  article_scope = article_scope.preload(taggings: :tag)
  # Retrieve articles tagged to this specific item
  if item
    article_scope = article_scope.for_item_sku(item.sku)
    articles = article_scope.to_a
    if item.primary_product_line
      pl_articles = process(product_line: item.primary_product_line, product_category: item.product_category).articles
      articles += pl_articles
      # since we added product line articles, we have to resort now using both item and product line in our array
    end
  elsif product_line
    article_scope = article_scope.for_product_line_url_without_order(product_line.slug_ltree.to_s)
    # A product category can only be applied if a product line is specified
    if product_category
      # Look for articles tagged for this product category OR with no product category specified
      article_scope = article_scope.where(Article[:applies_to_product_category_ids].overlap([product_category.id].compact).or(Article[:applies_to_product_category_ids].eq('{}')))
    elsif (pc_ids = product_line.product_category_ids.compact).present?
      # .compact ensures no nil values which would cause PG::NullValueNotAllowed error with PostgreSQL's && operator
      article_scope = article_scope.where(Article[:applies_to_product_category_ids].overlap(pc_ids).or(Article[:applies_to_product_category_ids].eq('{}')))
    end
    articles = article_scope.to_a
  else
    articles = article_scope.to_a
  end

  if add_vote_data
    articles = articles.sort_by{|a| [-(a.positive_votes.to_i + a.sort_booster.to_i), -(a.vote_count.to_i + a.sort_booster.to_i), a.subject] }.uniq
  else
    articles = articles.sort_by(&:subject)
  end

  Result.new(articles: articles)
end