Class: DigitalAsset
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- DigitalAsset
- Extended by:
- FriendlyId
- Includes:
- Models::Auditable, Models::ItemScopable, Models::Taggable, PgSearch::Model
- Defined in:
- app/models/digital_asset.rb
Overview
== Schema Information
Table name: digital_assets
Database name: primary
id :integer not null, primary key
ai_metadata_suggestions :jsonb
ai_visual_description :text
air_date :date
asset :jsonb
attachment_format :string(10)
attachment_height :integer
attachment_mime_type :string
attachment_name :string
attachment_size :integer
attachment_uid :string
attachment_width :integer
background_color :string
category :string(255)
cloudflare_data :jsonb not null
cloudflare_uid :string
duration_in_seconds :integer
expanded_description :text
fingerprint :bigint
fingerprint_legacy :string
image_colorspace :string
image_dpi :integer
inactive :boolean default(FALSE), not null
linked_assets_uids :string default([]), is an Array
locales :string default([]), not null, is an Array
location :string
merged_from_ids :integer default([]), is an Array
meta_description :text
meta_keywords :string
meta_title :string(255)
notes :text
position :integer default(100), not null
poster_format :string
poster_mime_type :string
poster_name :string
poster_offset :integer
poster_uid :string
reference_number :string
series :string
slug :string(140)
source :string
structured_transcript_json :jsonb
sub_header :string(255)
thumbnail_url :string
title :string(255)
transcribed_at :datetime
transcript :text
transcription_state :enum default("pending")
translations :jsonb
type :string
url :string(255)
video_has_no_spoken_words :boolean default(FALSE)
vision_analyzed_at :datetime
vision_model_used :string
youtube_caption_synced_at :datetime
youtube_chapters_draft :jsonb
youtube_chapters_generation_error :text
youtube_chapters_generation_status :string
youtube_description :string
youtube_privacy_status :string
youtube_synced_at :datetime
youtube_title :string
youtube_upload_date :datetime
youtube_upload_status :string
created_at :datetime not null
updated_at :datetime not null
assemblyai_transcript_id :string
asset_file_id :string
cloudinary_asset_id :string
creator_id :integer
legacy_wistia_id :string(255)
poster_image_id :integer
purge_cache_request_id :string
updater_id :integer
youtube_id :string
youtube_thumbnail_image_id :integer
Indexes
by_type_inactive_id (type,inactive,id)
index_digital_assets_on_asset_file_id (asset_file_id) UNIQUE
index_digital_assets_on_cloudflare_uid (cloudflare_uid)
index_digital_assets_on_creator_id (creator_id)
index_digital_assets_on_inactive (inactive)
index_digital_assets_on_merged_from_ids (merged_from_ids) USING gin
index_digital_assets_on_poster_image_id (poster_image_id)
index_digital_assets_on_poster_offset (poster_offset)
index_digital_assets_on_slug (slug)
index_digital_assets_on_source (source)
index_digital_assets_on_transcription_state (transcription_state)
index_digital_assets_on_translations (translations) USING gin
index_digital_assets_on_type_and_slug (type,slug) UNIQUE
index_digital_assets_on_updater_id (updater_id)
index_digital_assets_on_url (url)
index_digital_assets_on_vision_analyzed_at (vision_analyzed_at)
index_digital_assets_on_youtube_thumbnail_image_id (youtube_thumbnail_image_id)
index_images_on_fingerprint (fingerprint) WHERE (((type)::text = 'Image'::text) AND (fingerprint IS NOT NULL))
type_category (type,category)
type_entity_id (type,legacy_wistia_id)
type_title (type,title)
Foreign Keys
fk_rails_... (creator_id => parties.id)
fk_rails_... (poster_image_id => digital_assets.id) ON DELETE => nullify
fk_rails_... (updater_id => parties.id)
fk_rails_... (youtube_thumbnail_image_id => digital_assets.id) ON DELETE => nullify
Constant Summary collapse
- POPULAR_TAGS =
Checkbox labels on image/video forms. Website PDP stills use WYS image profiles on the item—see Image::POPULAR_TAGS_FORM_HINT.
%w[for-product-page for-support-page no-index].freeze
- HIDDEN_TAGS =
Recognised hidden tags.
%w[installation-plan pdf-thumbnail room-configuration video-poster auto-generated review-avatar].freeze
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Schedulable
Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
-
#force_new_slug ⇒ Object
Returns the value of attribute force_new_slug.
-
#refresh_cache ⇒ Object
Returns the value of attribute refresh_cache.
- #title ⇒ Object readonly
- #url ⇒ Object readonly
Has many collapse
- #digital_asset_product_lines ⇒ ActiveRecord::Relation<DigitalAssetProductLine>
- #generated_images ⇒ ActiveRecord::Relation<GeneratedImage>
- #product_lines ⇒ ActiveRecord::Relation<ProductLine>
- #site_maps ⇒ ActiveRecord::Relation<SiteMap>
Methods included from Models::Taggable
Has and belongs to many collapse
- #items ⇒ ActiveRecord::Relation<Item>
- #opportunities ⇒ ActiveRecord::Relation<Opportunity>
- #parties ⇒ ActiveRecord::Relation<Party>
- #product_categories ⇒ ActiveRecord::Relation<ProductCategory>
-
#reviews ⇒ ActiveRecord::Relation<Review>
Legacy associations removed: showcases, showcase_rooms.
Class Method Summary collapse
-
.active ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are active.
-
.all_locales ⇒ Object
NOTE: all_tags method provided by Models::Taggable concern.
- .available_banner_tags ⇒ Object
- .available_og_image_tags ⇒ Object
-
.available_page_tags ⇒ Object
Generate the available 'for-' page tags for all CMS pages.
- .available_page_tags_with_paths ⇒ Object
-
.banner_tag_for(page_id) ⇒ Object
Build the banner image tag for a given CMS page id.
-
.by_category ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by category.
-
.by_item_ids ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by item ids.
-
.by_item_skus ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by item skus.
-
.by_party_ids ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by party ids.
-
.by_product_category_id_direct ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product category id direct.
-
.by_product_category_id_direct_or_optional ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product category id direct or optional.
-
.by_product_line_id ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product line id.
-
.by_product_line_id_direct ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product line id direct.
-
.by_product_line_path ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product line path.
-
.categorized ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are categorized.
-
.exclude_tags ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are exclude tags.
-
.images ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are images.
-
.invalidated ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are invalidated.
-
.localized_for ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are localized for.
-
.localized_for_or_not ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are localized for or not.
-
.not_by_party_ids ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are not by party ids.
-
.not_by_product_line_id ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are not by product line id.
-
.not_by_product_line_path ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are not by product line path.
-
.og_image_tag_for(page_id) ⇒ Object
Build the og:image tag for a given CMS page id.
-
.page_tag_for(page_id) ⇒ Object
Build the canonical page tag for a given CMS page id (e.g. "towel-warmer").
- .ransackable_scopes(_auth_object = nil) ⇒ Object
-
.related_to_item_id ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are related to item id.
-
.show_hidden_tags ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are show hidden tags.
-
.tag_presence ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are tag presence.
-
.tagged_with_all ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are tagged with all.
-
.valid ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are valid.
-
.videos ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are videos.
-
.with_product_line_urls ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are with product line urls.
-
.without_product_categories ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are without product categories.
-
.without_product_lines ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are without product lines.
Instance Method Summary collapse
- #alerts ⇒ Object
- #all_my_items ⇒ Object
- #asset_identifier ⇒ Object
- #cross_links_opportunities_to_parties ⇒ Object
- #dimensions ⇒ Object
-
#externally_referenced? ⇒ Boolean
True when some record we do NOT own points at this asset, so a purge must retain it (vs. owned dependents like embeddings/profiles, which cascade).
- #file_basename ⇒ Object
-
#invalidate!(reason: nil) ⇒ Boolean
Quarantine this asset: stamp the moment its file was detected permanently broken so it drops out of
activefetches/embedding and starts the 30-day recovery clock before InvalidDigitalAssetPurgeWorker hard-deletes it. - #is_image? ⇒ Boolean
- #is_video? ⇒ Boolean
- #product_lines_display ⇒ Object
- #product_lines_for_sorting ⇒ Object
- #purge_edge_cache ⇒ Object
- #sanitize_urls ⇒ Object
- #seo_title ⇒ Object
- #should_generate_new_friendly_id? ⇒ Boolean
- #should_sanitize_urls? ⇒ Boolean
- #slug_candidates ⇒ Object
- #tags_display ⇒ Object
- #thumbnail_url ⇒ Object
- #touch_related ⇒ Object
Methods included from Models::Taggable
#add_tag, all_tags, #has_tag?, normalize_tag_names, not_tagged_with, #remove_tag, #tag_list, #tag_list=, #taggable_type_for_tagging, tagged_with, #tags, #tags=, tags_cloud, tags_exclude, tags_include, with_all_tags, with_any_tags, without_all_tags, without_any_tags
Methods included from Models::ItemScopable
by_product_category_id, by_product_category_path, by_product_category_path_exact, by_product_category_url, by_product_category_url_exact, by_product_line_path_full, by_product_line_url, by_product_line_url_full, not_by_product_category_id
Methods included from Models::Auditable
#all_skipped_columns, #audit_reference_data, #creator, #should_not_save_version, #stamp_record, #updater
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransortable_attributes, #to_relation
Methods included from Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#force_new_slug ⇒ Object
Returns the value of attribute force_new_slug.
125 126 127 |
# File 'app/models/digital_asset.rb', line 125 def force_new_slug @force_new_slug end |
#refresh_cache ⇒ Object
Returns the value of attribute refresh_cache.
125 126 127 |
# File 'app/models/digital_asset.rb', line 125 def refresh_cache @refresh_cache end |
#title ⇒ Object (readonly)
144 |
# File 'app/models/digital_asset.rb', line 144 validates :title, presence: true |
#url ⇒ Object (readonly)
145 |
# File 'app/models/digital_asset.rb', line 145 validates :url, uniqueness: { if: :url } |
Class Method Details
.active ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are active. Active Record Scope
162 |
# File 'app/models/digital_asset.rb', line 162 scope :active, -> { where(inactive: false, invalid_at: nil) } |
.all_locales ⇒ Object
NOTE: all_tags method provided by Models::Taggable concern
372 373 374 |
# File 'app/models/digital_asset.rb', line 372 def self.all_locales pluck(Arel.sql('distinct unnest(locales) as locale')).compact.sort end |
.available_banner_tags ⇒ Object
569 570 571 572 573 |
# File 'app/models/digital_asset.rb', line 569 def self. PagesController.page_ids.reject { |page_id| page_id.start_with?('h-') }.map do |page_id| (page_id) end.uniq.sort end |
.available_og_image_tags ⇒ Object
581 582 583 584 585 |
# File 'app/models/digital_asset.rb', line 581 def self. PagesController.page_ids.reject { |page_id| page_id.start_with?('h-') }.map do |page_id| og_image_tag_for(page_id) end.uniq.sort end |
.available_page_tags ⇒ Object
Generate the available 'for-' page tags for all CMS pages.
Uses the same convention as the cohesive migration:
'for-' + page_id.tr('/', '-').parameterize + '-page'
545 546 547 548 549 |
# File 'app/models/digital_asset.rb', line 545 def self. PagesController.page_ids.reject { |page_id| page_id.start_with?('h-') }.map do |page_id| "for-#{page_id.tr('/', '-').parameterize}-page" end.uniq.sort end |
.available_page_tags_with_paths ⇒ Object
551 552 553 554 555 556 |
# File 'app/models/digital_asset.rb', line 551 def self. PagesController.page_ids.reject { |page_id| page_id.start_with?('h-') }.each_with_object({}) do |page_id, hash| tag = "for-#{page_id.tr('/', '-').parameterize}-page" hash[tag] = "/#{page_id}" end end |
.banner_tag_for(page_id) ⇒ Object
Build the banner image tag for a given CMS page id.
e.g. banner_tag_for("floor-heating/bathroom") => "banner-for-floor-heating-bathroom-page"
565 566 567 |
# File 'app/models/digital_asset.rb', line 565 def self.(page_id) "banner-for-#{page_id.to_s.tr('/', '-').parameterize}-page" end |
.by_category ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by category. Active Record Scope
224 |
# File 'app/models/digital_asset.rb', line 224 scope :by_category, ->(cat) { where(category: cat) } |
.by_item_ids ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by item ids. Active Record Scope
225 226 227 228 229 230 |
# File 'app/models/digital_asset.rb', line 225 scope :by_item_ids, ->(*item_ids) { item_ids = [item_ids].flatten.filter_map(&:presence).uniq return none if item_ids.blank? where('exists(select 1 from digital_assets_items dai where dai.item_id IN (?) and dai.digital_asset_id = digital_assets.id)', item_ids) } |
.by_item_skus ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by item skus. Active Record Scope
275 |
# File 'app/models/digital_asset.rb', line 275 scope :by_item_skus, ->(*item_skus) { joins(:items).where(items: { sku: item_skus }) } |
.by_party_ids ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by party ids. Active Record Scope
294 295 296 297 298 299 |
# File 'app/models/digital_asset.rb', line 294 scope :by_party_ids, ->(*party_ids) { party_ids = [party_ids].flatten.filter_map(&:presence).uniq return none if party_ids.blank? where('exists(select 1 from digital_assets_parties dap inner join parties p on p.id = dap.party_id where (dap.party_id IN (:party_ids) OR p.customer_id IN (:party_ids)) and dap.digital_asset_id = digital_assets.id)', party_ids: party_ids) } |
.by_product_category_id_direct ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product category id direct. Active Record Scope
205 206 207 208 209 210 |
# File 'app/models/digital_asset.rb', line 205 scope :by_product_category_id_direct, ->(*pc_ids) { ids = [pc_ids].flatten return none if ids.empty? where('exists(select 1 from digital_assets_product_categories dapc where dapc.digital_asset_id = digital_assets.id and dapc.product_category_id in (?))', ids) } |
.by_product_category_id_direct_or_optional ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product category id direct or optional. Active Record Scope
211 212 213 214 215 216 217 218 219 220 |
# File 'app/models/digital_asset.rb', line 211 scope :by_product_category_id_direct_or_optional, ->(*pc_ids) { ids = [pc_ids].flatten return none if ids.empty? sql = <<-EOS exists(select 1 from digital_assets_product_categories dapc where dapc.digital_asset_id = digital_assets.id and dapc.product_category_id in (?)) OR not exists(select 1 from digital_assets_product_categories dapc where dapc.digital_asset_id = digital_assets.id) EOS where(sql, ids) } |
.by_product_line_id ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product line id. Active Record Scope
181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'app/models/digital_asset.rb', line 181 scope :by_product_line_id, ->(*pl_ids) { ids = [pl_ids].flatten.filter_map(&:presence).map(&:to_i).uniq return none if ids.empty? where( 'EXISTS(SELECT 1 FROM digital_asset_product_lines dapl ' \ 'INNER JOIN product_lines pl ON pl.id = dapl.product_line_id ' \ 'WHERE dapl.digital_asset_id = digital_assets.id ' \ 'AND pl.ltree_path_ids <@ ANY(SELECT ltree_path_ids FROM product_lines WHERE id = ANY(ARRAY[?]::integer[])))', ids ) } |
.by_product_line_id_direct ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product line id direct. Active Record Scope
175 176 177 178 179 180 |
# File 'app/models/digital_asset.rb', line 175 scope :by_product_line_id_direct, ->(*pl_ids) { ids = [pl_ids].flatten.filter_map(&:presence).uniq return none if ids.empty? where('EXISTS(SELECT 1 FROM digital_asset_product_lines dapl WHERE dapl.digital_asset_id = digital_assets.id AND dapl.product_line_id IN (?))', ids) } |
.by_product_line_path ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product line path. Active Record Scope
324 325 326 327 328 329 330 331 332 333 |
# File 'app/models/digital_asset.rb', line 324 scope :by_product_line_path, ->(slug_path) { return all if slug_path.blank? where( 'EXISTS(SELECT 1 FROM digital_asset_product_lines dapl ' \ 'INNER JOIN product_lines pl ON pl.id = dapl.product_line_id ' \ 'WHERE dapl.digital_asset_id = digital_assets.id AND pl.ltree_path_slugs <@ ?)', slug_path ) } |
.categorized ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are categorized. Active Record Scope
308 |
# File 'app/models/digital_asset.rb', line 308 scope :categorized, -> { where.not(category: nil) } |
.exclude_tags ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are exclude tags. Active Record Scope
280 281 282 283 284 285 |
# File 'app/models/digital_asset.rb', line 280 scope :exclude_tags, ->(*tag_names) { tag_names = [tag_names].flatten.filter_map(&:presence).map(&:downcase) return all if tag_names.empty? not_tagged_with(tag_names) } |
.images ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are images. Active Record Scope
309 |
# File 'app/models/digital_asset.rb', line 309 scope :images, -> { where(type: 'Image') } |
.invalidated ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are invalidated. Active Record Scope
167 |
# File 'app/models/digital_asset.rb', line 167 scope :invalidated, -> { where.not(invalid_at: nil) } |
.localized_for ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are localized for. Active Record Scope
292 |
# File 'app/models/digital_asset.rb', line 292 scope :localized_for, ->(*locales) { where.overlap(locales: LocaleUtility.locales_and_fallbacks(locales)) } |
.localized_for_or_not ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are localized for or not. Active Record Scope
293 |
# File 'app/models/digital_asset.rb', line 293 scope :localized_for_or_not, ->(*locales) { localized_for(locales).or(where(DigitalAsset[:locales].eq('{}'))) } |
.not_by_party_ids ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are not by party ids. Active Record Scope
302 303 304 305 306 307 |
# File 'app/models/digital_asset.rb', line 302 scope :not_by_party_ids, ->(*party_ids) { party_ids = [party_ids].flatten.filter_map(&:presence).uniq return all if party_ids.blank? where('NOT exists(select 1 from digital_assets_parties dap inner join parties p on p.id = dap.party_id where (dap.party_id IN (:party_ids) OR p.customer_id IN (:party_ids)) and dap.digital_asset_id = digital_assets.id)', party_ids: party_ids) } |
.not_by_product_line_id ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are not by product line id. Active Record Scope
193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'app/models/digital_asset.rb', line 193 scope :not_by_product_line_id, ->(*pl_ids) { ids = [pl_ids].flatten.compact.map(&:to_i) return all if ids.empty? where.not( 'EXISTS(SELECT 1 FROM digital_asset_product_lines dapl ' \ 'INNER JOIN product_lines pl ON pl.id = dapl.product_line_id ' \ 'WHERE dapl.digital_asset_id = digital_assets.id ' \ 'AND pl.ltree_path_ids <@ ANY(SELECT ltree_path_ids FROM product_lines WHERE id = ANY(ARRAY[?]::integer[])))', ids ) } |
.not_by_product_line_path ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are not by product line path. Active Record Scope
337 338 339 340 341 342 343 344 345 346 |
# File 'app/models/digital_asset.rb', line 337 scope :not_by_product_line_path, ->(slug_path) { return all if slug_path.blank? where.not( 'EXISTS(SELECT 1 FROM digital_asset_product_lines dapl ' \ 'INNER JOIN product_lines pl ON pl.id = dapl.product_line_id ' \ 'WHERE dapl.digital_asset_id = digital_assets.id AND pl.ltree_path_slugs <@ ?)', slug_path ) } |
.og_image_tag_for(page_id) ⇒ Object
Build the og:image tag for a given CMS page id.
e.g. og_image_tag_for("floor-heating/bathroom") => "og-image-for-floor-heating-bathroom-page"
577 578 579 |
# File 'app/models/digital_asset.rb', line 577 def self.og_image_tag_for(page_id) "og-image-for-#{page_id.to_s.tr('/', '-').parameterize}-page" end |
.page_tag_for(page_id) ⇒ Object
Build the canonical page tag for a given CMS page id (e.g. "towel-warmer").
559 560 561 |
# File 'app/models/digital_asset.rb', line 559 def self.page_tag_for(page_id) "for-#{page_id.to_s.tr('/', '-').parameterize}-page" end |
.ransackable_scopes(_auth_object = nil) ⇒ Object
364 365 366 367 368 |
# File 'app/models/digital_asset.rb', line 364 def self.ransackable_scopes(_auth_object = nil) %i[keyword_search by_product_line_id by_product_line_path by_product_category_id_direct tag_presence by_party_ids by_item_ids related_to_item_id show_hidden_tags exclude_tags tags_include not_by_product_line_id not_by_party_ids] end |
.related_to_item_id ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are related to item id. Active Record Scope
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 267 268 269 270 271 272 273 274 |
# File 'app/models/digital_asset.rb', line 234 scope :related_to_item_id, ->(item_id) { return none if item_id.blank? item = Item.find_by(id: item_id) return none unless item # Get product line path for ltree ancestor matching pl_path = item.primary_pl_path_slugs pc_path = item.pc_path_slugs # Build OR conditions for: # 1. Directly linked to the item # 2. Linked to item's product line or any ancestor (using ltree) # 3. Linked to item's product category or any ancestor (using ltree) conditions = [] binds = {} # Direct item link conditions << 'EXISTS(SELECT 1 FROM digital_assets_items dai WHERE dai.item_id = :item_id AND dai.digital_asset_id = digital_assets.id)' binds[:item_id] = item_id # Product line link (item's PL path is descendant of or equal to the image's PL path) if pl_path.present? conditions << "EXISTS(SELECT 1 FROM digital_asset_product_lines dapl INNER JOIN product_lines pl ON pl.id = dapl.product_line_id WHERE dapl.digital_asset_id = digital_assets.id AND :pl_path <@ pl.ltree_path_slugs)" binds[:pl_path] = pl_path end # Product category link (item's PC path is descendant of or equal to the image's PC path) if pc_path.present? conditions << "EXISTS(SELECT 1 FROM digital_assets_product_categories dapc INNER JOIN product_categories pc ON pc.id = dapc.product_category_id WHERE dapc.digital_asset_id = digital_assets.id AND :pc_path <@ pc.ltree_path_slugs)" binds[:pc_path] = pc_path end where(conditions.join(' OR '), binds) } |
.show_hidden_tags ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are show hidden tags. Active Record Scope
278 |
# File 'app/models/digital_asset.rb', line 278 scope :show_hidden_tags, ->(val) { val.to_b ? all : not_tagged_with(HIDDEN_TAGS) } |
.tag_presence ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are tag presence. Active Record Scope
287 288 289 290 291 |
# File 'app/models/digital_asset.rb', line 287 scope :tag_presence, ->(tag_present) { return if tag_present.blank? tag_present.to_b ? joins(:taggings).distinct : where.missing(:taggings) } |
.tagged_with_all ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are tagged with all. Active Record Scope
277 |
# File 'app/models/digital_asset.rb', line 277 scope :tagged_with_all, ->(*tag_names) { (*tag_names) } |
.valid ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are valid. Active Record Scope
164 |
# File 'app/models/digital_asset.rb', line 164 scope :valid, -> { where(invalid_at: nil) } |
.videos ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are videos. Active Record Scope
310 |
# File 'app/models/digital_asset.rb', line 310 scope :videos, -> { where(type: 'Video') } |
.with_product_line_urls ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are with product line urls. Active Record Scope
311 312 313 314 315 316 317 318 319 320 321 |
# File 'app/models/digital_asset.rb', line 311 scope :with_product_line_urls, -> { all.select_append(' ARRAY( select pl.slug_ltree::text from product_lines pl inner join digital_asset_product_lines dapl on dapl.product_line_id = pl.id where dapl.digital_asset_id = digital_assets.id ) as product_line_urls ') } |
.without_product_categories ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are without product categories. Active Record Scope
222 |
# File 'app/models/digital_asset.rb', line 222 scope :without_product_categories, -> { where.missing(:product_categories) } |
.without_product_lines ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are without product lines. Active Record Scope
223 |
# File 'app/models/digital_asset.rb', line 223 scope :without_product_lines, -> { where.missing(:digital_asset_product_lines) } |
Instance Method Details
#alerts ⇒ Object
376 377 378 379 380 |
# File 'app/models/digital_asset.rb', line 376 def alerts r = [] r << 'SEO Title is too long, keep it at 65 characters or less' if &.size&.> 65 r end |
#all_my_items ⇒ Object
527 528 529 530 531 532 533 534 535 536 537 538 539 540 |
# File 'app/models/digital_asset.rb', line 527 def all_my_items item_ids = items.active.ids plids = product_line_ids pcids = product_category_ids if plids.present? && pcids.present? = Item.active.condition_new = .by_product_line_id(plids) if plids.present? = .by_product_category_id(pcids) if pcids.present? item_ids += .ids end item_ids.uniq! Item.where(id: item_ids).order(:sku) end |
#asset_identifier ⇒ Object
433 434 435 |
# File 'app/models/digital_asset.rb', line 433 def asset_identifier SecureRandom.base58(6).downcase end |
#cross_links_opportunities_to_parties ⇒ Object
484 485 486 487 |
# File 'app/models/digital_asset.rb', line 484 def cross_links_opportunities_to_parties self.party_ids = party_ids | opportunities.pluck(:customer_id) true end |
#digital_asset_product_lines ⇒ ActiveRecord::Relation<DigitalAssetProductLine>
132 |
# File 'app/models/digital_asset.rb', line 132 has_many :digital_asset_product_lines, -> { order(:position) }, inverse_of: :digital_asset, validate: false |
#dimensions ⇒ Object
453 454 455 456 457 |
# File 'app/models/digital_asset.rb', line 453 def dimensions return unless .present? && .present? "#{} x #{}" end |
#externally_referenced? ⇒ Boolean
True when some record we do NOT own points at this asset, so a purge must
retain it (vs. owned dependents like embeddings/profiles, which cascade).
Base assets expose no external references; subclasses (Image) override.
404 405 406 |
# File 'app/models/digital_asset.rb', line 404 def externally_referenced? false end |
#file_basename ⇒ Object
408 409 410 411 412 |
# File 'app/models/digital_asset.rb', line 408 def file_basename return if .blank? File.basename(, '.*') end |
#generated_images ⇒ ActiveRecord::Relation<GeneratedImage>
135 |
# File 'app/models/digital_asset.rb', line 135 has_many :generated_images, foreign_key: :source_image_id, dependent: :destroy, inverse_of: :source_image |
#invalidate!(reason: nil) ⇒ Boolean
Quarantine this asset: stamp the moment its file was detected permanently
broken so it drops out of active fetches/embedding and starts the 30-day
recovery clock before InvalidDigitalAssetPurgeWorker hard-deletes it.
Idempotent — never moves the clock once set (a re-detection shouldn't extend
the retention window). Returns true if it newly invalidated.
390 391 392 393 394 395 396 397 398 |
# File 'app/models/digital_asset.rb', line 390 def invalidate!(reason: nil) # Atomic conditional update — only the first writer flips invalid_at, so # concurrent callers can't overwrite it and extend the 30-day purge clock. # (update_all bypasses the in-memory attribute; reload if you need it.) return false if self.class.where(id: id, invalid_at: nil).update_all(invalid_at: Time.current).zero? Rails.logger.warn("[DigitalAsset] Invalidated ##{id} (#{self.class.name})#{reason && ": #{reason}"}") true end |
#is_image? ⇒ Boolean
478 479 480 481 482 |
# File 'app/models/digital_asset.rb', line 478 def is_image? return true if type == 'Image' false end |
#is_video? ⇒ Boolean
472 473 474 475 476 |
# File 'app/models/digital_asset.rb', line 472 def is_video? return true if type == 'Video' false end |
#items ⇒ ActiveRecord::Relation<Item>
138 |
# File 'app/models/digital_asset.rb', line 138 has_and_belongs_to_many :items, validate: false |
#opportunities ⇒ ActiveRecord::Relation<Opportunity>
142 |
# File 'app/models/digital_asset.rb', line 142 has_and_belongs_to_many :opportunities, validate: false, inverse_of: :digital_assets |
#parties ⇒ ActiveRecord::Relation<Party>
139 |
# File 'app/models/digital_asset.rb', line 139 has_and_belongs_to_many :parties, validate: false |
#product_categories ⇒ ActiveRecord::Relation<ProductCategory>
137 |
# File 'app/models/digital_asset.rb', line 137 has_and_belongs_to_many :product_categories, validate: false |
#product_lines ⇒ ActiveRecord::Relation<ProductLine>
133 |
# File 'app/models/digital_asset.rb', line 133 has_many :product_lines, through: :digital_asset_product_lines, validate: false |
#product_lines_display ⇒ Object
449 450 451 |
# File 'app/models/digital_asset.rb', line 449 def product_lines_display product_lines.map(&:name).join(', ') end |
#product_lines_for_sorting ⇒ Object
441 442 443 |
# File 'app/models/digital_asset.rb', line 441 def product_lines_for_sorting product_lines.map(&:self_and_ancestors).flatten.map(&:slug_ltree).map { |slt| "product-line-#{slt}" }.join(' ') end |
#purge_edge_cache ⇒ Object
489 490 491 492 |
# File 'app/models/digital_asset.rb', line 489 def purge_edge_cache urls = site_maps.map(&:url) EdgeCacheWorker.perform_async('urls' => urls) if urls.present? end |
#reviews ⇒ ActiveRecord::Relation<Review>
Legacy associations removed: showcases, showcase_rooms
141 |
# File 'app/models/digital_asset.rb', line 141 has_and_belongs_to_many :reviews, validate: false |
#sanitize_urls ⇒ Object
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 |
# File 'app/models/digital_asset.rb', line 494 def sanitize_urls sd = Seo::DeparameterizeLinks.new ld = Seo::HtmlLinkSanitizer.new imgf = Seo::ImageMissingSizeFiller.new if .present? logger.info "Sanitizing urls in expanded_description for digital asset:#{id}" h = h = ld.process(h).html_out h = sd.process(h).html_out h = imgf.process(h).html_out # Nokogiri URL-encodes spaces in href attributes, corrupting {{ locale }} to {{%20locale%20}}. h = h.gsub(/\{\{%20([\w_]+)%20\}\}/) { "{{#{Regexp.last_match(1)}}}" } self. = h end return if transcript.blank? logger.info "Sanitizing urls in transcript for digital asset:#{id}" h = transcript h = ld.process(h).html_out h = sd.process(h).html_out h = imgf.process(h).html_out # Nokogiri URL-encodes spaces in href attributes, corrupting {{ locale }} to {{%20locale%20}}. h = h.gsub(/\{\{%20([\w_]+)%20\}\}/) { "{{#{Regexp.last_match(1)}}}" } self.transcript = h end |
#seo_title ⇒ Object
414 415 416 417 |
# File 'app/models/digital_asset.rb', line 414 def seo_title s = .presence || title.presence || file_basename s&.first(133) end |
#should_generate_new_friendly_id? ⇒ Boolean
437 438 439 |
# File 'app/models/digital_asset.rb', line 437 def should_generate_new_friendly_id? || title_changed? || || url_changed? || force_new_slug.to_b end |
#should_sanitize_urls? ⇒ Boolean
522 523 524 525 |
# File 'app/models/digital_asset.rb', line 522 def should_sanitize_urls? ( && .present?) || (transcript_changed? && transcript.present?) end |
#site_maps ⇒ ActiveRecord::Relation<SiteMap>
134 |
# File 'app/models/digital_asset.rb', line 134 has_many :site_maps, as: :resource, dependent: :destroy |
#slug_candidates ⇒ Object
419 420 421 422 423 424 425 426 427 |
# File 'app/models/digital_asset.rb', line 419 def slug_candidates sc = [] if url.present? sc << [:url] sc << %i[url asset_identifier] end sc << %i[seo_title asset_identifier] sc end |
#tags_display ⇒ Object
445 446 447 |
# File 'app/models/digital_asset.rb', line 445 def .join(', ') end |
#thumbnail_url ⇒ Object
429 430 431 |
# File 'app/models/digital_asset.rb', line 429 def thumbnail_url # Placeholder, implement in specialized class end |
#touch_related ⇒ Object
459 460 461 462 463 464 465 466 467 468 469 470 |
# File 'app/models/digital_asset.rb', line 459 def return unless @refresh_cache.to_b if product_line_ids.present? paths = ProductLine.where(id: product_line_ids).pluck(:ltree_path_ids).compact unless paths.empty? ProductLine.where(ProductLine[:ltree_path_ids].ltree_descendant(paths)) .find_each(&:touch) end end items.find_each(&:touch) end |