Skip to content

Add embeddability hooks and transfer expiration policies (v0.2.0)#1

Merged
rameerez merged 2 commits into
mainfrom
v0.2.0-embeddability-and-transfer-expiration
Mar 18, 2026
Merged

Add embeddability hooks and transfer expiration policies (v0.2.0)#1
rameerez merged 2 commits into
mainfrom
v0.2.0-embeddability-and-transfer-expiration

Conversation

@rameerez
Copy link
Copy Markdown
Owner

Summary

This release makes wallets embeddable by other gems (like usage_credits) and adds expiration-aware transfers that preserve source bucket expirations by default.

Embeddability Hooks

The core models (Wallet, Transaction, Allocation, Transfer) now support subclassing so other gems can reuse the wallet ledger with their own:

  • Table namesembedded_table_name class attribute
  • Configurationconfig_provider lambda/object
  • Callbackscallbacks_module for custom dispatch
  • Related modelstransaction_class_name, allocation_class_name, transfer_class_name

This allows usage_credits (and similar gems) to build on the same ledger core with their own tables, configuration, and callbacks — without colliding with vanilla wallets usage in the same app.

Transfers now enforce a same-class constraint to prevent accidental cross-system transfers (Wallets::Wallet cannot transfer directly to UsageCredits::Wallet).

Transfer Expiration Policies

New expiration_policy field on transfers with three modes:

Policy Behavior
:preserve (default) Keeps original expiration buckets. If a transfer consumes multiple source buckets with different expirations, the receiver gets multiple inbound credit transactions.
:none Receiver gets evergreen (non-expiring) credits.
:fixed Set a specific expires_at on all inbound credits.
# Preserve source expirations (default)
sender.transfer_to(receiver, 100)

# Make transferred credits evergreen
sender.transfer_to(receiver, 100, expiration_policy: :none)

# Apply a fixed expiration
sender.transfer_to(receiver, 100, expires_at: 30.days.from_now)

Configurable default:

Wallets.configure do |config|
  config.transfer_expiration_policy = :preserve  # or :none
end

Schema Changes

  • Added expiration_policy column to transfers table (default: "preserve")
  • Removed outbound_transaction_id and inbound_transaction_id FK columns from transfers — now derived via transfer_id on transactions (supports multiple inbound legs)

Rails Compatibility

  • Extended support back to Rails 6.1 (was 7.2+)
  • Added Appraisals for Rails 6.1, 7.0, 7.1, 7.2, 8.0

CI Improvements

Test workflow now runs db:migrate:reset before tests to catch migration template / schema drift early in the default matrix.

Documentation

  • Major README rewrite with clear wallets vs usage_credits comparison
  • Real-world examples: telecom/data plans, games, marketplaces, loyalty programs, gig economy
  • Transfer expiration behavior fully documented

Test plan

  • All existing tests pass
  • New transfer expiration policy tests cover preserve/none/fixed modes
  • Embeddability test verifies subclass isolation
  • CI runs against Rails 6.1 through 8.0
  • Migration template generates correct schema (no outbound/inbound FK columns, has expiration_policy)

🤖 Generated with Claude Code

rameerez and others added 2 commits March 18, 2026 02:04
This release makes wallets embeddable by other gems (like usage_credits)
and adds expiration-aware transfers that preserve source bucket expirations
by default.

Embeddability:
- Wallet, Transaction, Allocation, Transfer models now support subclassing
  with overridable table names, config providers, callbacks, and related
  model class names via class_attribute hooks
- Multiple wallet systems can coexist in the same Rails app without collision
- Transfers now enforce same-class constraint to prevent accidental
  cross-system transfers

Transfer expiration policies:
- New `expiration_policy` column on transfers (preserve/none/fixed)
- :preserve (default) keeps source bucket expirations; splits into multiple
  inbound legs when consuming buckets with different expirations
- :none creates evergreen inbound credits
- :fixed applies a specific expires_at to all inbound credits
- Configurable default via `config.transfer_expiration_policy`

Schema changes:
- Added expiration_policy column to transfers table
- Removed outbound_transaction_id/inbound_transaction_id FKs from transfers
  (now derived via transfer_id on transactions to support multiple inbound legs)

Rails compatibility:
- Extended support back to Rails 6.1 (was 7.2+)
- Added Appraisals for Rails 6.1, 7.0, 7.1, 7.2, 8.0

CI improvements:
- Test workflow now runs db:migrate:reset before tests to catch
  migration template / schema drift early

Documentation:
- Major README rewrite with wallets vs usage_credits comparison
- Real-world examples: telecom, games, marketplaces, loyalty, gig economy
- Transfer expiration behavior fully documented

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MySQL 8+ doesn't allow default values on JSON columns. Use conditional
helper methods to set jsonb for PostgreSQL, json for others, and skip
the default value for MySQL (models handle nil gracefully).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rameerez rameerez merged commit 46aea0d into main Mar 18, 2026
11 of 12 checks passed
@rameerez rameerez deleted the v0.2.0-embeddability-and-transfer-expiration branch March 18, 2026 02:29
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.

1 participant