page_structured_data 1.0.14 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e36ab494ebd4db8743deb6d8415e826c2186f7c28a7e2927c8a08df4decabd6
4
- data.tar.gz: b48c8723a6ad4a3ee5aa44be5e6b36d6764abbd433a3daedb1e3278f32cf7b2b
3
+ metadata.gz: f455392a943824c8c84fbeb1a95e0250d9f959f98c8e5361c5d57d1f38140d91
4
+ data.tar.gz: 306f4a1246e196db8a7e58bb89aa1a73454c3fc873c3431818a1c0b2cdb5bc93
5
5
  SHA512:
6
- metadata.gz: 1c9dd473ced4959c0acc17940b212d0c71b760efe3a0cfe119d74845ab10b76e081363b26d4138f6b5d66a5e62d0f7c36623c5603ac78ec8565350e2cd53dbcf
7
- data.tar.gz: eefd736852c02d3ec1f02a58e7911f931d8882ce09854364c9b028cd1a11aa1872c3fe1a79beb4c379b8468e1b3db7871a00331dc47522573da9abc64bfa6dce
6
+ metadata.gz: 6a0261a5b6a280806f36fccea7676b6f0656e0fab4f0ab3f13087984923f904b0895a88921527783bdbecaa30c3f653ee16ee8e1fd4dfdc88a294047203fa093
7
+ data.tar.gz: f8cc5db7758b6170705eb55fa53208d1b38f5cf69343e734c6e62ded077916cc7d74ff7d8a54cbff7ecbed287cbe2f8fc5aaab77c0f425abcf97610837075872
data/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ All notable changes to this project are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 1.0.15 - 2026-08-01
8
+
9
+ - Add optional page-level `social_description` support for distinct Open Graph
10
+ and X/Twitter descriptions with fallback to the standard SEO description.
11
+ - Escape JSON-LD independently of host-app Active Support configuration before
12
+ rendering script tags as HTML-safe.
13
+ - Stop rendering invalid current-page-only breadcrumb JSON-LD while preserving
14
+ explicit breadcrumb hierarchies.
15
+ - Render Google-compatible `text` for discussion forum posts while retaining
16
+ `articleBody` for backward compatibility, and warn when author or post
17
+ content is missing.
18
+ - Ensure the documented `rake test` command executes the complete Rails test
19
+ suite and constrain test dependencies to compatible Minitest and Rails 7.0
20
+ SQLite lines.
21
+
7
22
  ## 1.0.14 - 2026-07-05
8
23
 
9
24
  - Add page-level `robots` meta tag support.
data/README.md CHANGED
@@ -51,15 +51,12 @@ Configure application-wide defaults in an initializer:
51
51
  Rails.application.config.after_initialize do
52
52
  PageStructuredData.config do |config|
53
53
  config.base_app_name = "AwesomestApp"
54
- config.render_default_breadcrumb_json_ld = true
55
54
  end
56
55
  end
57
56
  ```
58
57
 
59
58
  `base_app_name` is appended to generated page titles.
60
59
 
61
- `render_default_breadcrumb_json_ld` controls whether pages without an explicit breadcrumb render current-page-only breadcrumb JSON-LD. It defaults to `true` for backward compatibility. Set it to `false` if you only want breadcrumb JSON-LD when a `PageStructuredData::Breadcrumbs` object is passed to the page.
62
-
63
60
  For example:
64
61
 
65
62
  ```ruby
@@ -98,6 +95,7 @@ Set `@page_meta` in the controller or view before the layout renders:
98
95
  title: "Home",
99
96
  extra_title: "Official Page",
100
97
  description: "Welcome to my page",
98
+ social_description: "See what is new on my page",
101
99
  image: image_url("social/home.png"),
102
100
  canonical_url: home_url,
103
101
  fallback_image: image_url("social/default.png"),
@@ -105,6 +103,12 @@ Set `@page_meta` in the controller or view before the layout renders:
105
103
  )
106
104
  ```
107
105
 
106
+ `description` renders the standard SEO description. `social_description` is
107
+ optional and renders the Open Graph and X/Twitter descriptions when it is
108
+ nonblank. When `social_description` is nil or blank, those social tags fall
109
+ back to `description`. This allows intentional social copy without changing the
110
+ standard SEO description.
111
+
108
112
  The generated title is built from:
109
113
 
110
114
  1. `title`
@@ -139,7 +143,11 @@ Pass the breadcrumbs into the page object:
139
143
 
140
144
  This renders `BreadcrumbList` JSON-LD similar to Google's breadcrumb structured data format.
141
145
 
142
- Current compatibility note: when no breadcrumb object is passed, `PageStructuredData::Page` renders current-page-only breadcrumb JSON-LD by default. To opt out, set `config.render_default_breadcrumb_json_ld = false`.
146
+ Breadcrumb JSON-LD renders only when the resulting list contains at least two
147
+ items. Pages without a breadcrumb hierarchy do not emit a current-page-only
148
+ list because Google does not accept one-item breadcrumb data. The existing
149
+ `render_default_breadcrumb_json_ld` configuration remains accepted for
150
+ compatibility, but it cannot make an invalid one-item list render.
143
151
 
144
152
  ## Structured Page Types
145
153
 
@@ -378,6 +386,8 @@ post_page_type = PageStructuredData::PageTypes::DiscussionForumPosting.new(
378
386
  ```
379
387
 
380
388
  Only pass engagement counts that are public and visible on the rendered page.
389
+ `DiscussionForumPosting` renders the supplied `text` as both `text` and the
390
+ backward-compatible `articleBody` property.
381
391
 
382
392
  ### Page-Local Site Name
383
393
 
@@ -401,7 +411,7 @@ PageStructuredData::Page.new(
401
411
 
402
412
  ### Breadcrumb JSON-LD Control
403
413
 
404
- Control generated breadcrumb JSON-LD per page:
414
+ Suppress an explicit breadcrumb hierarchy for one page:
405
415
 
406
416
  ```ruby
407
417
  PageStructuredData::Page.new(
@@ -410,16 +420,9 @@ PageStructuredData::Page.new(
410
420
  )
411
421
  ```
412
422
 
413
- When the global default is disabled, a page can still opt in:
414
-
415
- ```ruby
416
- PageStructuredData.render_default_breadcrumb_json_ld = false
417
-
418
- PageStructuredData::Page.new(
419
- title: "Standalone Page",
420
- render_breadcrumb_json_ld: true
421
- )
422
- ```
423
+ Pages need at least one hierarchy item plus the current page before breadcrumb
424
+ JSON-LD renders. Setting `render_breadcrumb_json_ld: true` does not synthesize
425
+ missing hierarchy data.
423
426
 
424
427
  ### Paginated Archive
425
428
 
@@ -452,6 +455,7 @@ PageStructuredData::Page.new(
452
455
  PageStructuredData::Page.new(
453
456
  title:,
454
457
  description: nil,
458
+ social_description: nil,
455
459
  image: nil,
456
460
  extra_title: "",
457
461
  breadcrumb: nil,
@@ -465,8 +469,11 @@ PageStructuredData::Page.new(
465
469
  )
466
470
  ```
467
471
 
472
+ `social_description` overrides `description` for Open Graph and X/Twitter
473
+ description tags when nonblank. It does not change the standard SEO description
474
+ tag.
468
475
  `base_app_name` overrides `PageStructuredData.base_app_name` for one page. Pass an empty string to suppress the global app name for a specific page.
469
- `render_breadcrumb_json_ld` can be set to `true` or `false` for one page. Leave it as `nil` to use the global `PageStructuredData.render_default_breadcrumb_json_ld` behavior for generated default breadcrumbs. Explicit breadcrumb objects still render when the global default is disabled unless the page sets `render_breadcrumb_json_ld: false`.
476
+ `render_breadcrumb_json_ld` can be set to `false` to suppress an explicit breadcrumb object. A true value never renders a breadcrumb list with fewer than two items.
470
477
  `robots` can be a string or array and renders a `<meta name="robots">` tag when present.
471
478
 
472
479
  Important methods:
@@ -474,6 +481,7 @@ Important methods:
474
481
  - `page_title`: returns the composed page title.
475
482
  - `json_lds`: returns the JSON-LD script tags for breadcrumbs and page type data.
476
483
  - `resolved_image`: returns `image` or `fallback_image`.
484
+ - `resolved_social_description`: returns a nonblank `social_description` or falls back to `description`.
477
485
  - `robots_content`: returns the rendered robots directives.
478
486
  - `warnings`: returns soft validation warnings for the page and page types.
479
487
  - `valid?`: returns `true` when `warnings` is empty.
data/Rakefile CHANGED
@@ -6,3 +6,8 @@ load "rails/tasks/engine.rake"
6
6
  load "rails/tasks/statistics.rake"
7
7
 
8
8
  require "bundler/gem_tasks"
9
+
10
+ desc "Run the complete test suite"
11
+ task :test do
12
+ ruby File.expand_path("bin/rails", __dir__), "test"
13
+ end
@@ -22,11 +22,10 @@ module PageStructuredData
22
22
  end
23
23
 
24
24
  def json_ld(current_page_title:)
25
- %(
26
- <script type="application/ld+json">
27
- #{to_h(current_page_title: current_page_title).to_json}
28
- </script>
29
- )
25
+ node = to_h(current_page_title: current_page_title)
26
+ return if node[:itemListElement].size < 2
27
+
28
+ JsonLd.script_tag(node)
30
29
  end
31
30
 
32
31
  private
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/string/output_safety'
4
+
5
+ module PageStructuredData
6
+ # Builds JSON-LD script tags with escaping that does not depend on host-app
7
+ # Active Support configuration.
8
+ module JsonLd
9
+ module_function
10
+
11
+ def script_tag(node)
12
+ json = ActiveSupport::JSON.encode(node)
13
+ escaped_json = ERB::Util.json_escape(json)
14
+
15
+ %(
16
+ <script type="application/ld+json">
17
+ #{escaped_json}
18
+ </script>
19
+ )
20
+ end
21
+ end
22
+ end
@@ -3,14 +3,15 @@
3
3
  module PageStructuredData
4
4
  # Basic page metadata for any page
5
5
  class Page
6
- attr_reader :title, :description, :image, :extra_title, :breadcrumb, :page_type, :page_types, :canonical_url,
7
- :fallback_image, :base_app_name, :render_breadcrumb_json_ld, :robots
6
+ attr_reader :title, :description, :social_description, :image, :extra_title, :breadcrumb, :page_type, :page_types,
7
+ :canonical_url, :fallback_image, :base_app_name, :render_breadcrumb_json_ld, :robots
8
8
 
9
- def initialize(title:, description: nil, image: nil, # rubocop:disable Metrics/ParameterLists
9
+ def initialize(title:, description: nil, social_description: nil, image: nil, # rubocop:disable Metrics/ParameterLists
10
10
  extra_title: '', breadcrumb: nil, page_type: nil, page_types: nil, canonical_url: nil,
11
11
  fallback_image: nil, base_app_name: nil, render_breadcrumb_json_ld: nil, robots: nil)
12
12
  @title = title
13
13
  @description = description
14
+ @social_description = social_description
14
15
  @image = image
15
16
  @extra_title = extra_title
16
17
  @breadcrumb = breadcrumb
@@ -48,6 +49,10 @@ module PageStructuredData
48
49
  image || fallback_image
49
50
  end
50
51
 
52
+ def resolved_social_description
53
+ social_description.presence || description
54
+ end
55
+
51
56
  def robots_content
52
57
  Array(robots).compact.join(',')
53
58
  end
@@ -60,11 +60,7 @@ module PageStructuredData
60
60
  end
61
61
 
62
62
  def json_ld
63
- %(
64
- <script type="application/ld+json">
65
- #{to_h.to_json}
66
- </script>
67
- )
63
+ JsonLd.script_tag(to_h)
68
64
  end
69
65
 
70
66
  def warnings
@@ -4,11 +4,28 @@ module PageStructuredData
4
4
  module PageTypes
5
5
  # schema.org structured data for discussion forum posts.
6
6
  class DiscussionForumPosting < Article
7
+ def to_h
8
+ super.tap do |node|
9
+ node[:text] = article_body if article_body.present?
10
+ end
11
+ end
12
+
13
+ def warnings
14
+ super + discussion_forum_warnings
15
+ end
16
+
7
17
  private
8
18
 
9
19
  def schema_type
10
20
  'DiscussionForumPosting'
11
21
  end
22
+
23
+ def discussion_forum_warnings
24
+ warnings = []
25
+ warnings << 'author is required' if authors.empty?
26
+ warnings << 'text or image is required' unless article_body.present? || images.any?(&:present?)
27
+ warnings
28
+ end
12
29
  end
13
30
  end
14
31
  end
@@ -33,11 +33,7 @@ module PageStructuredData
33
33
  end
34
34
 
35
35
  def json_ld
36
- %(
37
- <script type="application/ld+json">
38
- #{to_h.to_json}
39
- </script>
40
- )
36
+ JsonLd.script_tag(to_h)
41
37
  end
42
38
 
43
39
  def warnings
@@ -29,11 +29,7 @@ module PageStructuredData
29
29
  end
30
30
 
31
31
  def json_ld
32
- %(
33
- <script type="application/ld+json">
34
- #{to_h.to_json}
35
- </script>
36
- )
32
+ JsonLd.script_tag(to_h)
37
33
  end
38
34
 
39
35
  def warnings
@@ -2,6 +2,7 @@
2
2
 
3
3
  <% title = page&.page_title %>
4
4
  <% description = page&.description %>
5
+ <% social_description = page&.resolved_social_description %>
5
6
  <% image = page&.resolved_image || default_image_url || nil %>
6
7
  <% canonical_url = page&.canonical_url %>
7
8
  <% robots_content = page&.robots_content %>
@@ -23,8 +24,8 @@
23
24
  <% end %>
24
25
 
25
26
  <meta property="og:title" content="<%= title %>">
26
- <% if description.present? %>
27
- <meta property="og:description" content="<%= description %>">
27
+ <% if social_description.present? %>
28
+ <meta property="og:description" content="<%= social_description %>">
28
29
  <% end %>
29
30
  <% if image.present? %>
30
31
  <meta property="og:image" content="<%= image %>">
@@ -32,8 +33,8 @@
32
33
 
33
34
  <meta name="twitter:card" content="summary_large_image">
34
35
  <meta name="twitter:title" content="<%= title %>">
35
- <% if description.present? %>
36
- <meta name="twitter:description" content="<%= description %>">
36
+ <% if social_description.present? %>
37
+ <meta name="twitter:description" content="<%= social_description %>">
37
38
  <% end %>
38
39
  <% if image.present? %>
39
40
  <meta name="twitter:image" content="<%= image %>">
@@ -1,3 +1,3 @@
1
1
  module PageStructuredData
2
- VERSION = "1.0.14"
2
+ VERSION = "1.0.15"
3
3
  end
@@ -1,6 +1,7 @@
1
1
  require "page_structured_data/version"
2
2
  require "page_structured_data/engine"
3
3
  require_relative "../app/src/page_structured_data/anchors"
4
+ require_relative "../app/src/page_structured_data/json_ld"
4
5
  require_relative "../app/src/page_structured_data/breadcrumbs"
5
6
  require_relative "../app/src/page_structured_data/page_types/schema_node"
6
7
  require_relative "../app/src/page_structured_data/page_types/person"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: page_structured_data
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.14
4
+ version: 1.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jey Geethan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-05 00:00:00.000000000 Z
11
+ date: 2026-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -47,6 +47,7 @@ files:
47
47
  - Rakefile
48
48
  - app/src/page_structured_data/anchors.rb
49
49
  - app/src/page_structured_data/breadcrumbs.rb
50
+ - app/src/page_structured_data/json_ld.rb
50
51
  - app/src/page_structured_data/page.rb
51
52
  - app/src/page_structured_data/page_types/article.rb
52
53
  - app/src/page_structured_data/page_types/blog_posting.rb