page_structured_data 1.0.11 → 1.0.12

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: 9ffd04020a9a4ba900bb7d4bbdac9dd827d48535073a30c657865469df9e1988
4
- data.tar.gz: 4b560fee8a434599acd2ae619fdc7e4a0973f29aa77f8b95191a270047767931
3
+ metadata.gz: 885b125f1d2fed94a1e0bba7d2c70877f0dba9c5f4ebde59c52f970f2fc4fce4
4
+ data.tar.gz: 513764c63a8dae5e609d2e92ea1e99dee3fe0555a3727afa24c3d2afdcc64ffd
5
5
  SHA512:
6
- metadata.gz: '08329eddfdf9f7e3682a6016ca84d25b1867e4ce8ebca4c4e7654536b4dff59b8d825351b4489b69342c42d2cc52ab41d96bba1f3cc8adf5a1f8180330d660ba'
7
- data.tar.gz: 270c98e7c0071b9e09e75482a5d766ec685f8a7da103c3ddb15f4df03e895b98fcc52d57e50b93cbfddd992de7d0fbdf6d5de9542b07fec52d0eab977da2903f
6
+ metadata.gz: 1e4530a70cf06b4d81cab2322759500bdb29acb31ef8c33846f40d9095cb981c2272feb0fe6c57b6b5c363915e59ab7300071d618799459d2e34317bfc1434e8
7
+ data.tar.gz: 6a900ab594f816e1b2edd833a350a15c0837503af68f3efa0a475ac723e05b21bdc66cbad39384c680a2dfab4dfd173137dd9a05f245f236df450e757f162f98
data/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to this project are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 1.0.12 - 2026-05-06
8
+
9
+ - Add reusable interaction statistics for article-like page types.
10
+ - Add `PageStructuredData::PageTypes::DiscussionForumPosting` for forum-style posts.
11
+ - Render Twitter Card meta tags with `name` attributes.
12
+ - Update gem metadata to mention Organization and WebSite JSON-LD support.
13
+
7
14
  ## 1.0.11 - 2026-05-06
8
15
 
9
16
  - Add optional `description` and `founder` support to organization page types.
data/README.md CHANGED
@@ -14,6 +14,8 @@ It helps Rails applications render:
14
14
  - Google-compatible JSON-LD structured data
15
15
  - Breadcrumb structured data
16
16
  - Article structured data for `BlogPosting` and `NewsArticle`
17
+ - Discussion forum post structured data
18
+ - Interaction statistics for public engagement counts
17
19
  - Organization and WebSite structured data
18
20
 
19
21
  ## Requirements
@@ -141,6 +143,7 @@ PageStructuredData includes page types for:
141
143
 
142
144
  - [`BlogPosting`](https://schema.org/BlogPosting)
143
145
  - [`NewsArticle`](https://schema.org/NewsArticle)
146
+ - [`DiscussionForumPosting`](https://schema.org/DiscussionForumPosting)
144
147
  - [`Organization`](https://schema.org/Organization)
145
148
  - [`WebSite`](https://schema.org/WebSite)
146
149
 
@@ -173,6 +176,42 @@ article_page_type = PageStructuredData::PageTypes::BlogPosting.new(
173
176
 
174
177
  For news pages, use `PageStructuredData::PageTypes::NewsArticle` with the same arguments.
175
178
 
179
+ Article-like page types can include public engagement counts as interaction statistics:
180
+
181
+ ```ruby
182
+ article_page_type = PageStructuredData::PageTypes::BlogPosting.new(
183
+ headline: @article.title,
184
+ published_at: @article.published_at,
185
+ updated_at: @article.updated_at,
186
+ likes_count: @article.likes_count,
187
+ comments_count: @article.comments_count,
188
+ shares_count: @article.shares_count
189
+ )
190
+ ```
191
+
192
+ Only include engagement counts that are public and visible on the rendered page.
193
+
194
+ Use `DiscussionForumPosting` when the current page represents a public user-authored forum or community post:
195
+
196
+ ```ruby
197
+ forum_post_page_type = PageStructuredData::PageTypes::DiscussionForumPosting.new(
198
+ headline: @post.title,
199
+ text: @post.content_plaintext,
200
+ url: post_url(@post),
201
+ published_at: @post.created_at,
202
+ updated_at: @post.updated_at,
203
+ authors: [
204
+ {
205
+ name: @post.user.name,
206
+ url: user_url(@post.user)
207
+ }
208
+ ],
209
+ interaction_statistics: [
210
+ PageStructuredData::PageTypes::InteractionStatistic.comment(@post.comments_count)
211
+ ]
212
+ )
213
+ ```
214
+
176
215
  Use `Organization` when the current page represents an organization:
177
216
 
178
217
  ```ruby
@@ -272,7 +311,15 @@ PageStructuredData::PageTypes::BlogPosting.new(
272
311
  published_at:,
273
312
  updated_at:,
274
313
  images: [],
275
- authors: []
314
+ authors: [],
315
+ image: nil,
316
+ article_body: nil,
317
+ text: nil,
318
+ url: nil,
319
+ interaction_statistics: [],
320
+ likes_count: nil,
321
+ comments_count: nil,
322
+ shares_count: nil
276
323
  )
277
324
  ```
278
325
 
@@ -282,17 +329,70 @@ PageStructuredData::PageTypes::NewsArticle.new(
282
329
  published_at:,
283
330
  updated_at:,
284
331
  images: [],
285
- authors: []
332
+ authors: [],
333
+ image: nil,
334
+ article_body: nil,
335
+ text: nil,
336
+ url: nil,
337
+ interaction_statistics: [],
338
+ likes_count: nil,
339
+ comments_count: nil,
340
+ shares_count: nil
286
341
  )
287
342
  ```
288
343
 
289
344
  `authors` should be an array of hashes with `:name` and `:url` keys.
345
+ `image` is a convenience option for one image URL. Use `images` when passing multiple image URLs.
346
+ `text` is an alias for `article_body`.
347
+ `interaction_statistics` should be an array of `PageStructuredData::PageTypes::InteractionStatistic` objects or schema-compatible hashes.
290
348
 
291
349
  Important methods:
292
350
 
293
351
  - `to_h`: returns a structured hash for article JSON-LD.
294
352
  - `json_ld`: returns an article JSON-LD script tag.
295
353
 
354
+ ```ruby
355
+ PageStructuredData::PageTypes::DiscussionForumPosting.new(
356
+ headline:,
357
+ published_at:,
358
+ updated_at:,
359
+ images: [],
360
+ authors: [],
361
+ image: nil,
362
+ article_body: nil,
363
+ text: nil,
364
+ url: nil,
365
+ interaction_statistics: [],
366
+ likes_count: nil,
367
+ comments_count: nil,
368
+ shares_count: nil
369
+ )
370
+ ```
371
+
372
+ ### Interaction Statistics
373
+
374
+ ```ruby
375
+ PageStructuredData::PageTypes::InteractionStatistic.new(
376
+ interaction_type: :like,
377
+ user_interaction_count: 42,
378
+ interaction_service: nil
379
+ )
380
+ ```
381
+
382
+ Convenience constructors are also available:
383
+
384
+ ```ruby
385
+ PageStructuredData::PageTypes::InteractionStatistic.like(42)
386
+ PageStructuredData::PageTypes::InteractionStatistic.comment(12)
387
+ PageStructuredData::PageTypes::InteractionStatistic.share(7)
388
+ ```
389
+
390
+ Supported shorthand interaction types are `:like`, `:comment`, and `:share`. Custom schema.org action types can be passed as strings or hashes.
391
+
392
+ Important methods:
393
+
394
+ - `to_h`: returns a structured hash for `InteractionCounter` JSON-LD.
395
+
296
396
  ### Organization Page Type
297
397
 
298
398
  ```ruby
@@ -4,18 +4,26 @@ module PageStructuredData
4
4
  module PageTypes
5
5
  # Shared structured data for schema.org article-like page types.
6
6
  class Article
7
- attr_reader :headline, :images, :published_at, :updated_at, :authors
7
+ attr_reader :headline, :images, :published_at, :updated_at, :authors, :article_body, :url,
8
+ :interaction_statistics, :likes_count, :comments_count, :shares_count
8
9
 
9
- def initialize(headline:, published_at:, updated_at:, images: [], authors: [])
10
+ def initialize(headline:, published_at:, updated_at:, images: [], authors: [], image: nil, article_body: nil, text: nil,
11
+ url: nil, interaction_statistics: [], likes_count: nil, comments_count: nil, shares_count: nil)
10
12
  @headline = headline
11
- @images = images
13
+ @images = image.present? ? Array(image) : Array(images)
12
14
  @published_at = published_at
13
15
  @updated_at = updated_at
14
- @authors = authors
16
+ @authors = Array(authors)
17
+ @article_body = article_body || text
18
+ @url = url
19
+ @interaction_statistics = Array(interaction_statistics)
20
+ @likes_count = likes_count
21
+ @comments_count = comments_count
22
+ @shares_count = shares_count
15
23
  end
16
24
 
17
25
  def to_h
18
- {
26
+ node = {
19
27
  '@context': 'https://schema.org',
20
28
  '@type': schema_type,
21
29
  headline: headline,
@@ -30,6 +38,12 @@ module PageStructuredData
30
38
  }
31
39
  end,
32
40
  }
41
+
42
+ node[:articleBody] = article_body if article_body.present?
43
+ node[:url] = url if url.present?
44
+ node[:interactionStatistic] = interaction_statistics_to_h if interaction_statistics_to_h.any?
45
+
46
+ node
33
47
  end
34
48
 
35
49
  def json_ld
@@ -45,6 +59,34 @@ module PageStructuredData
45
59
  def schema_type
46
60
  raise NotImplementedError, "#{self.class.name} must define #schema_type"
47
61
  end
62
+
63
+ def interaction_statistics_to_h
64
+ @interaction_statistics_to_h ||= all_interaction_statistics.map do |interaction_statistic|
65
+ if interaction_statistic.respond_to?(:to_h)
66
+ interaction_statistic.to_h
67
+ else
68
+ interaction_statistic
69
+ end
70
+ end
71
+ end
72
+
73
+ def all_interaction_statistics
74
+ interaction_statistics + count_interaction_statistics
75
+ end
76
+
77
+ def count_interaction_statistics
78
+ [
79
+ count_interaction_statistic(:like, likes_count),
80
+ count_interaction_statistic(:comment, comments_count),
81
+ count_interaction_statistic(:share, shares_count),
82
+ ].compact
83
+ end
84
+
85
+ def count_interaction_statistic(interaction_type, count)
86
+ return if count.nil?
87
+
88
+ InteractionStatistic.new(interaction_type: interaction_type, user_interaction_count: count)
89
+ end
48
90
  end
49
91
  end
50
92
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PageStructuredData
4
+ module PageTypes
5
+ # schema.org structured data for discussion forum posts.
6
+ class DiscussionForumPosting < Article
7
+ private
8
+
9
+ def schema_type
10
+ 'DiscussionForumPosting'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PageStructuredData
4
+ module PageTypes
5
+ # schema.org InteractionCounter structured data.
6
+ class InteractionStatistic
7
+ ACTION_TYPES = {
8
+ like: 'LikeAction',
9
+ likes: 'LikeAction',
10
+ comment: 'CommentAction',
11
+ comments: 'CommentAction',
12
+ share: 'ShareAction',
13
+ shares: 'ShareAction',
14
+ }.freeze
15
+
16
+ attr_reader :interaction_type, :user_interaction_count, :interaction_service
17
+
18
+ def self.like(user_interaction_count)
19
+ new(interaction_type: :like, user_interaction_count: user_interaction_count)
20
+ end
21
+
22
+ def self.comment(user_interaction_count)
23
+ new(interaction_type: :comment, user_interaction_count: user_interaction_count)
24
+ end
25
+
26
+ def self.share(user_interaction_count)
27
+ new(interaction_type: :share, user_interaction_count: user_interaction_count)
28
+ end
29
+
30
+ def initialize(interaction_type:, user_interaction_count:, interaction_service: nil)
31
+ @interaction_type = interaction_type
32
+ @user_interaction_count = user_interaction_count
33
+ @interaction_service = interaction_service
34
+ end
35
+
36
+ def to_h
37
+ node = {
38
+ '@type': 'InteractionCounter',
39
+ interactionType: interaction_type_to_h,
40
+ userInteractionCount: user_interaction_count,
41
+ }
42
+
43
+ node[:interactionService] = object_to_h(interaction_service) if interaction_service.present?
44
+
45
+ node
46
+ end
47
+
48
+ private
49
+
50
+ def interaction_type_to_h
51
+ return object_to_h(interaction_type) if interaction_type.respond_to?(:to_h)
52
+
53
+ type = ACTION_TYPES.fetch(interaction_type.to_s.to_sym, interaction_type.to_s)
54
+
55
+ { '@type': type }
56
+ end
57
+
58
+ def object_to_h(object)
59
+ return object.to_h if object.respond_to?(:to_h)
60
+
61
+ object
62
+ end
63
+ end
64
+ end
65
+ end
@@ -18,9 +18,9 @@
18
18
  <meta property="og:description" content="<%= description %>">
19
19
  <meta property="og:image" content="<%= image %>">
20
20
 
21
- <meta property="twitter:card" content="summary_large_image">
22
- <meta property="twitter:title" content="<%= title %>">
23
- <meta property="twitter:description" content="<%= description %>">
24
- <meta property="twitter:image" content="<%= image %>">
21
+ <meta name="twitter:card" content="summary_large_image">
22
+ <meta name="twitter:title" content="<%= title %>">
23
+ <meta name="twitter:description" content="<%= description %>">
24
+ <meta name="twitter:image" content="<%= image %>">
25
25
 
26
26
  <%= page&.json_lds&.html_safe %>
@@ -1,3 +1,3 @@
1
1
  module PageStructuredData
2
- VERSION = "1.0.11"
2
+ VERSION = "1.0.12"
3
3
  end
@@ -2,9 +2,11 @@ require "page_structured_data/version"
2
2
  require "page_structured_data/engine"
3
3
  require_relative "../app/src/page_structured_data/anchors"
4
4
  require_relative "../app/src/page_structured_data/breadcrumbs"
5
+ require_relative "../app/src/page_structured_data/page_types/interaction_statistic"
5
6
  require_relative "../app/src/page_structured_data/page_types/article"
6
7
  require_relative "../app/src/page_structured_data/page_types/blog_posting"
7
8
  require_relative "../app/src/page_structured_data/page_types/news_article"
9
+ require_relative "../app/src/page_structured_data/page_types/discussion_forum_posting"
8
10
  require_relative "../app/src/page_structured_data/page_types/organization"
9
11
  require_relative "../app/src/page_structured_data/page_types/web_site"
10
12
  require_relative "../app/src/page_structured_data/page"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: page_structured_data
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.11
4
+ version: 1.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jey Geethan
@@ -32,7 +32,8 @@ dependencies:
32
32
  version: '9.0'
33
33
  description: PageStructuredData gives Rails applications a small page object and view
34
34
  partial for rendering page titles, basic meta tags, Open Graph tags, Twitter card
35
- tags, breadcrumb JSON-LD, and article JSON-LD.
35
+ tags, breadcrumb JSON-LD, article and forum post JSON-LD, Organization JSON-LD,
36
+ WebSite JSON-LD, and public interaction statistics.
36
37
  email:
37
38
  - opensource@rocketapex.com
38
39
  executables: []
@@ -48,6 +49,8 @@ files:
48
49
  - app/src/page_structured_data/page.rb
49
50
  - app/src/page_structured_data/page_types/article.rb
50
51
  - app/src/page_structured_data/page_types/blog_posting.rb
52
+ - app/src/page_structured_data/page_types/discussion_forum_posting.rb
53
+ - app/src/page_structured_data/page_types/interaction_statistic.rb
51
54
  - app/src/page_structured_data/page_types/news_article.rb
52
55
  - app/src/page_structured_data/page_types/organization.rb
53
56
  - app/src/page_structured_data/page_types/web_site.rb