Skip to content

Conversation

@k1bs
Copy link
Collaborator

@k1bs k1bs commented Jan 12, 2026

Add github ci yml and ensure rubygems will read from github releases.

Add root level members routes. Add routes for enrollments.

@k1bs k1bs requested a review from fastjames January 12, 2026 18:50
@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

Summary by CodeRabbit

  • New Features
    • Top-level Members API and a Member Enrollments resource; program-scoped members now support member-scoped operations via an optional member id.
  • Chores
    • Minimum Ruby raised to 3.0, CI workflow added (tests + lint), RuboCop target bumped, Gemfile compatibility notes and local example config updated.
  • Tests
    • New integration specs and many VCR fixtures; added VCR sensitive-data filters.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds CI workflow, raises Ruby target to 3.0, updates dependencies, introduces top-level Members plus ProgramMember and MemberEnrollments resources, adds Client#members and ssl_verify init option, updates Programs#members signature, adds VCR fixtures/filters, and adjusts specs/cassettes and documentation to the new API surface.

Changes

Cohort / File(s) Summary
CI workflow
.github/workflows/ci.yml
Adds GitHub Actions CI with test (Ruby matrix 3.0,3.1,3.2,3.4,4.0 -> bundle exec rspec) and lint (Ruby 3.2 -> bundle exec rubocop).
Ruby target & lint metadata
data_nexus.gemspec, .rubocop.yml
Bumps required Ruby to >= 3.0.0, adds spec.metadata['changelog_uri'], and sets RuboCop AllCops.TargetRubyVersion to 3.0.
Dependencies
Gemfile
Adds public_suffix ("< 7.0") and erb ("~> 4.0") with compatibility comments.
Client surface
lib/data_nexus/client.rb
Adds public members accessor and new ssl_verify initialize keyword wired into configuration.
New top-level resource
lib/data_nexus/resources/members.rb
Adds DataNexus::Resources::Members with list, find, and update methods; supports filtering and pagination.
Program-scoped member resource
lib/data_nexus/resources/programs.rb, lib/data_nexus/resources/program_member.rb, lib/data_nexus/resources/program_members.rb
Adds ProgramMember for member-scoped operations; changes Programs#members to members(member_id = nil) returning ProgramMembers (listing) or ProgramMember (member-scoped); removes per-member methods from ProgramMembers.
Member enrollments resource
lib/data_nexus/resources/member_enrollments.rb
Adds DataNexus::Resources::MemberEnrollments with CRUD: create, find, update, delete under /api/members/:member_id/enrollments.
Member consents docs & tests
lib/data_nexus/resources/member_consents.rb, spec/integration/member_consents_spec.rb, spec/cassettes/member_consents/*.yml
Updates examples and tests to use programs(id).members(member_id).consents.*; reindents and standardizes consent cassette headers and updates IDs/timestamps.
VCR fixtures — new/updated cassettes
spec/cassettes/members/*.yml, spec/cassettes/member_enrollments/*.yml, spec/cassettes/member_consents/*.yml
Adds many member and enrollment cassettes (list, find, paginated, filters, create/update/delete) and updates consent cassettes (IDs, timestamps, headers).
Specs — new and modified
spec/integration/members_spec.rb, spec/integration/member_enrollments_spec.rb, spec/integration/program_members_spec.rb, spec/integration/member_consents_spec.rb
Adds integration suites for Members and MemberEnrollments; updates program members and consents specs to use members(member_id) pattern and fixed consent IDs.
VCR config & local example
spec/support/vcr.rb, .mise.local.toml.example
Adds VCR sensitive-data filters for DATANEXUS_TEST_BORN_ON and DATANEXUS_TEST_ENROLLMENT_ID; example local config adds DATANEXUS_TEST_ENROLLMENT_ID.
Cassettes formatting/metadata
spec/cassettes/member_consents/{create,delete,find,update}.yml
Re-indents and standardizes cassette header formatting; updates recorded timestamps, request IDs, and endpoint IDs.
Docs & README updates
README.md
Reflects new API paths (member-scoped members(member_id)), top-level client.members.*, and Member Enrollments usage; updates examples and development commands.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Programs as ProgramsResource
  participant ProgramMember as ProgramMemberResource
  participant API as DataNexusAPI
  Note over Client,API: Member-scoped consent creation flow
  Client->>Programs: programs(program_id)
  Programs->>ProgramMember: members(member_id)
  Client->>ProgramMember: consents.create(consent_payload)
  ProgramMember->>API: POST /api/members/:member_id/consents { consent: ... }
  API-->>ProgramMember: 201 Created { data: ... }
  ProgramMember-->>Client: created consent
Loading
sequenceDiagram
  participant Client
  participant Members as MembersResource
  participant API as DataNexusAPI
  Note over Client,API: Top-level members listing and enrollments CRUD
  Client->>Members: members.list(born_on: ...)
  Members->>API: GET /api/members?born_on=...
  API-->>Members: 200 { data: [...], cursors }
  Members-->>Client: Collection
  Client->>Members: members.find(member_id)
  Members->>API: GET /api/members/:member_id
  API-->>Members: 200 { data: ... }
  Members-->>Client: member data
  Client->>Members: members(member_id).enrollments.create(enrollment: ...)
  Members->>API: POST /api/members/:member_id/enrollments { enrollment: ... }
  API-->>Members: 201 { data: ... }
  Members-->>Client: enrollment data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title '4763' is vague and non-descriptive, using only a ticket number without conveying meaningful information about the changeset. Use a descriptive title like 'Add GitHub CI workflow, top-level members routes, and enrollments support' to clearly summarize the main changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description relates to the changeset, mentioning GitHub CI YAML, members routes, and enrollments routes which align with the actual changes made.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.



📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd63c81 and eec947f.

📒 Files selected for processing (1)
  • README.md
🔇 Additional comments (5)
README.md (5)

7-11: Verify that the git tag will exist before merging.

The installation instructions reference tag v0.1.0. Ensure this tag is created and corresponds to a GitHub release before merging this documentation update.


28-28: LGTM! API signature changes are well-documented and consistent.

The updated API signatures follow a clearer RESTful pattern with .members(member_id) providing better resource nesting. All examples are syntactically correct and consistently updated throughout the documentation.

Also applies to: 67-67, 74-76, 82-84, 91-99, 104-106, 111-114, 119-120, 199-199


122-156: LGTM! Member Enrollments section is well-structured.

The new Member Enrollments documentation follows the established patterns, includes all CRUD operations, and provides clear, syntactically correct examples.


157-193: LGTM! Top-Level Members section provides comprehensive documentation.

The new top-level Members API is well-documented with multiple filtering options, pagination examples, and all standard operations (list, find, update).


215-221: LGTM! Development section now uses standard Ruby tooling.

The update to use bundle install and bundle exec commands aligns with standard Ruby development practices and makes the setup more straightforward for contributors.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
spec/integration/member_consents_spec.rb (2)

41-41: Consider documenting why the consent ID is hardcoded.

The consent_id is hardcoded to 2 while other identifiers (program_id, member_id) are fetched from environment variables. This creates an inconsistent pattern. If this is intentional due to VCR cassette recordings, a brief comment explaining this would help future maintainers understand the reasoning.

💡 Suggested comment
 describe 'finding a consent' do
+    # Consent ID matches the recorded VCR cassette
     let(:consent_id) { 2 }

55-55: Same hardcoded ID pattern repeated.

Consider extracting the shared consent_id to the top-level describe block to reduce duplication and make it clear this ID is shared across find, update, and delete tests.

♻️ Suggested refactor
 RSpec.describe 'Member Consents', :vcr, order: :defined do
   let(:api_key) { ENV.fetch('DATANEXUS_API_KEY', 'test-api-key') }
   let(:base_url) { ENV.fetch('DATANEXUS_BASE_URL', 'http://localhost:4000') }
   let(:ssl_verify) { ENV.fetch('DATANEXUS_SSL_VERIFY', 'true') == 'true' }
   let(:program_id) { ENV.fetch('DATANEXUS_TEST_PROGRAM_ID', 'test-program-id') }
   let(:member_id) { ENV.fetch('DATANEXUS_TEST_MEMBER_ID', 'test-member-id') }
+  # Consent ID for find/update/delete tests - matches recorded VCR cassettes
+  let(:consent_id) { 2 }
   let(:client) { DataNexus::Client.new(api_key: api_key, base_url: base_url, ssl_verify: ssl_verify) }

Then remove the individual let(:consent_id) declarations from each nested describe block.

Also applies to: 69-69

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6497b9c and 2db1cb3.

⛔ Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • .rubocop.yml
  • Gemfile
  • data_nexus.gemspec
  • spec/cassettes/member_consents/create.yml
  • spec/cassettes/member_consents/delete.yml
  • spec/cassettes/member_consents/find.yml
  • spec/cassettes/member_consents/update.yml
  • spec/integration/member_consents_spec.rb
  • spec/support/vcr.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/ci.yml
🔇 Additional comments (10)
.rubocop.yml (1)

6-6: LGTM!

The TargetRubyVersion update to 3.0 is consistent with the required_ruby_version in the gemspec, ensuring RuboCop analyzes code with the correct Ruby syntax expectations.

spec/support/vcr.rb (1)

15-16: LGTM!

Good addition of the BORN_ON filter to protect date-of-birth PII in VCR cassettes. The pattern is consistent with existing sensitive data filters.

data_nexus.gemspec (2)

15-15: LGTM!

The Ruby version bump to >= 3.0.0 is consistent with the RuboCop target and Gemfile compatibility pins.


19-19: LGTM!

Adding changelog_uri pointing to GitHub releases aligns with the PR objective to have RubyGems read from GitHub releases. This provides users with direct access to release notes from the gem's RubyGems page.

spec/integration/member_consents_spec.rb (1)

3-3: LGTM!

Adding order: :defined ensures the test execution order matches the CRUD lifecycle (create → find → update → delete), which is appropriate for integration tests that may have implicit dependencies.

Gemfile (1)

19-24: Version constraints for Ruby 3.0/3.1 compatibility are correct.

The pins for public_suffix < 7.0 and erb ~> 4.0 are accurate—both 7.0+ versions require Ruby >= 3.2. These constraints properly support the Ruby 3.0 and 3.1 entries in the CI matrix. Consider adding a TODO to remove these constraints once Ruby 3.0/3.1 support is dropped.

spec/cassettes/member_consents/update.yml (1)

3-40: LGTM!

Cassette properly records the PATCH interaction with sensitive data correctly filtered using placeholders (<API_KEY>, <MEMBER_ID>, <PROGRAM_ID>).

spec/cassettes/member_consents/create.yml (1)

3-42: LGTM!

Cassette properly records the POST interaction for consent creation. Sensitive data is correctly filtered, and the test phone number (+15558675309) is an appropriate choice for test fixtures.

spec/cassettes/member_consents/delete.yml (1)

3-36: LGTM!

Cassette properly records the DELETE interaction with correct 204 No Content response. Sensitive data is appropriately filtered.

spec/cassettes/member_consents/find.yml (1)

3-40: LGTM!

Cassette properly records the GET interaction for consent retrieval. Consent ID 2 is used consistently with other cassettes (update, delete), and sensitive data is correctly filtered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/data_nexus/resources/program_members.rb (1)

57-61: Add pagination parameters to allowed_params.

The list method silently filters out pagination parameters (after, before, first, last) because they're not in allowed_params. However, the class documentation explicitly shows pagination usage in line 17: collection = client.programs("uuid").members.list(first: 50). This contradicts the implementation and breaks the documented example. Add these parameters to allowed_params to match the Members#list pattern (see lib/data_nexus/resources/members.rb lines 67-71).

🤖 Fix all issues with AI agents
In @lib/data_nexus/resources/program_member.rb:
- Around line 89-92: The household method currently constructs a Collection via
Collection.new(response, resource: self, params: {}), but ProgramMember lacks a
list method so Collection#next_page will raise NoMethodError; either implement a
ProgramMember#list signature that accepts params like (after: nil, **kwargs) and
delegates to the same endpoint (e.g., perform the GET to
"#{base_path}/#{member_id}/household" with pagination params and return a
Collection or items), or avoid returning a Collection by returning a
non-paginated object (e.g., a plain parsed response or a different wrapper) when
the API does not support pagination; update the household method accordingly and
ensure any code relying on Collection#next_page can call ProgramMember#list if
you implement it.
🧹 Nitpick comments (2)
spec/integration/members_spec.rb (1)

82-90: Consider verifying the updated field value in the response.

The update test verifies the response structure but doesn't confirm that phone_number was actually updated in the returned data. This could mask issues where the API accepts the request but doesn't apply the change.

💡 Suggested enhancement
     it 'returns the updated member', vcr: { cassette_name: 'members/update' } do
       response = client.members.update(
         member_id,
         member: { phone_number: '+15551234567' }
       )

       expect(response).to be_a(Hash)
       expect(response).to have_key(:data)
+      expect(response[:data]).to have_key(:phone_number)
     end
spec/integration/member_enrollments_spec.rb (1)

25-33: Consider using a separate VCR cassette for this test case.

This test reuses the member_enrollments/create cassette from the previous test but sends a different payload (only enrolled_at vs. both dates). If the cassette was recorded with the first test's payload, the HTTP interaction may not match, potentially causing test flakiness or false passes.

Suggested fix
-    it 'auto-injects the program_id', vcr: { cassette_name: 'member_enrollments/create' } do
+    it 'auto-injects the program_id', vcr: { cassette_name: 'member_enrollments/create_with_program_id' } do
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2db1cb3 and 796920d.

📒 Files selected for processing (25)
  • .mise.local.toml.example
  • lib/data_nexus/client.rb
  • lib/data_nexus/resources/member_consents.rb
  • lib/data_nexus/resources/member_enrollments.rb
  • lib/data_nexus/resources/members.rb
  • lib/data_nexus/resources/program_member.rb
  • lib/data_nexus/resources/program_members.rb
  • lib/data_nexus/resources/programs.rb
  • spec/cassettes/member_enrollments/create.yml
  • spec/cassettes/member_enrollments/delete.yml
  • spec/cassettes/member_enrollments/find.yml
  • spec/cassettes/member_enrollments/update.yml
  • spec/cassettes/members/find.yml
  • spec/cassettes/members/list.yml
  • spec/cassettes/members/list_by_born_on.yml
  • spec/cassettes/members/list_by_name.yml
  • spec/cassettes/members/list_by_program.yml
  • spec/cassettes/members/list_by_updated_since.yml
  • spec/cassettes/members/list_paginated.yml
  • spec/cassettes/members/update.yml
  • spec/integration/member_consents_spec.rb
  • spec/integration/member_enrollments_spec.rb
  • spec/integration/members_spec.rb
  • spec/integration/program_members_spec.rb
  • spec/support/vcr.rb
✅ Files skipped from review due to trivial changes (6)
  • spec/cassettes/members/list.yml
  • spec/cassettes/member_enrollments/delete.yml
  • spec/cassettes/members/list_by_name.yml
  • spec/cassettes/members/list_paginated.yml
  • spec/cassettes/members/find.yml
  • spec/cassettes/member_enrollments/create.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • spec/support/vcr.rb
  • spec/integration/member_consents_spec.rb
🧰 Additional context used
🧬 Code graph analysis (3)
spec/integration/members_spec.rb (2)
lib/data_nexus/client.rb (1)
  • members (59-61)
lib/data_nexus/resources/members.rb (3)
  • list (66-76)
  • find (86-89)
  • update (102-105)
lib/data_nexus/resources/programs.rb (1)
lib/data_nexus/client.rb (1)
  • members (59-61)
lib/data_nexus/client.rb (1)
lib/data_nexus/resources/programs.rb (1)
  • members (61-67)
🪛 Brakeman (7.1.1)
lib/data_nexus/resources/member_enrollments.rb

[medium] 109-109: Possible SQL injection
Type: SQL Injection
Confidence: Medium
More info: https://brakemanscanner.org/docs/warning_types/sql_injection/

(SQL Injection)

🔇 Additional comments (25)
.mise.local.toml.example (1)

5-12: LGTM!

The updated comment provides clearer context about the purpose of these environment variables, and the new DATANEXUS_TEST_ENROLLMENT_ID variable follows the established naming convention and placeholder pattern. This addition properly supports the new member enrollment test fixtures being introduced in this PR.

lib/data_nexus/resources/member_consents.rb (2)

11-28: Documentation examples are consistent with the new fluent API.

The updated examples correctly demonstrate the new nested resource pattern where member_id is passed to .members() rather than to the terminal action. All examples are internally consistent across the class documentation.


61-111: LGTM!

Method-level documentation examples are consistent with the class-level documentation and follow the same fluent API pattern.

spec/cassettes/members/update.yml (1)

1-44: VCR cassette properly configured with appropriate placeholders.

Sensitive identifiers are correctly replaced with placeholders (<MEMBER_ID>, <API_KEY>, <PROGRAM_ID>, <BORN_ON>, <EMPLOYEE_ID>). The cassette captures the expected PATCH request/response flow for member updates.

spec/cassettes/members/list_by_born_on.yml (1)

1-56: LGTM!

The cassette correctly captures the born_on filter scenario with proper placeholder usage in both the query parameter and response body. Pagination headers are preserved for testing cursor-based pagination.

spec/cassettes/members/list_by_program.yml (1)

1-96: VCR cassette captures list-by-program scenario correctly.

The cassette properly uses placeholders for sensitive identifiers. The selective placeholder usage (where some born_on/employee_id values are placeholders while others are literal test data) is a common VCR pattern when filtering specific values that appear in multiple test fixtures.

spec/cassettes/members/list_by_updated_since.yml (1)

1-96: LGTM!

The cassette properly captures the updated_since filter scenario. Using a fixed timestamp (2020-01-01T00:00:00Z) is appropriate for reproducible test playback, as this is a filter parameter rather than sensitive data.

lib/data_nexus/resources/program_members.rb (1)

20-66: LGTM!

The ProgramMembers resource is well-structured with clear documentation, proper separation of concerns (listing-only, with member-specific operations delegated to ProgramMember), and follows idiomatic Ruby patterns for parameter filtering.

spec/cassettes/member_enrollments/find.yml (1)

1-41: LGTM!

The VCR cassette is properly structured with sensitive data correctly filtered (<MEMBER_ID>, <ENROLLMENT_ID>, <API_KEY>, <PROGRAM_ID>). The response structure aligns with the expected enrollment data format.

spec/integration/members_spec.rb (1)

1-64: LGTM!

The integration tests provide comprehensive coverage for the Members resource, including listing, filtering, pagination, and CRUD operations. Environment variable handling with sensible defaults is well-implemented, and the VCR cassette usage ensures reproducible tests.

spec/cassettes/member_enrollments/update.yml (1)

1-41: LGTM!

The VCR cassette properly records the PATCH request for updating enrollment expiration. Sensitive data is correctly filtered, and the response confirms the expires_at field was updated as expected.

lib/data_nexus/resources/programs.rb (2)

61-67: LGTM!

The dual-mode members method is a clean implementation of the fluent API pattern. The optional parameter approach allows both listing (programs(id).members.list) and member-specific operations (programs(id).members(member_id).find) through a single intuitive interface. Documentation clearly explains both usage patterns.


3-4: LGTM!

The require statements are properly ordered, with the new program_member dependency added alongside the existing program_members require.

spec/integration/member_enrollments_spec.rb (1)

1-72: Well-structured integration tests covering CRUD operations.

The test suite covers the full lifecycle of member enrollments with proper VCR cassette usage, environment variable defaults for flexibility, and appropriate assertions on response structure.

lib/data_nexus/client.rb (2)

52-61: LGTM!

The members accessor follows the same pattern as programs, providing a clean and consistent API surface.


84-91: Good handling of the ssl_verify parameter.

The ternary on line 90 correctly distinguishes between an explicit false value and a missing/nil value, ensuring ssl_verify: false is respected.

lib/data_nexus/resources/program_member.rb (1)

1-134: Well-structured resource with clear documentation.

The ProgramMember class provides a clean fluent API for accessing member-specific operations and nested resources (consents, enrollments). The YARD documentation is thorough with practical examples.

lib/data_nexus/resources/member_enrollments.rb (3)

108-110: False positive: This is an HTTP DELETE, not SQL.

The Brakeman SQL injection warning is a false positive. The connection.delete method performs an HTTP DELETE request against the REST API, not a SQL query. The enrollment_id is interpolated into a URL path, which is the expected behavior for RESTful resource deletion.


66-70: Good auto-injection of program_id with implicit override behavior.

The merge ensures the resource chain's program_id is always used, which prevents accidental mismatches. Note that if a caller passes program_id in the enrollment hash, it will be silently overwritten. This is likely the intended behavior per the @note documentation.


1-122: Clean CRUD implementation for member enrollments.

The resource provides a complete set of operations with consistent patterns and good documentation. The program_id injection is well-documented via the @note tag.

lib/data_nexus/resources/members.rb (2)

66-76: Clean parameter filtering implementation.

Using slice with explicit allowed params followed by compact is a good defensive pattern that prevents unexpected query parameters from reaching the API.


1-117: Well-implemented top-level Members resource.

The resource provides listing with flexible filtering, pagination support via Collection, and standard find/update operations. Documentation is thorough with practical examples.

spec/integration/program_members_spec.rb (3)

48-53: LGTM!

The fluent API pattern members(member_id).find is cleaner and more readable than passing the ID as a method argument. The test appropriately validates the response structure.


59-66: LGTM!

The fluent API pattern is consistently applied for the update operation. The test validates that the response contains the expected :data wrapper.


72-77: LGTM!

The fluent API pattern for household is consistent with other member operations. Correctly expects a DataNexus::Collection for the list of household members.

Copy link

@fastjames fastjames left a comment

Choose a reason for hiding this comment

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

🍰

@k1bs k1bs merged commit 93e4d54 into main Jan 14, 2026
7 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Feb 3, 2026
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.

2 participants