Skip to content

[phase-1a] Gem rename: instagram_basic_display_api → instagram_graph_api#1

Merged
craigphares merged 4 commits into
mainfrom
claude/phase-1a-gem-rename
May 31, 2026
Merged

[phase-1a] Gem rename: instagram_basic_display_api → instagram_graph_api#1
craigphares merged 4 commits into
mainfrom
claude/phase-1a-gem-rename

Conversation

@craigphares
Copy link
Copy Markdown
Member

@craigphares craigphares commented May 30, 2026

Phase 1a — Gem rename + Graph read parity

Plan: linkmyphotos-rails/docs/plans/instagram-publishing/phase-1a-gem-rename.md
Master pointer: linkmyphotos-rails/docs/plans/instagram-publishing/README.md
Companion PR (Rails Gemfile + constant rename): sixoverground/linkmyphotos-rails#1113

Summary

Initial implementation of the renamed gem. Successor to instagram_basic_display_api (Meta retired Basic Display in late 2024). Source did not migrate from the old repo — the read modules were reimplemented from scratch against Graph endpoints, which is what the plan called for.

  • Top-level module: InstagramBasicDisplayAPIInstagramGraphAPI
  • Gemspec: instagram_graph_api v1.0.0, MIT, homepage points at this repo
  • HTTP stack: Faraday 2 (no faraday_middleware); Hashie::Mash wraps responses
  • Read endpoints (Graph):
    • GET /meClient#user, Client#me
    • GET /me/mediaClient#user_recent_media, Client#recent_media
    • GET /{media-id}Client#media_item, Client#media
    • GET /refresh_access_token?grant_type=ig_refresh_tokenClient#refresh_access_token
  • Errors: Faraday response middleware raises typed errors (BadRequest, Unauthorized, Forbidden, NotFound, TooManyRequests, InternalServerError, BadGateway, ServiceUnavailable, GatewayTimeout)
  • CI: GitHub Actions on Ruby 3.1 / 3.2 / 3.3

Plan deviation: signature table reconciliation

The plan's "Public API preservation" table lists Client#me, Client#user(id:, access_token:), Client#recent_media, Client#media(id:). None of the actual linkmyphotos-rails callsites use those exact signatures — they call client.user(fields:), client.user_recent_media(limit:, after:), client.media_item(id), client.refresh_access_token (verified via grep across app/).

To preserve real behavior and match the planned API surface, both shapes are exposed:

Actual callsite (preserved) Planned alias (added)
Client#user(id = 'me', fields:) Client#me(fields:)
Client#user_recent_media(limit:, after:, fields:) Client#recent_media(limit:, after:, fields:)
Client#media_item(id, fields:) Client#media(id:, fields:)
Client#refresh_access_token() — (same)

Rails Gemfile bump is mechanical; nothing in app/ has to change beyond the constant rename. Flagging for review in case you'd rather collapse to just the planned names and update the Rails callsites instead (would balloon phase 1a's blast radius into the Rails repo).

Files

lib/instagram_graph_api.rb
lib/instagram_graph_api/{api,client,configuration,connection,error,raise_http_exception,version}.rb
lib/instagram_graph_api/client/{access_token,media,users}.rb
spec/spec_helper.rb
spec/instagram_graph_api_spec.rb
spec/instagram_graph_api/client/{access_token,media,users}_spec.rb
spec/fixtures/{user,media,recent_media,refresh_token}.json
spec/fixtures/errors/{400,401,403,429,500}.json
instagram_graph_api.gemspec
Gemfile, Rakefile, .rspec, .gitignore, CHANGELOG.md, LICENSE.txt, README.md
.github/workflows/ci.yml

Testing

Per the phase plan's Testing section:

  • Per-module RSpec — every public method on every client module.
  • WebMock fixtures for happy path responses; one fixture per known Graph error (400, 401, 403, 429, 500).
  • Specs run with bundle exec rspec.

Local result:

$ bundle exec rspec
…
24 examples, 0 failures

gem build instagram_graph_api.gemspec also succeeds.

Deferred to 1b

Publish (single image, carousel, video, reels, stories), stories/reels/tagged-media reads, insights, comments + replies. See phase-1b-gem-publish.md.

Out of scope for this run

  • Old repo archive. Plan calls for archiving sixoverground/instagram_basic_display_api with a README pointer to this repo. That's a manual step on your end (and the old repo isn't in my MCP scope).
  • Tagging v1.0.0. The Rails companion PR (sixoverground/linkmyphotos-rails#1113) points at the claude/phase-1a-gem-rename branch for CI; on merge here, tag v1.0.0 and switch the Rails Gemfile line to tag: 'v1.0.0' in a follow-up commit.

https://claude.ai/code/session_01JUGDMo9kxbU4otf7zpL4Hx

Initial implementation of the renamed gem, successor to
instagram_basic_display_api (Meta retired Basic Display).

- Top-level module: InstagramGraphAPI
- Faraday 2 (no faraday_middleware) + Hashie::Mash response wrapping
- Read endpoints reimplemented against the Graph API:
  - GET /me (Client#user, Client#me)
  - GET /me/media (Client#user_recent_media, Client#recent_media)
  - GET /{media-id} (Client#media_item, Client#media)
  - GET /refresh_access_token (Client#refresh_access_token)
- Public method signatures preserved for the existing linkmyphotos-rails
  callsites; alias methods added to match the planned API surface used by
  phase 5a (reads switch to Graph).
- Error classes (BadRequest, Unauthorized, Forbidden, NotFound,
  TooManyRequests, InternalServerError, BadGateway, ServiceUnavailable,
  GatewayTimeout) raised via a Faraday response middleware.
- RSpec coverage for every public method on every client module, plus
  401/403/429/500 error paths. 24 examples, no failures.
- GitHub Actions CI on Ruby 3.1, 3.2, 3.3.

Publish + expanded read surfaces deferred to phase 1b.

Plan: linkmyphotos-rails/docs/plans/instagram-publishing/phase-1a-gem-rename.md

https://claude.ai/code/session_01JUGDMo9kxbU4otf7zpL4Hx
Copilot AI review requested due to automatic review settings May 30, 2026 22:11
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces the renamed instagram_graph_api Ruby gem (successor to instagram_basic_display_api) and implements phase-1a read-only parity against Instagram Graph endpoints, including typed HTTP error handling and CI coverage.

Changes:

  • Implemented InstagramGraphAPI client with Graph read endpoints (/me, /me/media, /{media-id}, /refresh_access_token) and Hashie::Mash response wrapping.
  • Added Faraday 2 connection + response middleware that raises typed errors on non-2xx responses.
  • Added RSpec/WebMock fixture-based test suite and GitHub Actions CI for Ruby 3.1/3.2/3.3.

Reviewed changes

Copilot reviewed 33 out of 34 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
lib/instagram_graph_api.rb Defines gem entrypoint, configuration extension, and module-level client delegation.
lib/instagram_graph_api/version.rb Introduces gem version constant (1.0.0).
lib/instagram_graph_api/configuration.rb Adds configurable options (token, host, user-agent, version) and reset behavior.
lib/instagram_graph_api/connection.rb Creates Faraday connection and installs JSON + error-raising middleware.
lib/instagram_graph_api/error.rb Defines base error type and typed subclasses for HTTP statuses.
lib/instagram_graph_api/raise_http_exception.rb Implements Faraday middleware mapping HTTP status codes to typed errors.
lib/instagram_graph_api/api.rb Implements get/post/delete helpers and wraps responses in Hashie::Mash.
lib/instagram_graph_api/client.rb Defines Client and mixes in endpoint modules.
lib/instagram_graph_api/client/users.rb Implements user and me methods for /me (and id-based lookup).
lib/instagram_graph_api/client/media.rb Implements media list and item reads, plus alias methods for planned API surface.
lib/instagram_graph_api/client/access_token.rb Implements long-lived token refresh call.
instagram_graph_api.gemspec Defines gem metadata and runtime/dev dependencies (Faraday, Hashie, RSpec, WebMock).
Gemfile Minimal gem development Gemfile loading the gemspec.
Rakefile Adds default rake task to run specs and Bundler gem tasks.
.rspec Configures RSpec runner defaults.
.gitignore Adds standard Ruby/gem build artifacts and bundler ignores.
README.md Documents installation, usage examples, method surface, and error types.
CHANGELOG.md Introduces initial changelog entry for 1.0.0 and scope notes.
LICENSE.txt Adds MIT license text.
.github/workflows/ci.yml Adds CI to build gem and run specs across Ruby 3.1–3.3.
spec/spec_helper.rb Sets up WebMock + helpers for fixture loading and request stubbing.
spec/instagram_graph_api_spec.rb Tests version, client creation, and configuration defaults/reset.
spec/instagram_graph_api/client/users_spec.rb Tests user endpoints, alias behavior, and (some) error mapping.
spec/instagram_graph_api/client/media_spec.rb Tests media list, pagination cursor access, and item fetch + 404 behavior.
spec/instagram_graph_api/client/access_token_spec.rb Tests token refresh endpoint and unauthorized error case.
spec/fixtures/user.json Fixture for /me user payload.
spec/fixtures/recent_media.json Fixture for /me/media paged list payload.
spec/fixtures/media.json Fixture for /{media-id} payload.
spec/fixtures/refresh_token.json Fixture for refresh token response payload.
spec/fixtures/errors/400.json Fixture for 400 error payload.
spec/fixtures/errors/401.json Fixture for 401 error payload.
spec/fixtures/errors/403.json Fixture for 403 error payload.
spec/fixtures/errors/429.json Fixture for 429 error payload.
spec/fixtures/errors/500.json Fixture for 500 error payload.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +10 to +25
def connection
options = {
headers: {
'Accept' => 'application/json',
'User-Agent' => user_agent
},
url: api_url
}

Faraday.new(options) do |conn|
conn.request :url_encoded
conn.response :raise_http_exception
conn.response :json, content_type: /\bjson$/
conn.adapter Faraday.default_adapter
end
end
Comment on lines +5 to +10
VALID_OPTIONS_KEYS = %i[
access_token
api_url
api_version
user_agent
].freeze
Comment on lines +42 to +55
describe 'error handling' do
{
400 => InstagramGraphAPI::BadRequest,
401 => InstagramGraphAPI::Unauthorized,
403 => InstagramGraphAPI::Forbidden,
429 => InstagramGraphAPI::TooManyRequests,
500 => InstagramGraphAPI::InternalServerError
}.each do |status, error_class|
it "raises #{error_class} on HTTP #{status}" do
stub_graph_get('me', response_fixture: "errors/#{status}.json", status: status)
expect { client.user }.to raise_error(error_class)
end
end
end
Comment on lines +53 to +55
it 'raises NotFound on 404' do
stub_graph_get('bogus_id', response_fixture: 'errors/400.json', status: 404)
expect { client.media_item('bogus_id') }.to raise_error(InstagramGraphAPI::NotFound)
Comment on lines +16 to +20
def self.method_missing(method, *args, &block)
return super unless client.respond_to?(method)

client.send(method, *args, &block)
end
- connection.rb: memoize the Faraday instance per API instance (was
  rebuilt on every request); drop the redundant content_type: argument
  on :json (Faraday strips the charset and the default already matches
  application/json correctly).
- instagram_graph_api.rb: cache the client locally inside method_missing
  so it's not built twice per delegation; switch send -> public_send.
- configuration.rb: drop api_version — was defaulted/exposed but never
  used in path construction (graph.instagram.com handles versioning at
  the host level); confirmed via grep no code reads it.
- specs: extend error-mapping coverage to 404/502/503/504 (the
  middleware already maps them); add dedicated errors/404.json fixture
  so the 404 path test no longer borrows the 400 payload.

29 examples, 0 failures locally.

https://claude.ai/code/session_01JUGDMo9kxbU4otf7zpL4Hx
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 38 changed files in this pull request and generated 2 comments.

Comment thread lib/instagram_graph_api/client/media.rb Outdated
get('me/media', fields: fields, limit: limit, after: after)
end

# Alias matching the planned API table (used by future phase 5a).
Comment on lines +11 to +17
@connection ||= Faraday.new(
url: api_url,
headers: {
'Accept' => 'application/json',
'User-Agent' => user_agent
}
) do |conn|
- connection.rb: set open_timeout (10s) and timeout (30s) on the Faraday
  request so a hung Instagram response can't block Sidekiq workers
  indefinitely. Constants on the Connection module if a caller ever
  wants to override them.
- client/media.rb: drop the stray "future phase 5a" comment from the
  alias methods — that's multi-repo-plan context that doesn't belong in
  the gem source. Shortened the surrounding comments to just describe
  the alias.

29 examples, 0 failures.

https://claude.ai/code/session_01JUGDMo9kxbU4otf7zpL4Hx
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 38 changed files in this pull request and generated 2 comments.

Comment thread README.md Outdated
Comment on lines +44 to +48
| Method | Signature | Notes |
| --- | --- | --- |
| `Client#user` | `(id = 'me', fields:)` → `Hashie::Mash` | `id` defaults to `me`; pass an IG user id to look up another user (limited by Graph permissions). |
| `Client#me` | `(fields:)` → `Hashie::Mash` | Alias for `user('me', fields:)`. |
| `Client#user_recent_media` | `(limit:, after:, fields:)` → `Hashie::Mash` with `.data` + `.paging` | Paged. |
Comment thread README.md Outdated
Comment on lines +49 to +52
| `Client#recent_media` | `(limit:, after:, fields:)` → `Hashie::Mash` | Alias for `user_recent_media`. |
| `Client#media_item` | `(id, fields:)` → `Hashie::Mash` | Positional `id`. |
| `Client#media` | `(id:, fields:)` → `Hashie::Mash` | Keyword `id:`. Alias for `media_item`. |
| `Client#refresh_access_token` | `()` → `Hashie::Mash` with `.access_token` | Long-lived token refresh. |
Copilot review flagged that the signature column showed (limit:) /
(fields:) etc., implying required arguments — even though the actual
implementations default them. Replaced the bare kwargs with their real
defaults (limit: 25, after: nil, fields: DEFAULT_*_FIELDS) so the
table reflects how the methods can actually be called.

The other half of the same review claimed the table used a leading
|| prefix and rendered with an empty first column — the source uses
single | throughout (GFM-compliant); no change needed there.

https://claude.ai/code/session_01JUGDMo9kxbU4otf7zpL4Hx
@craigphares craigphares merged commit 9435b05 into main May 31, 2026
3 checks passed
@craigphares craigphares deleted the claude/phase-1a-gem-rename branch May 31, 2026 00:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants