Module: Models::SchemaMarkup

Extended by:
ActiveSupport::Concern
Included in:
Article
Defined in:
app/concerns/models/schema_markup.rb

Instance Method Summary collapse

Instance Method Details

#add_schema(schema) ⇒ Object

Schema markup helper methods



7
8
9
10
11
12
13
# File 'app/concerns/models/schema_markup.rb', line 7

def add_schema(schema)
  return unless schema.is_a?(Hash)

  schema['@context'] = 'https://schema.org' unless schema['@context']
  self.schema_markup ||= []
  self.schema_markup << schema
end

#clear_schema_markupObject



124
125
126
# File 'app/concerns/models/schema_markup.rb', line 124

def clear_schema_markup
  self.schema_markup = []
end

#consolidated_faq_page_schemaObject

Build a single FAQPage schema merging: (1) FAQPage mainEntity from schema_markup, (2) FAQs from embedded blocks (by ID via FaqPresenter).
Deduplicates by question name. Returns a hash suitable for JSON-LD or nil if nothing to output.



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
108
109
110
# File 'app/concerns/models/schema_markup.rb', line 70

def consolidated_faq_page_schema
  embedded_ids = embedded_faq_ids_from_content
  extracted_faq_schemas = (schema_markup || []).select { |s| s['@type'] == 'FAQPage' }
  extracted_entities = extracted_faq_schemas.flat_map { |s| (s['mainEntity'] || s[:mainEntity] || []).map(&:with_indifferent_access) }

  embedded_entities = if embedded_ids.any?
    ordered_ids = embedded_ids.uniq
    faqs = ArticleFaq
      .where(id: ordered_ids)
      .published
      .order(Arel.sql("ARRAY_POSITION(ARRAY[#{ordered_ids.join(',')}]::integer[], id)"))
    if faqs.empty?
      []
    else
      FaqPresenter.new(faqs).schema_dot_org_structure.mainEntity.map(&:to_json_struct)
    end
  else
    []
  end

  all_entities = (extracted_entities + embedded_entities)
  return nil if all_entities.empty?

  # Deduplicate by question name (first occurrence wins)
  seen = Set.new
  main_entity = all_entities.filter_map do |entity|
    name = entity['name'] || entity[:name]
    next if name.blank? || seen.include?(name)

    seen.add(name)
    entity
  end

  return nil if main_entity.empty?

  {
    '@context' => 'https://schema.org',
    '@type' => 'FAQPage',
    'mainEntity' => main_entity
  }
end

#content_has_embedded_faq_schema?Boolean

Check if content has embedded FAQ oEmbed blocks for extractor/rules that need to skip or strip that block

Returns:

  • (Boolean)


113
114
115
116
117
118
119
120
121
122
# File 'app/concerns/models/schema_markup.rb', line 113

def content_has_embedded_faq_schema?
  return false unless respond_to?(:localized_solution)

  rendered = localized_solution.to_s

  return true if rendered.match?(/wy-faq-embed|data-wy-oembed="faq"/i)
  return true if rendered.include?('application/ld+json') && rendered.include?('"FAQPage"')

  false
end

#embedded_faq_ids_from_contentObject

Collect FAQ IDs from embedded oEmbed blocks (data-faq-ids), in document order.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'app/concerns/models/schema_markup.rb', line 49

def embedded_faq_ids_from_content
  return [] unless respond_to?(:localized_solution)

  content = localized_solution.to_s
  return [] if content.blank?

  ids = []

  doc = Nokogiri::HTML(content)
  doc.css('figure.wy-faq-embed, figure[data-wy-oembed="faq"]').each do |figure|
    figure['data-faq-ids'].to_s.split(',').each do |id_str|
      id = id_str.strip.to_i
      ids << id if id.positive?
    end
  end

  ids.uniq
end

#has_schema_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


21
22
23
# File 'app/concerns/models/schema_markup.rb', line 21

def has_schema_type?(type)
  schema_types.include?(type)
end

#render_schema_markupObject



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'app/concerns/models/schema_markup.rb', line 31

def render_schema_markup
  # Build one consolidated FAQPage (extracted schema_markup FAQ + embedded oEmbed FAQs), then other schemas
  consolidated_faq = consolidated_faq_page_schema
  other_schemas = (schema_markup || []).reject { |s| s['@type'] == 'FAQPage' }

  parts = []
  parts << consolidated_faq if consolidated_faq.present?
  parts.concat(other_schemas)

  return '' if parts.empty?

  parts.map do |schema|
    hash = schema.is_a?(Hash) ? schema : schema.with_indifferent_access
    "<script type=\"application/ld+json\">#{hash.to_json}</script>"
  end.join("\n").html_safe
end

#schema_countObject



128
129
130
# File 'app/concerns/models/schema_markup.rb', line 128

def schema_count
  schema_markup&.length || 0
end

#schema_typesObject



15
16
17
18
19
# File 'app/concerns/models/schema_markup.rb', line 15

def schema_types
  return [] unless schema_markup.present?

  schema_markup.map { |schema| schema['@type'] }.compact.uniq
end

#schemas_by_type(type) ⇒ Object



25
26
27
28
29
# File 'app/concerns/models/schema_markup.rb', line 25

def schemas_by_type(type)
  return [] unless schema_markup.present?

  schema_markup.select { |schema| schema['@type'] == type }
end