Class: CardDB::Resources::Records

Inherits:
Base
  • Object
show all
Includes:
FilterOperators
Defined in:
lib/carddb/resources/records.rb

Overview

Records resource for searching and fetching records

Instance Attribute Summary

Attributes inherited from Base

#client, #config, #connection

Instance Method Summary collapse

Methods included from FilterOperators

#contains, #eq, #gt, #gte, #ilike, #is_not_null, #is_null, #like, #lt, #lte, #neq, #not_within, #within

Methods inherited from Base

#initialize

Constructor Details

This class inherits a constructor from CardDB::Resources::Base

Instance Method Details

#delete_batch(input:) ⇒ Object

Create a dry-run or destructive bulk delete/reconciliation job.



97
98
99
100
101
102
# File 'lib/carddb/resources/records.rb', line 97

def delete_batch(input:)
  config.require_secret_credential!('records.delete_batch')

  data = connection.execute(QueryBuilder.delete_dataset_records, { input: input })
  DatasetRecordDeleteJob.new(data['datasetRecordsDelete'], client: client)
end

#fetch(id, cache: nil, include_pricing: false) ⇒ Record?

Fetch a single record by ID

Parameters:

  • id (String)

    The record UUID

  • cache (Boolean, nil) (defaults to: nil)

    Whether to cache (nil = use config setting)

  • include_pricing (Boolean) (defaults to: false)

    Include live TCGPlayer pricing when configured

Returns:

  • (Record, nil)

    The record or nil if not found



285
286
287
288
289
290
291
292
293
294
295
# File 'lib/carddb/resources/records.rb', line 285

def fetch(id, cache: nil, include_pricing: false)
  key = cache_key('records', 'fetch', id: id, include_pricing: include_pricing)
  with_cache(key, resource: :records, cache: cache) do
    query = QueryBuilder.fetch_record(include_pricing: include_pricing)
    data = connection.execute(query, { id: id })

    return nil unless data['fetchRecord']

    Record.new(data['fetchRecord'], client: client)
  end
end

#fetch_many(ids, include_pricing: false) ⇒ Array<Record>

Fetch multiple records by IDs

Parameters:

  • ids (Array<String>)

    Array of record UUIDs (max 1000)

  • include_pricing (Boolean) (defaults to: false)

    Include live TCGPlayer pricing when configured

Returns:

  • (Array<Record>)

    Array of records

Raises:

  • (ArgumentError)

    If more than 1000 IDs provided



410
411
412
413
414
415
416
417
# File 'lib/carddb/resources/records.rb', line 410

def fetch_many(ids, include_pricing: false)
  raise ArgumentError, 'Maximum 1000 IDs allowed' if ids.length > 1000

  query = QueryBuilder.fetch_records(include_pricing: include_pricing)
  data = connection.execute(query, { ids: ids })

  (data['fetchRecords'] || []).map { |r| Record.new(r, client: client) }
end

#get(identifier:, dataset_key:, publisher_slug: nil, game_key: nil, cache: nil, include_pricing: false) ⇒ Record?

Get a record by its identifier value

Looks up the record using the dataset’s designated identifier field. Returns nil if the dataset has no identifier field or if no matching record is found.

Examples:

record = client.records.get(identifier: "xy1-1", dataset_key: "cards")

Parameters:

  • identifier (String)

    The identifier value to look up

  • dataset_key (String)

    Dataset key (required)

  • publisher_slug (String, nil) (defaults to: nil)

    Publisher slug (uses default if not provided)

  • game_key (String, nil) (defaults to: nil)

    Game key (uses default if not provided)

  • cache (Boolean, nil) (defaults to: nil)

    Whether to cache (nil = use config setting)

  • include_pricing (Boolean) (defaults to: false)

    Include live TCGPlayer pricing when configured

Returns:

  • (Record, nil)

    The record or nil if not found



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/carddb/resources/records.rb', line 312

def get(
  identifier:,
  dataset_key:,
  publisher_slug: nil,
  game_key: nil,
  cache: nil,
  include_pricing: false
)
  resolved_publisher = resolve_publisher(publisher_slug)
  resolved_game = resolve_game(game_key)

  validate_access!(resolved_publisher, resolved_game)

  key = cache_key('records', 'get', publisher_slug: resolved_publisher, game_key: resolved_game,
                                    dataset_key: dataset_key, identifier: identifier,
                                    include_pricing: include_pricing)
  with_cache(key, resource: :records, cache: cache) do
    query = QueryBuilder.fetch_record_by_identifier(include_pricing: include_pricing)
    variables = {
      publisherSlug: resolved_publisher,
      gameKey: resolved_game,
      datasetKey: dataset_key,
      identifier: identifier
    }

    data = connection.execute(query, variables)

    return nil unless data['fetchRecordByIdentifier']

    Record.new(data['fetchRecordByIdentifier'], client: client)
  end
end

#get_by_dataset_identifier(dataset_id:, identifier:, cache: nil, include_pricing: false) ⇒ Object

Get a publisher-accessible record by dataset ID and identifier value.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/carddb/resources/records.rb', line 68

def get_by_dataset_identifier(dataset_id:, identifier:, cache: nil, include_pricing: false)
  key = cache_key(
    'records',
    'get_by_dataset_identifier',
    dataset_id: dataset_id,
    identifier: identifier,
    include_pricing: include_pricing
  )
  with_cache(key, resource: :records, cache: cache) do
    query = QueryBuilder.dataset_record(
      dataset_id: dataset_id,
      identifier: identifier,
      include_pricing: include_pricing
    )
    data = connection.execute(query, { datasetId: dataset_id, identifier: identifier })

    data['datasetRecord'] ? Record.new(data['datasetRecord'], client: client) : nil
  end
end

#get_delete_job(id, cache: nil) ⇒ Object

Get one bulk delete/reconciliation job.



105
106
107
108
109
110
111
# File 'lib/carddb/resources/records.rb', line 105

def get_delete_job(id, cache: nil)
  key = cache_key('records', 'get_delete_job', id: id)
  with_cache(key, resource: :records, cache: cache) do
    data = connection.execute(QueryBuilder.dataset_record_delete_job, { id: id })
    data['datasetRecordDeleteJob'] ? DatasetRecordDeleteJob.new(data['datasetRecordDeleteJob'], client: client) : nil
  end
end

#get_many(identifiers:, dataset_key:, publisher_slug: nil, game_key: nil, cache: nil, include_pricing: false) ⇒ Array<Record>

Get multiple records by their identifier values

Looks up records using the dataset’s designated identifier field. Missing identifiers are omitted from the result.

Examples:

records = client.records.get_many(
  identifiers: ["xy1-1", "xy1-2"],
  dataset_key: "cards"
)

Parameters:

  • identifiers (Array<String>)

    Identifier values to look up (max 1000)

  • dataset_key (String)

    Dataset key (required)

  • publisher_slug (String, nil) (defaults to: nil)

    Publisher slug (uses default if not provided)

  • game_key (String, nil) (defaults to: nil)

    Game key (uses default if not provided)

  • cache (Boolean, nil) (defaults to: nil)

    Whether to cache (nil = use config setting)

  • include_pricing (Boolean) (defaults to: false)

    Include live TCGPlayer pricing when configured

Returns:

  • (Array<Record>)

    Matching records

Raises:

  • (ArgumentError)

    If more than 1000 identifiers are provided



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/carddb/resources/records.rb', line 364

def get_many(
  identifiers:,
  dataset_key:,
  publisher_slug: nil,
  game_key: nil,
  cache: nil,
  include_pricing: false
)
  raise ArgumentError, 'Maximum 1000 identifiers allowed' if identifiers.length > 1000
  return [] if identifiers.empty?

  resolved_publisher = resolve_publisher(publisher_slug)
  resolved_game = resolve_game(game_key)

  validate_access!(resolved_publisher, resolved_game)

  key = cache_key(
    'records',
    'get_many',
    publisher_slug: resolved_publisher,
    game_key: resolved_game,
    dataset_key: dataset_key,
    identifiers: identifiers,
    include_pricing: include_pricing
  )
  with_cache(key, resource: :records, cache: cache) do
    query = QueryBuilder.fetch_records_by_identifier(include_pricing: include_pricing)
    variables = {
      publisherSlug: resolved_publisher,
      gameKey: resolved_game,
      datasetKey: dataset_key,
      identifiers: identifiers
    }

    data = connection.execute(query, variables)

    (data['fetchRecordsByIdentifier'] || []).map { |record| Record.new(record, client: client) }
  end
end

#list(dataset_id:, filter: nil, order_by: nil, first: nil, after: nil, last: nil, before: nil, validate_schema: nil, cache: nil, &block) ⇒ Object

List records in a publisher-accessible dataset by dataset ID. rubocop:disable Metrics/MethodLength



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/carddb/resources/records.rb', line 11

def list(
  dataset_id:,
  filter: nil,
  order_by: nil,
  first: nil,
  after: nil,
  last: nil,
  before: nil,
  validate_schema: nil,
  cache: nil,
  &block
)
  final_filter = block_given? ? FilterBuilder.build(&block) : filter
  query = QueryBuilder.list_dataset_records(
    dataset_id: dataset_id,
    filter: final_filter,
    order_by: order_by,
    first: first,
    after: after,
    last: last,
    before: before,
    validate_schema: validate_schema
  )
  variables = build_variables(
    datasetId: dataset_id,
    filter: final_filter,
    orderBy: order_by,
    first: first,
    after: after,
    last: last,
    before: before,
    validateSchema: validate_schema
  )
  key = cache_key('records', 'list', **variables)

  data = with_cache(key, resource: :records, cache: cache) { connection.execute(query, variables) }

  Collection.new(
    data['datasetRecords'],
    item_class: Record,
    next_page_loader: lambda { |cursor|
      list(
        dataset_id: dataset_id,
        filter: final_filter,
        order_by: order_by,
        first: first,
        after: cursor,
        validate_schema: validate_schema,
        cache: cache
      )
    },
    client: client
  )
end

#list_delete_jobs(publisher_id:, dataset_id: nil, status: nil, first: nil, after: nil, cache: nil) ⇒ Object

List bulk delete/reconciliation jobs for a publisher.



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/carddb/resources/records.rb', line 114

def list_delete_jobs(publisher_id:, dataset_id: nil, status: nil, first: nil, after: nil, cache: nil)
  query = QueryBuilder.dataset_record_delete_jobs(
    publisher_id: publisher_id,
    dataset_id: dataset_id,
    status: status,
    first: first,
    after: after
  )
  variables = build_variables(
    publisherId: publisher_id,
    datasetId: dataset_id,
    status: status,
    first: first,
    after: after
  )
  key = cache_key('records', 'list_delete_jobs', **variables)

  data = with_cache(key, resource: :records, cache: cache) { connection.execute(query, variables) }

  Collection.new(
    data['datasetRecordDeleteJobs'],
    item_class: DatasetRecordDeleteJob,
    next_page_loader: lambda { |cursor|
      list_delete_jobs(
        publisher_id: publisher_id,
        dataset_id: dataset_id,
        status: status,
        first: first,
        after: cursor,
        cache: cache
      )
    },
    client: client
  )
end

#search(dataset_key:, publisher_slug: nil, game_key: nil, filter: nil, search: nil, order_by: nil, resolve_links: nil, first: nil, after: nil, validate_schema: nil, include_pricing: false) {|FilterBuilder| ... } ⇒ Collection<Record>

Search for records in a dataset

rubocop:disable Metrics/MethodLength, Metrics/ParameterLists

Examples:

Basic search

records = client.records.search(dataset_key: "cards")

With filter DSL

records = client.records.search(dataset_key: "cards") do
  where(type: "creature")
  where(hp: gte(100))
end

With hash filter

records = client.records.search(
  dataset_key: "cards",
  filter: { "type" => "creature" }
)

Parameters:

  • dataset_key (String)

    Dataset key (required)

  • publisher_slug (String, nil) (defaults to: nil)

    Publisher slug (uses default if not provided)

  • game_key (String, nil) (defaults to: nil)

    Game key (uses default if not provided)

  • filter (Hash, nil) (defaults to: nil)

    Filter conditions

  • search (String, nil) (defaults to: nil)

    Full-text search

  • order_by (String, nil) (defaults to: nil)

    Field to order by (e.g., “name:asc”)

  • resolve_links (Array<String>, nil) (defaults to: nil)

    Link fields to resolve

  • first (Integer, nil) (defaults to: nil)

    Maximum number of results

  • after (String, nil) (defaults to: nil)

    Cursor for pagination

  • validate_schema (Boolean, nil) (defaults to: nil)

    Whether to validate against schema

  • include_pricing (Boolean) (defaults to: false)

    Include live TCGPlayer pricing when configured

Yields:

Returns:



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/carddb/resources/records.rb', line 197

def search(
  dataset_key:,
  publisher_slug: nil,
  game_key: nil,
  filter: nil,
  search: nil,
  order_by: nil,
  resolve_links: nil,
  first: nil,
  after: nil,
  validate_schema: nil,
  include_pricing: false,
  &block
)
  resolved_publisher = resolve_publisher(publisher_slug)
  resolved_game = resolve_game(game_key)

  validate_access!(resolved_publisher, resolved_game)

  # Build filter from DSL block if provided
  final_filter = if block_given?
                   FilterBuilder.build(&block)
                 else
                   filter
                 end

  query = QueryBuilder.search_records(
    publisher_slug: resolved_publisher,
    game_key: resolved_game,
    dataset_key: dataset_key,
    filter: final_filter,
    search: search,
    order_by: order_by,
    resolve_links: resolve_links,
    first: first,
    after: after,
    validate_schema: validate_schema,
    include_pricing: include_pricing
  )

  variables = build_variables(
    publisherSlug: resolved_publisher,
    gameKey: resolved_game,
    datasetKey: dataset_key,
    filter: final_filter,
    search: search,
    orderBy: order_by,
    resolveLinks: resolve_links,
    first: first,
    after: after,
    validateSchema: validate_schema
  )

  data = connection.execute(query, variables)

  # Create next page loader that preserves the filter block
  search_params = {
    dataset_key: dataset_key,
    publisher_slug: publisher_slug,
    game_key: game_key,
    filter: final_filter,
    search: search,
    order_by: order_by,
    resolve_links: resolve_links,
    first: first,
    validate_schema: validate_schema,
    include_pricing: include_pricing
  }

  next_page_loader = lambda do |cursor|
    search(**search_params, after: cursor)
  end

  Collection.new(
    data['searchRecords'],
    item_class: Record,
    next_page_loader: next_page_loader,
    client: client
  )
end

#upsert_batch(input:) ⇒ Object

Start an import-backed batch upsert, or return a dry-run result.



89
90
91
92
93
94
# File 'lib/carddb/resources/records.rb', line 89

def upsert_batch(input:)
  config.require_secret_credential!('records.upsert_batch')

  data = connection.execute(QueryBuilder.upsert_dataset_records, { input: input })
  DatasetRecordsUpsertPayload.new(data['datasetRecordsUpsert'], client: client)
end

#wait_for_delete_job(id, interval: 1, timeout: 300) ⇒ Object

Poll a delete job until it reaches a terminal state.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/carddb/resources/records.rb', line 151

def wait_for_delete_job(id, interval: 1, timeout: 300)
  started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)

  loop do
    job = get_delete_job(id, cache: false)
    raise Error, "Delete job '#{id}' was not found" unless job
    return job if job.completed? || job.failed?

    elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
    raise Error, "Timed out waiting for delete job '#{id}'" if elapsed >= timeout

    sleep interval
  end
end