basecradle 0.10.2 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/README.md +60 -3
- data/lib/basecradle/client.rb +20 -6
- data/lib/basecradle/errors.rb +9 -4
- data/lib/basecradle/serialization.rb +52 -8
- data/lib/basecradle/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b1fc584abdde2edfd9233ea937bf054ba6fb672708b6c815014d46a1dc4d59c2
|
|
4
|
+
data.tar.gz: bc53a3e49ad9eb40fd21e5fb2fe424d054738359a30eb2c137a26c8a5f634d27
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c603f01c320e465db82dc376ad78cfca4d52c63db107580be7366d2a2c3b79f99e1d5b3f356917bd1526b9f63bb294747cc218a1e7cf71e8fc22caa41ccc4b34
|
|
7
|
+
data.tar.gz: 401d5b9b58f5dc726fb3bc457180954b582e077b8b0aa224c0f92513a39c4122bac16ea56dfff4746e44e2b252cc88ab8a6cf12b178d989ee1eed5b2caf1d02a
|
data/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,87 @@ section**. The newest heading is always the version `lib/basecradle/version.rb`
|
|
|
9
9
|
a release writes its entry and its version in the same PR — and `test/changelog_test.rb`
|
|
10
10
|
fails CI if the two ever disagree.
|
|
11
11
|
|
|
12
|
+
## [0.10.3] - 2026-09-30
|
|
13
|
+
|
|
14
|
+
### Security
|
|
15
|
+
|
|
16
|
+
- **`Marshal.dump` and `to_yaml` no longer emit a `Client`'s token**
|
|
17
|
+
([#205](https://github.com/basecradle/basecradle-ruby/issues/205)). 0.10.1 shut the
|
|
18
|
+
JSON door on a client; `Marshal` and Psych walk instance variables directly, consult no
|
|
19
|
+
`as_json`, and need no ActiveSupport to do it — so both still wrote the raw `bc_uat_`
|
|
20
|
+
credential into their output. They are the worse pair, because they are what puts a
|
|
21
|
+
token *at rest*: ActiveSupport's cache stores marshal what you write, so
|
|
22
|
+
`Rails.cache.write("bc", bc)` landed it in Redis, memcached or a file store; a
|
|
23
|
+
Marshal-backed session landed it in the session; Delayed::Job YAMLs its handler into
|
|
24
|
+
the database. (Queue backends that serialize arguments as **JSON** — ActiveJob and
|
|
25
|
+
Sidekiq among them — go through `to_json`, and so were already closed in 0.10.1.)
|
|
26
|
+
`Marshal.dump(bc)`, `bc.to_yaml`,
|
|
27
|
+
`YAML.dump(conn: bc)` and the `Marshal.load(Marshal.dump(bc))` deep-copy idiom now all
|
|
28
|
+
raise `BaseCradle::NotSerializableError`, the same typed error with the same message
|
|
29
|
+
the JSON door already raised. Long-standing and latent; no token is known to have been
|
|
30
|
+
emitted.
|
|
31
|
+
- **The collection door was the wider one, not the narrower one.** Through JSON, a
|
|
32
|
+
collection never reached the token — ActiveSupport's `Enumerable#as_json` shadows
|
|
33
|
+
`Object#as_json`, so `bc.messages.to_json` serialized fetched records, never the client
|
|
34
|
+
held in an ivar. `Marshal` and Psych have no such shadow, so
|
|
35
|
+
**`Marshal.dump(bc.messages)` reached the credential where `bc.messages.to_json` never
|
|
36
|
+
did** — and a query is the likelier of the two to be handed to a cache. Every lazy
|
|
37
|
+
collection (`bc.timelines`, `bc.messages`, `timeline.tasks`, any `.filter(...)`, the
|
|
38
|
+
`Paginator` behind them) now refuses through all four doors; the refusals are on the
|
|
39
|
+
shared `NotSerializable` mixin, so a resource added later is covered by construction.
|
|
40
|
+
**`.to_a` is only half the remedy for a dumper** — the array it hands back is full of
|
|
41
|
+
models that each hold the client, so `Marshal.dump(bc.messages.to_a)` lands back on the
|
|
42
|
+
same error. Dump `bc.messages.to_a.map(&:to_h)`, which the refusal now says.
|
|
43
|
+
- **Anything holding a client refuses too — models included, and that is the fix.** Both
|
|
44
|
+
walkers recurse, so `Marshal.dump(message)` and `message.to_yaml` reached the client's
|
|
45
|
+
token where `message.to_json` (which serializes the wire record) never did. They now
|
|
46
|
+
raise, naming `BaseCradle::Client` as what was reached. **If you cache or enqueue
|
|
47
|
+
models, cache the record instead** — `message.to_h` is the wire `Hash`, holds no
|
|
48
|
+
client, and marshals exactly as it always did; rebuild with
|
|
49
|
+
`BaseCradle::Message.new(record, client: bc)` when you need the verbs back. Nothing
|
|
50
|
+
else changed: `to_json`, `as_json`, `to_h`, `inspect`, every field reader and
|
|
51
|
+
`dup`/`clone` (which do not go through `Marshal`) are untouched. This matches the
|
|
52
|
+
Python SDK, whose `__reduce__` refusal says the same thing — *“Nothing holding a client
|
|
53
|
+
can be serialized either … copy the record's data instead.”*
|
|
54
|
+
|
|
55
|
+
### Known limitation
|
|
56
|
+
|
|
57
|
+
- **YAML reaches the `Enumerator` escape that 0.10.1 documented for JSON, on plain
|
|
58
|
+
Ruby.** Every refusal here is on this SDK's own objects; an `Enumerator` you build from
|
|
59
|
+
a collection (`bc.messages.each`, `bc.messages.lazy`) is a plain Ruby object this SDK
|
|
60
|
+
does not own and may not patch. Psych iterates one to dump it — with **no
|
|
61
|
+
ActiveSupport required**, where the `to_json` version is inert without it — so
|
|
62
|
+
`bc.messages.each.to_yaml` fires the page-by-page GET loop and *then* raises
|
|
63
|
+
`NotSerializableError` on the first record, which holds the client: the requests are
|
|
64
|
+
spent and no document comes out. `Marshal` is the exception — Ruby itself refuses to
|
|
65
|
+
dump an `Enumerator` at all. No token is exposed through any of them. Call `.first(n)`
|
|
66
|
+
or `.to_a` before a renderer, and `.map(&:to_h)` too before a dumper.
|
|
67
|
+
- **Four doors is every hook Ruby gives a library**, not every way bytes can be made. A
|
|
68
|
+
serializer that reads instance variables directly rather than through `as_json`,
|
|
69
|
+
`to_json`, `marshal_dump` or `encode_with` is outside what this SDK can intercept.
|
|
70
|
+
Keep a client out of one.
|
|
71
|
+
|
|
72
|
+
### Migrating
|
|
73
|
+
|
|
74
|
+
Nothing to change unless you `Marshal.dump` or `to_yaml` something that holds a client.
|
|
75
|
+
If you do:
|
|
76
|
+
|
|
77
|
+
- **Caching or enqueuing a model** — `Rails.cache.write(key, message)`,
|
|
78
|
+
`Rails.cache.fetch(key) { bc.messages.get(id) }` — now raises
|
|
79
|
+
`BaseCradle::NotSerializableError` where it used to write your token to the store.
|
|
80
|
+
Cache `message.to_h`, the wire `Hash`, and rebuild with
|
|
81
|
+
`BaseCradle::Message.new(record, client: bc)` if you need the verbs back.
|
|
82
|
+
- **Caching or enqueuing a client or a collection** — serialize nothing; build a client
|
|
83
|
+
where you need one (`BaseCradle::Client.new(token)`), moving the token only through
|
|
84
|
+
whatever you already trust with secrets. For a collection, `.to_a` / `.first(n)` is
|
|
85
|
+
enough for JSON but **not** for a dumper — use `bc.messages.to_a.map(&:to_h)`, since
|
|
86
|
+
the models in that array each hold the client.
|
|
87
|
+
- **Deep-copying with `Marshal.load(Marshal.dump(x))`** — refuses at the dump for the
|
|
88
|
+
same objects. `dup` and `clone` do not go through `Marshal` and are unchanged.
|
|
89
|
+
|
|
90
|
+
If any of these ran in production against a real store, treat the token as disclosed and
|
|
91
|
+
rotate it: `bc.sessions` lists your credentials and `session.revoke` retires one.
|
|
92
|
+
|
|
12
93
|
## [0.10.2] - 2026-09-30
|
|
13
94
|
|
|
14
95
|
### Security
|
|
@@ -484,6 +565,7 @@ the Python SDK's behavior in idiomatic Ruby. Zero runtime dependencies.
|
|
|
484
565
|
- **Quality bars** — a README-as-tested-doc harness (every example runs against a mocked
|
|
485
566
|
API) and a spec drift-guard (CI fails if the live API grows beyond the SDK).
|
|
486
567
|
|
|
568
|
+
[0.10.3]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.10.3
|
|
487
569
|
[0.10.2]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.10.2
|
|
488
570
|
[0.10.1]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.10.1
|
|
489
571
|
[0.10.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.10.0
|
data/README.md
CHANGED
|
@@ -375,10 +375,15 @@ something Rails renders comes out right too. Three caveats worth knowing:
|
|
|
375
375
|
## What does not serialize: clients and collections
|
|
376
376
|
|
|
377
377
|
Two things in this SDK are *not* records, and serializing either raises
|
|
378
|
-
`BaseCradle::NotSerializableError` rather than emitting something
|
|
378
|
+
`BaseCradle::NotSerializableError` rather than emitting something — and under `Marshal`
|
|
379
|
+
and YAML, so does anything *holding* one, models included (see below). Four doors refuse:
|
|
380
|
+
`to_json`, `as_json`, `Marshal.dump` and `to_yaml` — every hook Ruby gives a library to
|
|
381
|
+
intercept. A serializer that reads instance variables directly instead of through those
|
|
382
|
+
hooks is outside what any library can stop, so keep a client out of one.
|
|
379
383
|
|
|
380
384
|
```ruby
|
|
381
385
|
require "basecradle"
|
|
386
|
+
require "yaml"
|
|
382
387
|
|
|
383
388
|
bc = BaseCradle::Client.new
|
|
384
389
|
|
|
@@ -394,6 +399,18 @@ rescue BaseCradle::NotSerializableError => e
|
|
|
394
399
|
puts e.message.include?(".to_a") # => true
|
|
395
400
|
end
|
|
396
401
|
|
|
402
|
+
begin
|
|
403
|
+
Marshal.dump(bc) # and Rails.cache.write("bc", bc), a job argument
|
|
404
|
+
rescue BaseCradle::NotSerializableError => e
|
|
405
|
+
puts e.message.include?("bc_uat_") # => true
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
begin
|
|
409
|
+
bc.messages.to_yaml # YAML walks the ivars, and reaches the client
|
|
410
|
+
rescue BaseCradle::NotSerializableError => e
|
|
411
|
+
puts e.message.include?(".to_a") # => true
|
|
412
|
+
end
|
|
413
|
+
|
|
397
414
|
puts bc.messages.first(20).to_json # serialize the page you actually asked for
|
|
398
415
|
```
|
|
399
416
|
|
|
@@ -410,10 +427,50 @@ walk — but a client's instance variables are eight collections, so serializing
|
|
|
410
427
|
ran those loops too, on its way to the credential.) Call `.to_a` or `.first(n)` yourself
|
|
411
428
|
and serialize that, so how much you fetch is a visible act in your code.
|
|
412
429
|
|
|
413
|
-
|
|
430
|
+
**`Marshal` and YAML are the same refusal for a different reason.** Both walk instance
|
|
431
|
+
variables directly, so neither needed ActiveSupport to reach the token — and neither is
|
|
432
|
+
shadowed the way `Enumerable#as_json` shadowed the ivar walk, so `Marshal.dump(bc.messages)`
|
|
433
|
+
reached the credential where `bc.messages.to_json` never did. They are also where a leaked
|
|
434
|
+
token stops being a log line and becomes a token *at rest*: ActiveSupport's cache stores
|
|
435
|
+
marshal what you write, so `Rails.cache.write("bc", bc)` landed it in Redis, memcached or
|
|
436
|
+
a file; a Marshal-backed session landed it in the session; Delayed::Job YAMLs its handler
|
|
437
|
+
into the database. `Marshal.load(Marshal.dump(x))`, the deep-copy idiom, refuses at the
|
|
438
|
+
dump. (Queue backends that serialize arguments as **JSON** — ActiveJob and Sidekiq among
|
|
439
|
+
them — go through `to_json`, and so were already closed in 0.10.1.)
|
|
440
|
+
|
|
441
|
+
**Anything *holding* a client refuses too, models included** — both walkers recurse, so
|
|
442
|
+
`Marshal.dump(message)` reaches the client and raises naming `BaseCradle::Client`. That is
|
|
443
|
+
the fix, not a limitation: before, it emitted the token. It is also why **`.to_a` is only
|
|
444
|
+
half the remedy for a dumper**: the array it hands back is full of models that each hold
|
|
445
|
+
the client, so `Marshal.dump(bc.messages.to_a)` lands straight back on the same error.
|
|
446
|
+
Dump the wire records — which is what you wanted in a cache anyway:
|
|
447
|
+
|
|
448
|
+
```ruby
|
|
449
|
+
require "basecradle"
|
|
450
|
+
|
|
451
|
+
bc = BaseCradle::Client.new
|
|
452
|
+
message = bc.messages.first
|
|
453
|
+
|
|
454
|
+
record = message.to_h # the wire Hash, no client in it
|
|
455
|
+
puts Marshal.load(Marshal.dump(record)) == record # => true
|
|
456
|
+
|
|
457
|
+
page = bc.messages.first(20).map(&:to_h) # a whole page, no client in it
|
|
458
|
+
puts Marshal.load(Marshal.dump(page)) == page # => true
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Rebuild a model from a cached record with `BaseCradle::Message.new(record, client: bc)`
|
|
462
|
+
when you need its verbs back.
|
|
463
|
+
|
|
464
|
+
Every refusal happens before any HTTP — but they are on the SDK's own objects. An
|
|
414
465
|
`Enumerator` you built from one is a plain Ruby object this SDK does not own, so
|
|
415
466
|
`render json: { recent: bc.messages.lazy }` and `bc.messages.each.to_json` still page the
|
|
416
|
-
whole resource.
|
|
467
|
+
whole resource. **YAML reaches that escape on plain Ruby**, where the `to_json` version
|
|
468
|
+
needs ActiveSupport: Psych iterates an `Enumerator` to dump it, so
|
|
469
|
+
`bc.messages.each.to_yaml` fires the page-by-page GET loop and *then* raises
|
|
470
|
+
`NotSerializableError` on the first record (which holds the client) — the requests are
|
|
471
|
+
spent, and no document comes out. (`Marshal` is the exception: Ruby itself refuses to
|
|
472
|
+
dump an `Enumerator` at all.) No token is exposed through any of them. Call `.first(n)`
|
|
473
|
+
or `.to_a` before handing a query to a renderer, and `.map(&:to_h)` too before a dumper.
|
|
417
474
|
|
|
418
475
|
`bc.inspect` and `"#{bc}"` are redacted for the same reason — Ruby's default `inspect`
|
|
419
476
|
dumps every instance variable, which would print the token into every exception message
|
data/lib/basecradle/client.rb
CHANGED
|
@@ -243,14 +243,28 @@ module BaseCradle
|
|
|
243
243
|
|
|
244
244
|
private
|
|
245
245
|
|
|
246
|
-
# Why +to_json+ / +as_json+
|
|
247
|
-
# a record, and the thing it would
|
|
246
|
+
# Why every serializer refuses — +to_json+ / +as_json+, +Marshal.dump+, +to_yaml+
|
|
247
|
+
# (see BaseCradle::NotSerializable). A client is not a record, and the thing it would
|
|
248
|
+
# emit is a live credential.
|
|
249
|
+
#
|
|
250
|
+
# The last sentence is for the caller who never named a client: Marshal and Psych
|
|
251
|
+
# recurse, so dumping a model or a resource reaches the client it holds and lands
|
|
252
|
+
# here. Telling that caller only about clients would be telling them about an object
|
|
253
|
+
# they did not pass — the Python SDK's +__reduce__+ is worded for the same reason
|
|
254
|
+
# ("copying a resource or a record reaches this too — copy the record's data").
|
|
255
|
+
#
|
|
256
|
+
# And it has to name +to_h+, because the first remedy does not hold through those two
|
|
257
|
+
# doors: a model serializes as its record through JSON, but <tt>Marshal.dump(bc.me)</tt>
|
|
258
|
+
# walks to the client again. Advice that fails through the door the reader just used
|
|
259
|
+
# is worse than none.
|
|
248
260
|
def serialization_refusal
|
|
249
261
|
"#{self.class} is a connection holding your bc_uat_ token, not a record. " \
|
|
250
|
-
"Serializing it writes the raw credential into whatever you were rendering
|
|
251
|
-
"logging, and a token in a log is a token to rotate.
|
|
252
|
-
"meant instead (bc.me, a timeline, a message), or
|
|
253
|
-
"connection itself."
|
|
262
|
+
"Serializing it writes the raw credential into whatever you were rendering, " \
|
|
263
|
+
"logging or storing, and a token in a log or a cache is a token to rotate. " \
|
|
264
|
+
"Serialize the record you meant instead (bc.me, a timeline, a message), or " \
|
|
265
|
+
"bc.base_url to name the connection itself. Marshal and YAML reach a client " \
|
|
266
|
+
"through anything holding one, models included, so dump the wire record rather " \
|
|
267
|
+
"than the object: model.to_h holds no client."
|
|
254
268
|
end
|
|
255
269
|
|
|
256
270
|
# Record what the mint response said about the credential just issued. Private: only
|
data/lib/basecradle/errors.rb
CHANGED
|
@@ -30,10 +30,15 @@ module BaseCradle
|
|
|
30
30
|
# this response form). The SDK raises rather than return an ambiguous nil.
|
|
31
31
|
class MissingFieldError < Error; end
|
|
32
32
|
|
|
33
|
-
# A Client or a collection resource was serialized
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
33
|
+
# A Client or a collection resource was serialized — +to_json+ / +as_json+,
|
|
34
|
+
# +Marshal.dump+, or +to_yaml+ / +YAML.dump+. Neither is a record: a Client holds your
|
|
35
|
+
# bearer token, and a collection is a lazy query that holds the client and would page
|
|
36
|
+
# the whole API from inside your renderer. The SDK refuses loudly rather than emit a
|
|
37
|
+
# credential or a heap address — see BaseCradle::NotSerializable.
|
|
38
|
+
#
|
|
39
|
+
# It is also how a caller who serialized *neither* gets here: +Marshal+ and Psych
|
|
40
|
+
# recurse, so dumping a model reaches the client it holds and raises this, naming
|
|
41
|
+
# BaseCradle::Client. Dump the record's own data, +model.to_h+, which holds no client.
|
|
37
42
|
class NotSerializableError < Error; end
|
|
38
43
|
|
|
39
44
|
# The request never got an API response (DNS failure, refused connection, timeout).
|
|
@@ -18,6 +18,17 @@ module BaseCradle
|
|
|
18
18
|
# Without ActiveSupport the same calls were merely useless: +"#<BaseCradle::Client:0x…>"+,
|
|
19
19
|
# a heap address that differs every run.
|
|
20
20
|
#
|
|
21
|
+
# +Marshal+ and Psych are that same act through two doors ActiveSupport never touched.
|
|
22
|
+
# Both walk instance variables directly and neither consults +as_json+, so closing the
|
|
23
|
+
# JSON door left them open, needing no ActiveSupport to be reachable. They are also the
|
|
24
|
+
# worse pair, because they are what puts a credential *at rest*: ActiveSupport's cache
|
|
25
|
+
# stores marshal what you write, so <tt>Rails.cache.write("bc", bc)</tt> landed the
|
|
26
|
+
# token in Redis, memcached or a file; a Marshal-backed session landed it in the
|
|
27
|
+
# session; Delayed::Job YAMLs its handler into the database. And nothing shadows the
|
|
28
|
+
# ivar walk the way +Enumerable#as_json+ shadows +Object#as_json+ — so
|
|
29
|
+
# <tt>Marshal.dump(bc.messages)</tt> reached the token where <tt>bc.messages.to_json</tt>
|
|
30
|
+
# never did. Both doors now raise, on the same mixin, so every includer is covered.
|
|
31
|
+
#
|
|
21
32
|
# Includers supply +serialization_refusal+; the default below is the honest fallback
|
|
22
33
|
# for one that does not, so the failure is still a rescuable BaseCradle::Error rather
|
|
23
34
|
# than a NoMethodError out of a renderer.
|
|
@@ -34,6 +45,24 @@ module BaseCradle
|
|
|
34
45
|
raise NotSerializableError, serialization_refusal
|
|
35
46
|
end
|
|
36
47
|
|
|
48
|
+
# +Marshal.dump+, and so every store built on it: ActiveSupport's cache backends, a
|
|
49
|
+
# Marshal-backed session, and the <tt>Marshal.load(Marshal.dump(x))</tt> deep-copy
|
|
50
|
+
# idiom. Marshal reaches for this hook before it walks ivars, so defining it is what
|
|
51
|
+
# stops the walk.
|
|
52
|
+
#
|
|
53
|
+
# Deliberately unpaired with a +marshal_load+: the pair only matters to something
|
|
54
|
+
# being loaded, and nothing can be loaded because nothing is ever dumped.
|
|
55
|
+
def marshal_dump
|
|
56
|
+
raise NotSerializableError, serialization_refusal
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Psych's hook — +to_yaml+, +YAML.dump+, and anything either one nests. Psych prefers
|
|
60
|
+
# +encode_with+ over its own ivar walk for any object that defines it. The coder is
|
|
61
|
+
# unused: it is the thing we decline to fill.
|
|
62
|
+
def encode_with(_coder)
|
|
63
|
+
raise NotSerializableError, serialization_refusal
|
|
64
|
+
end
|
|
65
|
+
|
|
37
66
|
private
|
|
38
67
|
|
|
39
68
|
def serialization_refusal
|
|
@@ -59,16 +88,31 @@ module BaseCradle
|
|
|
59
88
|
|
|
60
89
|
private
|
|
61
90
|
|
|
62
|
-
#
|
|
91
|
+
# One message, four doors — and it names both harms because which one you get depends
|
|
92
|
+
# on the door, measured on this tree. Through JSON, ActiveSupport's
|
|
63
93
|
# +Enumerable#as_json+ shadows +Object#as_json+, so a collection serialized as its
|
|
64
|
-
# fetched records
|
|
65
|
-
#
|
|
94
|
+
# fetched records and the client in its ivars was never walked: the harm is the
|
|
95
|
+
# unbounded fetch. +Marshal+ and Psych have no such shadow and walked the ivars
|
|
96
|
+
# instead, reaching the token and writing it wherever the dump went.
|
|
97
|
+
#
|
|
98
|
+
# Naming only the fetch would have told a +Rails.cache.write+ caller their credential
|
|
99
|
+
# was slow to write rather than at rest in Redis — a wrong diagnosis being worse than
|
|
100
|
+
# a missing one. Naming both keeps one refusal per resource, which is what stops the
|
|
101
|
+
# doors drifting apart.
|
|
102
|
+
#
|
|
103
|
+
# The remedy is two-part for the same reason. <tt>.to_a</tt> alone is enough for JSON,
|
|
104
|
+
# where a model serializes as its record — but the array it hands back is full of
|
|
105
|
+
# models that each hold the client, so <tt>Marshal.dump(bc.messages.to_a)</tt> lands
|
|
106
|
+
# straight back here. Measured, not assumed.
|
|
66
107
|
def serialization_refusal
|
|
67
|
-
"#{self.class} is a lazy, auto-paginating query
|
|
68
|
-
"record to serialize. Serializing it runs a
|
|
69
|
-
"resource from inside your renderer
|
|
70
|
-
"
|
|
71
|
-
"
|
|
108
|
+
"#{self.class} is a lazy, auto-paginating query holding your connection, not a " \
|
|
109
|
+
"record, so there is no one record to serialize. Serializing it either runs a " \
|
|
110
|
+
"page-by-page GET loop over the whole resource from inside your renderer and " \
|
|
111
|
+
"emits every record it fetched, or writes out the connection it holds — your " \
|
|
112
|
+
"bc_uat_ token with it. Call .to_a (or .first(n), or .filter(...).to_a) and " \
|
|
113
|
+
"serialize that — then how much you fetch is a visible act in your own code. " \
|
|
114
|
+
"Marshal and YAML reach the client through the records too, so dump the wire " \
|
|
115
|
+
"records rather than the models: .to_a.map(&:to_h)."
|
|
72
116
|
end
|
|
73
117
|
end
|
|
74
118
|
end
|
data/lib/basecradle/version.rb
CHANGED