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 Method Summary collapse

Methods inherited from BaseService

#log_debug, #log_error, #log_info, #log_warning, #logger, #options, #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)



31
32
33
# File 'app/services/item/image_retriever.rb', line 31

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

Instance Method Details

#ignore_individual_query_limits?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'app/services/item/image_retriever.rb', line 91

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

#item_images_query(item, limit: nil) ⇒ Object



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

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



123
124
125
126
127
# File 'app/services/item/image_retriever.rb', line 123

def item_images_query_limit
  return if ignore_individual_query_limits?

  options[:item_images_query_limit] || 25
end

#item_primary_image(item) ⇒ Object



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

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 & opts_tags).present?
  end

  ipi
end

#item_primary_product_line_image(item) ⇒ Object



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

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.map(&:id).compact.uniq).present? && !ippc_ids.include?(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.



131
132
133
# File 'app/services/item/image_retriever.rb', line 131

def library_product_line_images(item)
  product_line_query(item)
end

#max_imagesObject

Setting a maximum of 100 by default



71
72
73
# File 'app/services/item/image_retriever.rb', line 71

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)


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

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



135
136
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 135

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.map(&:id).compact.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).pluck(:id)
  secondary_ancestor_ids = (secondary_pl_ids - direct_secondary_ids)
  item_pc_id = item.product_category_id

  item_images = 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 & direct_secondary_ids).present?
                       1
                     elsif (img_pl_ids & primary_ancestor_ids).present?
                       2
                     elsif (img_pl_ids & secondary_ancestor_ids).present?
                       3
                     else
                       4
                     end

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

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

  item_images
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