og_pilot_ruby 0.1.3 → 0.2.1

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: e14139a28eb73f49d006b651becb58d4068c0988b53dc60d824fcb63ce5cdf1b
4
- data.tar.gz: a39dabc07a52b76c5cff89bd822f8303a2d744701c1223c788917e878fae8487
3
+ metadata.gz: 34c76008dde8c56d665a3eece4869fa7f28ec0ad656ef2cae798da373956d055
4
+ data.tar.gz: 1a2e055c83968ed81d0ca747457403c865c045d37acd5694f9ac5fc221497d57
5
5
  SHA512:
6
- metadata.gz: bd80040f049488ed5052b20dd2ff71d6c099b47d69eabfd626544a8230dadd139a7c976ab256ee474f8d1f1ab1a5f1364b83954a2995d0d2f574200802f1222c
7
- data.tar.gz: 324610fd591ff18eb0c9be51f538e4fdd8325ed28572909afde1fb2ce2cc51edc33622828505e8f30ad46f25b623cfadc1de87a5f1deba79ae579589c2645493
6
+ metadata.gz: efb3af2ede82a7519c855dfd6e9bbbfc17940a22ce91a12f3bac1e3e63e65fba92e61b9554ba8e5f2a39cc47a2557ef215595b4776d71dbef1c232333cc5d16b
7
+ data.tar.gz: e3f0bc81c47bfce15490c8348c0db2a32c601b6761200dd25487baf0294799890ebb5b7d864ebf44ea800f5d3a2f9393b433a19f8c8a0df117c2ed3d8a934dc2
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # OG Pilot Ruby
2
2
 
3
+ > [!IMPORTANT]
4
+ > An active [OG Pilot](https://ogpilot.com?ref=og_pilot_ruby) subscription is required to use this gem.
5
+
3
6
  A small Ruby client for generating OG Pilot Open Graph images via signed JWTs.
4
7
 
5
8
  ## Installation
@@ -57,6 +60,78 @@ image_url = OgPilotRuby.create_image(
57
60
  If you omit `iat`, OG Pilot will cache the image indefinitely. Provide an `iat` to
58
61
  refresh the cache daily.
59
62
 
63
+ ## Parameters
64
+
65
+ All parameters are embedded in the signed JWT payload; the only query param is `token`.
66
+ The gem handles `iss` (domain) and `sub` (API key prefix) automatically.
67
+
68
+ ### Core parameters
69
+
70
+ | Parameter | Required | Default | Description |
71
+ |---------------|----------|----------|---------------------------------------------------------------|
72
+ | `template` | No | `"page"` | Template name |
73
+ | `title` | Yes | — | Primary title text |
74
+ | `description` | No | — | Subtitle or supporting text |
75
+ | `logo_url` | No | — | Logo image URL |
76
+ | `image_url` | No | — | Hero image URL |
77
+ | `bg_color` | No | — | Background color (hex format) |
78
+ | `text_color` | No | — | Text color (hex format) |
79
+ | `iat` | No | — | Issued-at timestamp for daily cache busting |
80
+ | `path` | No | auto-set | Request path for image rendering context. When provided, it overrides auto-resolution (see [Path handling](#path-handling)) |
81
+
82
+ ### Ruby options
83
+
84
+ | Option | Default | Description |
85
+ |-----------|---------|--------------------------------------------------------------------------|
86
+ | `json` | `false` | When `true`, sends `Accept: application/json` and parses the JSON response |
87
+ | `headers` | — | Additional HTTP headers to include with the request |
88
+ | `default` | `false` | Forces `path` to `/` when `true`, unless a manual `path` is provided (see [Path handling](#path-handling)) |
89
+
90
+ ### Template-specific parameters
91
+
92
+ | Template | Parameters |
93
+ |-------------|------------------------------------------------------------------------------------|
94
+ | `page` | `title`, `description` |
95
+ | `blog_post` | `title`, `author_name`, `author_avatar_url`, `publish_date` (ISO 8601) |
96
+ | `podcast` | `title`, `episode_date` (ISO 8601) |
97
+ | `product` | `title`, `unique_selling_point` |
98
+ | `event` | `title`, `event_date`, `event_location` |
99
+ | `book` | `title`, `description`, `book_author`, `book_series_number`, `book_description`, `book_genre` |
100
+ | `portfolio` | `title` |
101
+ | `company` | `title`, `company_logo_url`, `description` (note: `image_url` is ignored) |
102
+
103
+ ### Path handling
104
+
105
+ The `path` parameter enhances OG Pilot analytics by tracking which OG images perform better across different pages on your site. Without it, all analytics would be aggregated under the `/` path, making it difficult to understand how individual pages or content types are performing. By automatically capturing the request path, you get granular insights into click-through rates and engagement for each OG image.
106
+
107
+ The client automatically injects a `path` parameter on every request:
108
+
109
+ | Option | Behavior |
110
+ |------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
111
+ | `default: false` | Uses the current request path when available. In Rails, prefers `request.fullpath` (via the per-request store), then falls back to Rack/CGI env vars (`REQUEST_URI`, `PATH_INFO`). If no path can be determined, uses `/`. |
112
+ | `default: true` | Forces the `path` parameter to `/`, regardless of the current request (unless `path` is provided explicitly). |
113
+ | `path: "/..."` | Uses the provided path verbatim (normalized to start with `/`), overriding auto-resolution. |
114
+
115
+ **Example:**
116
+
117
+ ```ruby
118
+ image_url = OgPilotRuby.create_image(
119
+ template: "blog_post",
120
+ title: "How to Build Amazing OG Images",
121
+ default: true
122
+ )
123
+ ```
124
+
125
+ Manual override:
126
+
127
+ ```ruby
128
+ image_url = OgPilotRuby.create_image(
129
+ template: "page",
130
+ title: "Hello OG Pilot",
131
+ path: "/pricing?plan=pro"
132
+ )
133
+ ```
134
+
60
135
  Fetch JSON metadata instead:
61
136
 
62
137
  ```ruby
@@ -15,7 +15,14 @@ module OgPilotRuby
15
15
  @config = config
16
16
  end
17
17
 
18
- def create_image(params = {}, json: false, iat: nil, headers: {})
18
+ def create_image(params = {}, json: false, iat: nil, headers: {}, default: false)
19
+ params ||= {}
20
+ params = params.dup
21
+ # Always include a path; manual overrides win, otherwise resolve from the current request.
22
+ manual_path = params.key?(:path) ? params[:path] : params["path"]
23
+ params.delete("path") if params.key?("path")
24
+ params[:path] = manual_path.to_s.strip.empty? ? resolved_path(default:) : normalize_path(manual_path)
25
+
19
26
  uri = build_uri(params, iat:)
20
27
  response = request(uri, json:, headers:)
21
28
 
@@ -96,5 +103,79 @@ module OgPilotRuby
96
103
  def api_key_prefix
97
104
  api_key!.slice(0, 8)
98
105
  end
106
+
107
+ # Rails-first path resolution with Rack/CGI env fallback.
108
+ def resolved_path(default:)
109
+ return "/" if default
110
+
111
+ path = rails_fullpath
112
+ path = env_fullpath if path.nil? || path.empty?
113
+ normalize_path(path)
114
+ end
115
+
116
+ def rails_fullpath
117
+ return unless defined?(::Rails)
118
+
119
+ request = rails_request_from_store || rails_request_from_thread
120
+ fullpath = request.fullpath if request&.respond_to?(:fullpath)
121
+ fullpath unless fullpath.nil? || fullpath.empty?
122
+ end
123
+
124
+ def rails_request_from_store
125
+ return unless defined?(::RequestStore) && ::RequestStore.respond_to?(:store)
126
+
127
+ store = ::RequestStore.store
128
+ store[:action_dispatch_request] ||
129
+ store[:"action_dispatch.request"] ||
130
+ store[:request]
131
+ end
132
+
133
+ def rails_request_from_thread
134
+ Thread.current[:og_pilot_request] ||
135
+ Thread.current[:action_dispatch_request] ||
136
+ Thread.current[:"action_dispatch.request"] ||
137
+ Thread.current[:request]
138
+ end
139
+
140
+ def env_fullpath
141
+ request_uri = ENV["REQUEST_URI"]
142
+ return request_uri unless request_uri.nil? || request_uri.empty?
143
+
144
+ original_fullpath = ENV["ORIGINAL_FULLPATH"]
145
+ return original_fullpath unless original_fullpath.nil? || original_fullpath.empty?
146
+
147
+ path_info = ENV["PATH_INFO"]
148
+ return build_fullpath_from_path_info(path_info) unless path_info.nil? || path_info.empty?
149
+
150
+ request_path = ENV["REQUEST_PATH"]
151
+ return request_path unless request_path.nil? || request_path.empty?
152
+
153
+ nil
154
+ end
155
+
156
+ def build_fullpath_from_path_info(path_info)
157
+ query = ENV["QUERY_STRING"].to_s
158
+ return path_info if query.empty?
159
+
160
+ "#{path_info}?#{query}"
161
+ end
162
+
163
+ def normalize_path(path)
164
+ cleaned = path.to_s.strip
165
+ return "/" if cleaned.empty?
166
+
167
+ cleaned = extract_request_uri(cleaned)
168
+ cleaned = "/#{cleaned}" unless cleaned.start_with?("/")
169
+ cleaned
170
+ end
171
+
172
+ def extract_request_uri(value)
173
+ return value unless value.start_with?("http://", "https://")
174
+
175
+ uri = URI.parse(value)
176
+ uri.request_uri || "/"
177
+ rescue URI::InvalidURIError
178
+ value
179
+ end
99
180
  end
100
181
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module OgPilotRuby
4
4
  module RailsHelper
5
- def create_image(params = {}, json: false, iat: nil, headers: {}, **keyword_params)
6
- OgPilotRuby.create_image(params, json:, iat:, headers:, **keyword_params)
5
+ def create_image(params = {}, json: false, iat: nil, headers: {}, default: false, **keyword_params)
6
+ OgPilotRuby.create_image(params, json:, iat:, headers:, default:, **keyword_params)
7
7
  end
8
8
  end
9
9
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  if defined?(Rails::Railtie)
4
+ require_relative "request_store_middleware"
5
+
4
6
  module OgPilotRuby
5
7
  # Rails integration for OG Pilot Ruby
6
8
  class Railtie < Rails::Railtie
@@ -9,6 +11,10 @@ if defined?(Rails::Railtie)
9
11
  inflect.acronym 'OgPilotRuby'
10
12
  end
11
13
  end
14
+
15
+ initializer 'og_pilot_ruby.middleware' do |app|
16
+ app.middleware.use OgPilotRuby::RequestStoreMiddleware
17
+ end
12
18
  end
13
19
  end
14
20
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OgPilotRuby
4
+ # Middleware that stores the current request in Thread.current
5
+ # so OgPilotRuby::Client can access it for path resolution.
6
+ class RequestStoreMiddleware
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ request = ActionDispatch::Request.new(env)
13
+ Thread.current[:og_pilot_request] = request
14
+ @app.call(env)
15
+ ensure
16
+ Thread.current[:og_pilot_request] = nil
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OgPilotRuby
4
- VERSION = "0.1.3"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/og_pilot_ruby.rb CHANGED
@@ -25,9 +25,9 @@ module OgPilotRuby
25
25
  Client.new(config)
26
26
  end
27
27
 
28
- def create_image(params = {}, json: false, iat: nil, headers: {}, **keyword_params)
28
+ def create_image(params = {}, json: false, iat: nil, headers: {}, default: false, **keyword_params)
29
29
  params ||= {}
30
- client.create_image(params.merge(keyword_params), json:, iat:, headers:)
30
+ client.create_image(params.merge(keyword_params), json:, iat:, headers:, default:)
31
31
  end
32
32
  end
33
33
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: og_pilot_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sunergos IT LLC
@@ -72,6 +72,7 @@ files:
72
72
  - lib/og_pilot_ruby/jwt_encoder.rb
73
73
  - lib/og_pilot_ruby/rails_helper.rb
74
74
  - lib/og_pilot_ruby/railtie.rb
75
+ - lib/og_pilot_ruby/request_store_middleware.rb
75
76
  - lib/og_pilot_ruby/version.rb
76
77
  homepage: https://ogpilot.com
77
78
  licenses: