ndb-rails 0.1.0 → 0.1.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/README.md +41 -5
- data/lib/ndb/rails/errors.rb +6 -3
- data/lib/ndb/rails/persistence.rb +126 -19
- data/lib/ndb/rails/record.rb +8 -7
- data/lib/ndb/rails/relation.rb +14 -0
- 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: cb272d6aadee253c818c30de3c3820c510903f06d42dac23dd56837bb585280a
|
|
4
|
+
data.tar.gz: 4ccfbb876e868ebea9b229a5ef44292ad3240ed55933a46e3a5a432158f25012
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c24be778d6fc65e219b829d7c8219e810ecc3505a39c0670077e07a17c60a37824d5814a6869dd6926fa52eed09a85b6615622779be126e8c87e582ece20a0d
|
|
7
|
+
data.tar.gz: 6360ba54f2208b4b438f269cfbbfd172fe0ebf9fd6907df523369d5241f88eec5a36ca617504dbde0553624fff00156b09c7cb892b05790d34e00cc4dde51f55
|
data/README.md
CHANGED
|
@@ -112,13 +112,21 @@ call = Call.find("c1") # raises Ndb::NotFound
|
|
|
112
112
|
Call.find_by(status: "failed") # nil when missing
|
|
113
113
|
Call.find_by!(status: "x") # raises NotFound
|
|
114
114
|
|
|
115
|
-
call.update!(status: "done") #
|
|
115
|
+
call.update!(status: "done") # $set of the changed fields only, filtered by id and version
|
|
116
116
|
call.version # the document version
|
|
117
117
|
|
|
118
|
+
call.increment!(:duration) # atomic $inc on the daemon, refreshes the attribute and the version
|
|
119
|
+
call.decrement!(:duration, 5)
|
|
120
|
+
|
|
118
121
|
call.destroy
|
|
122
|
+
|
|
123
|
+
# Atomic writes on every match of a where (it needs the id or an indexed field).
|
|
124
|
+
# update takes the operators of the client and returns the number of modified documents.
|
|
125
|
+
Call.where(status: "failed").update({ "$set" => { "status" => "retry" } })
|
|
126
|
+
Call.where(status: "retry").delete # returns how many it deleted
|
|
119
127
|
```
|
|
120
128
|
|
|
121
|
-
- Every load asks for the version (`_v`). `save`, `update` and `destroy`
|
|
129
|
+
- Every load asks for the version (`_v`). `save`, `update` and `destroy` put the id and `_v` in the filter of the write. If another writer changed the document first, the filter matches nothing and you get `Ndb::StaleObjectError` (code `stale_object`).
|
|
122
130
|
- Setting an attribute to `nil` and saving removes the field from the document.
|
|
123
131
|
- A duplicate id raises `RecordNotUnique` (code `duplicate_id`).
|
|
124
132
|
- `persisted?`, `new_record?`, `destroyed?`, `id` and `version` work as in ActiveRecord.
|
|
@@ -132,6 +140,32 @@ rescue Ndb::StaleObjectError
|
|
|
132
140
|
end
|
|
133
141
|
```
|
|
134
142
|
|
|
143
|
+
## Expiry (TTL like Redis)
|
|
144
|
+
|
|
145
|
+
Give the model an `expires: true` attribute. The daemon sets it for you.
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
class Session < Ndb::Record
|
|
149
|
+
primary_key :sid
|
|
150
|
+
|
|
151
|
+
attribute :user, :string
|
|
152
|
+
attribute :expires_at, :datetime, expires: true
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
Session.create!({ sid: "s1", user: "u" }, expire_in: 1.hour) # also create, save, update (seconds, a Duration or "30m")
|
|
156
|
+
session = Session.find("s1")
|
|
157
|
+
|
|
158
|
+
session.expire(30.minutes) # EXPIRE: sets expires_at in the object, other unsaved changes stay
|
|
159
|
+
session.ttl # => 1799, -1 (never expires), or nil (missing or expired)
|
|
160
|
+
session.persist_ttl! # PERSIST: removes the expiry
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- `persist_ttl!` is not called `persist!`: ActiveModel already uses `persisted?` for something else.
|
|
164
|
+
- `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`.
|
|
165
|
+
- `expire` and `persist_ttl!` raise `Ndb::NotFound` for a missing or expired document, and `Ndb::TtlNotEnabled` when the model has no `expires: true` attribute.
|
|
166
|
+
- 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.
|
|
167
|
+
- 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).
|
|
168
|
+
|
|
135
169
|
## Query
|
|
136
170
|
|
|
137
171
|
Relations are lazy and immutable. The request goes out when you call `to_a`, `first`, `each`, `count` and so on.
|
|
@@ -161,7 +195,7 @@ Call.where(status: "answered").first(5)
|
|
|
161
195
|
Call.where(status: "failed").exists?
|
|
162
196
|
```
|
|
163
197
|
|
|
164
|
-
- Chained `where` calls are ANDed.
|
|
198
|
+
- Chained `where` calls are ANDed. See the [query language](https://github.com/thadeu/ndb/blob/main/docs/dev/query.md) for the full operator list.
|
|
165
199
|
- The order of the `order` keys is the order of the sort. The id is always the last key.
|
|
166
200
|
- `page(n).per(size)` sets offset paging. The default page size is 25.
|
|
167
201
|
- `pluck` asks the daemon for only those fields.
|
|
@@ -295,7 +329,7 @@ A blob behaves like a read-only IO (`read`, `each_chunk`, `eof?`, `rewind`). A l
|
|
|
295
329
|
|
|
296
330
|
## Errors
|
|
297
331
|
|
|
298
|
-
The transport and daemon errors are defined by the gem `ndb`. They all live in the `Ndb` module (`Ndb::Error`, `Ndb::NotFound`, ...). For an ActiveRecord-like `rescue`, this gem adds
|
|
332
|
+
The transport and daemon errors are defined by the gem `ndb`. They all live in the `Ndb` module (`Ndb::Error`, `Ndb::NotFound`, ...). For an ActiveRecord-like `rescue`, this gem adds one alias: `Ndb::RecordNotUnique` is `Ndb::UniqueViolation` (the same class). `Ndb::StaleObjectError`, `Ndb::RecordInvalid` and `Ndb::RecordNotSaved` exist only here, and they inherit from `Ndb::Error`. Every error has `code` (the daemon code) and `status` (the HTTP status).
|
|
299
333
|
|
|
300
334
|
| Class | Code |
|
|
301
335
|
|---|---|
|
|
@@ -303,7 +337,8 @@ The transport and daemon errors are defined by the gem `ndb`. They all live in t
|
|
|
303
337
|
| `NotReady` | `not_ready` |
|
|
304
338
|
| `NotFound` | `not_found` |
|
|
305
339
|
| `UnknownCollection` | `unknown_collection` |
|
|
306
|
-
| `StaleObjectError` | `
|
|
340
|
+
| `StaleObjectError` | `stale_object` (raised by the gem, not sent by the daemon) |
|
|
341
|
+
| `InvalidUpdate` and its subclasses | `unknown_operator`, `no_update`, `conflicting_update`, `invalid_field`, `unindexed_many`, `empty_filter` |
|
|
307
342
|
| `RecordNotUnique` | `unique_violation`, `duplicate_id` |
|
|
308
343
|
| `CatalogConflict` | `catalog_conflict` |
|
|
309
344
|
| `CatalogInvalid` | `catalog_invalid` |
|
|
@@ -312,6 +347,7 @@ The transport and daemon errors are defined by the gem `ndb`. They all live in t
|
|
|
312
347
|
| `InvalidCursor` | `cursor_invalid`, `cursor_mismatch`, `cursor_expired` |
|
|
313
348
|
| `WatchLagged` | `watch_lagged` |
|
|
314
349
|
| `HistoryTruncated` | `history_truncated` |
|
|
350
|
+
| `TtlNotEnabled`, `TtlDerived` | `ttl_not_enabled`, `ttl_derived` |
|
|
315
351
|
| `RecordInvalid` | ActiveModel validation failed (has `record`) |
|
|
316
352
|
| `RecordNotSaved` | `save!` returned false without errors |
|
|
317
353
|
| `ConnectionError` | cannot reach the daemon. Subclasses: `ConnectError` (nothing was sent), `TimeoutError` |
|
data/lib/ndb/rails/errors.rb
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Ndb
|
|
4
|
-
# ActiveRecord-like
|
|
5
|
-
#
|
|
6
|
-
StaleObjectError = VersionConflict
|
|
4
|
+
# ActiveRecord-like name for a daemon error. It is the same class, so a
|
|
5
|
+
# rescue of either name catches the same error.
|
|
7
6
|
RecordNotUnique = UniqueViolation
|
|
8
7
|
|
|
8
|
+
# A save or destroy matched nothing: the record changed or was deleted
|
|
9
|
+
# since it was loaded (its "_v" guard did not match).
|
|
10
|
+
class StaleObjectError < Error; end
|
|
11
|
+
|
|
9
12
|
# Raised by save!/create!/update! when validations fail.
|
|
10
13
|
class RecordInvalid < Error
|
|
11
14
|
attr_reader :record
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
module Ndb
|
|
4
4
|
class RecordNotSaved < Error; end
|
|
5
5
|
|
|
6
|
-
# Writes: dirty attributes -> set
|
|
6
|
+
# Writes: dirty attributes -> $set/$unset, filtered by the id and the loaded
|
|
7
|
+
# version ("_v"). A write that matches nothing is a stale record.
|
|
7
8
|
module Persistence
|
|
8
9
|
extend ActiveSupport::Concern
|
|
9
10
|
|
|
@@ -12,24 +13,28 @@ module Ndb
|
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
class_methods do
|
|
15
|
-
def create(attrs = {}, &block)
|
|
16
|
-
return attrs.map { |a| create(a, &block) } if attrs.is_a?(Array)
|
|
16
|
+
def create(attrs = {}, expire_in: nil, **rest, &block)
|
|
17
|
+
return attrs.map { |a| create(a, expire_in: expire_in, &block) } if attrs.is_a?(Array)
|
|
18
|
+
|
|
19
|
+
attrs = attrs.merge(rest)
|
|
17
20
|
|
|
18
21
|
record = new(attrs)
|
|
19
22
|
|
|
20
23
|
yield record if block
|
|
21
|
-
record.save
|
|
24
|
+
record.save(expire_in: expire_in)
|
|
22
25
|
|
|
23
26
|
record
|
|
24
27
|
end
|
|
25
28
|
|
|
26
|
-
def create!(attrs = {}, &block)
|
|
27
|
-
return attrs.map { |a| create!(a, &block) } if attrs.is_a?(Array)
|
|
29
|
+
def create!(attrs = {}, expire_in: nil, **rest, &block)
|
|
30
|
+
return attrs.map { |a| create!(a, expire_in: expire_in, &block) } if attrs.is_a?(Array)
|
|
31
|
+
|
|
32
|
+
attrs = attrs.merge(rest)
|
|
28
33
|
|
|
29
34
|
record = new(attrs)
|
|
30
35
|
|
|
31
36
|
yield record if block
|
|
32
|
-
record.save!
|
|
37
|
+
record.save!(expire_in: expire_in)
|
|
33
38
|
|
|
34
39
|
record
|
|
35
40
|
end
|
|
@@ -71,43 +76,134 @@ module Ndb
|
|
|
71
76
|
persisted? ? [id] : nil
|
|
72
77
|
end
|
|
73
78
|
|
|
74
|
-
|
|
79
|
+
# expire_in (seconds, an ActiveSupport::Duration or "30m") makes the daemon
|
|
80
|
+
# set the expires field to now + expire_in in the same write. The model
|
|
81
|
+
# needs an `expires: true` attribute (Ndb::TtlNotEnabled otherwise).
|
|
82
|
+
def save(validate: true, expire_in: nil)
|
|
75
83
|
return false if validate && !valid?(new_record? ? :create : :update)
|
|
76
84
|
|
|
85
|
+
@expire_in = wire_duration(expire_in)
|
|
86
|
+
|
|
77
87
|
run_callbacks(:save) { new_record? ? create_record : update_record } ? true : false
|
|
88
|
+
ensure
|
|
89
|
+
@expire_in = nil
|
|
78
90
|
end
|
|
79
91
|
|
|
80
92
|
def save!(**)
|
|
81
93
|
save(**) || raise(errors.any? ? RecordInvalid.new(self) : RecordNotSaved.new("Failed to save the record"))
|
|
82
94
|
end
|
|
83
95
|
|
|
84
|
-
def update(attrs)
|
|
85
|
-
assign_attributes(attrs)
|
|
96
|
+
def update(attrs = {}, expire_in: nil, **rest)
|
|
97
|
+
assign_attributes(attrs.merge(rest))
|
|
98
|
+
|
|
99
|
+
save(expire_in: expire_in)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def update!(attrs = {}, expire_in: nil, **rest)
|
|
103
|
+
assign_attributes(attrs.merge(rest))
|
|
86
104
|
|
|
87
|
-
save
|
|
105
|
+
save!(expire_in: expire_in)
|
|
88
106
|
end
|
|
89
107
|
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
# Makes the stored document expire in a duration (like EXPIRE in Redis):
|
|
109
|
+
# `session.expire(30.minutes)`. The version and the expires attribute are
|
|
110
|
+
# refreshed; other unsaved changes stay unsaved. Raises Ndb::NotFound for
|
|
111
|
+
# a missing or expired document and Ndb::TtlNotEnabled without an
|
|
112
|
+
# `expires: true` attribute.
|
|
113
|
+
def expire(duration)
|
|
114
|
+
result = Rails.client.expire(self.class.collection_name, id, in: wire_duration(duration))
|
|
92
115
|
|
|
93
|
-
|
|
116
|
+
refresh_expiry(result, Time.iso8601(result["expires_at"]))
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Removes the expiry of the stored document (PERSIST in Redis). It is not
|
|
120
|
+
# called `persist!` or `persisted?`, which mean something else in
|
|
121
|
+
# ActiveModel.
|
|
122
|
+
def persist_ttl!
|
|
123
|
+
refresh_expiry(Rails.client.persist(self.class.collection_name, id), nil)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The seconds left (Integer), -1 when the document never expires, nil when
|
|
127
|
+
# it is missing or expired.
|
|
128
|
+
def ttl
|
|
129
|
+
Rails.client.ttl(self.class.collection_name, id)
|
|
94
130
|
end
|
|
95
131
|
|
|
96
132
|
def destroy
|
|
97
133
|
result = run_callbacks(:destroy) do
|
|
98
|
-
|
|
134
|
+
delete_record if persisted?
|
|
99
135
|
@destroyed = true
|
|
100
136
|
end
|
|
101
137
|
|
|
102
138
|
result ? self : false
|
|
103
139
|
end
|
|
104
140
|
|
|
141
|
+
# Adds by (default 1) to a number attribute in one atomic write on the
|
|
142
|
+
# daemon, and refreshes the attribute and the version. Other unsaved
|
|
143
|
+
# changes stay unsaved.
|
|
144
|
+
def increment!(attribute, by = 1)
|
|
145
|
+
add_to!(attribute, by)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def decrement!(attribute, by = 1)
|
|
149
|
+
add_to!(attribute, -by)
|
|
150
|
+
end
|
|
151
|
+
|
|
105
152
|
def ==(other)
|
|
106
153
|
other.instance_of?(self.class) && !id.nil? && id == other.id
|
|
107
154
|
end
|
|
108
155
|
|
|
109
156
|
private
|
|
110
157
|
|
|
158
|
+
def wire_duration(value)
|
|
159
|
+
value.is_a?(ActiveSupport::Duration) ? value.to_i : value
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def refresh_expiry(result, expires_at)
|
|
163
|
+
@version = result["v"]
|
|
164
|
+
|
|
165
|
+
field = self.class.expires_field
|
|
166
|
+
|
|
167
|
+
return self unless field
|
|
168
|
+
|
|
169
|
+
public_send("#{field}=", expires_at)
|
|
170
|
+
clear_attribute_changes([field])
|
|
171
|
+
|
|
172
|
+
self
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def record_filter
|
|
176
|
+
filter = { self.class.primary_key => id }
|
|
177
|
+
|
|
178
|
+
filter["_v"] = version if version
|
|
179
|
+
|
|
180
|
+
filter
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def delete_record
|
|
184
|
+
deleted = self.class.ndb_delete(record_filter)["deleted"]
|
|
185
|
+
|
|
186
|
+
raise StaleObjectError.new("#{self.class.name} #{id} changed or was deleted", code: "stale_object") if deleted.zero?
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def add_to!(attribute, by)
|
|
190
|
+
name = attribute.to_s
|
|
191
|
+
|
|
192
|
+
raise RecordNotSaved, "Cannot update a record that is not saved" unless persisted?
|
|
193
|
+
raise ArgumentError, "#{name} is not an attribute of #{self.class.name}" unless self.class.attribute_names.include?(name)
|
|
194
|
+
|
|
195
|
+
result = self.class.ndb_update({ self.class.primary_key => id }, { "$inc" => { name => by } })
|
|
196
|
+
|
|
197
|
+
raise NotFound.new("#{self.class.name} #{id} was deleted", code: "not_found") if result["matched"].zero?
|
|
198
|
+
|
|
199
|
+
@version = result["doc"]["_v"]
|
|
200
|
+
|
|
201
|
+
public_send("#{name}=", result["doc"][name])
|
|
202
|
+
clear_attribute_changes([name])
|
|
203
|
+
|
|
204
|
+
self
|
|
205
|
+
end
|
|
206
|
+
|
|
111
207
|
def loaded!(version)
|
|
112
208
|
@persisted = true
|
|
113
209
|
@version = version
|
|
@@ -117,7 +213,7 @@ module Ndb
|
|
|
117
213
|
|
|
118
214
|
def create_record
|
|
119
215
|
run_callbacks(:create) do
|
|
120
|
-
result = self.class.ndb_insert(insert_document)
|
|
216
|
+
result = self.class.ndb_insert(insert_document, expire_in: @expire_in)
|
|
121
217
|
|
|
122
218
|
self.id = result["id"] if id.nil?
|
|
123
219
|
loaded!(result["v"])
|
|
@@ -130,10 +226,12 @@ module Ndb
|
|
|
130
226
|
run_callbacks(:update) do
|
|
131
227
|
set, unset = changed_fields
|
|
132
228
|
|
|
133
|
-
unless set.empty? && unset.empty?
|
|
134
|
-
result = self.class.ndb_update(
|
|
229
|
+
unless set.empty? && unset.empty? && @expire_in.nil?
|
|
230
|
+
result = self.class.ndb_update(record_filter, update_ops(set, unset), expire_in: @expire_in)
|
|
135
231
|
|
|
136
|
-
|
|
232
|
+
raise StaleObjectError.new("#{self.class.name} #{id} changed or was deleted", code: "stale_object") if result["matched"].zero?
|
|
233
|
+
|
|
234
|
+
@version = result["doc"]["_v"]
|
|
137
235
|
end
|
|
138
236
|
|
|
139
237
|
changes_applied
|
|
@@ -142,6 +240,15 @@ module Ndb
|
|
|
142
240
|
end
|
|
143
241
|
end
|
|
144
242
|
|
|
243
|
+
def update_ops(set, unset)
|
|
244
|
+
ops = {}
|
|
245
|
+
|
|
246
|
+
ops["$set"] = set unless set.empty?
|
|
247
|
+
ops["$unset"] = unset.to_h { |name| [name, true] } unless unset.empty?
|
|
248
|
+
|
|
249
|
+
ops.empty? ? { "$set" => {} } : ops
|
|
250
|
+
end
|
|
251
|
+
|
|
145
252
|
def insert_document
|
|
146
253
|
attribute_names.each_with_object({}) do |name, doc|
|
|
147
254
|
value = json_attribute(name)
|
data/lib/ndb/rails/record.rb
CHANGED
|
@@ -124,7 +124,7 @@ module Ndb
|
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
-
# Every load asks for "_v", so a loaded record can
|
|
127
|
+
# Every load asks for "_v", so a loaded record can guard its write with it.
|
|
128
128
|
def ndb_find(body)
|
|
129
129
|
ensure_collection!
|
|
130
130
|
|
|
@@ -137,22 +137,23 @@ 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
|
+
# Atomic update by filter, see Ndb::Client#update.
|
|
147
|
+
def ndb_update(filter, ops, **options)
|
|
147
148
|
ensure_collection!
|
|
148
149
|
|
|
149
|
-
Rails.client.update(collection_name,
|
|
150
|
+
Rails.client.update(collection_name, filter, ops, **options)
|
|
150
151
|
end
|
|
151
152
|
|
|
152
|
-
def ndb_delete(
|
|
153
|
+
def ndb_delete(filter, **options)
|
|
153
154
|
ensure_collection!
|
|
154
155
|
|
|
155
|
-
Rails.client.delete(collection_name,
|
|
156
|
+
Rails.client.delete(collection_name, filter, **options)
|
|
156
157
|
end
|
|
157
158
|
|
|
158
159
|
private
|
data/lib/ndb/rails/relation.rb
CHANGED
|
@@ -78,6 +78,20 @@ module Ndb
|
|
|
78
78
|
query
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
# Atomic writes. The daemon matches the where and writes in one step on
|
|
82
|
+
# every match, so the where needs the id or an indexed field
|
|
83
|
+
# (Ndb::UnindexedMany otherwise). ops is the operator Hash of
|
|
84
|
+
# Ndb::Client#update ({ "$inc" => { "n" => 1 } }); returns the number of
|
|
85
|
+
# modified documents.
|
|
86
|
+
def update(ops, expire_in: nil)
|
|
87
|
+
klass.ndb_update(Conditions.combine(values[:where]), ops, many: true, expire_in: expire_in).fetch("modified")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Deletes every match (same rule as update) and returns how many.
|
|
91
|
+
def delete
|
|
92
|
+
klass.ndb_delete(Conditions.combine(values[:where]), many: true).fetch("deleted")
|
|
93
|
+
end
|
|
94
|
+
|
|
81
95
|
# Loaders
|
|
82
96
|
|
|
83
97
|
def load
|
data/lib/ndb/rails/version.rb
CHANGED