basecradle 0.9.0 → 0.10.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52b94c60f28a4c6e40decde0c297a0129a7f772a3b418f1a92fca3f380278982
4
- data.tar.gz: 107fd61b78588132abba3e3d958714f65dd572a5de40f5c690abd58d35f658e1
3
+ metadata.gz: 3acdf9b8dad4d29a40926030c480cc415b96ffff03c56a121838b4fdb42cd638
4
+ data.tar.gz: 2b88488a27cae407aa366cce9899da840b7ea1e74db86d21c511f0f2b1ac47d7
5
5
  SHA512:
6
- metadata.gz: 56a8ab9c7774f5765cfb2b08096488c1784558095e8ae4e433a13ebd6ed502afee0871a571a0e01db81b77adbf8399a8466d351c4c5c43794dc9ea24c6b6f74c
7
- data.tar.gz: d15b3da9c230433eeaddd427e4298dddce8042d37e1a4e6de5dd1e8a4b7384901ed7b41db5ef8d1f804787b21aef63bdf2db05757da50a702768f28fe45a81fa
6
+ metadata.gz: 7f753c98128c1935e0503dd1963917ac1918e171401b4a39f6c1b650cd716213f02c52b1dd538a54bb201de0ede89af19e7ab2ec9abfa79d2eba68d71faa5f57
7
+ data.tar.gz: 31772a4161d2e3563a835ffbd2c9ae5a2d9e285a157ca2772be3213037297b168f8e89b67e8290a7897a77a009add74b9769bf5e623ac5256ec19f26eece04d9
data/CHANGELOG.md CHANGED
@@ -4,6 +4,57 @@ All notable changes to this project are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ One deliberate departure from that format: this changelog keeps **no `Unreleased`
8
+ section**. The newest heading is always the version `lib/basecradle/version.rb` builds —
9
+ a release writes its entry and its version in the same PR — and `test/changelog_test.rb`
10
+ fails CI if the two ever disagree.
11
+
12
+ ## [0.10.0] - 2026-09-30
13
+
14
+ ### Fixed
15
+
16
+ - **A model serializes as the record it stands for**
17
+ ([#191](https://github.com/basecradle/basecradle-ruby/issues/191)). `ApiObject` defined
18
+ no `to_json`, so serializing *any* model — `bc.me`, a `Timeline`, a `Message`, any
19
+ content object — fell through to Ruby's default and emitted a heap address:
20
+ `"#<BaseCradle::MessageContent:0x000072be94b09…>"`. Silently, with no field names and a
21
+ different value every run. `model.to_json` and `JSON.generate(model)` now emit the
22
+ record, and so does a model nested in an array or a hash. What `to_h` holds is exactly
23
+ what goes out: nothing renamed, nothing dropped, and a field newer than this SDK
24
+ release serialized like any other.
25
+ `WebhookEventHeaders` is unchanged — it subclasses `Hash`, so it already serialized as
26
+ the delivery's headers.
27
+ Long-standing; 0.9.0's typing of `timeline.items` content only widened which read path
28
+ reached it. **0.9.0's Migrating note on `to_json` is superseded by this release.**
29
+
30
+ ### Added
31
+
32
+ - **`as_json`**, ActiveSupport's serialization hook, returning the same wire `Hash` as
33
+ `to_h`. It is what reaches a model nested inside a structure Rails renders
34
+ (`render json: { user: bc.me }`); a bare `render json: model` calls `to_json` directly
35
+ and never reaches it. Two things to know, neither of which a Rails habit expects:
36
+ - **It returns the same `Hash` as `to_h`, not a copy** — read-only by the same
37
+ convention, where every ActiveSupport `as_json` builds a fresh one. So
38
+ `model.as_json.merge!(extra)` rewrites the model's wire record, and for a wrapped
39
+ child its parent's too. Use `model.to_h.dup` (shallow) if you need to touch it.
40
+ - **Options are accepted and ignored, `only:` and `except:` included** — a model is a
41
+ read of one record, not a presenter. `as_json(except: ["ingest_url"])` returns the
42
+ whole record, silently, and it *disagrees* with `to_json`: with ActiveSupport loaded
43
+ `model.to_json(except: [...])` does redact, because the option reaches `Hash#to_json`.
44
+ Do not route redaction through either — build the subset with
45
+ `model.to_h.except("ingest_url")`.
46
+
47
+ `to_json` forwards its argument to `Hash#to_json` rather than swallowing it, so
48
+ `JSON.pretty_generate` pretty-prints; what an unknown option does is the host app's
49
+ `json` version's business (3.x raises, the 2.x older Rubies ship ignores it).
50
+
51
+ `to_s` is untouched, so interpolating a model (`"#{model}"`, `puts model`) still shows
52
+ the default object form. `inspect` names the model and its fields; `to_json` / `to_h`
53
+ give the record.
54
+
55
+ Minor rather than patch: the `to_json` output is a fix, but `as_json` is new public
56
+ surface and 0.9.0 documented the old behavior as expected — semver takes the higher.
57
+
7
58
  ## [0.9.0] - 2026-09-30
8
59
 
9
60
  ### Changed
@@ -46,6 +97,9 @@ enrichments come with them — but three things that worked on a `Hash` no longe
46
97
  field names — because `ApiObject` defines no `to_json`. That is long-standing for
47
98
  every other model in the SDK and now reaches this path too. Serialize
48
99
  `item.content.to_h` instead.
100
+ > ⚠️ **Superseded in [0.10.0](#0100---2026-09-30)**, which gives `ApiObject` a
101
+ > `to_json`. `item.content.to_json` emits the record; the `to_h` workaround above is
102
+ > no longer needed. Left as written for the historical record.
49
103
  - **`inspect` and `==` change, both for the better.** `inspect` names the model and its
50
104
  wire fields (`#<BaseCradle::MessageContent body, uuid>`) rather than printing a `Hash`;
51
105
  and equality now *holds* between an item's content and the same record fetched
@@ -342,6 +396,7 @@ the Python SDK's behavior in idiomatic Ruby. Zero runtime dependencies.
342
396
  - **Quality bars** — a README-as-tested-doc harness (every example runs against a mocked
343
397
  API) and a spec drift-guard (CI fails if the live API grows beyond the SDK).
344
398
 
399
+ [0.10.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.10.0
345
400
  [0.9.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.9.0
346
401
  [0.8.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.8.0
347
402
  [0.7.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.7.0
data/README.md CHANGED
@@ -324,6 +324,41 @@ timeline = bc.timelines.create(name: "Incident response")
324
324
  timeline.add_participant(nova)
325
325
  ```
326
326
 
327
+ ## Reading models, and serializing them
328
+
329
+ Every model is a read-only, wire-exact view of one API JSON object. Field readers mirror
330
+ the wire names; `model[key]` is the raw escape hatch for a field newer than your SDK
331
+ release; `model.to_h` is the wire `Hash` itself.
332
+
333
+ Models serialize as the record they stand for — what `to_h` holds is what goes out, so
334
+ logging or re-emitting one gives you the wire record rather than an object address:
335
+
336
+ ```ruby
337
+ require "basecradle"
338
+ require "json"
339
+
340
+ bc = BaseCradle::Client.new
341
+ message = bc.messages.first
342
+
343
+ puts message.content.to_json # {"uuid":"019e...","body":"Hello from a peer."}
344
+ puts JSON.generate([message.content]) # nested in an array
345
+ puts({ latest: message.content }.to_json) # nested in a hash
346
+ ```
347
+
348
+ `as_json` is ActiveSupport's hook and returns the same wire `Hash`, so a model nested in
349
+ something Rails renders comes out right too. Three caveats worth knowing:
350
+
351
+ - **`as_json` returns the same `Hash` as `to_h`, not a copy** — read-only by the same
352
+ convention. Mutating it rewrites the model's record. Use `model.to_h.dup` (shallow).
353
+ - **Options are accepted and ignored, `only:` and `except:` included.** A model is a read
354
+ of one record, not a presenter, so `as_json(except: ["ingest_url"])` returns the whole
355
+ record, silently — and it *disagrees* with `to_json`, which under ActiveSupport does
356
+ redact, because the option reaches `Hash#to_json`. Do not route redaction through
357
+ either: build the subset with `model.to_h.except("ingest_url")`.
358
+ - **`to_json` forwards its argument** to `Hash#to_json`, so `JSON.pretty_generate`
359
+ pretty-prints. What an unknown option does is your `json` version's business — 3.x
360
+ raises, the 2.x older Rubies ship ignores it.
361
+
327
362
  ## Development
328
363
 
329
364
  ```bash
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  require_relative "errors"
4
6
 
5
7
  module BaseCradle
@@ -61,6 +63,46 @@ module BaseCradle
61
63
  @data
62
64
  end
63
65
 
66
+ # The wire record, for a serializer — ActiveSupport's hook, and what reaches a model
67
+ # nested inside a structure Rails renders (<tt>render json: { user: bc.me }</tt>).
68
+ # A bare <tt>render json: model</tt> calls +to_json+ instead and never comes here.
69
+ #
70
+ # Returns +to_h+: the wire keys, nothing renamed, nothing dropped, and a field newer
71
+ # than this release carried like any other. (It is the record *as this object holds
72
+ # it* — for a Timeline fetched whole, that is the API's two-key envelope merged into
73
+ # one object, so it carries +items+ alongside the timeline's own fields.)
74
+ #
75
+ # Two traps, because neither is what a Rails habit expects:
76
+ #
77
+ # - **It is the same Hash +to_h+ returns, not a copy** — read-only by the same
78
+ # convention, where every ActiveSupport +as_json+ builds a fresh one. So
79
+ # <tt>model.as_json.merge!(extra)</tt> rewrites the model's wire record, and for a
80
+ # wrapped child its parent's too (a child wraps the parent's own nested Hash).
81
+ # +to_h.dup+ if you need to touch it; note that is shallow.
82
+ # - **Options are accepted and ignored, +only:+ and +except:+ included** — a model is
83
+ # a read of one record, not a presenter. +as_json(except: ["ingest_url"])+ returns
84
+ # the whole record, *silently*. Worse, it disagrees with +to_json+: with
85
+ # ActiveSupport loaded <tt>model.to_json(except: [...])</tt> *does* redact, because
86
+ # the option reaches +Hash#to_json+. Do not rely on either — build the subset
87
+ # yourself with <tt>model.to_h.except("ingest_url")</tt>.
88
+ def as_json(*)
89
+ to_h
90
+ end
91
+
92
+ # The wire record as JSON. Without this a model fell through to Ruby's default and
93
+ # emitted a heap address — silently, with no field names and a different value every
94
+ # run. +JSON.generate+ and +Array#to_json+ / +Hash#to_json+ call this, so a model
95
+ # nested in either serializes correctly too.
96
+ #
97
+ # The argument is forwarded to +Hash#to_json+ rather than swallowed, so
98
+ # +JSON.pretty_generate+ pretty-prints — and what an unknown option does is the host
99
+ # app's json version's business, not this SDK's: json 3.x raises on an unknown
100
+ # keyword, the json 2.x that older Rubies ship ignores it, and ActiveSupport acts on
101
+ # +only:+ / +except:+. See +as_json+ above: do not route redaction through either.
102
+ def to_json(*args)
103
+ to_h.to_json(*args)
104
+ end
105
+
64
106
  def ==(other)
65
107
  other.instance_of?(self.class) && other.to_h == @data
66
108
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BaseCradle
4
- VERSION = "0.9.0"
4
+ VERSION = "0.10.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: basecradle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Drawk Kwast