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)

Parameters:

  • options (Hash) (defaults to: {})

    Options hash (forwarded to BaseService)

Options Hash (options):

  • :tags (String, Array<String>)

    filter image library retrieval to images having any of these tags

  • :tags_all (String, Array<String>)

    filter image library retrieval to images having all of these tags

  • :exclude_tags (Array<String>)

    tags to exclude from all queries (e.g., DigitalAsset::HIDDEN_TAGS)

  • :strict_tags (Boolean)

    require strict tag matching

  • :locales (Array<String>)

    filter images by these locales (or no locale)

  • :item_images_query_limit (Integer)

    max images to retrieve from the item library (default 10)

  • :product_line_query_limit (Integer)

    max images to retrieve from the product line library (default 5)

  • :max_images (Integer)

    at max, return only this number of images

  • :ignore_individual_query_limits (Boolean)

    if true, all default limits are ignored (default false)

  • :ignore_item_primary_image (Boolean)

    true/false

  • :ignore_product_line_primary_image (Boolean)

    true/false

  • :ignore_kit_components (Boolean)

    true/false, do not process kit components for image retrieval

  • :ignore_image_ids (Array<Integer>)

    image ids to ignore, useful for compound queries

  • :min_width (Integer)

    minimum image width

  • :min_height (Integer)

    minimum image height

  • :include_secondary_product_lines (Boolean)

    include images from the item's secondary product lines (and their ancestors)

  • :respect_primary_image_tags (Boolean)

    restrict primary images by their tags

  • :logger (Logger)

    custom logger (defaults to Rails.logger)



52
53
54
# File 'app/services/item/image_retriever.rb', line 52

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

Instance Method Details

#ignore_individual_query_limits?Boolean

Returns:

  • (Boolean)


112
113
114
# File 'app/services/item/image_retriever.rb', line 112

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

#item_images_query(item, limit: nil) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'app/services/item/image_retriever.rb', line 128

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



144
145
146
147
148
# File 'app/services/item/image_retriever.rb', line 144

def item_images_query_limit
  return if ignore_individual_query_limits?

  options[:item_images_query_limit] || 25
end

#item_primary_image(item) ⇒ Object



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

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



116
117
118
119
120
121
122
123
124
125
126
# File 'app/services/item/image_retriever.rb', line 116

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.



152
153
154
# File 'app/services/item/image_retriever.rb', line 152

def library_product_line_images(item)
  product_line_query(item)
end

#max_imagesObject

Setting a maximum of 100 by default



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

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)


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'app/services/item/image_retriever.rb', line 67

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



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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'app/services/item/image_retriever.rb', line 156

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



215
216
217
218
219
# File 'app/services/item/image_retriever.rb', line 215

def product_line_query_limit
  return if ignore_individual_query_limits?

  options[:product_line_query_limit] || 10
end