better_seo 0.4.0 → 0.6.0

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: 21564a3e273f4c6274fcafaddc9dc0b9e5e36524c1ca2a548ae48e455490034f
4
- data.tar.gz: d82e2faf6d565f8be930ca24e9c3af7b60de23c8ed02dbcc0d14390c6725580b
3
+ metadata.gz: 4b1f457db06f6ed1a5654363c639448945eb0c8ee11f311203333edf3de5b499
4
+ data.tar.gz: bbb3f5e0f831a1f3437b1dd171b4e7c8cc953fc654b102670037da19cc96ae73
5
5
  SHA512:
6
- metadata.gz: 656e243f281d61d4afac21dfdd2f18be27659ae695bc5d6770146fc7e234135a21e221847157d75bb50b1d41d2798094e87868fc8d91cac1eee0fe38e651c6eb
7
- data.tar.gz: b65f741d51b2b192c2456356081ad908145d68f93d37e64c6ba5d45c3ae838820120d7c852592dd60d1f3da54e2bf21e88316bede5c2e9971d0af24d2cecbe50
6
+ metadata.gz: '0978f9a96df030f0b10c10e409c9938c7cca9d5b881212c10135de040f6e51cbd1631f5b6d76ffe9af9efad722336d4f522edb7abf6872d7abd86ae2e516f7ce'
7
+ data.tar.gz: e3fcdde3228d074d75f3a47abcfb81c5145c827191512642745254df0c93c8be1f7a2f82c0d9aaed783c64951dc1b753b436e928fd29d6fc22fce6091b97ea7b
data/CHANGELOG.md CHANGED
@@ -7,6 +7,71 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2025-01-23
11
+
12
+ ### Added
13
+ - Structured Data (JSON-LD) support with comprehensive Schema.org implementation
14
+ - `StructuredData::Base` - Generic base class for all structured data types
15
+ - `StructuredData::Organization` - Company/organization information with full schema support
16
+ - `StructuredData::Article` - Blog posts, news articles with author/publisher metadata
17
+ - `StructuredData::Person` - Author profiles, team members with social links
18
+ - `StructuredData::Generator` - Helper class with factory methods
19
+ - Structured data features
20
+ - Fluent API with method chaining for all types
21
+ - Automatic nested object handling (Person within Article, etc.)
22
+ - JSON-LD generation with `to_json` and `to_script_tag` methods
23
+ - Schema.org compliant output format
24
+ - Address handling with PostalAddress schema
25
+ - Social profile links with sameAs property
26
+ - Rails integration patterns
27
+ - View helpers for structured data
28
+ - Layout integration examples
29
+ - Complete production examples
30
+ - Comprehensive documentation
31
+ - 300+ lines of structured data documentation in README
32
+ - Complete examples for Organization, Article, Person
33
+ - Rails integration patterns and helper methods
34
+ - Nested data examples
35
+
36
+ ### Test Coverage
37
+ - 491 tests passing (+107 from v0.5.0)
38
+ - 99.64% code coverage (820/823 lines)
39
+ - 117 new tests for structured data functionality
40
+
41
+ ## [0.5.0] - 2025-01-23
42
+
43
+ ### Added
44
+ - Sitemap generation system with comprehensive XML sitemap support
45
+ - `Sitemap::UrlEntry` - Individual URL entry with full sitemap.org protocol support
46
+ - `Sitemap::Builder` - Fluent API for building sitemaps with method chaining
47
+ - `Sitemap::Generator` - High-level generator with multiple generation strategies
48
+ - Sitemap generation methods
49
+ - `generate` - Generate from block with fluent builder
50
+ - `generate_from` - Generate from array of URLs
51
+ - `generate_from_collection` - Generate from model collections with lambda support
52
+ - `write_to_file` - Write sitemap directly to file with automatic directory creation
53
+ - URL entry features
54
+ - Support for all sitemap attributes: loc, lastmod, changefreq, priority
55
+ - Automatic date formatting (Date, Time, DateTime objects)
56
+ - XML entity escaping for security
57
+ - Validation for URL format, protocol (HTTP/HTTPS), and required fields
58
+ - Dynamic attribute generation
59
+ - Lambda/Proc support for dynamic lastmod, changefreq, and priority
60
+ - Conditional logic based on model attributes
61
+ - Rails integration examples
62
+ - Controller actions for dynamic sitemap serving
63
+ - Rake tasks for sitemap generation
64
+ - Service object patterns for production use
65
+ - Multi-model sitemap examples
66
+ - Comprehensive error handling
67
+ - 28 error class tests added
68
+ - Full coverage of all error types
69
+
70
+ ### Test Coverage
71
+ - 384 tests passing (+98 from v0.4.0)
72
+ - 99.71% code coverage (681/683 lines)
73
+ - 69 new tests for sitemap functionality
74
+
10
75
  ## [0.4.0] - 2025-01-23
11
76
 
12
77
  ### Added
data/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  A comprehensive SEO gem for Ruby and Rails applications. BetterSeo provides a clean, fluent DSL for managing meta tags, Open Graph, Twitter Cards, structured data, sitemaps, and more.
4
4
 
5
- [![Tests](https://img.shields.io/badge/tests-286%20passing-brightgreen)](https://github.com/yourusername/better_seo)
6
- [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/yourusername/better_seo)
5
+ [![Tests](https://img.shields.io/badge/tests-491%20passing-brightgreen)](https://github.com/yourusername/better_seo)
6
+ [![Coverage](https://img.shields.io/badge/coverage-99.64%25-brightgreen)](https://github.com/yourusername/better_seo)
7
7
  [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.0.0-red)](https://www.ruby-lang.org)
8
8
  [![Rails](https://img.shields.io/badge/rails-%3E%3D%206.1-red)](https://rubyonrails.org)
9
9
 
10
10
  ## Features
11
11
 
12
- ### ✅ Implemented (v0.2.0)
12
+ ### ✅ Implemented (v0.6.0)
13
13
 
14
14
  - **Core Configuration System**
15
15
  - Singleton configuration with block-style setup
@@ -38,23 +38,51 @@ A comprehensive SEO gem for Ruby and Rails applications. BetterSeo provides a cl
38
38
  - Automatic HTML safety with `raw` helper
39
39
  - Integration with global configuration defaults
40
40
 
41
+ - **Sitemap Generation**
42
+ - **XML Sitemap Builder**: Fluent API for building sitemaps
43
+ - **Sitemap Generator**: Generate from blocks, arrays, or model collections
44
+ - **URL Entry**: Full sitemap.org protocol support (loc, lastmod, changefreq, priority)
45
+ - **Dynamic Generation**: Lambda support for dynamic attributes
46
+ - **File Writing**: Write sitemaps directly to files
47
+ - **Rails Integration**: Controller actions and Rake tasks
48
+ - **Validation**: Automatic URL validation (format, protocol)
49
+ - **Method Chaining**: Fluent interface for adding multiple URLs
50
+
51
+ - **Structured Data (JSON-LD)**
52
+ - **Base Class**: Generic structured data with full Schema.org support
53
+ - **Organization**: Company/organization information with address, social profiles
54
+ - **Article**: Blog posts, news articles with author, publisher, metadata
55
+ - **Person**: Author profiles, team members with job title, social links
56
+ - **Generator Helper**: Factory methods and batch script tag generation
57
+ - **Nested Data**: Automatic handling of complex object relationships
58
+ - **JSON-LD Output**: Valid Schema.org JSON-LD format
59
+ - **Rails Integration**: View helpers and layout integration
60
+
41
61
  ### 🚧 Planned
42
62
 
43
- - **Advanced Generators** (v0.5.0)
44
- - Schema.org JSON-LD generator
45
- - Breadcrumbs generator
63
+ - **Additional Structured Data Types** (v0.7.0)
64
+ - Product (e-commerce with price, availability)
65
+ - BreadcrumbList (navigation breadcrumbs)
66
+ - LocalBusiness (physical locations)
67
+ - Event (conferences, webinars)
68
+ - FAQPage, HowTo, Recipe
69
+
70
+ - **Advanced Generators** (v0.7.0)
71
+ - Breadcrumbs HTML generator
46
72
  - AMP HTML generator
73
+ - Canonical URL management
47
74
 
48
- - **Advanced Rails Integration** (v0.5.0)
75
+ - **Advanced Rails Integration** (v0.7.0)
49
76
  - Controller helpers for setting page SEO
50
77
  - Railtie for automatic initialization
51
78
  - Generator for initializer file
79
+ - Automatic meta tags from model attributes
52
80
 
53
- - **Sitemap Generation** (v0.5.0)
54
- - XML sitemap builder
55
- - Automatic Rails routes integration
56
- - Multi-language sitemap support
57
- - Sitemap index for large sites
81
+ - **Advanced Sitemap Features** (v0.7.0)
82
+ - Multi-language sitemap support (hreflang)
83
+ - Sitemap index for large sites (50,000+ URLs)
84
+ - Image/video sitemap extensions
85
+ - News sitemap support
58
86
 
59
87
  - **Advanced Features** (v0.6.0+)
60
88
  - robots.txt generator
@@ -64,10 +92,12 @@ A comprehensive SEO gem for Ruby and Rails applications. BetterSeo provides a cl
64
92
 
65
93
  ## Installation
66
94
 
95
+ ### For Production Use (when published to RubyGems)
96
+
67
97
  Add this line to your application's Gemfile:
68
98
 
69
99
  ```ruby
70
- gem 'better_seo'
100
+ gem 'better_seo', '~> 0.6.0'
71
101
  ```
72
102
 
73
103
  And then execute:
@@ -82,6 +112,23 @@ Or install it yourself as:
82
112
  gem install better_seo
83
113
  ```
84
114
 
115
+ ### For Development (from source)
116
+
117
+ Add this line to your application's Gemfile:
118
+
119
+ ```ruby
120
+ gem 'better_seo', git: 'https://github.com/alessiobussolari/better_seo.git', tag: 'v0.6.0'
121
+ ```
122
+
123
+ Or clone and build locally:
124
+
125
+ ```bash
126
+ git clone https://github.com/alessiobussolari/better_seo.git
127
+ cd better_seo
128
+ gem build better_seo.gemspec
129
+ gem install better_seo-0.6.0.gem
130
+ ```
131
+
85
132
  ## Quick Start
86
133
 
87
134
  ### 1. Configuration
@@ -555,6 +602,740 @@ Then use it in your views:
555
602
  og_image: url_for(@article.cover_image) %>
556
603
  ```
557
604
 
605
+ ### 6. Sitemap Generation
606
+
607
+ BetterSeo provides a comprehensive sitemap generation system with support for XML sitemaps, dynamic content, and model collections.
608
+
609
+ #### Basic Sitemap Generation
610
+
611
+ Generate a simple sitemap using the block syntax:
612
+
613
+ ```ruby
614
+ xml = BetterSeo::Sitemap::Generator.generate do |sitemap|
615
+ sitemap.add_url("https://example.com")
616
+ sitemap.add_url("https://example.com/about")
617
+ sitemap.add_url("https://example.com/contact")
618
+ end
619
+
620
+ puts xml
621
+ # <?xml version="1.0" encoding="UTF-8"?>
622
+ # <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
623
+ # <url>
624
+ # <loc>https://example.com</loc>
625
+ # <changefreq>weekly</changefreq>
626
+ # <priority>0.5</priority>
627
+ # </url>
628
+ # ...
629
+ # </urlset>
630
+ ```
631
+
632
+ #### URL Entry with Full Attributes
633
+
634
+ Add URLs with all sitemap attributes (lastmod, changefreq, priority):
635
+
636
+ ```ruby
637
+ xml = BetterSeo::Sitemap::Generator.generate do |sitemap|
638
+ sitemap.add_url(
639
+ "https://example.com",
640
+ lastmod: Date.today,
641
+ changefreq: "daily",
642
+ priority: 1.0
643
+ )
644
+
645
+ sitemap.add_url(
646
+ "https://example.com/blog",
647
+ lastmod: "2024-01-15",
648
+ changefreq: "weekly",
649
+ priority: 0.8
650
+ )
651
+
652
+ sitemap.add_url(
653
+ "https://example.com/about",
654
+ changefreq: "monthly",
655
+ priority: 0.5
656
+ )
657
+ end
658
+ ```
659
+
660
+ **Valid changefreq values**: `always`, `hourly`, `daily`, `weekly`, `monthly`, `yearly`, `never`
661
+
662
+ **Priority range**: 0.0 to 1.0 (default: 0.5)
663
+
664
+ #### Method Chaining
665
+
666
+ The builder supports fluent method chaining:
667
+
668
+ ```ruby
669
+ xml = BetterSeo::Sitemap::Generator.generate do |sitemap|
670
+ sitemap
671
+ .add_url("https://example.com", priority: 1.0)
672
+ .add_url("https://example.com/about", priority: 0.8)
673
+ .add_url("https://example.com/contact", priority: 0.6)
674
+ end
675
+ ```
676
+
677
+ #### Generate from Array
678
+
679
+ Create a sitemap from an array of URLs:
680
+
681
+ ```ruby
682
+ urls = [
683
+ "https://example.com",
684
+ "https://example.com/about",
685
+ "https://example.com/contact",
686
+ "https://example.com/blog"
687
+ ]
688
+
689
+ xml = BetterSeo::Sitemap::Generator.generate_from(
690
+ urls,
691
+ changefreq: "weekly",
692
+ priority: 0.7
693
+ )
694
+ ```
695
+
696
+ #### Generate from Model Collection
697
+
698
+ Generate sitemaps dynamically from your Rails models:
699
+
700
+ ```ruby
701
+ # Simple example with Post model
702
+ xml = BetterSeo::Sitemap::Generator.generate_from_collection(
703
+ Post.published,
704
+ url: ->(post) { "https://example.com/posts/#{post.slug}" },
705
+ lastmod: ->(post) { post.updated_at },
706
+ changefreq: "weekly",
707
+ priority: 0.8
708
+ )
709
+ ```
710
+
711
+ **Dynamic attributes with lambdas**:
712
+
713
+ ```ruby
714
+ xml = BetterSeo::Sitemap::Generator.generate_from_collection(
715
+ Article.all,
716
+ url: ->(article) { "https://example.com/articles/#{article.slug}" },
717
+ lastmod: ->(article) { article.updated_at },
718
+ changefreq: ->(article) do
719
+ article.featured? ? "daily" : "weekly"
720
+ end,
721
+ priority: ->(article) do
722
+ article.featured? ? 0.9 : 0.6
723
+ end
724
+ )
725
+ ```
726
+
727
+ #### Rails Routes Integration
728
+
729
+ Generate sitemap from Rails routes:
730
+
731
+ ```ruby
732
+ # config/routes.rb
733
+ Rails.application.routes.draw do
734
+ # Your routes...
735
+
736
+ # Sitemap endpoint
737
+ get '/sitemap.xml', to: 'sitemaps#index', defaults: { format: 'xml' }
738
+ end
739
+
740
+ # app/controllers/sitemaps_controller.rb
741
+ class SitemapsController < ApplicationController
742
+ def index
743
+ @sitemap_xml = generate_sitemap
744
+ render xml: @sitemap_xml
745
+ end
746
+
747
+ private
748
+
749
+ def generate_sitemap
750
+ BetterSeo::Sitemap::Generator.generate do |sitemap|
751
+ # Static pages
752
+ sitemap.add_url(root_url, priority: 1.0, changefreq: "daily")
753
+ sitemap.add_url(about_url, priority: 0.8, changefreq: "monthly")
754
+ sitemap.add_url(contact_url, priority: 0.7, changefreq: "monthly")
755
+
756
+ # Dynamic content from models
757
+ Post.published.find_each do |post|
758
+ sitemap.add_url(
759
+ post_url(post),
760
+ lastmod: post.updated_at,
761
+ changefreq: "weekly",
762
+ priority: 0.8
763
+ )
764
+ end
765
+
766
+ Category.all.find_each do |category|
767
+ sitemap.add_url(
768
+ category_url(category),
769
+ lastmod: category.updated_at,
770
+ changefreq: "weekly",
771
+ priority: 0.7
772
+ )
773
+ end
774
+ end
775
+ end
776
+ end
777
+ ```
778
+
779
+ #### Write Sitemap to File
780
+
781
+ Save sitemap directly to a file:
782
+
783
+ ```ruby
784
+ # In a Rake task or script
785
+ BetterSeo::Sitemap::Generator.write_to_file('public/sitemap.xml') do |sitemap|
786
+ sitemap.add_url("https://example.com", priority: 1.0)
787
+
788
+ Post.published.find_each do |post|
789
+ sitemap.add_url(
790
+ "https://example.com/posts/#{post.slug}",
791
+ lastmod: post.updated_at,
792
+ changefreq: "weekly",
793
+ priority: 0.8
794
+ )
795
+ end
796
+ end
797
+
798
+ # Returns the file path: "public/sitemap.xml"
799
+ ```
800
+
801
+ #### Rake Task for Sitemap Generation
802
+
803
+ Create a Rake task to regenerate your sitemap:
804
+
805
+ ```ruby
806
+ # lib/tasks/sitemap.rake
807
+ namespace :sitemap do
808
+ desc "Generate sitemap.xml"
809
+ task generate: :environment do
810
+ file_path = BetterSeo::Sitemap::Generator.write_to_file('public/sitemap.xml') do |sitemap|
811
+ # Add static pages
812
+ sitemap.add_url("#{ENV['SITE_URL']}", priority: 1.0, changefreq: "daily")
813
+ sitemap.add_url("#{ENV['SITE_URL']}/about", priority: 0.8)
814
+ sitemap.add_url("#{ENV['SITE_URL']}/contact", priority: 0.7)
815
+
816
+ # Add dynamic content
817
+ Post.published.find_each do |post|
818
+ sitemap.add_url(
819
+ "#{ENV['SITE_URL']}/posts/#{post.slug}",
820
+ lastmod: post.updated_at,
821
+ changefreq: "weekly",
822
+ priority: 0.8
823
+ )
824
+ end
825
+ end
826
+
827
+ puts "Sitemap generated at #{file_path}"
828
+ end
829
+ end
830
+
831
+ # Run with: rake sitemap:generate
832
+ ```
833
+
834
+ #### Using the Builder Directly
835
+
836
+ For more control, use the Builder class directly:
837
+
838
+ ```ruby
839
+ builder = BetterSeo::Sitemap::Builder.new
840
+
841
+ # Add URLs
842
+ builder.add_url("https://example.com", priority: 1.0)
843
+ builder.add_url("https://example.com/about", priority: 0.8)
844
+
845
+ # Add multiple URLs at once
846
+ builder.add_urls(
847
+ ["https://example.com/blog", "https://example.com/contact"],
848
+ changefreq: "weekly",
849
+ priority: 0.7
850
+ )
851
+
852
+ # Remove a URL
853
+ builder.remove_url("https://example.com/contact")
854
+
855
+ # Check size
856
+ puts builder.size # => 3
857
+
858
+ # Iterate over URLs
859
+ builder.each do |url|
860
+ puts "#{url.loc} - Priority: #{url.priority}"
861
+ end
862
+
863
+ # Generate XML
864
+ xml = builder.to_xml
865
+
866
+ # Validate all URLs
867
+ builder.validate! # Raises ValidationError if any URL is invalid
868
+
869
+ # Clear all URLs
870
+ builder.clear
871
+ ```
872
+
873
+ #### URL Entry Details
874
+
875
+ Work with individual URL entries:
876
+
877
+ ```ruby
878
+ entry = BetterSeo::Sitemap::UrlEntry.new(
879
+ "https://example.com/page",
880
+ lastmod: Date.today,
881
+ changefreq: "daily",
882
+ priority: 0.8
883
+ )
884
+
885
+ # Access attributes
886
+ entry.loc # => "https://example.com/page"
887
+ entry.lastmod # => "2024-01-15"
888
+ entry.changefreq # => "daily"
889
+ entry.priority # => 0.8
890
+
891
+ # Update attributes
892
+ entry.lastmod = Date.new(2024, 1, 20)
893
+ entry.changefreq = "weekly"
894
+ entry.priority = 0.9
895
+
896
+ # Generate XML for single entry
897
+ entry.to_xml
898
+ # <url>
899
+ # <loc>https://example.com/page</loc>
900
+ # <lastmod>2024-01-20</lastmod>
901
+ # <changefreq>weekly</changefreq>
902
+ # <priority>0.9</priority>
903
+ # </url>
904
+
905
+ # Convert to hash
906
+ entry.to_h
907
+ # {
908
+ # loc: "https://example.com/page",
909
+ # lastmod: "2024-01-20",
910
+ # changefreq: "weekly",
911
+ # priority: 0.9
912
+ # }
913
+
914
+ # Validate
915
+ entry.validate! # Raises ValidationError if invalid
916
+ ```
917
+
918
+ #### Advanced: Multi-Model Sitemap
919
+
920
+ Combine multiple models in a single sitemap:
921
+
922
+ ```ruby
923
+ xml = BetterSeo::Sitemap::Generator.generate do |sitemap|
924
+ # Homepage
925
+ sitemap.add_url("https://example.com", priority: 1.0, changefreq: "daily")
926
+
927
+ # Blog posts
928
+ Post.published.find_each do |post|
929
+ sitemap.add_url(
930
+ "https://example.com/posts/#{post.slug}",
931
+ lastmod: post.updated_at,
932
+ changefreq: post.featured? ? "daily" : "weekly",
933
+ priority: post.featured? ? 0.9 : 0.7
934
+ )
935
+ end
936
+
937
+ # Categories
938
+ Category.all.find_each do |category|
939
+ sitemap.add_url(
940
+ "https://example.com/categories/#{category.slug}",
941
+ lastmod: category.updated_at,
942
+ changefreq: "weekly",
943
+ priority: 0.6
944
+ )
945
+ end
946
+
947
+ # Static pages
948
+ %w[about contact privacy terms].each do |page|
949
+ sitemap.add_url(
950
+ "https://example.com/#{page}",
951
+ changefreq: "monthly",
952
+ priority: 0.5
953
+ )
954
+ end
955
+ end
956
+ ```
957
+
958
+ #### Validation
959
+
960
+ All URLs are automatically validated when generating:
961
+
962
+ ```ruby
963
+ # This will raise BetterSeo::ValidationError
964
+ xml = BetterSeo::Sitemap::Generator.generate do |sitemap|
965
+ sitemap.add_url("") # Error: Location is required
966
+ sitemap.add_url("not-a-valid-url") # Error: Invalid URL format
967
+ sitemap.add_url("ftp://example.com") # Error: Must be HTTP/HTTPS
968
+ end
969
+
970
+ # Validate manually
971
+ builder = BetterSeo::Sitemap::Builder.new
972
+ builder.add_url("https://example.com")
973
+ builder.validate! # Returns true if all URLs valid
974
+ ```
975
+
976
+ #### Complete Example: Production Sitemap
977
+
978
+ ```ruby
979
+ # app/services/sitemap_generator_service.rb
980
+ class SitemapGeneratorService
981
+ def self.generate
982
+ BetterSeo::Sitemap::Generator.write_to_file(Rails.root.join('public', 'sitemap.xml')) do |sitemap|
983
+ add_static_pages(sitemap)
984
+ add_blog_posts(sitemap)
985
+ add_categories(sitemap)
986
+ add_products(sitemap) if defined?(Product)
987
+ end
988
+ end
989
+
990
+ private_class_method def self.add_static_pages(sitemap)
991
+ sitemap.add_url(Rails.application.routes.url_helpers.root_url, priority: 1.0, changefreq: "daily")
992
+ sitemap.add_url(Rails.application.routes.url_helpers.about_url, priority: 0.8, changefreq: "monthly")
993
+ sitemap.add_url(Rails.application.routes.url_helpers.contact_url, priority: 0.7, changefreq: "monthly")
994
+ end
995
+
996
+ private_class_method def self.add_blog_posts(sitemap)
997
+ Post.published.find_each do |post|
998
+ sitemap.add_url(
999
+ Rails.application.routes.url_helpers.post_url(post),
1000
+ lastmod: post.updated_at,
1001
+ changefreq: post.frequently_updated? ? "daily" : "weekly",
1002
+ priority: calculate_post_priority(post)
1003
+ )
1004
+ end
1005
+ end
1006
+
1007
+ private_class_method def self.add_categories(sitemap)
1008
+ Category.all.find_each do |category|
1009
+ sitemap.add_url(
1010
+ Rails.application.routes.url_helpers.category_url(category),
1011
+ lastmod: category.posts.maximum(:updated_at),
1012
+ changefreq: "weekly",
1013
+ priority: 0.6
1014
+ )
1015
+ end
1016
+ end
1017
+
1018
+ private_class_method def self.add_products(sitemap)
1019
+ Product.available.find_each do |product|
1020
+ sitemap.add_url(
1021
+ Rails.application.routes.url_helpers.product_url(product),
1022
+ lastmod: product.updated_at,
1023
+ changefreq: "daily",
1024
+ priority: product.featured? ? 0.95 : 0.75
1025
+ )
1026
+ end
1027
+ end
1028
+
1029
+ private_class_method def self.calculate_post_priority(post)
1030
+ base_priority = 0.7
1031
+ base_priority += 0.2 if post.featured?
1032
+ base_priority += 0.1 if post.comments_count > 10
1033
+ [base_priority, 1.0].min
1034
+ end
1035
+ end
1036
+
1037
+ # Call from rake task or controller:
1038
+ # SitemapGeneratorService.generate
1039
+ ```
1040
+
1041
+ ### 7. Structured Data (JSON-LD)
1042
+
1043
+ BetterSeo provides comprehensive support for Schema.org structured data using JSON-LD format, helping search engines better understand your content.
1044
+
1045
+ #### Basic Usage
1046
+
1047
+ Create structured data objects and generate JSON-LD script tags:
1048
+
1049
+ ```ruby
1050
+ # Create an Organization
1051
+ org = BetterSeo::StructuredData::Organization.new
1052
+ org.name("Acme Corporation")
1053
+ org.url("https://www.acme.com")
1054
+ org.logo("https://www.acme.com/logo.png")
1055
+ org.description("Leading provider of innovative solutions")
1056
+
1057
+ # Generate JSON-LD script tag
1058
+ org.to_script_tag
1059
+ # <script type="application/ld+json">
1060
+ # {
1061
+ # "@context": "https://schema.org",
1062
+ # "@type": "Organization",
1063
+ # "name": "Acme Corporation",
1064
+ # ...
1065
+ # }
1066
+ # </script>
1067
+ ```
1068
+
1069
+ #### Available Types
1070
+
1071
+ **Organization** - Company/organization information:
1072
+
1073
+ ```ruby
1074
+ org = BetterSeo::StructuredData::Organization.new
1075
+ org.name("Tech Innovations Inc")
1076
+ org.url("https://techinnovations.com")
1077
+ org.logo("https://techinnovations.com/logo.png")
1078
+ org.description("Innovative technology solutions")
1079
+ org.email("contact@techinnovations.com")
1080
+ org.telephone("+1-555-0100")
1081
+ org.address(
1082
+ street: "123 Tech Boulevard",
1083
+ city: "San Francisco",
1084
+ region: "CA",
1085
+ postal_code: "94105",
1086
+ country: "US"
1087
+ )
1088
+ org.same_as([
1089
+ "https://twitter.com/techinnovations",
1090
+ "https://linkedin.com/company/techinnovations"
1091
+ ])
1092
+ org.founding_date("2015-03-20")
1093
+ ```
1094
+
1095
+ **Article** - Blog posts, news articles, content:
1096
+
1097
+ ```ruby
1098
+ article = BetterSeo::StructuredData::Article.new
1099
+ article.headline("The Future of Web Development")
1100
+ article.description("An in-depth analysis of emerging trends")
1101
+ article.image("https://example.com/article-image.jpg")
1102
+ article.author("Jane Smith") # Or use Person object
1103
+ article.date_published("2024-01-15T09:00:00Z")
1104
+ article.date_modified("2024-01-20T14:30:00Z")
1105
+ article.url("https://example.com/articles/future-of-web-dev")
1106
+ article.word_count(2500)
1107
+ article.keywords(["Web Development", "Technology", "Trends"])
1108
+ article.article_section("Technology")
1109
+ ```
1110
+
1111
+ **Person** - Author profiles, team members:
1112
+
1113
+ ```ruby
1114
+ person = BetterSeo::StructuredData::Person.new
1115
+ person.name("Dr. Jane Smith")
1116
+ person.given_name("Jane")
1117
+ person.family_name("Smith")
1118
+ person.email("jane@example.com")
1119
+ person.url("https://janesmith.dev")
1120
+ person.image("https://janesmith.dev/profile.jpg")
1121
+ person.job_title("Chief Technology Officer")
1122
+ person.telephone("+1-555-0199")
1123
+ person.same_as([
1124
+ "https://twitter.com/janesmith",
1125
+ "https://linkedin.com/in/janesmith"
1126
+ ])
1127
+ ```
1128
+
1129
+ #### Method Chaining
1130
+
1131
+ All structured data classes support fluent method chaining:
1132
+
1133
+ ```ruby
1134
+ org = BetterSeo::StructuredData::Organization.new
1135
+ .name("Acme Corp")
1136
+ .url("https://acme.com")
1137
+ .logo("https://acme.com/logo.png")
1138
+ .description("Innovation at its finest")
1139
+ ```
1140
+
1141
+ #### Nested Structured Data
1142
+
1143
+ Combine multiple structured data objects:
1144
+
1145
+ ```ruby
1146
+ # Create publisher organization
1147
+ publisher = BetterSeo::StructuredData::Organization.new
1148
+ publisher.name("Tech Publishing Co")
1149
+ publisher.logo("https://techpub.com/logo.png")
1150
+
1151
+ # Create author person
1152
+ author = BetterSeo::StructuredData::Person.new
1153
+ author.name("Jane Smith")
1154
+ author.email("jane@techpub.com")
1155
+ author.url("https://janesmith.dev")
1156
+
1157
+ # Create article with nested data
1158
+ article = BetterSeo::StructuredData::Article.new
1159
+ article.headline("Introduction to Ruby on Rails")
1160
+ article.description("A comprehensive guide for beginners")
1161
+ article.image("https://example.com/rails-guide.jpg")
1162
+ article.author(author) # Nested Person
1163
+ article.publisher(publisher) # Nested Organization
1164
+ article.date_published("2024-01-15")
1165
+
1166
+ # Generates nested JSON-LD automatically
1167
+ article.to_script_tag
1168
+ ```
1169
+
1170
+ #### Using the Generator Helper
1171
+
1172
+ The Generator class provides convenient factory methods:
1173
+
1174
+ ```ruby
1175
+ # Create with block
1176
+ org = BetterSeo::StructuredData::Generator.organization do |o|
1177
+ o.name("Acme Corp")
1178
+ o.url("https://acme.com")
1179
+ o.logo("https://acme.com/logo.png")
1180
+ end
1181
+
1182
+ article = BetterSeo::StructuredData::Generator.article do |a|
1183
+ a.headline("Amazing Article")
1184
+ a.author("John Doe")
1185
+ a.date_published("2024-01-15")
1186
+ end
1187
+
1188
+ person = BetterSeo::StructuredData::Generator.person do |p|
1189
+ p.name("John Doe")
1190
+ p.email("john@example.com")
1191
+ end
1192
+
1193
+ # Generate multiple script tags at once
1194
+ tags = BetterSeo::StructuredData::Generator.generate_script_tags([org, article, person])
1195
+ # Returns all three script tags joined with newlines
1196
+ ```
1197
+
1198
+ #### Rails Integration
1199
+
1200
+ Add structured data to your Rails views:
1201
+
1202
+ ```erb
1203
+ <%# app/views/articles/show.html.erb %>
1204
+ <%
1205
+ author = BetterSeo::StructuredData::Generator.person do |p|
1206
+ p.name(@article.author.name)
1207
+ p.email(@article.author.email)
1208
+ p.url(@article.author.website)
1209
+ end
1210
+
1211
+ article_sd = BetterSeo::StructuredData::Generator.article do |a|
1212
+ a.headline(@article.title)
1213
+ a.description(@article.excerpt)
1214
+ a.image(url_for(@article.cover_image))
1215
+ a.author(author)
1216
+ a.date_published(@article.published_at.iso8601)
1217
+ a.date_modified(@article.updated_at.iso8601)
1218
+ a.url(article_url(@article))
1219
+ a.word_count(@article.word_count)
1220
+ a.keywords(@article.tags.pluck(:name))
1221
+ end
1222
+ %>
1223
+
1224
+ <%== article_sd.to_script_tag %>
1225
+ ```
1226
+
1227
+ Or in a helper:
1228
+
1229
+ ```ruby
1230
+ # app/helpers/structured_data_helper.rb
1231
+ module StructuredDataHelper
1232
+ def article_structured_data(article)
1233
+ author = BetterSeo::StructuredData::Generator.person do |p|
1234
+ p.name(article.author.name)
1235
+ p.url(author_url(article.author))
1236
+ end
1237
+
1238
+ article_sd = BetterSeo::StructuredData::Generator.article do |a|
1239
+ a.headline(article.title)
1240
+ a.description(article.excerpt)
1241
+ a.author(author)
1242
+ a.date_published(article.published_at.iso8601)
1243
+ a.url(article_url(article))
1244
+ end
1245
+
1246
+ article_sd.to_script_tag.html_safe
1247
+ end
1248
+
1249
+ def organization_structured_data
1250
+ org = BetterSeo::StructuredData::Generator.organization do |o|
1251
+ o.name(Rails.application.config.site_name)
1252
+ o.url(root_url)
1253
+ o.logo(image_url('logo.png'))
1254
+ o.same_as([
1255
+ "https://twitter.com/yourcompany",
1256
+ "https://facebook.com/yourcompany"
1257
+ ])
1258
+ end
1259
+
1260
+ org.to_script_tag.html_safe
1261
+ end
1262
+ end
1263
+ ```
1264
+
1265
+ Then in your layout:
1266
+
1267
+ ```erb
1268
+ <%# app/views/layouts/application.html.erb %>
1269
+ <head>
1270
+ ...
1271
+ <%= organization_structured_data %>
1272
+ </head>
1273
+ ```
1274
+
1275
+ And in article views:
1276
+
1277
+ ```erb
1278
+ <%# app/views/articles/show.html.erb %>
1279
+ <%= article_structured_data(@article) %>
1280
+ ```
1281
+
1282
+ #### Complete Example
1283
+
1284
+ ```ruby
1285
+ # Create complete structured data for a blog article
1286
+ publisher = BetterSeo::StructuredData::Generator.organization do |o|
1287
+ o.name("Tech Blog Publishing")
1288
+ o.url("https://techblog.com")
1289
+ o.logo("https://techblog.com/logo.png")
1290
+ end
1291
+
1292
+ author = BetterSeo::StructuredData::Generator.person do |p|
1293
+ p.name("Dr. Sarah Johnson")
1294
+ p.email("sarah@techblog.com")
1295
+ p.url("https://sarahjohnson.dev")
1296
+ p.job_title("Senior Technology Writer")
1297
+ end
1298
+
1299
+ article = BetterSeo::StructuredData::Generator.article do |a|
1300
+ a.headline("The Complete Guide to Ruby on Rails in 2024")
1301
+ a.description("Everything you need to know about Rails")
1302
+ a.image([
1303
+ "https://techblog.com/images/rails-2024-1.jpg",
1304
+ "https://techblog.com/images/rails-2024-2.jpg"
1305
+ ])
1306
+ a.author(author)
1307
+ a.publisher(publisher)
1308
+ a.date_published("2024-01-15T09:00:00Z")
1309
+ a.date_modified("2024-01-20T15:30:00Z")
1310
+ a.url("https://techblog.com/rails-complete-guide-2024")
1311
+ a.word_count(3500)
1312
+ a.keywords(["Ruby on Rails", "Web Development", "2024"])
1313
+ a.article_section("Programming")
1314
+ end
1315
+
1316
+ # Get JSON-LD
1317
+ json_ld = article.to_json
1318
+
1319
+ # Get script tag for HTML
1320
+ script_tag = article.to_script_tag
1321
+
1322
+ # Or generate all at once
1323
+ all_tags = BetterSeo::StructuredData::Generator.generate_script_tags([
1324
+ publisher,
1325
+ author,
1326
+ article
1327
+ ])
1328
+ ```
1329
+
1330
+ #### Benefits
1331
+
1332
+ - **SEO Enhancement**: Help search engines understand your content better
1333
+ - **Rich Snippets**: Enable rich results in search results (ratings, images, etc.)
1334
+ - **Type Safety**: Fluent API with method chaining
1335
+ - **Nested Data**: Automatic handling of complex relationships
1336
+ - **Standards Compliant**: Follows Schema.org specifications
1337
+ - **Easy Integration**: Works seamlessly with Rails views and helpers
1338
+
558
1339
  ## Configuration Reference
559
1340
 
560
1341
  ### Global Configuration
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BetterSeo
4
- VERSION = "0.4.0"
4
+ VERSION = "0.6.0"
5
5
  end
data/lib/better_seo.rb CHANGED
@@ -14,6 +14,14 @@ require_relative "better_seo/generators/meta_tags_generator"
14
14
  require_relative "better_seo/generators/open_graph_generator"
15
15
  require_relative "better_seo/generators/twitter_cards_generator"
16
16
  require_relative "better_seo/rails/helpers/seo_helper"
17
+ require_relative "better_seo/sitemap/url_entry"
18
+ require_relative "better_seo/sitemap/builder"
19
+ require_relative "better_seo/sitemap/generator"
20
+ require_relative "better_seo/structured_data/base"
21
+ require_relative "better_seo/structured_data/organization"
22
+ require_relative "better_seo/structured_data/article"
23
+ require_relative "better_seo/structured_data/person"
24
+ require_relative "better_seo/structured_data/generator"
17
25
 
18
26
  module BetterSeo
19
27
  class << self
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better_seo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - alessiobussolari
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-22 00:00:00.000000000 Z
11
+ date: 2025-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -53,8 +53,8 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.22'
55
55
  description: BetterSeo provides a clean, fluent DSL for managing meta tags, Open Graph,
56
- Twitter Cards, and more. Features include automatic HTML generation, validation,
57
- Rails view helpers, and 100% test coverage.
56
+ Twitter Cards, XML sitemaps, and more. Features include automatic HTML generation,
57
+ dynamic sitemap generation, validation, Rails integration, and 99.7% test coverage.
58
58
  email:
59
59
  - alessio@cosmic.tech
60
60
  executables: []