ndb-rails 0.1.0 → 0.1.1
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/README.md +27 -0
- data/lib/ndb/rails/persistence.rb +68 -16
- data/lib/ndb/rails/record.rb +2 -2
- data/lib/ndb/rails/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: 93088e1e11be89558d972761a9e2a7a565d617f165b90361e732240ee92c9361
|
|
4
|
+
data.tar.gz: e9e5eca9058d1bfa6ea840c4cc8fb17c734c69d5373b933f48b1d3dfb968ebfd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c509825a020a9efff7a220a3fbbb842bde284f32c66d66368948234de6aa258585a0cac17b4da57feb1ff44b123fbf5c8d6cde8f998978f51aa406500cbb10ee
|
|
7
|
+
data.tar.gz: 853957c1575a0653c04f91bd8f16efb65415cd42656ab5a9de44f39360820d434d63d2df2a54301ec1964805c4b286dcdde969f04fefe54b79ebd5b6784452ed
|
data/README.md
CHANGED
|
@@ -132,6 +132,32 @@ rescue Ndb::StaleObjectError
|
|
|
132
132
|
end
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
+
## Expiry (TTL like Redis)
|
|
136
|
+
|
|
137
|
+
Give the model an `expires: true` attribute. The daemon sets it for you.
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
class Session < Ndb::Record
|
|
141
|
+
primary_key :sid
|
|
142
|
+
|
|
143
|
+
attribute :user, :string
|
|
144
|
+
attribute :expires_at, :datetime, expires: true
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
Session.create!({ sid: "s1", user: "u" }, expire_in: 1.hour) # also create, save, update (seconds, a Duration or "30m")
|
|
148
|
+
session = Session.find("s1")
|
|
149
|
+
|
|
150
|
+
session.expire(30.minutes) # EXPIRE: sets expires_at in the object, other unsaved changes stay
|
|
151
|
+
session.ttl # => 1799, -1 (never expires), or nil (missing or expired)
|
|
152
|
+
session.persist_ttl! # PERSIST: removes the expiry
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- `persist_ttl!` is not called `persist!`: ActiveModel already uses `persisted?` for something else.
|
|
156
|
+
- `expire_in:` on `create`, `save` and `update` goes in the same write. `expire_in` wins over the `expires_at` value. After a write with `expire_in`, load the record again (`Session.find`) to see `expires_at`.
|
|
157
|
+
- `expire` and `persist_ttl!` raise `Ndb::NotFound` for a missing or expired document, and `Ndb::TtlNotEnabled` when the model has no `expires: true` attribute.
|
|
158
|
+
- The class-level `ttl 24.hours` stays: every document expires that long after its time field. `expire` and `persist_ttl!` raise `Ndb::TtlDerived` for it.
|
|
159
|
+
- Disk space of expired documents returns after compaction, and fully only with `wal_retention`; see [Freeing disk](https://github.com/thadeu/ndb/blob/main/docs/dev/configuration.md#freeing-disk).
|
|
160
|
+
|
|
135
161
|
## Query
|
|
136
162
|
|
|
137
163
|
Relations are lazy and immutable. The request goes out when you call `to_a`, `first`, `each`, `count` and so on.
|
|
@@ -312,6 +338,7 @@ The transport and daemon errors are defined by the gem `ndb`. They all live in t
|
|
|
312
338
|
| `InvalidCursor` | `cursor_invalid`, `cursor_mismatch`, `cursor_expired` |
|
|
313
339
|
| `WatchLagged` | `watch_lagged` |
|
|
314
340
|
| `HistoryTruncated` | `history_truncated` |
|
|
341
|
+
| `TtlNotEnabled`, `TtlDerived` | `ttl_not_enabled`, `ttl_derived` |
|
|
315
342
|
| `RecordInvalid` | ActiveModel validation failed (has `record`) |
|
|
316
343
|
| `RecordNotSaved` | `save!` returned false without errors |
|
|
317
344
|
| `ConnectionError` | cannot reach the daemon. Subclasses: `ConnectError` (nothing was sent), `TimeoutError` |
|
|
@@ -12,24 +12,28 @@ module Ndb
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
class_methods do
|
|
15
|
-
def create(attrs = {}, &block)
|
|
16
|
-
return attrs.map { |a| create(a, &block) } if attrs.is_a?(Array)
|
|
15
|
+
def create(attrs = {}, expire_in: nil, **rest, &block)
|
|
16
|
+
return attrs.map { |a| create(a, expire_in: expire_in, &block) } if attrs.is_a?(Array)
|
|
17
|
+
|
|
18
|
+
attrs = attrs.merge(rest)
|
|
17
19
|
|
|
18
20
|
record = new(attrs)
|
|
19
21
|
|
|
20
22
|
yield record if block
|
|
21
|
-
record.save
|
|
23
|
+
record.save(expire_in: expire_in)
|
|
22
24
|
|
|
23
25
|
record
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
def create!(attrs = {}, &block)
|
|
27
|
-
return attrs.map { |a| create!(a, &block) } if attrs.is_a?(Array)
|
|
28
|
+
def create!(attrs = {}, expire_in: nil, **rest, &block)
|
|
29
|
+
return attrs.map { |a| create!(a, expire_in: expire_in, &block) } if attrs.is_a?(Array)
|
|
30
|
+
|
|
31
|
+
attrs = attrs.merge(rest)
|
|
28
32
|
|
|
29
33
|
record = new(attrs)
|
|
30
34
|
|
|
31
35
|
yield record if block
|
|
32
|
-
record.save!
|
|
36
|
+
record.save!(expire_in: expire_in)
|
|
33
37
|
|
|
34
38
|
record
|
|
35
39
|
end
|
|
@@ -71,26 +75,57 @@ module Ndb
|
|
|
71
75
|
persisted? ? [id] : nil
|
|
72
76
|
end
|
|
73
77
|
|
|
74
|
-
|
|
78
|
+
# expire_in (seconds, an ActiveSupport::Duration or "30m") makes the daemon
|
|
79
|
+
# set the expires field to now + expire_in in the same write. The model
|
|
80
|
+
# needs an `expires: true` attribute (Ndb::TtlNotEnabled otherwise).
|
|
81
|
+
def save(validate: true, expire_in: nil)
|
|
75
82
|
return false if validate && !valid?(new_record? ? :create : :update)
|
|
76
83
|
|
|
84
|
+
@expire_in = wire_duration(expire_in)
|
|
85
|
+
|
|
77
86
|
run_callbacks(:save) { new_record? ? create_record : update_record } ? true : false
|
|
87
|
+
ensure
|
|
88
|
+
@expire_in = nil
|
|
78
89
|
end
|
|
79
90
|
|
|
80
91
|
def save!(**)
|
|
81
92
|
save(**) || raise(errors.any? ? RecordInvalid.new(self) : RecordNotSaved.new("Failed to save the record"))
|
|
82
93
|
end
|
|
83
94
|
|
|
84
|
-
def update(attrs)
|
|
85
|
-
assign_attributes(attrs)
|
|
95
|
+
def update(attrs = {}, expire_in: nil, **rest)
|
|
96
|
+
assign_attributes(attrs.merge(rest))
|
|
97
|
+
|
|
98
|
+
save(expire_in: expire_in)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def update!(attrs = {}, expire_in: nil, **rest)
|
|
102
|
+
assign_attributes(attrs.merge(rest))
|
|
103
|
+
|
|
104
|
+
save!(expire_in: expire_in)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Makes the stored document expire in a duration (like EXPIRE in Redis):
|
|
108
|
+
# `session.expire(30.minutes)`. The version and the expires attribute are
|
|
109
|
+
# refreshed; other unsaved changes stay unsaved. Raises Ndb::NotFound for
|
|
110
|
+
# a missing or expired document and Ndb::TtlNotEnabled without an
|
|
111
|
+
# `expires: true` attribute.
|
|
112
|
+
def expire(duration)
|
|
113
|
+
result = Rails.client.expire(self.class.collection_name, id, in: wire_duration(duration))
|
|
86
114
|
|
|
87
|
-
|
|
115
|
+
refresh_expiry(result, Time.iso8601(result["expires_at"]))
|
|
88
116
|
end
|
|
89
117
|
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
# Removes the expiry of the stored document (PERSIST in Redis). It is not
|
|
119
|
+
# called `persist!` or `persisted?`, which mean something else in
|
|
120
|
+
# ActiveModel.
|
|
121
|
+
def persist_ttl!
|
|
122
|
+
refresh_expiry(Rails.client.persist(self.class.collection_name, id), nil)
|
|
123
|
+
end
|
|
92
124
|
|
|
93
|
-
|
|
125
|
+
# The seconds left (Integer), -1 when the document never expires, nil when
|
|
126
|
+
# it is missing or expired.
|
|
127
|
+
def ttl
|
|
128
|
+
Rails.client.ttl(self.class.collection_name, id)
|
|
94
129
|
end
|
|
95
130
|
|
|
96
131
|
def destroy
|
|
@@ -108,6 +143,23 @@ module Ndb
|
|
|
108
143
|
|
|
109
144
|
private
|
|
110
145
|
|
|
146
|
+
def wire_duration(value)
|
|
147
|
+
value.is_a?(ActiveSupport::Duration) ? value.to_i : value
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def refresh_expiry(result, expires_at)
|
|
151
|
+
@version = result["v"]
|
|
152
|
+
|
|
153
|
+
field = self.class.expires_field
|
|
154
|
+
|
|
155
|
+
return self unless field
|
|
156
|
+
|
|
157
|
+
public_send("#{field}=", expires_at)
|
|
158
|
+
clear_attribute_changes([field])
|
|
159
|
+
|
|
160
|
+
self
|
|
161
|
+
end
|
|
162
|
+
|
|
111
163
|
def loaded!(version)
|
|
112
164
|
@persisted = true
|
|
113
165
|
@version = version
|
|
@@ -117,7 +169,7 @@ module Ndb
|
|
|
117
169
|
|
|
118
170
|
def create_record
|
|
119
171
|
run_callbacks(:create) do
|
|
120
|
-
result = self.class.ndb_insert(insert_document)
|
|
172
|
+
result = self.class.ndb_insert(insert_document, expire_in: @expire_in)
|
|
121
173
|
|
|
122
174
|
self.id = result["id"] if id.nil?
|
|
123
175
|
loaded!(result["v"])
|
|
@@ -130,8 +182,8 @@ module Ndb
|
|
|
130
182
|
run_callbacks(:update) do
|
|
131
183
|
set, unset = changed_fields
|
|
132
184
|
|
|
133
|
-
unless set.empty? && unset.empty?
|
|
134
|
-
result = self.class.ndb_update(id, set: set, unset: unset, if_version: version)
|
|
185
|
+
unless set.empty? && unset.empty? && @expire_in.nil?
|
|
186
|
+
result = self.class.ndb_update(id, set: set, unset: unset, if_version: version, expire_in: @expire_in)
|
|
135
187
|
|
|
136
188
|
@version = result["v"]
|
|
137
189
|
end
|
data/lib/ndb/rails/record.rb
CHANGED
|
@@ -137,10 +137,10 @@ module Ndb
|
|
|
137
137
|
Rails.client.aggregate(collection_name, body)
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
-
def ndb_insert(doc)
|
|
140
|
+
def ndb_insert(doc, expire_in: nil)
|
|
141
141
|
ensure_collection!
|
|
142
142
|
|
|
143
|
-
Rails.client.insert(collection_name, [doc]).first
|
|
143
|
+
Rails.client.insert(collection_name, [doc], expire_in: expire_in).first
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
def ndb_update(id, **change)
|
data/lib/ndb/rails/version.rb
CHANGED