rest-easy 1.4.0 → 1.4.2
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 +75 -1
- data/lib/rest_easy/meta.rb +45 -2
- data/lib/rest_easy/resource.rb +22 -5
- data/lib/rest_easy/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a192d482f07dc654cae8267e3166742ee45d90c4bf960f54f632f94ca11c20a6
|
|
4
|
+
data.tar.gz: 74b41469d03fb34c19616774f9ccb97dfd3191f937dfcf2e96560986333ccf12
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b493ff7a6f39fa303665e6e09174b2f664d35960e18864dbb39e3d2cf95fe3ea13543f4384fc3a4c4656d8ada2b029f486ccf99ac6e2d9dda983c74e05717f6b
|
|
7
|
+
data.tar.gz: fdec9a5343a0bf002fe43b1532849ec7a28520ee95392e4beddcd0e11af1b3fd2ec0c9c2c11eceeafb65ea8dd640281fe521f1613022bc43f60615ca0afc1095
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,78 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.4.2] - 2026-09-23
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **`update` no longer discards state it should carry forward.** It rebuilt
|
|
10
|
+
the instance's metadata and its change set from scratch instead of
|
|
11
|
+
inheriting them, which broke three things at once:
|
|
12
|
+
|
|
13
|
+
- An instance that had never been saved came back marked as persisted, so
|
|
14
|
+
`save` took the update branch: a PUT to the collection path with no id in
|
|
15
|
+
it, for a record the API has never seen, instead of a POST that creates
|
|
16
|
+
one. Building a record and adjusting it before the first save is the
|
|
17
|
+
ordinary way to use an immutable resource, and it could not produce a
|
|
18
|
+
correct create request.
|
|
19
|
+
- Chained updates kept only the last call's fields. After
|
|
20
|
+
`a.update(x: 1).update(y: 2)`, `__changes__` reported just `y` while `x`
|
|
21
|
+
read back as `1` on the instance, so a partial payload built from the
|
|
22
|
+
change set left out a field the object claimed to carry, and nothing
|
|
23
|
+
raised.
|
|
24
|
+
- Metadata set by a `before_parse` hook lives on the instance, not the
|
|
25
|
+
class, and was dropped. A consumer flagging parsed instances — to mark
|
|
26
|
+
the thin records a collection endpoint returns, say — lost the flag as
|
|
27
|
+
soon as anything called `update`.
|
|
28
|
+
|
|
29
|
+
`update` now inherits the receiver's metadata and merges its change set.
|
|
30
|
+
The metadata is copied rather than shared, so mutating an updated copy's
|
|
31
|
+
metadata still cannot reach the original's.
|
|
32
|
+
|
|
33
|
+
## [1.4.1] - 2026-08-18
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- **`Meta` no longer claims to implement methods it has no value for.**
|
|
38
|
+
`Meta#respond_to_missing?` returned `true` for every name, so any caller
|
|
39
|
+
that asks an object what it can do before calling it got a false yes, then
|
|
40
|
+
received the `nil` that `method_missing` returns for an unknown key as
|
|
41
|
+
though it were a real answer. Ruby does this constantly and implicitly —
|
|
42
|
+
coercion probes when a value is splatted or interpolated, serialisation
|
|
43
|
+
hooks, and any library that duck-types with `respond_to?` — so the failure
|
|
44
|
+
surfaced far from `Meta` and looked unrelated to it. Marshalling was the
|
|
45
|
+
sharpest case: `dump` silently wrote an empty payload and `load` then
|
|
46
|
+
raised `NoMethodError` from inside `method_missing`, which broke every
|
|
47
|
+
consumer caching resources in a store that marshals its entries.
|
|
48
|
+
|
|
49
|
+
Bare getters are now claimed only for keys the instance actually holds.
|
|
50
|
+
Setters and predicates are still claimed unconditionally, since writing is
|
|
51
|
+
how a key comes into existence and an unset predicate is meaningfully
|
|
52
|
+
`false`; no core Ruby probe uses those two shapes. Library-level
|
|
53
|
+
duck-typing can, though — ActiveSupport's `acts_like?(:date)` asks for
|
|
54
|
+
`acts_like_date?`, and `Meta` still answers yes — so the guarantee is
|
|
55
|
+
narrower than "nothing probes for `=` or `?`", and closing it properly is
|
|
56
|
+
tracked in #7. Operators are no longer claimed either: the check is
|
|
57
|
+
anchored to identifier-shaped names, so `<=` is not mistaken for a setter
|
|
58
|
+
(`method_missing` still is — #6).
|
|
59
|
+
`Resource::MetaCollector` had the identical defect and is fixed the same
|
|
60
|
+
way.
|
|
61
|
+
|
|
62
|
+
**Upgrading with a warm cache:** entries written by an earlier version are
|
|
63
|
+
not recoverable. A payload dumped before this fix carries `nil` where the
|
|
64
|
+
meta state should be, and loading it now raises `TypeError: instance of
|
|
65
|
+
RestEasy::Meta needs to have method 'marshal_load'` — legible, but still
|
|
66
|
+
an error. Consumers on a cache that survives deploys (`:file_store`,
|
|
67
|
+
Redis, Memcached) must flush it or bump the cache key namespace when
|
|
68
|
+
upgrading. A process-local `:memory_store` clears itself on restart and
|
|
69
|
+
needs nothing.
|
|
70
|
+
|
|
71
|
+
`method_missing` is unchanged: a bare getter for a key that was never set
|
|
72
|
+
still returns `nil` rather than raising, as the "gem author extensions"
|
|
73
|
+
example in `docs/model-architecture.md` depends on. `respond_to?`
|
|
74
|
+
therefore under-reports for those keys; reconciling the two halves
|
|
75
|
+
requires declaring meta keys up front and is tracked in #7.
|
|
76
|
+
|
|
5
77
|
## [1.4.0] - 2026-06-26
|
|
6
78
|
|
|
7
79
|
### Fixed
|
|
@@ -123,7 +195,9 @@
|
|
|
123
195
|
|
|
124
196
|
Initial release.
|
|
125
197
|
|
|
126
|
-
[Unreleased]: https://github.com/accodeing/rest-easy/compare/v1.4.
|
|
198
|
+
[Unreleased]: https://github.com/accodeing/rest-easy/compare/v1.4.2...HEAD
|
|
199
|
+
[1.4.2]: https://github.com/accodeing/rest-easy/compare/v1.4.1...v1.4.2
|
|
200
|
+
[1.4.1]: https://github.com/accodeing/rest-easy/compare/v1.4.0...v1.4.1
|
|
127
201
|
[1.4.0]: https://github.com/accodeing/rest-easy/compare/v1.3.1...v1.4.0
|
|
128
202
|
[1.3.1]: https://github.com/accodeing/rest-easy/compare/v1.3.0...v1.3.1
|
|
129
203
|
[1.3.0]: https://github.com/accodeing/rest-easy/compare/v1.2.0...v1.3.0
|
data/lib/rest_easy/meta.rb
CHANGED
|
@@ -6,6 +6,11 @@ module RestEasy
|
|
|
6
6
|
@data = { new: new_record, saved: saved, **defaults }
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
def initialize_copy(source)
|
|
10
|
+
super
|
|
11
|
+
@data = @data.dup
|
|
12
|
+
end
|
|
13
|
+
|
|
9
14
|
def new?
|
|
10
15
|
@data[:new]
|
|
11
16
|
end
|
|
@@ -25,8 +30,46 @@ module RestEasy
|
|
|
25
30
|
end
|
|
26
31
|
end
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
# Accessor shapes `method_missing` synthesises. Anchored, so operators
|
|
34
|
+
# stay out: `<=` ends with `=`, and claiming it would let `meta <= 5`
|
|
35
|
+
# read as a setter. `method_missing` itself is still unanchored and
|
|
36
|
+
# writes `@data[:<]` for that call — see issue #6.
|
|
37
|
+
#
|
|
38
|
+
# POSIX classes rather than `[a-zA-Z_]\w*`, because Ruby identifiers are
|
|
39
|
+
# not ASCII-only: `meta.företag = x` is a valid setter that
|
|
40
|
+
# `method_missing` stores, so `respond_to?` has to claim it too.
|
|
41
|
+
ACCESSOR_PATTERN = /\A[[:alpha:]_][[:word:]]*[=?]\z/
|
|
42
|
+
private_constant :ACCESSOR_PATTERN
|
|
43
|
+
|
|
44
|
+
# `method_missing` answers to any name, but this must not, because callers
|
|
45
|
+
# routinely ask an object what it can do before calling it — Ruby's own
|
|
46
|
+
# coercion probes (`to_hash` on double-splat, `to_ary`, `to_str`),
|
|
47
|
+
# serialisation hooks (`marshal_dump`, `init_with`), and any library that
|
|
48
|
+
# duck-types with `respond_to?`. Claiming those hands the caller the `nil`
|
|
49
|
+
# that `method_missing` returns for an unknown key as though it were an
|
|
50
|
+
# answer.
|
|
51
|
+
#
|
|
52
|
+
# Bare getters are therefore claimed only for keys actually held. Setters
|
|
53
|
+
# and predicates are claimed unconditionally: writing is how a key comes
|
|
54
|
+
# into existence, and an unset predicate is meaningfully `false`. No core
|
|
55
|
+
# Ruby probe uses those two shapes — though library-level duck-typing can,
|
|
56
|
+
# and ActiveSupport's `acts_like?(:date)` asking for `acts_like_date?` is
|
|
57
|
+
# a known false positive. Closing it properly means declaring meta keys
|
|
58
|
+
# up front, which is a breaking change — see issue #7.
|
|
59
|
+
#
|
|
60
|
+
# `@data` is guarded because `respond_to?` must never raise, and Ruby
|
|
61
|
+
# allocates an instance before initialising it: Psych's `revive` probes
|
|
62
|
+
# `init_with` on a bare allocation, and so does `Marshal.load` on a
|
|
63
|
+
# payload written before this fix.
|
|
64
|
+
#
|
|
65
|
+
# This under-claims a bare getter for a key that was never set, which
|
|
66
|
+
# `method_missing` still answers with `nil`. Reconciling the two halves
|
|
67
|
+
# means declaring meta keys up front, and that is a 2.0 — see issue #7.
|
|
68
|
+
def respond_to_missing?(name, include_private = false)
|
|
69
|
+
return true if ACCESSOR_PATTERN.match?(name.to_s)
|
|
70
|
+
return super unless @data
|
|
71
|
+
|
|
72
|
+
@data.key?(name.to_sym) || super
|
|
30
73
|
end
|
|
31
74
|
end
|
|
32
75
|
end
|
data/lib/rest_easy/resource.rb
CHANGED
|
@@ -119,8 +119,22 @@ module RestEasy
|
|
|
119
119
|
end
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
# Anchored so operators stay out — see Meta::ACCESSOR_PATTERN and
|
|
123
|
+
# issue #6. Only the setter shape here; unlike Meta, `method_missing`
|
|
124
|
+
# above has no predicate form.
|
|
125
|
+
SETTER_PATTERN = /\A[[:alpha:]_][[:word:]]*=\z/
|
|
126
|
+
private_constant :SETTER_PATTERN
|
|
127
|
+
|
|
128
|
+
# Same reasoning as Meta#respond_to_missing? — a blanket `true` makes
|
|
129
|
+
# this claim Ruby's implicit protocol methods and hand them the `nil`
|
|
130
|
+
# from `method_missing`. `@data` is guarded for the same reason too:
|
|
131
|
+
# `respond_to?` must never raise, and Psych and Marshal both probe a
|
|
132
|
+
# bare allocation before it is initialised.
|
|
133
|
+
def respond_to_missing?(name, include_private = false)
|
|
134
|
+
return true if SETTER_PATTERN.match?(name.to_s)
|
|
135
|
+
return super unless @data
|
|
136
|
+
|
|
137
|
+
@data.key?(name.to_sym) || super
|
|
124
138
|
end
|
|
125
139
|
end
|
|
126
140
|
|
|
@@ -584,7 +598,7 @@ module RestEasy
|
|
|
584
598
|
|
|
585
599
|
new_model = @model_attributes.merge(coerced)
|
|
586
600
|
new_instance = self.class.allocate
|
|
587
|
-
new_instance.send(:init_from_update, new_model, @api_data, coerced)
|
|
601
|
+
new_instance.send(:init_from_update, new_model, @api_data, __changes__.merge(coerced), @meta)
|
|
588
602
|
new_instance
|
|
589
603
|
end
|
|
590
604
|
|
|
@@ -787,11 +801,14 @@ module RestEasy
|
|
|
787
801
|
end
|
|
788
802
|
end
|
|
789
803
|
|
|
790
|
-
def init_from_update(new_model_attrs, original_api_data, changes)
|
|
804
|
+
def init_from_update(new_model_attrs, original_api_data, changes, meta)
|
|
791
805
|
@api_data = original_api_data
|
|
792
806
|
@model_attributes = new_model_attrs
|
|
793
807
|
@changes = changes
|
|
794
|
-
|
|
808
|
+
# Carried from the receiver, not rebuilt: an instance that was never
|
|
809
|
+
# saved stays new, so `save` still POSTs it, and metadata a parse hook
|
|
810
|
+
# set (which lives on the instance, not the class) survives the update.
|
|
811
|
+
@meta = meta.dup
|
|
795
812
|
end
|
|
796
813
|
end
|
|
797
814
|
end
|
data/lib/rest_easy/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rest-easy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jonas Schubert Erlandsson
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date: 2026-
|
|
13
|
+
date: 2026-09-23 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: dry-types
|