[phase-1a] Gem rename: instagram_basic_display_api → instagram_graph_api#1
Merged
Conversation
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
There was a problem hiding this comment.
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
InstagramGraphAPIclient with Graph read endpoints (/me,/me/media,/{media-id},/refresh_access_token) andHashie::Mashresponse 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
| 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
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 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1a — Gem rename + Graph read parity
Plan:
linkmyphotos-rails/docs/plans/instagram-publishing/phase-1a-gem-rename.mdMaster pointer:
linkmyphotos-rails/docs/plans/instagram-publishing/README.mdCompanion 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.InstagramBasicDisplayAPI→InstagramGraphAPIinstagram_graph_apiv1.0.0, MIT, homepage points at this repofaraday_middleware);Hashie::Mashwraps responsesGET /me—Client#user,Client#meGET /me/media—Client#user_recent_media,Client#recent_mediaGET /{media-id}—Client#media_item,Client#mediaGET /refresh_access_token?grant_type=ig_refresh_token—Client#refresh_access_tokenBadRequest,Unauthorized,Forbidden,NotFound,TooManyRequests,InternalServerError,BadGateway,ServiceUnavailable,GatewayTimeout)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 actuallinkmyphotos-railscallsites use those exact signatures — they callclient.user(fields:),client.user_recent_media(limit:, after:),client.media_item(id),client.refresh_access_token(verified via grep acrossapp/).To preserve real behavior and match the planned API surface, both shapes are exposed:
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()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
Testing
Per the phase plan's Testing section:
bundle exec rspec.Local result:
gem build instagram_graph_api.gemspecalso 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
sixoverground/instagram_basic_display_apiwith a README pointer to this repo. That's a manual step on your end (and the old repo isn't in my MCP scope).claude/phase-1a-gem-renamebranch for CI; on merge here, tagv1.0.0and switch the Rails Gemfile line totag: 'v1.0.0'in a follow-up commit.https://claude.ai/code/session_01JUGDMo9kxbU4otf7zpL4Hx