page_structured_data 1.0.11 → 1.0.13
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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +135 -4
- data/app/src/page_structured_data/page.rb +17 -6
- data/app/src/page_structured_data/page_types/article.rb +62 -12
- data/app/src/page_structured_data/page_types/discussion_forum_posting.rb +14 -0
- data/app/src/page_structured_data/page_types/interaction_statistic.rb +58 -0
- data/app/src/page_structured_data/page_types/organization.rb +20 -23
- data/app/src/page_structured_data/page_types/person.rb +29 -0
- data/app/src/page_structured_data/page_types/schema_node.rb +28 -0
- data/app/src/page_structured_data/page_types/web_site.rb +7 -15
- data/app/views/page_structured_data/_meta_tags.html.erb +16 -4
- data/lib/page_structured_data/version.rb +1 -1
- data/lib/page_structured_data.rb +4 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e263a2b3ad541c2b5e72aa79e71d47d72803ddb8a4ac286898e6c6f35105394
|
|
4
|
+
data.tar.gz: 96f01f8fc820456eb69ba5a351f8971022758088d0a5633240c38c7831df43f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 802b70166a18b5475e702822cfb7b0c565c7fc59d8dd5059831261676831e1150c95b8f0e1e11b0de493aa120f3e23f2ddd9a619238ebcdd5f25c8f3920d3d6e
|
|
7
|
+
data.tar.gz: bc818be71ea5d603ac3fb6b84e99dcf79fd76607f3272039cde49d65c772f365d812c0f7d260cdf7beda88762012994ae207b309e7a5dc21b94ab129608e0c9e
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ All notable changes to this project are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 1.0.13 - 2026-05-06
|
|
8
|
+
|
|
9
|
+
- Add `PageStructuredData::PageTypes::Person` for reusable schema.org person values.
|
|
10
|
+
- Accept richer article author values and omit blank nested schema fields.
|
|
11
|
+
- Add page-level `base_app_name` and `render_breadcrumb_json_ld` options.
|
|
12
|
+
- Omit blank description and image meta tags.
|
|
13
|
+
|
|
14
|
+
## 1.0.12 - 2026-05-06
|
|
15
|
+
|
|
16
|
+
- Add reusable interaction statistics for article-like page types.
|
|
17
|
+
- Add `PageStructuredData::PageTypes::DiscussionForumPosting` for forum-style posts.
|
|
18
|
+
- Render Twitter Card meta tags with `name` attributes.
|
|
19
|
+
- Update gem metadata to mention Organization and WebSite JSON-LD support.
|
|
20
|
+
|
|
7
21
|
## 1.0.11 - 2026-05-06
|
|
8
22
|
|
|
9
23
|
- Add optional `description` and `founder` support to organization page types.
|
data/README.md
CHANGED
|
@@ -14,6 +14,9 @@ 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
|
|
19
|
+
- Reusable Person structured data
|
|
17
20
|
- Organization and WebSite structured data
|
|
18
21
|
|
|
19
22
|
## Requirements
|
|
@@ -141,6 +144,8 @@ PageStructuredData includes page types for:
|
|
|
141
144
|
|
|
142
145
|
- [`BlogPosting`](https://schema.org/BlogPosting)
|
|
143
146
|
- [`NewsArticle`](https://schema.org/NewsArticle)
|
|
147
|
+
- [`DiscussionForumPosting`](https://schema.org/DiscussionForumPosting)
|
|
148
|
+
- [`Person`](https://schema.org/Person)
|
|
144
149
|
- [`Organization`](https://schema.org/Organization)
|
|
145
150
|
- [`WebSite`](https://schema.org/WebSite)
|
|
146
151
|
|
|
@@ -173,6 +178,42 @@ article_page_type = PageStructuredData::PageTypes::BlogPosting.new(
|
|
|
173
178
|
|
|
174
179
|
For news pages, use `PageStructuredData::PageTypes::NewsArticle` with the same arguments.
|
|
175
180
|
|
|
181
|
+
Article-like page types can include public engagement counts as interaction statistics:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
article_page_type = PageStructuredData::PageTypes::BlogPosting.new(
|
|
185
|
+
headline: @article.title,
|
|
186
|
+
published_at: @article.published_at,
|
|
187
|
+
updated_at: @article.updated_at,
|
|
188
|
+
likes_count: @article.likes_count,
|
|
189
|
+
comments_count: @article.comments_count,
|
|
190
|
+
shares_count: @article.shares_count
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Only include engagement counts that are public and visible on the rendered page.
|
|
195
|
+
|
|
196
|
+
Use `DiscussionForumPosting` when the current page represents a public user-authored forum or community post:
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
forum_post_page_type = PageStructuredData::PageTypes::DiscussionForumPosting.new(
|
|
200
|
+
headline: @post.title,
|
|
201
|
+
text: @post.content_plaintext,
|
|
202
|
+
url: post_url(@post),
|
|
203
|
+
published_at: @post.created_at,
|
|
204
|
+
updated_at: @post.updated_at,
|
|
205
|
+
authors: [
|
|
206
|
+
{
|
|
207
|
+
name: @post.user.name,
|
|
208
|
+
url: user_url(@post.user)
|
|
209
|
+
}
|
|
210
|
+
],
|
|
211
|
+
interaction_statistics: [
|
|
212
|
+
PageStructuredData::PageTypes::InteractionStatistic.comment(@post.comments_count)
|
|
213
|
+
]
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
176
217
|
Use `Organization` when the current page represents an organization:
|
|
177
218
|
|
|
178
219
|
```ruby
|
|
@@ -238,10 +279,15 @@ PageStructuredData::Page.new(
|
|
|
238
279
|
page_type: nil,
|
|
239
280
|
page_types: nil,
|
|
240
281
|
canonical_url: nil,
|
|
241
|
-
fallback_image: nil
|
|
282
|
+
fallback_image: nil,
|
|
283
|
+
base_app_name: nil,
|
|
284
|
+
render_breadcrumb_json_ld: nil
|
|
242
285
|
)
|
|
243
286
|
```
|
|
244
287
|
|
|
288
|
+
`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.
|
|
289
|
+
`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`.
|
|
290
|
+
|
|
245
291
|
Important methods:
|
|
246
292
|
|
|
247
293
|
- `page_title`: returns the composed page title.
|
|
@@ -272,7 +318,15 @@ PageStructuredData::PageTypes::BlogPosting.new(
|
|
|
272
318
|
published_at:,
|
|
273
319
|
updated_at:,
|
|
274
320
|
images: [],
|
|
275
|
-
authors: []
|
|
321
|
+
authors: [],
|
|
322
|
+
image: nil,
|
|
323
|
+
article_body: nil,
|
|
324
|
+
text: nil,
|
|
325
|
+
url: nil,
|
|
326
|
+
interaction_statistics: [],
|
|
327
|
+
likes_count: nil,
|
|
328
|
+
comments_count: nil,
|
|
329
|
+
shares_count: nil
|
|
276
330
|
)
|
|
277
331
|
```
|
|
278
332
|
|
|
@@ -282,17 +336,94 @@ PageStructuredData::PageTypes::NewsArticle.new(
|
|
|
282
336
|
published_at:,
|
|
283
337
|
updated_at:,
|
|
284
338
|
images: [],
|
|
285
|
-
authors: []
|
|
339
|
+
authors: [],
|
|
340
|
+
image: nil,
|
|
341
|
+
article_body: nil,
|
|
342
|
+
text: nil,
|
|
343
|
+
url: nil,
|
|
344
|
+
interaction_statistics: [],
|
|
345
|
+
likes_count: nil,
|
|
346
|
+
comments_count: nil,
|
|
347
|
+
shares_count: nil
|
|
286
348
|
)
|
|
287
349
|
```
|
|
288
350
|
|
|
289
|
-
`authors`
|
|
351
|
+
`authors` can be an array of hashes, `PageStructuredData::PageTypes::Person` objects, or other objects that respond to `to_h`.
|
|
352
|
+
`image` is a convenience option for one image URL. Use `images` when passing multiple image URLs.
|
|
353
|
+
`text` is an alias for `article_body`.
|
|
354
|
+
`interaction_statistics` should be an array of `PageStructuredData::PageTypes::InteractionStatistic` objects or schema-compatible hashes.
|
|
290
355
|
|
|
291
356
|
Important methods:
|
|
292
357
|
|
|
293
358
|
- `to_h`: returns a structured hash for article JSON-LD.
|
|
294
359
|
- `json_ld`: returns an article JSON-LD script tag.
|
|
295
360
|
|
|
361
|
+
```ruby
|
|
362
|
+
PageStructuredData::PageTypes::DiscussionForumPosting.new(
|
|
363
|
+
headline:,
|
|
364
|
+
published_at:,
|
|
365
|
+
updated_at:,
|
|
366
|
+
images: [],
|
|
367
|
+
authors: [],
|
|
368
|
+
image: nil,
|
|
369
|
+
article_body: nil,
|
|
370
|
+
text: nil,
|
|
371
|
+
url: nil,
|
|
372
|
+
interaction_statistics: [],
|
|
373
|
+
likes_count: nil,
|
|
374
|
+
comments_count: nil,
|
|
375
|
+
shares_count: nil
|
|
376
|
+
)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Interaction Statistics
|
|
380
|
+
|
|
381
|
+
```ruby
|
|
382
|
+
PageStructuredData::PageTypes::InteractionStatistic.new(
|
|
383
|
+
interaction_type: :like,
|
|
384
|
+
user_interaction_count: 42,
|
|
385
|
+
interaction_service: nil
|
|
386
|
+
)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Convenience constructors are also available:
|
|
390
|
+
|
|
391
|
+
```ruby
|
|
392
|
+
PageStructuredData::PageTypes::InteractionStatistic.like(42)
|
|
393
|
+
PageStructuredData::PageTypes::InteractionStatistic.comment(12)
|
|
394
|
+
PageStructuredData::PageTypes::InteractionStatistic.share(7)
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Supported shorthand interaction types are `:like`, `:comment`, and `:share`. Custom schema.org action types can be passed as strings or hashes.
|
|
398
|
+
|
|
399
|
+
Important methods:
|
|
400
|
+
|
|
401
|
+
- `to_h`: returns a structured hash for `InteractionCounter` JSON-LD.
|
|
402
|
+
|
|
403
|
+
### Person
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
PageStructuredData::PageTypes::Person.new(
|
|
407
|
+
name:,
|
|
408
|
+
url: nil,
|
|
409
|
+
image: nil,
|
|
410
|
+
same_as: []
|
|
411
|
+
)
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Use `Person` for article authors, organization founders, and other schema.org person values:
|
|
415
|
+
|
|
416
|
+
```ruby
|
|
417
|
+
author = PageStructuredData::PageTypes::Person.new(
|
|
418
|
+
name: "Jane Doe",
|
|
419
|
+
url: "https://example.com/jane"
|
|
420
|
+
)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Important methods:
|
|
424
|
+
|
|
425
|
+
- `to_h`: returns a compact structured hash for `Person` JSON-LD.
|
|
426
|
+
|
|
296
427
|
### Organization Page Type
|
|
297
428
|
|
|
298
429
|
```ruby
|
|
@@ -4,11 +4,11 @@ module PageStructuredData
|
|
|
4
4
|
# Basic page metadata for any page
|
|
5
5
|
class Page
|
|
6
6
|
attr_reader :title, :description, :image, :extra_title, :breadcrumb, :page_type, :page_types, :canonical_url,
|
|
7
|
-
:fallback_image
|
|
7
|
+
:fallback_image, :base_app_name, :render_breadcrumb_json_ld
|
|
8
8
|
|
|
9
9
|
def initialize(title:, description: nil, image: nil, # rubocop:disable Metrics/ParameterLists
|
|
10
10
|
extra_title: '', breadcrumb: nil, page_type: nil, page_types: nil, canonical_url: nil,
|
|
11
|
-
fallback_image: nil)
|
|
11
|
+
fallback_image: nil, base_app_name: nil, render_breadcrumb_json_ld: nil)
|
|
12
12
|
@title = title
|
|
13
13
|
@description = description
|
|
14
14
|
@image = image
|
|
@@ -18,6 +18,8 @@ module PageStructuredData
|
|
|
18
18
|
@page_types = page_types
|
|
19
19
|
@canonical_url = canonical_url
|
|
20
20
|
@fallback_image = fallback_image
|
|
21
|
+
@base_app_name = base_app_name
|
|
22
|
+
@render_breadcrumb_json_ld = render_breadcrumb_json_ld
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def title_with_hierarchies
|
|
@@ -28,8 +30,8 @@ module PageStructuredData
|
|
|
28
30
|
|
|
29
31
|
def page_title
|
|
30
32
|
result = title_with_hierarchies.join(separator)
|
|
31
|
-
if
|
|
32
|
-
result += separator +
|
|
33
|
+
if resolved_base_app_name.present?
|
|
34
|
+
result += separator + resolved_base_app_name
|
|
33
35
|
end
|
|
34
36
|
result
|
|
35
37
|
end
|
|
@@ -52,13 +54,22 @@ module PageStructuredData
|
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def breadcrumb_json_ld
|
|
57
|
+
return if render_breadcrumb_json_ld == false
|
|
55
58
|
return breadcrumb.json_ld(current_page_title: title) if breadcrumb.present?
|
|
56
|
-
return unless
|
|
59
|
+
return unless render_breadcrumb_json_ld?
|
|
57
60
|
|
|
58
61
|
Breadcrumbs.new.json_ld(current_page_title: title)
|
|
59
62
|
end
|
|
60
63
|
|
|
61
|
-
def
|
|
64
|
+
def render_breadcrumb_json_ld?
|
|
65
|
+
return render_breadcrumb_json_ld unless render_breadcrumb_json_ld.nil?
|
|
66
|
+
|
|
67
|
+
PageStructuredData.render_default_breadcrumb_json_ld
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def resolved_base_app_name
|
|
71
|
+
return base_app_name unless base_app_name.nil?
|
|
72
|
+
|
|
62
73
|
PageStructuredData.base_app_name
|
|
63
74
|
end
|
|
64
75
|
|
|
@@ -4,32 +4,42 @@ module PageStructuredData
|
|
|
4
4
|
module PageTypes
|
|
5
5
|
# Shared structured data for schema.org article-like page types.
|
|
6
6
|
class Article
|
|
7
|
-
|
|
7
|
+
include SchemaNode
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
attr_reader :headline, :images, :published_at, :updated_at, :authors, :article_body, :url,
|
|
10
|
+
:interaction_statistics, :likes_count, :comments_count, :shares_count
|
|
11
|
+
|
|
12
|
+
def initialize(headline:, published_at:, updated_at:, images: [], authors: [], image: nil, article_body: nil, text: nil,
|
|
13
|
+
url: nil, interaction_statistics: [], likes_count: nil, comments_count: nil, shares_count: nil)
|
|
10
14
|
@headline = headline
|
|
11
|
-
@images = images
|
|
15
|
+
@images = image.present? ? Array(image) : Array(images)
|
|
12
16
|
@published_at = published_at
|
|
13
17
|
@updated_at = updated_at
|
|
14
|
-
@authors = authors
|
|
18
|
+
@authors = Array(authors)
|
|
19
|
+
@article_body = article_body || text
|
|
20
|
+
@url = url
|
|
21
|
+
@interaction_statistics = Array(interaction_statistics)
|
|
22
|
+
@likes_count = likes_count
|
|
23
|
+
@comments_count = comments_count
|
|
24
|
+
@shares_count = shares_count
|
|
15
25
|
end
|
|
16
26
|
|
|
17
27
|
def to_h
|
|
18
|
-
{
|
|
28
|
+
node = {
|
|
19
29
|
'@context': 'https://schema.org',
|
|
20
30
|
'@type': schema_type,
|
|
21
31
|
headline: headline,
|
|
22
32
|
image: images,
|
|
23
33
|
datePublished: published_at,
|
|
24
34
|
dateModified: updated_at,
|
|
25
|
-
author: authors.map
|
|
26
|
-
{
|
|
27
|
-
'@type': 'Person',
|
|
28
|
-
name: author[:name],
|
|
29
|
-
url: author[:url],
|
|
30
|
-
}
|
|
31
|
-
end,
|
|
35
|
+
author: authors.map { |author| author_to_h(author) },
|
|
32
36
|
}
|
|
37
|
+
|
|
38
|
+
node[:articleBody] = article_body if article_body.present?
|
|
39
|
+
node[:url] = url if url.present?
|
|
40
|
+
node[:interactionStatistic] = interaction_statistics_to_h if interaction_statistics_to_h.any?
|
|
41
|
+
|
|
42
|
+
node
|
|
33
43
|
end
|
|
34
44
|
|
|
35
45
|
def json_ld
|
|
@@ -45,6 +55,46 @@ module PageStructuredData
|
|
|
45
55
|
def schema_type
|
|
46
56
|
raise NotImplementedError, "#{self.class.name} must define #schema_type"
|
|
47
57
|
end
|
|
58
|
+
|
|
59
|
+
def author_to_h(author)
|
|
60
|
+
return object_to_h(author) if author.respond_to?(:to_h) && !author.is_a?(Hash)
|
|
61
|
+
|
|
62
|
+
compact_node(
|
|
63
|
+
'@type': 'Person',
|
|
64
|
+
name: author[:name] || author['name'],
|
|
65
|
+
url: author[:url] || author['url'],
|
|
66
|
+
image: author[:image] || author['image'],
|
|
67
|
+
sameAs: author[:same_as] || author[:sameAs] || author['same_as'] || author['sameAs']
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def interaction_statistics_to_h
|
|
72
|
+
@interaction_statistics_to_h ||= all_interaction_statistics.map do |interaction_statistic|
|
|
73
|
+
if interaction_statistic.respond_to?(:to_h)
|
|
74
|
+
interaction_statistic.to_h
|
|
75
|
+
else
|
|
76
|
+
interaction_statistic
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def all_interaction_statistics
|
|
82
|
+
interaction_statistics + count_interaction_statistics
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def count_interaction_statistics
|
|
86
|
+
[
|
|
87
|
+
count_interaction_statistic(:like, likes_count),
|
|
88
|
+
count_interaction_statistic(:comment, comments_count),
|
|
89
|
+
count_interaction_statistic(:share, shares_count),
|
|
90
|
+
].compact
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def count_interaction_statistic(interaction_type, count)
|
|
94
|
+
return if count.nil?
|
|
95
|
+
|
|
96
|
+
InteractionStatistic.new(interaction_type: interaction_type, user_interaction_count: count)
|
|
97
|
+
end
|
|
48
98
|
end
|
|
49
99
|
end
|
|
50
100
|
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,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PageStructuredData
|
|
4
|
+
module PageTypes
|
|
5
|
+
# schema.org InteractionCounter structured data.
|
|
6
|
+
class InteractionStatistic
|
|
7
|
+
include SchemaNode
|
|
8
|
+
|
|
9
|
+
ACTION_TYPES = {
|
|
10
|
+
like: 'LikeAction',
|
|
11
|
+
likes: 'LikeAction',
|
|
12
|
+
comment: 'CommentAction',
|
|
13
|
+
comments: 'CommentAction',
|
|
14
|
+
share: 'ShareAction',
|
|
15
|
+
shares: 'ShareAction',
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
attr_reader :interaction_type, :user_interaction_count, :interaction_service
|
|
19
|
+
|
|
20
|
+
def self.like(user_interaction_count)
|
|
21
|
+
new(interaction_type: :like, user_interaction_count: user_interaction_count)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.comment(user_interaction_count)
|
|
25
|
+
new(interaction_type: :comment, user_interaction_count: user_interaction_count)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.share(user_interaction_count)
|
|
29
|
+
new(interaction_type: :share, user_interaction_count: user_interaction_count)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(interaction_type:, user_interaction_count:, interaction_service: nil)
|
|
33
|
+
@interaction_type = interaction_type
|
|
34
|
+
@user_interaction_count = user_interaction_count
|
|
35
|
+
@interaction_service = interaction_service
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_h
|
|
39
|
+
compact_node(
|
|
40
|
+
'@type': 'InteractionCounter',
|
|
41
|
+
interactionType: interaction_type_to_h,
|
|
42
|
+
userInteractionCount: user_interaction_count,
|
|
43
|
+
interactionService: object_to_h(interaction_service)
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def interaction_type_to_h
|
|
50
|
+
return object_to_h(interaction_type) if interaction_type.respond_to?(:to_h)
|
|
51
|
+
|
|
52
|
+
type = ACTION_TYPES.fetch(interaction_type.to_s.to_sym, interaction_type.to_s)
|
|
53
|
+
|
|
54
|
+
{ '@type': type }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -4,6 +4,8 @@ module PageStructuredData
|
|
|
4
4
|
module PageTypes
|
|
5
5
|
# Organization structured data for a page
|
|
6
6
|
class Organization
|
|
7
|
+
include SchemaNode
|
|
8
|
+
|
|
7
9
|
attr_reader :name, :url, :description, :logo, :same_as, :parent_organization, :founder
|
|
8
10
|
|
|
9
11
|
def initialize(name:, url:, description: nil, logo: nil, same_as: [], parent_organization: nil, founder: nil)
|
|
@@ -11,33 +13,23 @@ module PageStructuredData
|
|
|
11
13
|
@url = url
|
|
12
14
|
@description = description
|
|
13
15
|
@logo = logo
|
|
14
|
-
@same_as = same_as
|
|
16
|
+
@same_as = Array(same_as)
|
|
15
17
|
@parent_organization = parent_organization
|
|
16
18
|
@founder = founder
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def to_h # rubocop:disable Metrics/MethodLength
|
|
20
|
-
|
|
22
|
+
compact_node(
|
|
21
23
|
'@context': 'https://schema.org',
|
|
22
24
|
'@type': 'Organization',
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if parent_organization.present?
|
|
33
|
-
node[:parentOrganization] = {
|
|
34
|
-
'@type': 'Organization',
|
|
35
|
-
name: parent_organization[:name],
|
|
36
|
-
url: parent_organization[:url],
|
|
37
|
-
}
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
node
|
|
25
|
+
name: name,
|
|
26
|
+
url: url,
|
|
27
|
+
description: description,
|
|
28
|
+
logo: logo,
|
|
29
|
+
sameAs: same_as,
|
|
30
|
+
founder: object_to_h(founder),
|
|
31
|
+
parentOrganization: parent_organization_to_h
|
|
32
|
+
)
|
|
41
33
|
end
|
|
42
34
|
|
|
43
35
|
def json_ld
|
|
@@ -50,10 +42,15 @@ module PageStructuredData
|
|
|
50
42
|
|
|
51
43
|
private
|
|
52
44
|
|
|
53
|
-
def
|
|
54
|
-
return
|
|
45
|
+
def parent_organization_to_h
|
|
46
|
+
return object_to_h(parent_organization) if parent_organization.respond_to?(:to_h) && !parent_organization.is_a?(Hash)
|
|
47
|
+
return unless parent_organization.present?
|
|
55
48
|
|
|
56
|
-
|
|
49
|
+
compact_node(
|
|
50
|
+
'@type': 'Organization',
|
|
51
|
+
name: parent_organization[:name] || parent_organization['name'],
|
|
52
|
+
url: parent_organization[:url] || parent_organization['url']
|
|
53
|
+
)
|
|
57
54
|
end
|
|
58
55
|
end
|
|
59
56
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PageStructuredData
|
|
4
|
+
module PageTypes
|
|
5
|
+
# schema.org Person structured data.
|
|
6
|
+
class Person
|
|
7
|
+
include SchemaNode
|
|
8
|
+
|
|
9
|
+
attr_reader :name, :url, :image, :same_as
|
|
10
|
+
|
|
11
|
+
def initialize(name:, url: nil, image: nil, same_as: [])
|
|
12
|
+
@name = name
|
|
13
|
+
@url = url
|
|
14
|
+
@image = image
|
|
15
|
+
@same_as = Array(same_as)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_h
|
|
19
|
+
compact_node(
|
|
20
|
+
'@type': 'Person',
|
|
21
|
+
name: name,
|
|
22
|
+
url: url,
|
|
23
|
+
image: image,
|
|
24
|
+
sameAs: same_as
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PageStructuredData
|
|
4
|
+
module PageTypes
|
|
5
|
+
# Shared helpers for schema.org hash values.
|
|
6
|
+
module SchemaNode
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def compact_node(node)
|
|
10
|
+
node.each_with_object({}) do |(key, value), compacted|
|
|
11
|
+
next if blank_schema_value?(value)
|
|
12
|
+
|
|
13
|
+
compacted[key] = value
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def object_to_h(object)
|
|
18
|
+
return object.to_h if object.respond_to?(:to_h)
|
|
19
|
+
|
|
20
|
+
object
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def blank_schema_value?(value)
|
|
24
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -4,6 +4,8 @@ module PageStructuredData
|
|
|
4
4
|
module PageTypes
|
|
5
5
|
# WebSite structured data for a page
|
|
6
6
|
class WebSite
|
|
7
|
+
include SchemaNode
|
|
8
|
+
|
|
7
9
|
attr_reader :name, :url, :description, :publisher, :potential_action
|
|
8
10
|
|
|
9
11
|
def initialize(name:, url:, description: nil, publisher: nil, potential_action: nil)
|
|
@@ -15,18 +17,15 @@ module PageStructuredData
|
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def to_h
|
|
18
|
-
|
|
20
|
+
compact_node(
|
|
19
21
|
'@context': 'https://schema.org',
|
|
20
22
|
'@type': 'WebSite',
|
|
21
23
|
name: name,
|
|
22
24
|
url: url,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
node[:potentialAction] = potential_action if potential_action.present?
|
|
28
|
-
|
|
29
|
-
node
|
|
25
|
+
description: description,
|
|
26
|
+
publisher: object_to_h(publisher),
|
|
27
|
+
potentialAction: object_to_h(potential_action)
|
|
28
|
+
)
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
def json_ld
|
|
@@ -37,13 +36,6 @@ module PageStructuredData
|
|
|
37
36
|
)
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
private
|
|
41
|
-
|
|
42
|
-
def publisher_to_h
|
|
43
|
-
return publisher.to_h if publisher.respond_to?(:to_h)
|
|
44
|
-
|
|
45
|
-
publisher
|
|
46
|
-
end
|
|
47
39
|
end
|
|
48
40
|
end
|
|
49
41
|
end
|
|
@@ -11,16 +11,28 @@
|
|
|
11
11
|
<% end %>
|
|
12
12
|
|
|
13
13
|
<meta name="title" content="<%= title %>">
|
|
14
|
+
<% if description.present? %>
|
|
14
15
|
<meta name="description" content="<%= description %>">
|
|
16
|
+
<% end %>
|
|
17
|
+
<% if image.present? %>
|
|
15
18
|
<meta name="image" content="<%= image %>">
|
|
19
|
+
<% end %>
|
|
16
20
|
|
|
17
21
|
<meta property="og:title" content="<%= title %>">
|
|
22
|
+
<% if description.present? %>
|
|
18
23
|
<meta property="og:description" content="<%= description %>">
|
|
24
|
+
<% end %>
|
|
25
|
+
<% if image.present? %>
|
|
19
26
|
<meta property="og:image" content="<%= image %>">
|
|
27
|
+
<% end %>
|
|
20
28
|
|
|
21
|
-
<meta
|
|
22
|
-
<meta
|
|
23
|
-
|
|
24
|
-
<meta
|
|
29
|
+
<meta name="twitter:card" content="summary_large_image">
|
|
30
|
+
<meta name="twitter:title" content="<%= title %>">
|
|
31
|
+
<% if description.present? %>
|
|
32
|
+
<meta name="twitter:description" content="<%= description %>">
|
|
33
|
+
<% end %>
|
|
34
|
+
<% if image.present? %>
|
|
35
|
+
<meta name="twitter:image" content="<%= image %>">
|
|
36
|
+
<% end %>
|
|
25
37
|
|
|
26
38
|
<%= page&.json_lds&.html_safe %>
|
data/lib/page_structured_data.rb
CHANGED
|
@@ -2,9 +2,13 @@ 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/schema_node"
|
|
6
|
+
require_relative "../app/src/page_structured_data/page_types/person"
|
|
7
|
+
require_relative "../app/src/page_structured_data/page_types/interaction_statistic"
|
|
5
8
|
require_relative "../app/src/page_structured_data/page_types/article"
|
|
6
9
|
require_relative "../app/src/page_structured_data/page_types/blog_posting"
|
|
7
10
|
require_relative "../app/src/page_structured_data/page_types/news_article"
|
|
11
|
+
require_relative "../app/src/page_structured_data/page_types/discussion_forum_posting"
|
|
8
12
|
require_relative "../app/src/page_structured_data/page_types/organization"
|
|
9
13
|
require_relative "../app/src/page_structured_data/page_types/web_site"
|
|
10
14
|
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.
|
|
4
|
+
version: 1.0.13
|
|
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
|
|
35
|
+
tags, breadcrumb JSON-LD, article and forum post JSON-LD, Person, Organization,
|
|
36
|
+
and WebSite JSON-LD, and public interaction statistics.
|
|
36
37
|
email:
|
|
37
38
|
- opensource@rocketapex.com
|
|
38
39
|
executables: []
|
|
@@ -48,8 +49,12 @@ 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
|
|
56
|
+
- app/src/page_structured_data/page_types/person.rb
|
|
57
|
+
- app/src/page_structured_data/page_types/schema_node.rb
|
|
53
58
|
- app/src/page_structured_data/page_types/web_site.rb
|
|
54
59
|
- app/views/page_structured_data/_meta_tags.html.erb
|
|
55
60
|
- config/routes.rb
|