Class: DigitalAsset
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- DigitalAsset
- Extended by:
- FriendlyId
- Includes:
- Models::Auditable, Models::EdgeCachePurgeable, 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 Models::Schedulable
Models::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
(The all_tags method is provided by the 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.
-
.by_product_line_path_excluding ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product line path excluding.
-
.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
- #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::EdgeCachePurgeable
#edge_cache_urls, #enqueue_edge_cache_purge, #purge_edge_cache
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 Models::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.
126 127 128 |
# File 'app/models/digital_asset.rb', line 126 def force_new_slug @force_new_slug end |
#refresh_cache ⇒ Object
Returns the value of attribute refresh_cache.
126 127 128 |
# File 'app/models/digital_asset.rb', line 126 def refresh_cache @refresh_cache end |
#title ⇒ Object (readonly)
145 |
# File 'app/models/digital_asset.rb', line 145 validates :title, presence: true |
#url ⇒ Object (readonly)
146 |
# File 'app/models/digital_asset.rb', line 146 validates :url, uniqueness: { if: :url } |
Class Method Details
.active ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are active. Active Record Scope
163 |
# File 'app/models/digital_asset.rb', line 163 scope :active, -> { where(inactive: false, invalid_at: nil) } |
.all_locales ⇒ Object
(The all_tags method is provided by the Models::Taggable concern.)
400 401 402 |
# File 'app/models/digital_asset.rb', line 400 def self.all_locales pluck(Arel.sql('distinct unnest(locales) as locale')).compact.sort end |
.available_banner_tags ⇒ Object
592 593 594 595 596 |
# File 'app/models/digital_asset.rb', line 592 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
604 605 606 607 608 |
# File 'app/models/digital_asset.rb', line 604 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'
568 569 570 571 572 |
# File 'app/models/digital_asset.rb', line 568 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
574 575 576 577 578 579 |
# File 'app/models/digital_asset.rb', line 574 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"
588 589 590 |
# File 'app/models/digital_asset.rb', line 588 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
225 |
# File 'app/models/digital_asset.rb', line 225 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
226 227 228 229 230 231 |
# File 'app/models/digital_asset.rb', line 226 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
276 |
# File 'app/models/digital_asset.rb', line 276 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
295 296 297 298 299 300 |
# File 'app/models/digital_asset.rb', line 295 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
206 207 208 209 210 211 |
# File 'app/models/digital_asset.rb', line 206 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
212 213 214 215 216 217 218 219 220 221 |
# File 'app/models/digital_asset.rb', line 212 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
182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'app/models/digital_asset.rb', line 182 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
176 177 178 179 180 181 |
# File 'app/models/digital_asset.rb', line 176 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
325 326 327 328 329 330 331 332 333 334 |
# File 'app/models/digital_asset.rb', line 325 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 ) } |
.by_product_line_path_excluding ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are by product line path excluding. Active Record Scope
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'app/models/digital_asset.rb', line 360 scope :by_product_line_path_excluding, ->(slug_path, excluded_descendant_paths = []) { return all if slug_path.blank? excluded = Array(excluded_descendant_paths).compact_blank return by_product_line_path(slug_path) if excluded.empty? exclusion_sql = excluded.map { 'pl.ltree_path_slugs <@ ?' }.join(' OR ') 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 <@ ? AND NOT (#{exclusion_sql}))", slug_path, *excluded ) } |
.categorized ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are categorized. Active Record Scope
309 |
# File 'app/models/digital_asset.rb', line 309 scope :categorized, -> { where.not(category: nil) } |
.exclude_tags ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are exclude tags. Active Record Scope
281 282 283 284 285 286 |
# File 'app/models/digital_asset.rb', line 281 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
310 |
# File 'app/models/digital_asset.rb', line 310 scope :images, -> { where(type: 'Image') } |
.invalidated ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are invalidated. Active Record Scope
168 |
# File 'app/models/digital_asset.rb', line 168 scope :invalidated, -> { where.not(invalid_at: nil) } |
.localized_for ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are localized for. Active Record Scope
293 |
# File 'app/models/digital_asset.rb', line 293 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
294 |
# File 'app/models/digital_asset.rb', line 294 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
303 304 305 306 307 308 |
# File 'app/models/digital_asset.rb', line 303 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
194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'app/models/digital_asset.rb', line 194 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
338 339 340 341 342 343 344 345 346 347 |
# File 'app/models/digital_asset.rb', line 338 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"
600 601 602 |
# File 'app/models/digital_asset.rb', line 600 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").
582 583 584 |
# File 'app/models/digital_asset.rb', line 582 def self.page_tag_for(page_id) "for-#{page_id.to_s.tr('/', '-').parameterize}-page" end |
.ransackable_scopes(_auth_object = nil) ⇒ Object
392 393 394 395 396 |
# File 'app/models/digital_asset.rb', line 392 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
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 275 |
# File 'app/models/digital_asset.rb', line 235 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
279 |
# File 'app/models/digital_asset.rb', line 279 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
288 289 290 291 292 |
# File 'app/models/digital_asset.rb', line 288 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
278 |
# File 'app/models/digital_asset.rb', line 278 scope :tagged_with_all, ->(*tag_names) { (*tag_names) } |
.valid ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are valid. Active Record Scope
165 |
# File 'app/models/digital_asset.rb', line 165 scope :valid, -> { where(invalid_at: nil) } |
.videos ⇒ ActiveRecord::Relation<DigitalAsset>
A relation of DigitalAssets that are videos. Active Record Scope
311 |
# File 'app/models/digital_asset.rb', line 311 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
312 313 314 315 316 317 318 319 320 321 322 |
# File 'app/models/digital_asset.rb', line 312 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
223 |
# File 'app/models/digital_asset.rb', line 223 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
224 |
# File 'app/models/digital_asset.rb', line 224 scope :without_product_lines, -> { where.missing(:digital_asset_product_lines) } |
Instance Method Details
#alerts ⇒ Object
404 405 406 407 408 |
# File 'app/models/digital_asset.rb', line 404 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
550 551 552 553 554 555 556 557 558 559 560 561 562 563 |
# File 'app/models/digital_asset.rb', line 550 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
461 462 463 |
# File 'app/models/digital_asset.rb', line 461 def asset_identifier SecureRandom.base58(6).downcase end |
#cross_links_opportunities_to_parties ⇒ Object
512 513 514 515 |
# File 'app/models/digital_asset.rb', line 512 def cross_links_opportunities_to_parties self.party_ids = party_ids | opportunities.pluck(:customer_id) true end |
#digital_asset_product_lines ⇒ ActiveRecord::Relation<DigitalAssetProductLine>
133 |
# File 'app/models/digital_asset.rb', line 133 has_many :digital_asset_product_lines, -> { order(:position) }, inverse_of: :digital_asset, validate: false |
#dimensions ⇒ Object
481 482 483 484 485 |
# File 'app/models/digital_asset.rb', line 481 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.
432 433 434 |
# File 'app/models/digital_asset.rb', line 432 def externally_referenced? false end |
#file_basename ⇒ Object
436 437 438 439 440 |
# File 'app/models/digital_asset.rb', line 436 def file_basename return if .blank? File.basename(, '.*') end |
#generated_images ⇒ ActiveRecord::Relation<GeneratedImage>
136 |
# File 'app/models/digital_asset.rb', line 136 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.
418 419 420 421 422 423 424 425 426 |
# File 'app/models/digital_asset.rb', line 418 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
506 507 508 509 510 |
# File 'app/models/digital_asset.rb', line 506 def is_image? return true if type == 'Image' false end |
#is_video? ⇒ Boolean
500 501 502 503 504 |
# File 'app/models/digital_asset.rb', line 500 def is_video? return true if type == 'Video' false end |
#items ⇒ ActiveRecord::Relation<Item>
139 |
# File 'app/models/digital_asset.rb', line 139 has_and_belongs_to_many :items, validate: false |
#opportunities ⇒ ActiveRecord::Relation<Opportunity>
143 |
# File 'app/models/digital_asset.rb', line 143 has_and_belongs_to_many :opportunities, validate: false, inverse_of: :digital_assets |
#parties ⇒ ActiveRecord::Relation<Party>
140 |
# File 'app/models/digital_asset.rb', line 140 has_and_belongs_to_many :parties, validate: false |
#product_categories ⇒ ActiveRecord::Relation<ProductCategory>
138 |
# File 'app/models/digital_asset.rb', line 138 has_and_belongs_to_many :product_categories, validate: false |
#product_lines ⇒ ActiveRecord::Relation<ProductLine>
134 |
# File 'app/models/digital_asset.rb', line 134 has_many :product_lines, through: :digital_asset_product_lines, validate: false |
#product_lines_display ⇒ Object
477 478 479 |
# File 'app/models/digital_asset.rb', line 477 def product_lines_display product_lines.map(&:name).join(', ') end |
#product_lines_for_sorting ⇒ Object
469 470 471 |
# File 'app/models/digital_asset.rb', line 469 def product_lines_for_sorting product_lines.map(&:self_and_ancestors).flatten.map(&:slug_ltree).map { |slt| "product-line-#{slt}" }.join(' ') end |
#reviews ⇒ ActiveRecord::Relation<Review>
Legacy associations removed: showcases, showcase_rooms
142 |
# File 'app/models/digital_asset.rb', line 142 has_and_belongs_to_many :reviews, validate: false |
#sanitize_urls ⇒ Object
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 |
# File 'app/models/digital_asset.rb', line 517 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
442 443 444 445 |
# File 'app/models/digital_asset.rb', line 442 def seo_title s = .presence || title.presence || file_basename s&.first(133) end |
#should_generate_new_friendly_id? ⇒ Boolean
465 466 467 |
# File 'app/models/digital_asset.rb', line 465 def should_generate_new_friendly_id? || title_changed? || || url_changed? || force_new_slug.to_b end |
#should_sanitize_urls? ⇒ Boolean
545 546 547 548 |
# File 'app/models/digital_asset.rb', line 545 def should_sanitize_urls? ( && .present?) || (transcript_changed? && transcript.present?) end |
#site_maps ⇒ ActiveRecord::Relation<SiteMap>
135 |
# File 'app/models/digital_asset.rb', line 135 has_many :site_maps, as: :resource, dependent: :destroy |
#slug_candidates ⇒ Object
447 448 449 450 451 452 453 454 455 |
# File 'app/models/digital_asset.rb', line 447 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
473 474 475 |
# File 'app/models/digital_asset.rb', line 473 def .join(', ') end |
#thumbnail_url ⇒ Object
457 458 459 |
# File 'app/models/digital_asset.rb', line 457 def thumbnail_url # Placeholder, implement in specialized class end |
#touch_related ⇒ Object
487 488 489 490 491 492 493 494 495 496 497 498 |
# File 'app/models/digital_asset.rb', line 487 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 |