Module: Models::Publication
- Extended by:
- ActiveSupport::Concern
- Includes:
- Memery, HybridSearchable
- Included in:
- Item
- Defined in:
- app/concerns/models/publication.rb
Defined Under Namespace
Modules: ClassMethods
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.
-
.embeddable_content_types ⇒ Object
Content types for publication embeddings.
-
.generate_openai_query_embedding(query, model: 'text-embedding-3-small') ⇒ Array<Float>?
Generate query embedding using OpenAI text-embedding-3-small This matches the model used for stored publication embeddings (Different from Images which use Gemini Embedding 2).
-
.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_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
- #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
211 212 213 |
# File 'app/concerns/models/publication.rb', line 211 def available_in_canada available_service_locales.intersect?(%i[en-CA fr-CA]) end |
#available_in_usa ⇒ Object
206 207 208 |
# File 'app/concerns/models/publication.rb', line 206 def available_in_usa available_service_locales.include?(:'en-US') end |
#serve_in_locale ⇒ Object
Returns the value of attribute serve_in_locale.
10 11 12 |
# File 'app/concerns/models/publication.rb', line 10 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 107 |
# 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 using OpenAI (same model as stored publication embeddings) # NOTE: Publications use text-embedding-3-small, NOT Gemini like Images do = (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(',')}]" # Build raw SQL for minimum distance per item across all embeddings (primary + chunks) # Using raw SQL with from() to avoid ActiveRecord query manipulation issues with .count min_distance_sql = sanitize_sql_array([ <<~SQL.squish, SELECT embeddable_id, MIN(embedding::vector(#{dimensions}) <=> ?::vector(#{dimensions})) AS min_distance FROM content_embeddings_items WHERE embeddable_type = 'Item' AND (content_type = 'primary' OR content_type LIKE 'primary_chunk_%') AND embedding IS NOT NULL GROUP BY embeddable_id SQL vector_literal ]) # 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 |
.embeddable_content_types ⇒ Object
Content types for publication embeddings
456 457 458 |
# File 'app/concerns/models/publication.rb', line 456 def self. [:primary] end |
.generate_openai_query_embedding(query, model: 'text-embedding-3-small') ⇒ Array<Float>?
Generate query embedding using OpenAI text-embedding-3-small
This matches the model used for stored publication embeddings
(Different from Images which use Gemini Embedding 2)
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'app/concerns/models/publication.rb', line 130 def self.(query, model: 'text-embedding-3-small') return nil if query.blank? cache_key = "query_embedding:openai:#{model}:#{Digest::SHA256.hexdigest(query.downcase.strip)[0..15]}" # Try cache first cached = Rails.cache.read(cache_key) return cached if cached.present? # Use RubyLLM to generate embedding with OpenAI # RubyLLM.embed returns an Embedding object where .vectors is the array of floats result = RubyLLM.(query, model: model, provider: :openai, assume_model_exists: true) vector = result&.vectors # vectors IS the 1536-dimension array, not an array of arrays # Cache for 24 hours Rails.cache.write(cache_key, vector, expires_in: 24.hours) if vector.present? vector rescue RubyLLM::RateLimitError => e Rails.logger.warn "Rate limited generating publication query embedding: #{e.}" nil rescue RubyLLM::Error => e Rails.logger.error "RubyLLM error generating publication query embedding: #{e.}" nil rescue StandardError => e Rails.logger.error "Failed to generate OpenAI query embedding: #{e.}" ErrorReporting.warning(e, context: { query: query.truncate(100), model: model }, reason: 'embedding_generation_failed') nil end |
.hybrid_search_publications ⇒ ActiveRecord::Relation<Models::Publication>
A relation of Models::Publications that are hybrid search publications. Active Record Scope
110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'app/concerns/models/publication.rb', line 110 scope :hybrid_search_publications, ->(query, limit: 200) { return none if query.blank? ai_ids = ai_search_publications(query, limit: limit).pluck(:id) keyword_ids = begin keywords_search(query).limit(limit).pluck(:id) 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
18 |
# File 'app/concerns/models/publication.rb', line 18 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
25 |
# File 'app/concerns/models/publication.rb', line 25 scope :publications_for_online_portal, -> { publications.active.where(product_category_id: ProductCategory.where(show_in_sales_portal: true).or(ProductCategory.where(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
21 |
# File 'app/concerns/models/publication.rb', line 21 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
20 |
# File 'app/concerns/models/publication.rb', line 20 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
24 |
# File 'app/concerns/models/publication.rb', line 24 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
23 |
# File 'app/concerns/models/publication.rb', line 23 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
19 |
# File 'app/concerns/models/publication.rb', line 19 scope :with_publication_attached, -> { joins(:literature).includes(:literature) } |
Instance Method Details
#alias_sku ⇒ Object
Store the new sku in our alias chain
380 381 382 383 384 385 386 387 |
# File 'app/concerns/models/publication.rb', line 380 def alias_sku return unless is_publication? self.sku_aliases ||= [] self.sku_aliases.delete(sku) self.sku_aliases << sku_was if sku_changed? self.sku_aliases = self.sku_aliases.filter_map(&:presence).uniq end |
#analyze_pdf_images!(force: false) ⇒ Object
Queue vision analysis for this publication's PDF images
525 526 527 528 529 |
# File 'app/concerns/models/publication.rb', line 525 def analyze_pdf_images!(force: false) return unless is_publication? PublicationVisionWorker.perform_async(id, force: force) end |
#available_service_locales ⇒ Object
198 199 200 |
# File 'app/concerns/models/publication.rb', line 198 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
202 203 204 |
# File 'app/concerns/models/publication.rb', line 202 def available_to_all_locales? available_service_locales.size == LocaleUtility.service_locales.size end |
#check_redirection_path ⇒ Object
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
# File 'app/concerns/models/publication.rb', line 299 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
224 225 226 227 228 229 230 231 232 |
# File 'app/concerns/models/publication.rb', line 224 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
462 463 464 465 466 467 468 469 470 471 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 500 501 502 |
# File 'app/concerns/models/publication.rb', line 462 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
400 401 402 403 404 405 406 407 408 409 |
# File 'app/concerns/models/publication.rb', line 400 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
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 |
# File 'app/concerns/models/publication.rb', line 412 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
390 391 392 393 394 395 396 397 |
# File 'app/concerns/models/publication.rb', line 390 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
547 548 549 550 551 552 |
# File 'app/concerns/models/publication.rb', line 547 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
505 506 507 508 509 510 511 512 513 514 |
# File 'app/concerns/models/publication.rb', line 505 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
322 323 324 325 326 327 328 329 330 331 332 |
# File 'app/concerns/models/publication.rb', line 322 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
234 235 236 237 238 239 240 |
# File 'app/concerns/models/publication.rb', line 234 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
194 195 196 |
# File 'app/concerns/models/publication.rb', line 194 def friendly_locale_names (publication_locales || []).map { |l| LocaleUtility.language_name(l) }.uniq.compact end |
#generate_publication_name ⇒ Object
Embeds languages in name
243 244 245 246 247 248 |
# File 'app/concerns/models/publication.rb', line 243 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
433 434 435 436 437 |
# File 'app/concerns/models/publication.rb', line 433 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
28 29 30 |
# File 'app/concerns/models/publication.rb', line 28 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
540 541 542 543 544 |
# File 'app/concerns/models/publication.rb', line 540 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
517 518 519 520 521 522 |
# File 'app/concerns/models/publication.rb', line 517 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
334 335 336 337 338 339 340 341 342 343 344 345 346 347 |
# File 'app/concerns/models/publication.rb', line 334 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
182 183 184 185 186 |
# File 'app/concerns/models/publication.rb', line 182 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
216 217 218 |
# File 'app/concerns/models/publication.rb', line 216 def publication_available_locales available_service_locales end |
#publication_base_name_clean_of_language ⇒ Object
250 251 252 253 254 255 256 257 |
# File 'app/concerns/models/publication.rb', line 250 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_pdf_changed? ⇒ Boolean
439 440 441 |
# File 'app/concerns/models/publication.rb', line 439 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
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'app/concerns/models/publication.rb', line 260 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
373 374 375 376 377 |
# File 'app/concerns/models/publication.rb', line 373 def publication_url return unless is_publication? "https://#{WEB_HOSTNAME}/publications/#{sku}" end |
#publication_visible_to_public? ⇒ Boolean
220 221 222 |
# File 'app/concerns/models/publication.rb', line 220 def publication_visible_to_public? literature.present? && available_service_locales.present? end |
#publish_pdf_changed_event ⇒ Object
443 444 445 446 447 448 |
# File 'app/concerns/models/publication.rb', line 443 def publish_pdf_changed_event Rails.configuration.event_store.publish( Events::PublicationPdfChanged.new(data: { item_id: id }), stream_name: "Publication-#{id}" ) end |
#retrieve_publications_search_text ⇒ Object
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'app/concerns/models/publication.rb', line 349 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
188 189 190 191 192 |
# File 'app/concerns/models/publication.rb', line 188 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
292 293 294 295 296 297 |
# File 'app/concerns/models/publication.rb', line 292 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
532 533 534 535 536 |
# File 'app/concerns/models/publication.rb', line 532 def return false unless is_publication? super && !is_discontinued? && publication_visible_to_public? end |