Module: Models::Publication
- Extended by:
- ActiveSupport::Concern
- Includes:
- Memery, HybridSearchable
- Included in:
- Item
- Defined in:
- app/concerns/models/publication.rb
Overview
ActiveSupport::Concern mixin: publication.
Defined Under Namespace
Modules: ClassMethods
Constant Summary collapse
- CACHE_RELEVANT_ATTRIBUTES =
Attributes whose change alters what a product's edge-cached pages render for
this publication — its visibility, link text/slug, or which products surface
it. PDF/file replacement is covered separately via #publication_pdf_changed?. %w[is_discontinued publication_base_name name sku content_url primary_product_line_id].freeze
- PUBLIC_LOCALES =
Locales the public www serves a publication under (matches the www-edge
worker + admin bar locale lists and route_translator's available_locales). %w[en-US en-CA].freeze
Instance Attribute Summary collapse
-
#available_in_canada ⇒ Object
memoize :available_in_usa.
- #available_in_usa ⇒ Object
-
#serve_in_locale ⇒ Object
Returns the value of attribute serve_in_locale.
Has many collapse
-
#item_embeddings ⇒ ActiveRecord::Relation<ContentEmbedding::ItemEmbedding>
Association to the partitioned embeddings table for publication AI search.
Class Method Summary collapse
-
.ai_search_publications ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are ai search publications.
-
.ai_search_warning? ⇒ Boolean
Check if the last AI search had issues (call from controller after running scope).
-
.clear_ai_search_warning! ⇒ Object
Clear the AI search warning.
-
.cover_ratio_mismatch ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are cover ratio mismatch.
-
.embeddable_content_types ⇒ Object
Content types for publication embeddings.
-
.hybrid_search_publications ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are hybrid search publications.
-
.publications ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications.
-
.publications_for_online_portal ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications for online portal.
-
.publications_for_public ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications for public.
-
.publications_for_public_in_store ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications for public in store.
-
.publications_for_sales_portal ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications for sales portal.
-
.publications_for_support_portal ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications for support portal.
-
.with_publication_attached ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are with publication attached.
Instance Method Summary collapse
-
#alias_sku ⇒ Object
Store the new sku in our alias chain.
-
#analyze_pdf_images!(force: false) ⇒ Object
Queue vision analysis for this publication's PDF images.
- #available_service_locales ⇒ Object
- #available_to_all_locales? ⇒ Boolean
- #check_redirection_path ⇒ Object
- #compose_name_with_languages ⇒ Object
-
#content_for_embedding(_content_type = :primary) ⇒ Object
Generate content for semantic search embedding Uses extracted PDF text plus metadata.
-
#cover_image_url(options = {}) ⇒ Object
Pulls the primary image or generates one from the PDF if needed.
-
#create_primary_image_from_pdf ⇒ Object
Takes the PDF and generates a cover image which will be saved to the image library.
-
#default_publication_logistics ⇒ Object
Set sensible defaults for logistics, in case this publication has to ship.
-
#embeddable_locales ⇒ Object
For publications in multiple languages, return all locales.
-
#embedding_content_changed? ⇒ Boolean
Publications with PDF changes should regenerate embeddings.
-
#fast_country_discontinue ⇒ Object
Shortcut method when we deal with publications.
- #file_name_for_download(file_extension = nil) ⇒ Object
- #friendly_locale_names ⇒ Object
-
#generate_publication_name ⇒ Object
Embeds languages in name.
- #has_literature_changed? ⇒ Boolean
-
#locale_for_embedding ⇒ Object
Locale for embedding - uses publication_locales Publications can be in multiple languages, returns the first/primary.
-
#needs_vision_analysis? ⇒ Boolean
Check if vision analysis is needed for this publication.
- #perform_country_discontinue(store_id, discontinue) ⇒ Object
- #product_category_must_be_publication ⇒ Object
-
#publication_available_locales ⇒ Object
memoize :available_in_canada.
- #publication_base_name_clean_of_language ⇒ Object
-
#publication_cache_relevant_change? ⇒ Boolean
True when a publication change should refresh the edge cache of the products that surface it: a revision swap flips is_discontinued (and creates a new active record), a rename changes the PDF link slug, a replaced PDF changes the document.
-
#publication_edge_cache_urls(previous_product_line_id: nil) ⇒ Object
Every edge-cached www URL where this publication's PDF is surfaced, so one purge refreshes them all after a revision/discontinue/rename/PDF replacement: * product PDPs that surface it — the directly-attached items plus the products in its primary product line (both are how Item::PublicationRetriever finds it) — including their lazy /section/ document fragments, across both locales (Item#edge_cache_urls); * the product line landing page(s), which render documents INLINE (no lazy /section/ endpoint — see Www::ProductLinePresenter), across both locales; * the publication's own //publications/.pdf file URL.
- #publication_pdf_changed? ⇒ Boolean
-
#publication_sku_check ⇒ Object
Check that our publication is formatted with the version.
- #publication_url ⇒ Object
- #publication_visible_to_public? ⇒ Boolean
- #publish_pdf_changed_event ⇒ Object
- #publish_publication_updated_event ⇒ Object
- #retrieve_publications_search_text ⇒ Object
- #secondary_product_category_must_not_be_publication ⇒ Object
- #set_search_text ⇒ Object
-
#should_queue_embedding? ⇒ Boolean
Only embed active, public publications.
Methods included from HybridSearchable
Instance Attribute Details
#available_in_canada ⇒ Object
memoize :available_in_usa
185 186 187 |
# File 'app/concerns/models/publication.rb', line 185 def available_in_canada available_service_locales.intersect?(%i[en-CA fr-CA]) end |
#available_in_usa ⇒ Object
180 181 182 |
# File 'app/concerns/models/publication.rb', line 180 def available_in_usa available_service_locales.include?(:'en-US') end |
#serve_in_locale ⇒ Object
Returns the value of attribute serve_in_locale.
13 14 15 |
# File 'app/concerns/models/publication.rb', line 13 def serve_in_locale @serve_in_locale end |
Class Method Details
.ai_search_publications ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are ai search publications. Active Record Scope
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'app/concerns/models/publication.rb', line 63 scope :ai_search_publications, ->(query, limit: 200, max_distance: nil) { # Clear any previous warning self.last_ai_search_warning = nil return none if query.blank? # Generate query embedding via Gemini (matches stored unified vectors). = ContentEmbedding.(query) unless self.last_ai_search_warning = 'Could not generate AI embedding for your query. Please try a different search term.' return none end # Format vector for SQL with explicit dimension cast dimensions = .size vector_literal = "[#{.join(',')}]" # Minimum distance per item across all of its Gemini vectors (primary + chunks). min_distance_sql = sanitize_sql_array([ <<~SQL.squish, SELECT embeddable_id, MIN(unified_embedding::vector(#{dimensions}) <=> ?::vector(#{dimensions})) AS min_distance FROM content_embeddings_items WHERE embeddable_type = 'Item' AND embedding_model IN (?) AND unified_embedding IS NOT NULL GROUP BY embeddable_id SQL vector_literal, ContentEmbedding::UNIFIED_MODELS ]) # Use where(id: subquery) pattern which works better with count matching_ids_sql = "SELECT embeddable_id FROM (#{min_distance_sql}) AS distances" matching_ids_sql += sanitize_sql_array([' WHERE min_distance < ?', max_distance]) if max_distance.present? # Get matching item IDs and order by distance base_query = where("#{table_name}.id IN (#{matching_ids_sql})") .joins("INNER JOIN (#{min_distance_sql}) AS best_match ON best_match.embeddable_id = #{table_name}.id") .select("#{table_name}.*", 'best_match.min_distance AS neighbor_distance') .order('best_match.min_distance ASC') base_query.limit(limit) } |
.ai_search_warning? ⇒ Boolean
Check if the last AI search had issues (call from controller after running scope)
54 55 56 |
# File 'app/concerns/models/publication.rb', line 54 def self.ai_search_warning? last_ai_search_warning.present? end |
.clear_ai_search_warning! ⇒ Object
Clear the AI search warning
59 60 61 |
# File 'app/concerns/models/publication.rb', line 59 def self.clear_ai_search_warning! self.last_ai_search_warning = nil end |
.cover_ratio_mismatch ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are cover ratio mismatch. Active Record Scope
127 128 129 130 131 |
# File 'app/concerns/models/publication.rb', line 127 scope :cover_ratio_mismatch, ->(flag = true) { return all unless ActiveModel::Type::Boolean.new.cast(flag) where(primary_image_id: Image.not_letter_ratio.select(:id)) } |
.embeddable_content_types ⇒ Object
Content types for publication embeddings
507 508 509 |
# File 'app/concerns/models/publication.rb', line 507 def self. [:primary] end |
.hybrid_search_publications ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are hybrid search publications. Active Record Scope
109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'app/concerns/models/publication.rb', line 109 scope :hybrid_search_publications, ->(query, limit: 200) { return none if query.blank? ai_ids = ai_search_publications(query, limit: limit).ids keyword_ids = begin keywords_search(query).limit(limit).ids rescue PgSearch::EmptyQueryError [] end rrf_ranked_relation(ai_ids, keyword_ids, limit: limit) } |
.publications ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications. Active Record Scope
21 |
# File 'app/concerns/models/publication.rb', line 21 scope :publications, -> { where(arel_table[:pc_path_slugs].ltree_descendant(LtreePaths::PC_PUBLICATIONS)) } |
.publications_for_online_portal ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications for online portal. Active Record Scope
28 |
# File 'app/concerns/models/publication.rb', line 28 scope :publications_for_online_portal, -> { publications.active.where(product_category_id: ProductCategory.where.any_of({ show_in_sales_portal: true }, { show_in_support_portal: true }).select(:id)) } |
.publications_for_public ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications for public. Active Record Scope
24 |
# File 'app/concerns/models/publication.rb', line 24 scope :publications_for_public, -> { publications_for_public_in_store(Store::PUBLIC_STORE_IDS) } |
.publications_for_public_in_store ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications for public in store. Active Record Scope
23 |
# File 'app/concerns/models/publication.rb', line 23 scope :publications_for_public_in_store, ->(*store_ids) { publications.with_publication_attached.active.in_store(store_ids) } |
.publications_for_sales_portal ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications for sales portal. Active Record Scope
27 |
# File 'app/concerns/models/publication.rb', line 27 scope :publications_for_sales_portal, -> { publications.active.where(product_category_id: ProductCategory.for_sales_portal.select(:id)) } |
.publications_for_support_portal ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are publications for support portal. Active Record Scope
26 |
# File 'app/concerns/models/publication.rb', line 26 scope :publications_for_support_portal, -> { publications.active.where(product_category_id: ProductCategory.for_support_portal.select(:id)) } |
.with_publication_attached ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are with publication attached. Active Record Scope
22 |
# File 'app/concerns/models/publication.rb', line 22 scope :with_publication_attached, -> { joins(:literature).includes(:literature) } |
Instance Method Details
#alias_sku ⇒ Object
Store the new sku in our alias chain
354 355 356 357 358 359 360 361 |
# File 'app/concerns/models/publication.rb', line 354 def alias_sku return unless is_publication? self.sku_aliases ||= [] sku_aliases.delete(sku) sku_aliases << sku_was if sku_changed? self.sku_aliases = sku_aliases.filter_map(&:presence).uniq end |
#analyze_pdf_images!(force: false) ⇒ Object
Queue vision analysis for this publication's PDF images
576 577 578 579 580 |
# File 'app/concerns/models/publication.rb', line 576 def analyze_pdf_images!(force: false) return unless is_publication? PublicationVisionWorker.perform_async(id, force: force) end |
#available_service_locales ⇒ Object
172 173 174 |
# File 'app/concerns/models/publication.rb', line 172 def available_service_locales store_items.active.eager_load(store: :country).map { |si| si.store.locales_served }.flatten.uniq & LocaleUtility.service_locales end |
#available_to_all_locales? ⇒ Boolean
176 177 178 |
# File 'app/concerns/models/publication.rb', line 176 def available_to_all_locales? available_service_locales.size == LocaleUtility.service_locales.size end |
#check_redirection_path ⇒ Object
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'app/concerns/models/publication.rb', line 273 def check_redirection_path return if redirection_path.blank? # Check that our path is a valid uri begin uri = Addressable::URI.parse(redirection_path) # canonicalize our url uri.path = uri.path.gsub(%r{^/(en|fr)-(US|CA)}, '') uri.host = WEB_HOSTNAME_WITHOUT_PORT uri.port = APP_PORT_NUMBER unless APP_PORT_NUMBER == 80 uri.scheme = 'https' self.redirection_path = uri.to_s rescue StandardError => e errors.add(:redirection_path, "redirection path could not be parsed, #{e}") end end |
#compose_name_with_languages ⇒ Object
198 199 200 201 202 203 204 205 206 |
# File 'app/concerns/models/publication.rb', line 198 def compose_name_with_languages return if publication_base_name.blank? n = publication_base_name.dup if (fln = friendly_locale_names).present? n << " (#{fln.join(', ')})" end n end |
#content_for_embedding(_content_type = :primary) ⇒ Object
Generate content for semantic search embedding
Uses extracted PDF text plus metadata
513 514 515 516 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 544 545 546 547 548 549 550 551 552 553 |
# File 'app/concerns/models/publication.rb', line 513 def (_content_type = :primary) return nil unless is_publication? parts = [] # Title and basic info parts << "Publication: #{publication_base_name}" if publication_base_name.present? parts << "SKU: #{sku}" if sku.present? # Product line context (what product is this documentation for?) parts << "Product Line: #{primary_product_line.}" if primary_product_line.present? if product_lines.any? other_pls = product_lines.reject { |pl| pl == primary_product_line } parts << "Related Products: #{other_pls.map(&:name).join(', ')}" if other_pls.any? end # Category context (installation manual, datasheet, etc.) parts << "Type: #{product_category.name}" if product_category.present? # Languages parts << "Languages: #{friendly_locale_names.join(', ')}" if friendly_locale_names.any? # Curator-supplied search keywords (boost discoverability for known query terms) parts << "Keywords: #{search_keywords}" if search_keywords.present? # Claude's full-PDF analysis is the primary content source when available. # It covers text, diagrams, tables, and illustrations in a clean structured form, # making raw text extraction redundant (which tends to be garbled/concatenated). # Fall back to search_text only when no Claude analysis has been run yet. if pdf_image_descriptions.present? parts << "Content:\n#{pdf_image_descriptions}" elsif search_text.present? parts << "Content:\n#{search_text}" elsif literature.present? extracted = retrieve_publications_search_text parts << "Content:\n#{extracted}" if extracted.present? end parts.compact.join("\n\n") end |
#cover_image_url(options = {}) ⇒ Object
Pulls the primary image or generates one from the PDF if needed
374 375 376 377 378 379 380 381 382 383 |
# File 'app/concerns/models/publication.rb', line 374 def cover_image_url( = {}) = .dup.symbolize_keys image = primary_image image ||= create_primary_image_from_pdf if .delete(:create_if_missing) return unless image [:format] ||= 'jpeg' [:width] ||= 1200 if [:size].blank? image.image_url() end |
#create_primary_image_from_pdf ⇒ Object
Takes the PDF and generates a cover image which will be saved to the image library
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 |
# File 'app/concerns/models/publication.rb', line 386 def create_primary_image_from_pdf # Extract literature to file pdf_path = begin literature..path rescue StandardError nil end if pdf_path.nil? || !File.exist?(pdf_path) logger.error "Could not pull pdf for #{sku} to generate thumbnail" return end logger.info "Generating thumbnail for #{sku} from PDF path #{pdf_path} with name #{name}" if (image = Pdf::Utility::ImageCreator.new.process(pdf_path:, name:)) # destroy prior image if it exists primary_image&.destroy # link new one update_column(:primary_image_id, image.id) end image end |
#default_publication_logistics ⇒ Object
Set sensible defaults for logistics, in case this publication has to ship
364 365 366 367 368 369 370 371 |
# File 'app/concerns/models/publication.rb', line 364 def default_publication_logistics # we use a 0.05 default for weight, assuming a 5 piece regular stock paper 2000# # We also sort because base_weight should always be smaller than shipping weight self.base_weight, self.shipping_weight = [base_weight || 0.05, shipping_weight || base_weight || 0.05].sort self.shipping_width ||= 9 self.shipping_length ||= 12 self.shipping_height ||= 0.1 end |
#embeddable_locales ⇒ Object
For publications in multiple languages, return all locales
598 599 600 601 602 603 |
# File 'app/concerns/models/publication.rb', line 598 def return ['en'] unless is_publication? locales = publication_locales.presence || ['en'] locales.map(&:to_s).uniq end |
#embedding_content_changed? ⇒ Boolean
Publications with PDF changes should regenerate embeddings
556 557 558 559 560 561 562 563 564 565 |
# File 'app/concerns/models/publication.rb', line 556 def return false unless is_publication? saved_change_to_search_text? || saved_change_to_search_keywords? || saved_change_to_publication_base_name? || saved_change_to_primary_product_line_id? || saved_change_to_pdf_image_descriptions? || has_literature_changed? end |
#fast_country_discontinue ⇒ Object
Shortcut method when we deal with publications
296 297 298 299 300 301 302 303 304 305 306 |
# File 'app/concerns/models/publication.rb', line 296 def fast_country_discontinue unless @available_in_usa.nil? discontinue_usa = is_discontinued || !@available_in_usa.to_b perform_country_discontinue(1, discontinue_usa) end return if @available_in_canada.nil? discontinue_can = is_discontinued || !@available_in_canada.to_b perform_country_discontinue(2, discontinue_can) end |
#file_name_for_download(file_extension = nil) ⇒ Object
208 209 210 211 212 213 214 |
# File 'app/concerns/models/publication.rb', line 208 def file_name_for_download(file_extension = nil) return unless literature file_extension ||= Rack::Mime::MIME_TYPES.invert[literature.mime_type] file_extension ||= '.pdf' "#{name.parameterize}-#{id}#{file_extension}" end |
#friendly_locale_names ⇒ Object
168 169 170 |
# File 'app/concerns/models/publication.rb', line 168 def friendly_locale_names (publication_locales || []).map { |l| LocaleUtility.language_name(l) }.uniq.compact end |
#generate_publication_name ⇒ Object
Embeds languages in name
217 218 219 220 221 222 |
# File 'app/concerns/models/publication.rb', line 217 def generate_publication_name self.publication_locales = ['en'] if publication_locales.blank? self.name_en = compose_name_with_languages self.name_en_us = nil self.name_en_ca = nil end |
#has_literature_changed? ⇒ Boolean
407 408 409 410 411 |
# File 'app/concerns/models/publication.rb', line 407 def has_literature_changed? return false unless literature literature. || literature. end |
#item_embeddings ⇒ ActiveRecord::Relation<ContentEmbedding::ItemEmbedding>
Association to the partitioned embeddings table for publication AI search
31 32 33 |
# File 'app/concerns/models/publication.rb', line 31 has_many :item_embeddings, -> { where(embeddable_type: 'Item') }, class_name: 'ContentEmbedding::ItemEmbedding', foreign_key: :embeddable_id |
#locale_for_embedding ⇒ Object
Locale for embedding - uses publication_locales
Publications can be in multiple languages, returns the first/primary
591 592 593 594 595 |
# File 'app/concerns/models/publication.rb', line 591 def return 'en' unless is_publication? publication_locales&.first.to_s.presence || 'en' end |
#needs_vision_analysis? ⇒ Boolean
Check if vision analysis is needed for this publication
568 569 570 571 572 573 |
# File 'app/concerns/models/publication.rb', line 568 def needs_vision_analysis? return false unless is_publication? return false unless literature&. pdf_images_analyzed_at.blank? end |
#perform_country_discontinue(store_id, discontinue) ⇒ Object
308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'app/concerns/models/publication.rb', line 308 def perform_country_discontinue(store_id, discontinue) si = store_items.where(store_id: store_id).first_or_initialize(unit_cogs: 0, qty_on_hand: 0, qty_committed: 0, handling_charge: 0, location: 'AVAILABLE') if discontinue unless si.new_record? si.catalog_items.each(&:discontinue) si.is_discontinued = true si.save end else si.is_discontinued = false si.save si.catalog_items.each(&:activate) # implicit unhide end end |
#product_category_must_be_publication ⇒ Object
156 157 158 159 160 |
# File 'app/concerns/models/publication.rb', line 156 def product_category_must_be_publication return if product_category&.is_publication? errors.add(:product_category_id, 'cannot be a non-publication category') end |
#publication_available_locales ⇒ Object
memoize :available_in_canada
190 191 192 |
# File 'app/concerns/models/publication.rb', line 190 def publication_available_locales available_service_locales end |
#publication_base_name_clean_of_language ⇒ Object
224 225 226 227 228 229 230 231 |
# File 'app/concerns/models/publication.rb', line 224 def publication_base_name_clean_of_language words_list = publication_base_name.to_s.downcase.split(/(\w+)/).map { |l| l if l.present? && l.length > 3 }.uniq.compact languages = LocaleUtility.available_languages.map(&:downcase) matches = words_list & languages return if matches.blank? errors.add(:publication_base_name, "should not include the language: #{matches.join(', ')}, this is auto generated if you specify it in locale/language") end |
#publication_cache_relevant_change? ⇒ Boolean
True when a publication change should refresh the edge cache of the products
that surface it: a revision swap flips is_discontinued (and creates a new
active record), a rename changes the PDF link slug, a replaced PDF changes the
document. Drives Events::PublicationUpdated.
433 434 435 436 437 438 439 |
# File 'app/concerns/models/publication.rb', line 433 def publication_cache_relevant_change? return false unless is_publication? return true if destroyed? || previously_new_record? return true if publication_pdf_changed? saved_changes.keys.intersect?(CACHE_RELEVANT_ATTRIBUTES) end |
#publication_edge_cache_urls(previous_product_line_id: nil) ⇒ Object
Every edge-cached www URL where this publication's PDF is surfaced, so one
purge refreshes them all after a revision/discontinue/rename/PDF replacement:
- product PDPs that surface it — the directly-attached items plus the
products in its primary product line (both are how
Item::PublicationRetriever finds it) — including their lazy /section/
document fragments, across both locales (Item#edge_cache_urls); - the product line landing page(s), which render documents INLINE (no lazy
/section/ endpoint — see Www::ProductLinePresenter), across both locales; - the publication's own //publications/.pdf file URL.
Used by Publication::CachePurgeHandler.
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 |
# File 'app/concerns/models/publication.rb', line 472 def publication_edge_cache_urls(previous_product_line_id: nil) urls = [] product_ids = specific_items.ids # The current line plus (on a reassignment) the previous line, including # their descendants — the publication surfaces on products/landing pages at # its line and below (see Item::PublicationRetriever). line_ids = [] line_ids |= primary_product_line.self_and_descendants_ids if primary_product_line if previous_product_line_id.present? && (prev = ProductLine.find_by(id: previous_product_line_id)) line_ids |= prev.self_and_descendants_ids end if line_ids.present? product_ids |= Item.where(primary_product_line_id: line_ids).ids urls += SiteMap.where(resource_type: 'ProductLine', resource_id: line_ids).map(&:url) end if product_ids.present? urls += Item.where(id: product_ids) .includes(:site_maps, :catalog_item_site_maps) .flat_map(&:edge_cache_urls) end urls += PUBLIC_LOCALES.map { |loc| "#{WEB_URL}/#{loc}/publications/#{sku}.pdf" } if sku.present? urls.uniq end |
#publication_pdf_changed? ⇒ Boolean
413 414 415 |
# File 'app/concerns/models/publication.rb', line 413 def publication_pdf_changed? saved_change_to_literature_id? || has_literature_changed? end |
#publication_sku_check ⇒ Object
Check that our publication is formatted with the version
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 |
# File 'app/concerns/models/publication.rb', line 234 def publication_sku_check return true unless product_category.present? && is_publication? return true if sku.blank? && publication_base_name.blank? self.sku ||= publication_base_name&.parameterize&.upcase&.gsub(/[^a-zA-Z0-9-]/, '-')&.squeeze('-') original_sku = sku sku_match = sku.match(/(.*)-([[:alpha:]])$/) # Upcase by default res = false if sku_match && (sku_match.length == 3) self.sku = "#{sku_match[1]}-#{sku_match[2]}" sku_match[1] res = true elsif sku.present? # implicit -A sku self.sku = "#{sku}-A" res = true else errors.add(:sku, 'for publications must be formatted with a trailing alphabetical revision indicator, such as -a, -b, etc.') end self.sku = sku.upcase.gsub(/[_ ]/, '-').squeeze('-') if (public_short_name == original_sku) || (public_short_name == name) self.public_short_name = nil # redundant end if detailed_description_html == original_sku self.detailed_description_html = nil # useless, we want real description end res end |
#publication_url ⇒ Object
347 348 349 350 351 |
# File 'app/concerns/models/publication.rb', line 347 def publication_url return unless is_publication? "https://#{WEB_HOSTNAME}/publications/#{sku}" end |
#publication_visible_to_public? ⇒ Boolean
194 195 196 |
# File 'app/concerns/models/publication.rb', line 194 def publication_visible_to_public? literature.present? && available_service_locales.present? end |
#publish_pdf_changed_event ⇒ Object
417 418 419 420 421 422 |
# File 'app/concerns/models/publication.rb', line 417 def publish_pdf_changed_event Rails.configuration.event_store.publish( Events::PublicationPdfChanged.new(data: { item_id: id }), stream_name: "Publication-#{id}" ) end |
#publish_publication_updated_event ⇒ Object
441 442 443 444 445 446 447 448 449 450 451 452 |
# File 'app/concerns/models/publication.rb', line 441 def publish_publication_updated_event data = { item_id: id } # On a product-line reassignment, carry the OLD line id so the handler also # purges pages where this publication NO LONGER appears (they'd otherwise # keep listing it until TTL). saved_change_* is available in after_commit. line_change = saved_change_to_primary_product_line_id data[:previous_primary_product_line_id] = line_change.first if line_change Rails.configuration.event_store.publish( Events::PublicationUpdated.new(data:), stream_name: "Publication-#{id}" ) end |
#retrieve_publications_search_text ⇒ Object
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'app/concerns/models/publication.rb', line 323 def retrieve_publications_search_text return unless is_publication? return unless literature&. return unless File.exist?(literature..path) return unless literature..format == 'pdf' begin require 'pdf/reader' reader = PDF::Reader.new(literature..path) content = reader.pages.map(&:text).join("\n") content = content.squish content = content.squeeze('_') # removes ______ content = content.squeeze('.') # removes ....... content = content.squeeze('-') # removes ....... content = content.gsub(/ [_-]/, ' ') content = content.gsub(/[":•]/, '') content = content.squish ActionController::Base.helpers.sanitize(content) # sometimes we get crap so we have to do this, e.g. null string rescue StandardError => e logger.error "Unable to parse pdf content for publication id #{id}" logger.error e.inspect end end |
#secondary_product_category_must_not_be_publication ⇒ Object
162 163 164 165 166 |
# File 'app/concerns/models/publication.rb', line 162 def secondary_product_category_must_not_be_publication return unless secondary_product_category&.is_publication? errors.add(:secondary_product_category_id, 'cannot be a publication category') end |
#set_search_text ⇒ Object
266 267 268 269 270 271 |
# File 'app/concerns/models/publication.rb', line 266 def set_search_text content = retrieve_publications_search_text return if content.blank? update_column(:search_text, retrieve_publications_search_text) end |
#should_queue_embedding? ⇒ Boolean
Only embed active, public publications
583 584 585 586 587 |
# File 'app/concerns/models/publication.rb', line 583 def return false unless is_publication? super && !is_discontinued? && publication_visible_to_public? end |