0.0
The project is in a healthy, maintained state
Rails-specific helpers for CardDB, including model helpers, GraphQL batch loaders, and Railtie configuration.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 7.0
>= 0.3.5, < 1.0
>= 7.0
 Project Readme

CardDB Rails

Rails integration for the carddb gem.

This gem keeps the base CardDB client framework-agnostic and adds Rails-specific conveniences:

  • initializer and Railtie wiring
  • carddb_client controller helper
  • explicit Active Record model helpers
  • GraphQL::Batch loaders for CardDB records, games, datasets, and publishers
  • GraphQL::Dataloader sources for the same resource lookups

Installation

Add this line to your application's Gemfile:

gem 'carddb'
gem 'carddb-rails'

Then run:

bundle install
bin/rails generate carddb:install

Configuration

The generated initializer configures CardDB from Rails.application.credentials[:carddb] and wires Rails.cache and Rails.logger by default.

Expected credentials shape:

carddb:
  publishable_key: carddb_pk_xxx
  secret_key: carddb_sk_xxx
  access_token: carddb_oat_xxx
  api_key: carddb_legacy_xxx
  endpoint: https://carddb.xtda.org/query

Controller Helper

Controllers get a carddb_client helper:

class CardsController < ApplicationController
  def show
    @card = carddb_client.records.get(
      publisher_slug: 'pokemon-company',
      game_key: 'pokemon-tcg',
      dataset_key: 'cards',
      identifier: params[:id]
    )
  end
end

Model Helper

Include CardDB::Rails::HasCardDBRecord in models that hold a CardDB identifier locally.

class DeckEntry < ApplicationRecord
  include CardDB::Rails::HasCardDBRecord

  has_carddb_record \
    dataset_key: 'cards',
    publisher_slug: 'pokemon-company',
    game_key: 'pokemon-tcg',
    identifier: :card_identifier
end

This defines:

  • carddb_record
  • carddb_record!
  • reload_carddb_record
  • carddb_record_loaded?

Additional helpers are available for other CardDB resources:

class DeckTemplate < ApplicationRecord
  include CardDB::Rails::HasCardDBGame
  include CardDB::Rails::HasCardDBDataset

  has_carddb_game game_key: :game_key, publisher_slug: 'pokemon-company'
  has_carddb_dataset dataset_key: :dataset_key, publisher_slug: 'pokemon-company', game_key: :game_key
end

For deck-backed models, use HasCardDBDeck:

class TournamentDeck < ApplicationRecord
  include CardDB::Rails::HasCardDBDeck

  has_carddb_deck id: :carddb_deck_id
end

Or map a local record to an app-owned CardDB deck by external reference:

class TournamentDeck < ApplicationRecord
  include CardDB::Rails::HasCardDBDeck

  has_carddb_deck external_ref: :external_ref
end

That gives you:

  • carddb_deck
  • carddb_deck!
  • reload_carddb_deck
  • carddb_deck_loaded?

To batch-preload deck helpers for many records:

TournamentDeck.preload_carddb_decks(records)

If a model should expose the available datasets for its game, use HasCardDBDatasets:

class DeckTemplate < ApplicationRecord
  include CardDB::Rails::HasCardDBGame
  include CardDB::Rails::HasCardDBDatasets

  has_carddb_game game_key: :game_key, publisher_slug: 'pokemon-company'
  has_carddb_datasets publisher_slug: 'pokemon-company', game_key: :game_key
end

That gives you:

  • carddb_datasets
  • reload_carddb_datasets
  • carddb_datasets_loaded?
  • carddb_dataset('cards')

Example:

template.carddb_datasets
template.carddb_dataset('cards')
template.carddb_datasets(purpose: 'RULES', search: 'rotation')

You can also use dynamic values:

class CollectionItem < ApplicationRecord
  include CardDB::Rails::HasCardDBRecord

  has_carddb_record \
    dataset_key: :carddb_dataset_key,
    publisher_slug: :carddb_publisher_slug,
    game_key: :carddb_game_key,
    identifier: :carddb_identifier
end

Or define multiple named mappings:

class MatchResult < ApplicationRecord
  include CardDB::Rails::HasCardDBRecord

  has_carddb_record :home_card,
    dataset_key: 'cards',
    publisher_slug: 'pokemon-company',
    game_key: 'pokemon-tcg',
    identifier: :home_card_identifier

  has_carddb_record :away_card,
    dataset_key: 'cards',
    publisher_slug: 'pokemon-company',
    game_key: 'pokemon-tcg',
    identifier: :away_card_identifier
end

To batch-preload a CardDB helper for many records:

DeckEntry.preload_carddb_records(entries)

This uses CardDB's batch API and memoizes the loaded records back onto each model instance.

GraphQL::Batch Loader

This gem includes a GraphQL::Batch loader that fetches CardDB records in one remote batch request:

class Types::DeckEntry < Types::BaseObject
  field :carddb_record, Types::CarddbRecord, null: true

  def carddb_record
    CardDB::Rails::GraphQL::RecordLoader.for(
      dataset_key: 'cards',
      publisher_slug: 'pokemon-company',
      game_key: 'pokemon-tcg'
    ).load(object.card_identifier)
  end
end

If your Rails app already uses GraphQL::Batch, this prevents one CardDB HTTP request per node.

Available loaders:

  • CardDB::Rails::GraphQL::DeckLoader
  • CardDB::Rails::GraphQL::DeckByExternalRefLoader
  • CardDB::Rails::GraphQL::RecordLoader
  • CardDB::Rails::GraphQL::GameLoader
  • CardDB::Rails::GraphQL::DatasetLoader
  • CardDB::Rails::GraphQL::PublisherLoader

If your schema uses GraphQL::Dataloader instead of GraphQL::Batch, parallel sources are also available:

  • CardDB::Rails::GraphQL::Dataloader::RecordSource
  • CardDB::Rails::GraphQL::Dataloader::DeckSource
  • CardDB::Rails::GraphQL::Dataloader::DeckByExternalRefSource
  • CardDB::Rails::GraphQL::Dataloader::GameSource
  • CardDB::Rails::GraphQL::Dataloader::DatasetSource
  • CardDB::Rails::GraphQL::Dataloader::PublisherSource

Example:

class Types::DeckEntry < Types::BaseObject
  field :carddb_record, Types::CarddbRecord, null: true

  def carddb_record
    dataloader.with(
      CardDB::Rails::GraphQL::Dataloader::RecordSource,
      dataset_key: 'cards',
      publisher_slug: 'pokemon-company',
      game_key: object.game_key
    ).load(object.card_identifier)
  end
end

You can also use dataloader-backed field macros directly:

class Types::DeckEntry < Types::BaseObject
  carddb_record_dataloader_field :carddb_record,
    type: Types::CarddbRecord,
    identifier: :card_identifier,
    dataset_key: 'cards',
    publisher_slug: 'pokemon-company',
    game_key: :game_key
end

Deck fields are also available in both styles:

class Types::TournamentDeck < Types::BaseObject
  carddb_deck_field :carddb_deck,
    type: Types::CarddbDeck,
    id: :carddb_deck_id

  carddb_deck_dataloader_field :carddb_deck_async,
    type: Types::CarddbDeck,
    id: :carddb_deck_id
end

External-ref lookups use the same macros:

class Types::TournamentDeck < Types::BaseObject
  carddb_deck_field :carddb_deck,
    type: Types::CarddbDeck,
    external_ref: :external_ref

  carddb_deck_dataloader_field :carddb_deck_async,
    type: Types::CarddbDeck,
    external_ref: :external_ref
end

Deck Sync Helpers

For write flows, keep sync explicit with the provided service and job:

CardDB::Rails::DeckSyncService.call(
  external_ref: 'deck-ext-1',
  input: {
    title: 'Pikachu League Cup',
    visibility: 'PRIVATE'
  }
)
CardDB::Rails::DeckSyncJob.perform_later(
  external_ref: 'deck-ext-1',
  input: {
    title: 'Pikachu League Cup',
    visibility: 'PRIVATE'
  }
)

These helpers call client.decks.upsert_by_external_ref(...) and do not hide writes behind model callbacks.

Deck Import / Export Helpers

For explicit service-style import and export flows:

CardDB::Rails::DeckImportService.call(
  input: {
    deckId: 'deck_123',
    text: "4 Pikachu\n4 Rare Candy",
    format: 'SIMPLE_TEXT'
  }
)
CardDB::Rails::DeckExportService.call(
  deck_id: 'deck_123',
  format: 'JSON'
)

Deck Token Controller Helpers

Controllers automatically get token-aware helpers for deck preview/embed/access flows:

class DeckPreviewsController < ApplicationController
  def show
    @deck = carddb_preview_deck(params[:token])
  end
end

Available helpers:

  • carddb_preview_deck(token)
  • carddb_embed_deck(token)
  • carddb_access_deck(token)
  • carddb_exchange_deck_access_token(input)

GraphQL Field Helper

If you want a thin schema macro on top of the loader, extend CardDB::Rails::GraphQL::Helpers in your base object:

class Types::BaseObject < GraphQL::Schema::Object
  extend CardDB::Rails::GraphQL::Helpers
end

Then define CardDB-backed fields declaratively:

class Types::DeckEntry < Types::BaseObject
  carddb_record_field :carddb_record,
    type: Types::CarddbRecord,
    identifier: :card_identifier,
    dataset_key: 'cards',
    publisher_slug: 'pokemon-company',
    game_key: 'pokemon-tcg'
end

For a list relationship, use carddb_datasets_field:

class Types::DeckTemplate < Types::BaseObject
  carddb_datasets_field :carddb_datasets,
    type: [Types::CarddbDataset],
    publisher_slug: 'pokemon-company',
    game_key: :game_key
end

If the underlying model includes HasCardDBDatasets, this field uses the model helper directly.

If you prefer dataloader-oriented naming consistency, carddb_datasets_dataloader_field is also available.

Integration Coverage

The gem includes coverage for:

  • Railtie configuration wiring via CardDB::Rails.apply_rails_configuration!
  • install generator output for config/initializers/carddb.rb

You can also expose related CardDB resources directly:

class Types::DeckTemplate < Types::BaseObject
  carddb_game_field :carddb_game,
    type: Types::CarddbGame,
    game_key: :game_key,
    publisher_slug: 'pokemon-company'

  carddb_dataset_field :carddb_dataset,
    type: Types::CarddbDataset,
    dataset_key: :dataset_key,
    publisher_slug: 'pokemon-company',
    game_key: :game_key

  carddb_publisher_field :carddb_publisher,
    type: Types::CarddbPublisher,
    slug: :publisher_slug
end

Rails + GraphQL Example

For a GraphQL app that already uses GraphQL::Batch, a good pattern is:

class DeckEntry < ApplicationRecord
  include CardDB::Rails::HasCardDBRecord
  include CardDB::Rails::HasCardDBGame

  has_carddb_record \
    dataset_key: 'cards',
    publisher_slug: 'pokemon-company',
    game_key: :game_key,
    identifier: :card_identifier

  has_carddb_game \
    game_key: :game_key,
    publisher_slug: 'pokemon-company'
end
class Types::BaseObject < GraphQL::Schema::Object
  extend CardDB::Rails::GraphQL::Helpers
end
class Types::DeckEntry < Types::BaseObject
  field :id, ID, null: false

  carddb_record_field :carddb_record,
    type: Types::CarddbRecord,
    identifier: :card_identifier,
    dataset_key: 'cards',
    publisher_slug: 'pokemon-company',
    game_key: :game_key

  carddb_game_field :carddb_game,
    type: Types::CarddbGame,
    game_key: :game_key,
    publisher_slug: 'pokemon-company'
end

That keeps the Active Record model explicit while letting GraphQL batch remote CardDB lookups across sibling nodes.

If you want to expose all datasets for a game in GraphQL, a good pattern is to call the model helper directly:

class Types::DeckTemplate < Types::BaseObject
  field :carddb_datasets, [Types::CarddbDataset], null: false

  def carddb_datasets
    object.carddb_datasets
  end
end

Boundaries

This gem intentionally does not:

  • make CardDB objects behave like ActiveRecord::Relation
  • define remote CardDB fields directly on your models
  • perform implicit remote lookups in callbacks or validations
  • sync CardDB data into local tables

The intent is explicit remote access with Rails-friendly ergonomics.