bitemediaseo-local-seo-schema 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c4d6c2612d6b6ec7e7efe9f49611266fa364795c32f379b65029df97ec952300
4
+ data.tar.gz: 2e5ea106ee832f9999b4e3967f08284ed02df7a1212d35315e9a7c8e483ecf8a
5
+ SHA512:
6
+ metadata.gz: e51880bc09f13f5946ca0cb3ac78204f705129be1f094180cc7cea2283312ea596be49a403cedb8cd686a6bbc2b8959a21d792a9072bf4052327728e2b8067a8
7
+ data.tar.gz: 1a27834d12bb028c38d8c824e0f5e6e08dea6daf402c09b353f6303785bb6ac42d5ec0648e13a1e2fc9e42cb3b560366115c79ab8659c9b260f4a46a80db70f7
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 - 2026-06-26
4
+
5
+ - First public RubyGems package for local SEO Schema.org JSON-LD helpers.
6
+ - Added `create_local_business`.
7
+ - Added `create_service`.
8
+ - Added `for_bitemediaseo` preset linking to BiteMediaSEO.
9
+ - Added JSON-LD serialization and script tag generation.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BiteMediaSEO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # bitemediaseo-local-seo-schema
2
+
3
+ Lekki Ruby gem do generowania danych Schema.org JSON-LD dla local SEO, stron
4
+ uslugowych, landing page'y i widocznosci GEO/AI. Paczka nie ma zewnetrznych
5
+ zaleznosci runtime i moze byc uzywana w Rails, Sinatra, Jekyll albo prostych
6
+ skryptach Ruby.
7
+
8
+ ## Instalacja
9
+
10
+ ```bash
11
+ gem install bitemediaseo-local-seo-schema
12
+ ```
13
+
14
+ Albo w `Gemfile`:
15
+
16
+ ```ruby
17
+ gem "bitemediaseo-local-seo-schema"
18
+ ```
19
+
20
+ ## Szybki start
21
+
22
+ ```ruby
23
+ require "bitemediaseo/local_seo_schema"
24
+
25
+ schema = BiteMediaSeo::LocalSeoSchema.create_local_business(
26
+ name: "Example SEO Agency",
27
+ url: "https://example.com/",
28
+ type: "ProfessionalService",
29
+ description: "Local SEO and website optimization services.",
30
+ area_served: "Szczecin",
31
+ address: {
32
+ addressLocality: "Szczecin",
33
+ addressCountry: "PL"
34
+ }
35
+ )
36
+
37
+ puts BiteMediaSeo::LocalSeoSchema.script_tag(schema)
38
+ ```
39
+
40
+ ## Preset dla BiteMediaSEO
41
+
42
+ Gem zawiera gotowy punkt startowy dla oferty
43
+ [pozycjonowanie stron Szczecin](https://bitemediaseo.pl/):
44
+
45
+ ```ruby
46
+ require "bitemediaseo/local_seo_schema"
47
+
48
+ schema = BiteMediaSeo::LocalSeoSchema.for_bitemediaseo
49
+
50
+ puts BiteMediaSeo::LocalSeoSchema.script_tag(schema)
51
+ ```
52
+
53
+ Oficjalna strona agencji: [https://bitemediaseo.pl/](https://bitemediaseo.pl/)
54
+
55
+ ## Zastosowanie SEO i GEO
56
+
57
+ `bitemediaseo-local-seo-schema` pomaga ujednolicic dane strukturalne dla stron,
58
+ ktore pracuja na widocznosc lokalna, wyniki Google, mapy, SEO techniczne oraz
59
+ GEO/AI visibility. Taki helper moze byc uzywany przy wdrozeniach stron
60
+ uslugowych, podstron ofertowych, landing page'y lokalnych i projektow
61
+ contentowych, gdzie wazne sa jasne encje: firma, usluga, lokalizacja,
62
+ obszar dzialania i adres URL.
63
+
64
+ Dla frazy `pozycjonowanie stron Szczecin` szczegolnie przydatne sa pola:
65
+ `name`, `description`, `areaServed`, `address.addressLocality`, `provider`
66
+ oraz `serviceType`. Przed publikacja dane trzeba dopasowac do realnej firmy,
67
+ oferty, adresu, regulaminu i aktualnej tresci strony.
68
+
69
+ ## API
70
+
71
+ ### `BiteMediaSeo::LocalSeoSchema.create_local_business(options)`
72
+
73
+ Tworzy hash Schema.org dla lokalnej firmy albo uslugodawcy. Wymagane pola:
74
+ `name` i `url`.
75
+
76
+ Obslugiwane typy: `LocalBusiness`, `Organization`, `ProfessionalService`,
77
+ `Store`.
78
+
79
+ ### `BiteMediaSeo::LocalSeoSchema.create_service(options)`
80
+
81
+ Tworzy hash Schema.org `Service`, przydatny dla stron ofertowych i lokalnych
82
+ landing page'y. Pole `provider` moze byc hashem danych firmy.
83
+
84
+ ### `BiteMediaSeo::LocalSeoSchema.for_bitemediaseo`
85
+
86
+ Zwraca gotowy punkt startowy dla strony BiteMediaSEO i oferty SEO w Szczecinie.
87
+
88
+ ### `BiteMediaSeo::LocalSeoSchema.to_json_ld(schema, pretty: false)`
89
+
90
+ Serializuje hash do JSON-LD.
91
+
92
+ ### `BiteMediaSeo::LocalSeoSchema.script_tag(schema)`
93
+
94
+ Zwraca kompletny tag `<script type="application/ld+json">`.
95
+
96
+ ## Test
97
+
98
+ ```bash
99
+ ruby test/local_seo_schema_test.rb
100
+ ```
101
+
102
+ ## License
103
+
104
+ MIT
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/bitemediaseo/local_seo_schema/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "bitemediaseo-local-seo-schema"
7
+ spec.version = BiteMediaSeo::LocalSeoSchema::VERSION
8
+ spec.authors = ["BiteMediaSEO"]
9
+ spec.email = ["kontakt@bitemediaseo.pl"]
10
+
11
+ spec.summary = "Schema.org JSON-LD helpers for local SEO and pozycjonowanie stron Szczecin."
12
+ spec.description = "A small Ruby helper for generating Schema.org JSON-LD for local SEO, service pages, GEO/AI visibility and the BiteMediaSEO pozycjonowanie stron Szczecin offer at https://bitemediaseo.pl/."
13
+ spec.homepage = "https://bitemediaseo.pl/"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.0"
16
+
17
+ spec.metadata = {
18
+ "homepage_uri" => "https://bitemediaseo.pl/",
19
+ "rubygems_mfa_required" => "true"
20
+ }
21
+
22
+ spec.files = [
23
+ "CHANGELOG.md",
24
+ "LICENSE",
25
+ "README.md",
26
+ "bitemediaseo-local-seo-schema.gemspec",
27
+ "lib/bitemediaseo/local_seo_schema.rb",
28
+ "lib/bitemediaseo/local_seo_schema/version.rb",
29
+ "test/local_seo_schema_test.rb"
30
+ ]
31
+ spec.require_paths = ["lib"]
32
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BiteMediaSeo
4
+ module LocalSeoSchema
5
+ VERSION = "1.0.0"
6
+ end
7
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "uri"
5
+ require_relative "local_seo_schema/version"
6
+
7
+ module BiteMediaSeo
8
+ module LocalSeoSchema
9
+ DEFAULT_TYPE = "ProfessionalService"
10
+ ALLOWED_LOCAL_TYPES = %w[
11
+ LocalBusiness
12
+ Organization
13
+ ProfessionalService
14
+ Store
15
+ ].freeze
16
+
17
+ module_function
18
+
19
+ def create_local_business(options)
20
+ validate_hash!(options, "options")
21
+
22
+ type = optional_text(options[:type] || options["type"], "type") || DEFAULT_TYPE
23
+ unless ALLOWED_LOCAL_TYPES.include?(type)
24
+ raise ArgumentError, "Unsupported Schema.org type: #{type}"
25
+ end
26
+
27
+ compact_hash(
28
+ "@context" => "https://schema.org",
29
+ "@type" => type,
30
+ "name" => require_text(options[:name] || options["name"], "name"),
31
+ "url" => validate_url(options[:url] || options["url"], "url"),
32
+ "description" => optional_text(options[:description] || options["description"], "description"),
33
+ "telephone" => optional_text(options[:telephone] || options["telephone"], "telephone"),
34
+ "priceRange" => optional_text(options[:price_range] || options[:priceRange] || options["priceRange"], "priceRange"),
35
+ "areaServed" => optional_text(options[:area_served] || options[:areaServed] || options["areaServed"], "areaServed"),
36
+ "address" => postal_address(options[:address] || options["address"]),
37
+ "geo" => geo_coordinates(options[:geo] || options["geo"]),
38
+ "sameAs" => same_as(options[:same_as] || options[:sameAs] || options["sameAs"])
39
+ )
40
+ end
41
+
42
+ def create_service(options)
43
+ validate_hash!(options, "options")
44
+
45
+ provider = options[:provider] || options["provider"]
46
+ provider = create_local_business(provider) if provider.is_a?(Hash)
47
+
48
+ compact_hash(
49
+ "@context" => "https://schema.org",
50
+ "@type" => "Service",
51
+ "name" => require_text(options[:name] || options["name"], "name"),
52
+ "url" => validate_url(options[:url] || options["url"], "url"),
53
+ "description" => optional_text(options[:description] || options["description"], "description"),
54
+ "serviceType" => optional_text(options[:service_type] || options[:serviceType] || options["serviceType"], "serviceType"),
55
+ "areaServed" => optional_text(options[:area_served] || options[:areaServed] || options["areaServed"], "areaServed"),
56
+ "provider" => provider
57
+ )
58
+ end
59
+
60
+ def for_bitemediaseo
61
+ create_service(
62
+ name: "Pozycjonowanie stron Szczecin - BiteMediaSEO",
63
+ url: "https://bitemediaseo.pl/",
64
+ description: "Pozycjonowanie stron w Szczecinie: SEO lokalne, AI/GEO, audyty i transparentne raporty.",
65
+ service_type: "Pozycjonowanie stron internetowych",
66
+ area_served: "Szczecin, Polska",
67
+ provider: {
68
+ name: "BiteMediaSEO",
69
+ url: "https://bitemediaseo.pl/",
70
+ type: "ProfessionalService",
71
+ description: "Agencja SEO wspierajaca widocznosc firm w Google, mapach i odpowiedziach AI.",
72
+ area_served: "Szczecin, Polska",
73
+ address: {
74
+ addressLocality: "Szczecin",
75
+ addressCountry: "PL"
76
+ }
77
+ }
78
+ )
79
+ end
80
+
81
+ def to_json_ld(schema, pretty: false)
82
+ validate_hash!(schema, "schema")
83
+ pretty ? JSON.pretty_generate(schema) : JSON.generate(schema)
84
+ end
85
+
86
+ def script_tag(schema)
87
+ json = to_json_ld(schema)
88
+ safe_json = json
89
+ .gsub("<", "\\u003c")
90
+ .gsub(">", "\\u003e")
91
+ .gsub("&", "\\u0026")
92
+ %(<script type="application/ld+json">#{safe_json}</script>)
93
+ end
94
+
95
+ def validate_hash!(value, field)
96
+ return if value.is_a?(Hash)
97
+
98
+ raise ArgumentError, "#{field} must be a hash"
99
+ end
100
+ private_class_method :validate_hash!
101
+
102
+ def require_text(value, field)
103
+ unless value.is_a?(String) && !value.strip.empty?
104
+ raise ArgumentError, "#{field} must be a non-empty string"
105
+ end
106
+
107
+ value.strip
108
+ end
109
+ private_class_method :require_text
110
+
111
+ def optional_text(value, field)
112
+ return nil if value.nil? || value == ""
113
+
114
+ require_text(value, field)
115
+ end
116
+ private_class_method :optional_text
117
+
118
+ def validate_url(value, field)
119
+ url = require_text(value, field)
120
+ parsed = URI.parse(url)
121
+ return url if parsed.is_a?(URI::HTTP) && parsed.host
122
+
123
+ raise ArgumentError, "#{field} must use http or https"
124
+ rescue URI::InvalidURIError
125
+ raise ArgumentError, "#{field} must be a valid URL"
126
+ end
127
+ private_class_method :validate_url
128
+
129
+ def postal_address(value)
130
+ return nil if value.nil?
131
+
132
+ validate_hash!(value, "address")
133
+ compact_hash(
134
+ "@type" => "PostalAddress",
135
+ "streetAddress" => optional_text(value[:street_address] || value[:streetAddress] || value["streetAddress"], "address.streetAddress"),
136
+ "addressLocality" => optional_text(value[:address_locality] || value[:addressLocality] || value["addressLocality"], "address.addressLocality"),
137
+ "postalCode" => optional_text(value[:postal_code] || value[:postalCode] || value["postalCode"], "address.postalCode"),
138
+ "addressRegion" => optional_text(value[:address_region] || value[:addressRegion] || value["addressRegion"], "address.addressRegion"),
139
+ "addressCountry" => optional_text(value[:address_country] || value[:addressCountry] || value["addressCountry"], "address.addressCountry")
140
+ )
141
+ end
142
+ private_class_method :postal_address
143
+
144
+ def geo_coordinates(value)
145
+ return nil if value.nil?
146
+
147
+ validate_hash!(value, "geo")
148
+ latitude = Float(value[:latitude] || value["latitude"])
149
+ longitude = Float(value[:longitude] || value["longitude"])
150
+
151
+ {
152
+ "@type" => "GeoCoordinates",
153
+ "latitude" => latitude,
154
+ "longitude" => longitude
155
+ }
156
+ rescue ArgumentError, TypeError
157
+ raise ArgumentError, "geo latitude and longitude must be finite numbers"
158
+ end
159
+ private_class_method :geo_coordinates
160
+
161
+ def same_as(value)
162
+ return nil if value.nil?
163
+ raise ArgumentError, "sameAs must be an array of URLs" unless value.is_a?(Array)
164
+
165
+ urls = value.each_with_index.map { |url, index| validate_url(url, "sameAs[#{index}]") }
166
+ urls.empty? ? nil : urls
167
+ end
168
+ private_class_method :same_as
169
+
170
+ def compact_hash(hash)
171
+ hash.reject { |_key, value| value.nil? || value == [] || value == {} }
172
+ end
173
+ private_class_method :compact_hash
174
+ end
175
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require_relative "../lib/bitemediaseo/local_seo_schema"
5
+
6
+ class LocalSeoSchemaTest < Minitest::Test
7
+ def test_create_local_business
8
+ schema = BiteMediaSeo::LocalSeoSchema.create_local_business(
9
+ name: "Example SEO Agency",
10
+ url: "https://example.com/",
11
+ area_served: "Szczecin",
12
+ address: {
13
+ addressLocality: "Szczecin",
14
+ addressCountry: "PL"
15
+ },
16
+ geo: {
17
+ latitude: "53.4285",
18
+ longitude: 14.5528
19
+ }
20
+ )
21
+
22
+ assert_equal "https://schema.org", schema["@context"]
23
+ assert_equal "ProfessionalService", schema["@type"]
24
+ assert_equal "Szczecin", schema["address"]["addressLocality"]
25
+ assert_equal 53.4285, schema["geo"]["latitude"]
26
+ end
27
+
28
+ def test_bitemediaseo_preset
29
+ schema = BiteMediaSeo::LocalSeoSchema.for_bitemediaseo
30
+
31
+ assert_equal "Service", schema["@type"]
32
+ assert_equal "https://bitemediaseo.pl/", schema["url"]
33
+ assert_equal "BiteMediaSEO", schema["provider"]["name"]
34
+ end
35
+
36
+ def test_script_tag_escapes_html
37
+ schema = BiteMediaSeo::LocalSeoSchema.create_local_business(
38
+ name: "Example <Agency>",
39
+ url: "https://example.com/"
40
+ )
41
+
42
+ assert_includes BiteMediaSeo::LocalSeoSchema.script_tag(schema), "\\u003cAgency\\u003e"
43
+ end
44
+
45
+ def test_invalid_url
46
+ assert_raises(ArgumentError) do
47
+ BiteMediaSeo::LocalSeoSchema.create_local_business(
48
+ name: "Example",
49
+ url: "not-a-url"
50
+ )
51
+ end
52
+ end
53
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bitemediaseo-local-seo-schema
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - BiteMediaSEO
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-06-26 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A small Ruby helper for generating Schema.org JSON-LD for local SEO,
14
+ service pages, GEO/AI visibility and the BiteMediaSEO pozycjonowanie stron Szczecin
15
+ offer at https://bitemediaseo.pl/.
16
+ email:
17
+ - kontakt@bitemediaseo.pl
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - CHANGELOG.md
23
+ - LICENSE
24
+ - README.md
25
+ - bitemediaseo-local-seo-schema.gemspec
26
+ - lib/bitemediaseo/local_seo_schema.rb
27
+ - lib/bitemediaseo/local_seo_schema/version.rb
28
+ - test/local_seo_schema_test.rb
29
+ homepage: https://bitemediaseo.pl/
30
+ licenses:
31
+ - MIT
32
+ metadata:
33
+ homepage_uri: https://bitemediaseo.pl/
34
+ rubygems_mfa_required: 'true'
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '3.0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.5.0
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Schema.org JSON-LD helpers for local SEO and pozycjonowanie stron Szczecin.
54
+ test_files: []