Class: Item::ImageRetriever

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

Overview

Retrieves all the images associated with a particular item. Queries main image, product line main image,
image library for image tagged directly to the item or to the product line and its ancestor

Defined Under Namespace

Classes: Result

Instance Attribute Summary

Attributes inherited from BaseService

#options

Instance Method Summary collapse

Methods inherited from BaseService

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

Constructor Details

#initialize(options = {}) ⇒ ImageRetriever

==== Initialization options (pass as a hash)

  • +:tags+ - filter all image library images retrieval by these tags, specify one or many to query on the presence of any of these tags
  • +:locales+ - filter all images by those having any of these locales specifically or no locales at all
  • +:item_images_query_limit+ - how many images max to retrieve from the item library, default to 10
  • +:product_line_query_limit+ - how many images max to retrieve from the item library linked to the product line or its ancestor, default is 5
  • +:vignette_installation_images_query_limit+ - how many vignette room image to retrieve, default to 3
  • +:max_images+ - At max, return only this number of images
  • +:ignore_individual_query_limits+ true/false, default: false, if true then all default limits are ignored
  • +:ignore_item_primary_image+ - true/false
  • +:ignore_product_line_primary_image+ - true/false
  • +:ignore_vignette_installation_image+ - true/false
  • +:ignore_kit_components+ - true/false, do not process kit components for image retrieval
  • +:ignore_image_ids+ - array of image ids to ignore, useful if you build compound queries
  • +:min_width+ - The minimum image width
  • +:min_height+ - The minimum image height
  • +:include_secondary_product_lines+ - true/false, include images from the item's secondary product lines (and their ancestors)
  • +:exclude_tags+ - array of tags to exclude from all queries (e.g., DigitalAsset::HIDDEN_TAGS)
    ==== Examples

ir = Item::ImageRetriever.new(tags: 'for-product-page', item_images_query_limit: 20)



33
34
35
# File 'app/services/item/image_retriever.rb', line 33

def initialize(options = {})
  super(options.compact)
end

Instance Method Details

#ignore_individual_query_limits?Boolean

Returns:

  • (Boolean)


93
94
95
# File 'app/services/item/image_retriever.rb', line 93

def ignore_individual_query_limits?
  options[:ignore_individual_query_limits].present?
end

#item_images_query(item, limit: nil) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'app/services/item/image_retriever.rb', line 109

def item_images_query(item, limit: nil)
  return [] if options[:item_images_query_limit]&.zero?

  item_images = Image.active.by_item_ids(item.id).order(:position)
  item_images = item_images.where.not(id: options[:ignore_image_ids]) if options[:ignore_image_ids].present?
  item_images = item_images.tagged_with(options[:tags]) if options[:tags].present?
  item_images = item_images.tagged_with_all(options[:tags_all]) if options[:tags_all].present?
  item_images = item_images.not_tagged_with(options[:exclude_tags]) if options[:exclude_tags].present?
  item_images = item_images.localized_for_or_not(options[:locales]) if options[:locales].present?
  item_images = item_images.limit(item_images_query_limit) if item_images_query_limit
  item_images = item_images.where(Image[:attachment_width].gteq(options[:min_width])) if options[:min_width].present?
  item_images = item_images.where(Image[:attachment_height].gteq(options[:min_height])) if options[:min_height].present?
  item_images = item_images.limit(limit) if limit.present?
  item_images.to_a
end

#item_images_query_limitObject



125
126
127
128
129
# File 'app/services/item/image_retriever.rb', line 125

def item_images_query_limit
  return if ignore_individual_query_limits?

  options[:item_images_query_limit] || 25
end

#item_primary_image(item) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'app/services/item/image_retriever.rb', line 77

def item_primary_image(item)
  return if options[:ignore_item_primary_image].present?

  ipi = item.primary_image || item.new_item&.primary_image
  return unless ipi

  # If explicitly requested, apply tag filter to the primary image
  if options[:tags].present? && options[:respect_primary_image_tags].to_b
    image_tags = (ipi.tags || []).map(&:to_s)
    opts_tags = [options[:tags]].flatten.compact.map(&:to_s)
    return unless image_tags.intersect?(opts_tags)
  end

  ipi
end

#item_primary_product_line_image(item) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
# File 'app/services/item/image_retriever.rb', line 97

def item_primary_product_line_image(item)
  return if options[:ignore_product_line_primary_image].present?
  return unless (ippli = item.primary_product_line.try(:primary_image_inherited))

  # If we specified tags to filter on, we only pull if the primary image has this tag too
  # return unless !options[:strict_tags].to_b && options[:tags].nil? || (options[:tags].present? && ((ippli.tags || []) & options[:tags]).present?)
  # If the image has an applicable product category and it is not part of the item's product category id we discard
  return if (ippc_ids = ippli.all_my_applicable_product_categories.filter_map(&:id).uniq).present? && ippc_ids.exclude?(item.product_category_id)

  ippli
end

#library_product_line_images(item) ⇒ Object

Ordered product-line / category images (same rules as internal +product_line_query+).
Used by CRM image management to rank library results after item-specific and sibling tiers.



133
134
135
# File 'app/services/item/image_retriever.rb', line 133

def library_product_line_images(item)
  product_line_query(item)
end

#max_imagesObject

Setting a maximum of 100 by default



73
74
75
# File 'app/services/item/image_retriever.rb', line 73

def max_images
  options[:max_images] || 100
end

#process(item) ⇒ Object

Retrieves all product specifications available to a given item

==== Parameters

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

==== Returns

A Result object with

  • +all_images+ - an array of images combined
  • +images_grouped+ - hash wrapping all images as a single group (origin grouping removed)


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'app/services/item/image_retriever.rb', line 48

def process(item)
  item_to_use = item.new_item || item
  all_images = []
  all_images += [item_primary_image(item_to_use)].compact
  # all_images += [item_primary_product_line_image(item_to_use)].compact unless all_images.size >= max_images
  all_images += item_images_query(item_to_use) unless all_images.size >= max_images
  all_images += product_line_query(item_to_use) unless all_images.size >= max_images
  if !options[:ignore_kit_components].to_b && (kit_items = item_to_use.get_kit_items(spec_only: true)).present?
    kit_items.each do |kit_item|
      next if all_images.size >= max_images

      kit_options = options.dup
      kit_options[:item_images_query_limit] = 1
      kit_options[:max_images] = 1
      kit_options[:ignore_kit_components] = true
      kit_ir = Item::ImageRetriever.new(**kit_options)
      all_images += kit_ir.process(kit_item).all_images
    end
  end
  all_images = all_images.compact.uniq
  all_images = all_images[0..(max_images - 1)]
  Result.new(all_images:, images_grouped: { nil => all_images })
end

#product_line_query(item) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'app/services/item/image_retriever.rb', line 137

def product_line_query(item)
  return [] unless item.primary_product_line
  return [] if options[:product_line_query_limit]&.zero?

  primary_pl_ids = item.primary_product_line.self_and_ancestors_ids
  secondary_pl_ids =
    if options[:include_secondary_product_lines]
      item.product_lines.where.not(id: item.primary_product_line_id).flat_map(&:self_and_ancestors_ids)
    else
      []
    end
  pl_ids = (primary_pl_ids + secondary_pl_ids).uniq
  pc_ids = item.product_category.self_and_ancestors_ids
  item_images = Image.active.includes(:product_lines, :product_categories).order(:position)

  item_images = item_images.by_product_line_id_direct(pl_ids)
  item_images = item_images.by_product_category_id_direct_or_optional(pc_ids)
  item_images = item_images.tagged_with(options[:tags]) if options[:tags].present?
  item_images = item_images.tagged_with_all(options[:tags_all]) if options[:tags_all].present?
  item_images = item_images.not_tagged_with(options[:exclude_tags]) if options[:exclude_tags].present?
  item_images = item_images.limit(product_line_query_limit) if product_line_query_limit
  item_images = item_images.where(Image[:attachment_width].gteq(options[:min_width])) if options[:min_width].present?
  item_images = item_images.where(Image[:attachment_height].gteq(options[:min_height])) if options[:min_height].present?
  # We have to eliminate images which are irrelvant to our applicable product category
  item_images = item_images.select do |ii|
    ippc_ids = ii.all_my_applicable_product_categories.filter_map(&:id).uniq
    # Either the image has no product category applicable in which case it is included, otherwise the item product category has to be part of it
    ippc_ids.blank? || ippc_ids.include?(item.product_category_id)
  end

  # Prioritize images closest to the item's product line and product category
  primary_pl_id = item.primary_product_line_id
  primary_ancestor_ids = item.primary_product_line.ancestors.map(&:id)
  direct_secondary_ids = item.product_lines.where.not(id: primary_pl_id).ids
  secondary_ancestor_ids = (secondary_pl_ids - direct_secondary_ids)
  item_pc_id = item.product_category_id

  item_images.sort_by do |ii|
    img_pl_ids = ii.product_lines.map(&:id)
    img_pc_ids = ii.product_categories.map(&:id)

    pl_specificity = if img_pl_ids.include?(primary_pl_id)
                       0
                     elsif img_pl_ids.intersect?(direct_secondary_ids)
                       1
                     elsif img_pl_ids.intersect?(primary_ancestor_ids)
                       2
                     elsif img_pl_ids.intersect?(secondary_ancestor_ids)
                       3
                     else
                       4
                     end

    pc_specificity = img_pc_ids.include?(item_pc_id) ? 0 : 1

    [pl_specificity, pc_specificity, ii.position.to_i]
  end
end

#product_line_query_limitObject



196
197
198
199
200
# File 'app/services/item/image_retriever.rb', line 196

def product_line_query_limit
  return if ignore_individual_query_limits?

  options[:product_line_query_limit] || 10
end