activemodel-datastore 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +260 -40
- data/lib/active_model/datastore.rb +35 -18
- data/lib/active_model/datastore/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b11457bc388803b1f429027dd976ac9ed10250c9
|
4
|
+
data.tar.gz: 52247ef1a1e9b49ee738d756374c441b00bd9488
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8677a69f2faafa802c634829f01a86cc438295f157b71da0d431fe7bc3175f22154ecd03696a6d93444df42db86bd9d459d62271f5722b0b091c10d83e2bffa8
|
7
|
+
data.tar.gz: 4caa657d528f99eb06963e028224a489ee3c9c3d44460b53336383422db86e8d3733f18c940854b75307cdf57ccb2a343a09d93d67084007332c02b5656fae27
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -10,6 +10,8 @@ with Rails?
|
|
10
10
|
When you want a Rails app backed by a managed, massively-scalable datastore solution. Cloud Datastore
|
11
11
|
automatically handles sharding and replication, providing you with a highly available and durable
|
12
12
|
database that scales automatically to handle your applications' load.
|
13
|
+
|
14
|
+
[![Gem Version](https://badge.fury.io/rb/activemodel-datastore.svg)](https://badge.fury.io/rb/activemodel-datastore)
|
13
15
|
|
14
16
|
## Table of contents
|
15
17
|
|
@@ -17,10 +19,13 @@ database that scales automatically to handle your applications' load.
|
|
17
19
|
- [Model Example](#model)
|
18
20
|
- [Controller Example](#controller)
|
19
21
|
- [Retrieving Entities](#queries)
|
22
|
+
- [Datastore Consistency](#consistency)
|
23
|
+
- [Datastore Indexes](#indexes)
|
24
|
+
- [Datastore Emulator](#emulator)
|
20
25
|
- [Example Rails App](#rails)
|
21
|
-
- [
|
26
|
+
- [Track Changes](#track_changes)
|
22
27
|
- [Nested Forms](#nested)
|
23
|
-
- [
|
28
|
+
- [Datastore Gotchas](#gotchas)
|
24
29
|
|
25
30
|
## <a name="setup"></a>Setup
|
26
31
|
|
@@ -30,15 +35,22 @@ Generate your Rails app without ActiveRecord:
|
|
30
35
|
rails new my_app -O
|
31
36
|
```
|
32
37
|
|
38
|
+
You can remove the db/ directory as it won't be needed.
|
39
|
+
|
33
40
|
To install, add this line to your `Gemfile` and run `bundle install`:
|
34
41
|
|
35
42
|
```ruby
|
36
43
|
gem 'activemodel-datastore'
|
37
44
|
```
|
38
45
|
|
39
|
-
Google Cloud
|
46
|
+
Create a Google Cloud account [here](https://cloud.google.com) and create a project.
|
47
|
+
|
48
|
+
Google Cloud requires the Project ID and Service Account Credentials to connect to the Datastore API.
|
40
49
|
|
41
|
-
*Follow the [activation instructions](https://cloud.google.com/datastore/docs/activate) to
|
50
|
+
*Follow the [activation instructions](https://cloud.google.com/datastore/docs/activate) to enable the
|
51
|
+
Google Cloud Datastore API. When running on Google Cloud Platform environments the Service Account
|
52
|
+
credentials will be discovered automatically. When running on other environments (such as AWS or Heroku)
|
53
|
+
you need to create a service account with the role of editor and generate json credentials.*
|
42
54
|
|
43
55
|
Set your project id in an `ENV` variable named `GCLOUD_PROJECT`.
|
44
56
|
|
@@ -48,9 +60,10 @@ To locate your project ID:
|
|
48
60
|
2. From the projects list, select the name of your project.
|
49
61
|
3. On the left, click Dashboard. The project name and ID are displayed in the Dashboard.
|
50
62
|
|
51
|
-
|
52
|
-
|
53
|
-
|
63
|
+
If you have an external application running on a platform outside of Google Cloud you also need to
|
64
|
+
provide the Service Account credentials. They are specified in two additional `ENV` variables named
|
65
|
+
`SERVICE_ACCOUNT_CLIENT_EMAIL` and `SERVICE_ACCOUNT_PRIVATE_KEY`. The values for these two `ENV`
|
66
|
+
variables will be in the downloaded service account json credentials file.
|
54
67
|
|
55
68
|
```bash
|
56
69
|
SERVICE_ACCOUNT_PRIVATE_KEY = -----BEGIN PRIVATE KEY-----\nMIIFfb3...5dmFtABy\n-----END PRIVATE KEY-----\n
|
@@ -58,6 +71,11 @@ SERVICE_ACCOUNT_CLIENT_EMAIL = web-app@app-name.iam.gserviceaccount.com
|
|
58
71
|
```
|
59
72
|
|
60
73
|
On Heroku the `ENV` variables can be set under 'Settings' -> 'Config Variables'.
|
74
|
+
|
75
|
+
Active Model Datastore will then handle the authentication for you, and the datastore instance can
|
76
|
+
be accessed with `CloudDatastore.dataset`.
|
77
|
+
|
78
|
+
There is an example Puma config file [here](https://github.com/Agrimatics/activemodel-datastore/blob/master/test/support/datastore_example_rails_app/config/puma.rb).
|
61
79
|
|
62
80
|
## <a name="model"></a>Model Example
|
63
81
|
|
@@ -67,21 +85,71 @@ Let's start by implementing the model:
|
|
67
85
|
class User
|
68
86
|
include ActiveModel::Datastore
|
69
87
|
|
70
|
-
attr_accessor :email, :name, :
|
88
|
+
attr_accessor :email, :enabled, :name, :role, :state
|
89
|
+
|
90
|
+
def entity_properties
|
91
|
+
%w[email enabled name role]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
Data objects in Cloud Datastore are known as entities. Entities are of a kind. An entity has one
|
97
|
+
or more named properties, each of which can have one or more values. Think of them like this:
|
98
|
+
* 'Kind' (which is your table and the name of your Rails model)
|
99
|
+
* 'Entity' (which is the record from the table)
|
100
|
+
* 'Property' (which is the attribute of the record)
|
101
|
+
|
102
|
+
The `entity_properties` method defines an Array of properties that belong to the entity in cloud
|
103
|
+
datastore. Define the attributes of your model using `attr_accessor`. With this approach, Rails
|
104
|
+
deals solely with ActiveModel objects. The objects are converted to/from entities automatically
|
105
|
+
during save/query operations. You can still use virtual attributes on the model (such as the
|
106
|
+
`:state` attribute above) by simply excluding it from `entity_properties`. In this example state
|
107
|
+
is available to the model but won't be persisted with the entity in datastore.
|
108
|
+
|
109
|
+
Validations work as you would expect:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
class User
|
113
|
+
include ActiveModel::Datastore
|
114
|
+
|
115
|
+
attr_accessor :email, :enabled, :name, :role, :state
|
116
|
+
|
117
|
+
validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
|
118
|
+
validates :name, presence: true, length: { maximum: 30 }
|
119
|
+
|
120
|
+
def entity_properties
|
121
|
+
%w[email enabled name role]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
Callbacks work as you would expect. We have also added the ability to set default values through
|
127
|
+
[`default_property_value`](http://www.rubydoc.info/gems/activemodel-datastore/ActiveModel%2FDatastore:default_property_value)
|
128
|
+
and type cast the format of values through [`format_property_value`](http://www.rubydoc.info/gems/activemodel-datastore/ActiveModel%2FDatastore:format_property_value):
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class User
|
132
|
+
include ActiveModel::Datastore
|
133
|
+
|
134
|
+
attr_accessor :email, :enabled, :name, :role, :state
|
71
135
|
|
72
136
|
before_validation :set_default_values
|
137
|
+
after_validation :format_values
|
138
|
+
|
73
139
|
before_save { puts '** something can happen before save **'}
|
74
140
|
after_save { puts '** something can happen after save **'}
|
75
141
|
|
76
142
|
validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
|
77
143
|
validates :name, presence: true, length: { maximum: 30 }
|
144
|
+
validates :role, presence: true
|
78
145
|
|
79
146
|
def entity_properties
|
80
|
-
%w[email name
|
147
|
+
%w[email enabled name role]
|
81
148
|
end
|
82
149
|
|
83
150
|
def set_default_values
|
84
151
|
default_property_value :enabled, true
|
152
|
+
default_property_value :role, 1
|
85
153
|
end
|
86
154
|
|
87
155
|
def format_values
|
@@ -90,21 +158,6 @@ class User
|
|
90
158
|
end
|
91
159
|
```
|
92
160
|
|
93
|
-
Using `attr_accessor` the attributes of the model are defined. Validations and Callbacks all work
|
94
|
-
as you would expect. However, `entity_properties` is new. Data objects in Cloud Datastore
|
95
|
-
are known as entities. Entities are of a kind. An entity has one or more named properties, each
|
96
|
-
of which can have one or more values. Think of them like this:
|
97
|
-
* 'Kind' (which is your table)
|
98
|
-
* 'Entity' (which is the record from the table)
|
99
|
-
* 'Property' (which is the attribute of the record)
|
100
|
-
|
101
|
-
The `entity_properties` method defines an Array of the properties that belong to the entity in
|
102
|
-
cloud datastore. With this approach, Rails deals solely with ActiveModel objects. The objects are
|
103
|
-
converted to/from entities as needed during save/query operations.
|
104
|
-
|
105
|
-
We have also added the ability to set default property values and type cast the format of values
|
106
|
-
for entities.
|
107
|
-
|
108
161
|
## <a name="controller"></a>Controller Example
|
109
162
|
|
110
163
|
Now on to the controller! A scaffold generated controller works out of the box:
|
@@ -169,6 +222,14 @@ end
|
|
169
222
|
|
170
223
|
## <a name="queries"></a>Retrieving Entities
|
171
224
|
|
225
|
+
Each entity in Cloud Datastore has a key that uniquely identifies it. The key consists of the
|
226
|
+
following components:
|
227
|
+
|
228
|
+
* the kind of the entity, which is User in these examples
|
229
|
+
* an identifier for the individual entity, which can be either a a key name string or an integer numeric ID
|
230
|
+
* an optional ancestor path locating the entity within the Cloud Datastore hierarchy
|
231
|
+
|
232
|
+
#### [all(options = {})](http://www.rubydoc.info/gems/activemodel-datastore/ActiveModel%2FDatastore%2FClassMethods:all)
|
172
233
|
Queries entities using the provided options. When a limit option is provided queries up to the limit
|
173
234
|
and returns results with a cursor.
|
174
235
|
```ruby
|
@@ -194,8 +255,9 @@ users, cursor = User.all(limit: 7)
|
|
194
255
|
# @option options [Array] :where Adds a property filter of arrays in the format[name, operator, value].
|
195
256
|
```
|
196
257
|
|
258
|
+
#### [find(*ids, parent: nil)](http://www.rubydoc.info/gems/activemodel-datastore/ActiveModel%2FDatastore%2FClassMethods:find)
|
197
259
|
Find entity by id - this can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
|
198
|
-
The parent key is optional.
|
260
|
+
The parent key is optional. This method is a lookup by key and results will be strongly consistent.
|
199
261
|
```ruby
|
200
262
|
user = User.find(1)
|
201
263
|
|
@@ -205,18 +267,161 @@ user = User.find(1, parent: parent)
|
|
205
267
|
users = User.find(1, 2, 3)
|
206
268
|
```
|
207
269
|
|
208
|
-
|
270
|
+
#### [find_by(args)](http://www.rubydoc.info/gems/activemodel-datastore/ActiveModel%2FDatastore%2FClassMethods:find_by)
|
271
|
+
Queries for the first entity matching the specified condition.
|
209
272
|
```ruby
|
210
273
|
user = User.find_by(name: 'Joe')
|
211
274
|
|
212
275
|
user = User.find_by(name: 'Bryce', ancestor: parent)
|
213
276
|
```
|
214
277
|
|
215
|
-
|
278
|
+
Cloud Datastore has documentation on how [Datastore Queries](https://cloud.google.com/datastore/docs/concepts/queries#datastore-basic-query-ruby)
|
279
|
+
work, and pay special attention to the the [restrictions](https://cloud.google.com/datastore/docs/concepts/queries#restrictions_on_queries).
|
280
|
+
|
281
|
+
## <a name="consistency"></a>Datastore Consistency
|
282
|
+
|
283
|
+
Cloud Datastore is a non-relational databases, or NoSQL database. It distributes data over many
|
284
|
+
machines and uses synchronous replication over a wide geographic area. Because of this architecture
|
285
|
+
it offers a balance of strong and eventual consistency.
|
286
|
+
|
287
|
+
What is eventual consistency?
|
288
|
+
|
289
|
+
It means that an updated entity value may not be immediately visible when executing a query.
|
290
|
+
Eventual consistency is a theoretical guarantee that, provided no new updates to an entity are made,
|
291
|
+
all reads of the entity will eventually return the last updated value.
|
292
|
+
|
293
|
+
In the context of a Rails app, there are times that eventual consistency is not ideal. For example,
|
294
|
+
let's say you create a user entity with a key that looks like this:
|
295
|
+
|
296
|
+
`@key=#<Google::Cloud::Datastore::Key @kind="User", @id=1>`
|
297
|
+
|
298
|
+
and then immediately redirect to the index view of users. There is a good chance that your new user
|
299
|
+
is not yet visible in the list. If you perform a refresh on the index view a second or two later
|
300
|
+
the user will appear.
|
301
|
+
|
302
|
+
"Wait a minute!" you say. "This is crap!" you say. Fear not! We can make the query of users strongly
|
303
|
+
consistent. We just need to use entity groups and ancestor queries. An entity group is a hierarchy
|
304
|
+
formed by a root entity and its children. To create an entity group, you specify an ancestor path
|
305
|
+
for the entity which is a parent key as part of the child key.
|
216
306
|
|
217
|
-
|
307
|
+
Before using the `save` method, assign the `parent_key_id` attribute an ID. Let's say that 12345
|
308
|
+
represents the ID of the company that the users belong to. The key of the user entity will now
|
309
|
+
look like this:
|
218
310
|
|
219
|
-
|
311
|
+
`@key=#<Google::Cloud::Datastore::Key @kind="User", @id=1, @parent=#<Google::Cloud::Datastore::Key @kind="ParentUser", @id=12345>>`
|
312
|
+
|
313
|
+
All of the User entities will now belong to an entity group named ParentUser and can be queried by the
|
314
|
+
Company ID. When we query for the users we will provide User.parent_key(12345) as the ancestor option.
|
315
|
+
|
316
|
+
*Ancestor queries are always strongly consistent.*
|
317
|
+
|
318
|
+
However, there is a small downside. Entities with the same ancestor are limited to 1 write per second.
|
319
|
+
Also, the entity group relationship cannot be changed after creating the entity (as you can't modify
|
320
|
+
an entity's key after it has been saved).
|
321
|
+
|
322
|
+
The Users controller would now look like this:
|
323
|
+
|
324
|
+
```ruby
|
325
|
+
class UsersController < ApplicationController
|
326
|
+
before_action :set_user, only: [:show, :edit, :update, :destroy]
|
327
|
+
|
328
|
+
def index
|
329
|
+
@users = User.all(ancestor: User.parent_key(12345))
|
330
|
+
end
|
331
|
+
|
332
|
+
def show
|
333
|
+
end
|
334
|
+
|
335
|
+
def new
|
336
|
+
@user = User.new
|
337
|
+
end
|
338
|
+
|
339
|
+
def edit
|
340
|
+
end
|
341
|
+
|
342
|
+
def create
|
343
|
+
@user = User.new(user_params)
|
344
|
+
@user.parent_key_id = 12345
|
345
|
+
respond_to do |format|
|
346
|
+
if @user.save
|
347
|
+
format.html { redirect_to @user, notice: 'User was successfully created.' }
|
348
|
+
else
|
349
|
+
format.html { render :new }
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def update
|
355
|
+
respond_to do |format|
|
356
|
+
if @user.update(user_params)
|
357
|
+
format.html { redirect_to @user, notice: 'User was successfully updated.' }
|
358
|
+
else
|
359
|
+
format.html { render :edit }
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def destroy
|
365
|
+
@user.destroy
|
366
|
+
respond_to do |format|
|
367
|
+
format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
private
|
372
|
+
|
373
|
+
def set_user
|
374
|
+
@user = User.find(params[:id], parent: User.parent_key(12345))
|
375
|
+
end
|
376
|
+
|
377
|
+
def user_params
|
378
|
+
params.require(:user).permit(:email, :name)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
```
|
382
|
+
|
383
|
+
See here for the Cloud Datastore documentation on [Data Consistency](https://cloud.google.com/datastore/docs/concepts/structuring_for_strong_consistency).
|
384
|
+
|
385
|
+
## <a name="indexes"></a>Datastore Indexes
|
386
|
+
|
387
|
+
Every cloud datastore query requires an index. Yes, you read that correctly. Every single one. The
|
388
|
+
indexes contain entity keys in a sequence specified by the index's properties and, optionally,
|
389
|
+
the entity's ancestors.
|
390
|
+
|
391
|
+
There are two types of indexes, *built-in* and *composite*.
|
392
|
+
|
393
|
+
#### Built-in
|
394
|
+
By default, Cloud Datastore automatically predefines an index for each property of each entity kind.
|
395
|
+
These single property indexes are suitable for simple types of queries. These indexes are free and
|
396
|
+
do not count against your index limit.
|
397
|
+
|
398
|
+
#### Composite
|
399
|
+
Composite index multiple property values per indexed entity. Composite indexes support complex
|
400
|
+
queries and are defined in an index.yaml file.
|
401
|
+
|
402
|
+
Composite indexes are required for queries of the following form:
|
403
|
+
|
404
|
+
* queries with ancestor and inequality filters
|
405
|
+
* queries with one or more inequality filters on a property and one or more equality filters on other properties
|
406
|
+
* queries with a sort order on keys in descending order
|
407
|
+
* queries with multiple sort orders
|
408
|
+
* queries with one or more filters and one or more sort orders
|
409
|
+
|
410
|
+
*NOTE*: Inequality filters are LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL.
|
411
|
+
|
412
|
+
Google has excellent doc regarding datastore indexes [here](https://cloud.google.com/datastore/docs/concepts/indexes).
|
413
|
+
|
414
|
+
The datastore emulator generates composite indexes in an index.yaml file automatically. The file
|
415
|
+
can be found in /tmp/local_datastore/WEB-INF/index.yaml. If your localhost Rails app exercises every
|
416
|
+
possible query the application will issue, using every combination of filter and sort order, the
|
417
|
+
generated entries will represent your complete set of indexes.
|
418
|
+
|
419
|
+
One thing to note is that the datastore emulator caches indexes. As you add and modify application
|
420
|
+
code you might find that the local datastore index.yaml contains indexes that are no longer needed.
|
421
|
+
In this scenario try deleting the index.yaml and restarting the emulator. Navigate through your Rails
|
422
|
+
app and the index.yaml will be built from scratch.
|
423
|
+
|
424
|
+
## <a name="emulator"></a>Datastore Emulator
|
220
425
|
|
221
426
|
Install the Google Cloud SDK.
|
222
427
|
|
@@ -250,8 +455,26 @@ To create the local test datastore execute the following from the root of the pr
|
|
250
455
|
|
251
456
|
To start the local Cloud Datastore emulator:
|
252
457
|
|
253
|
-
$
|
458
|
+
$ cloud_datastore_emulator start --port=8180 tmp/local_datastore
|
254
459
|
|
460
|
+
## <a name="rails"></a>Example Rails App
|
461
|
+
|
462
|
+
There is an example Rails 5 app in the test directory [here](https://github.com/Agrimatics/activemodel-datastore/tree/master/test/support/datastore_example_rails_app).
|
463
|
+
|
464
|
+
```bash
|
465
|
+
$ bundle
|
466
|
+
$ cloud_datastore_emulator create tmp/local_datastore
|
467
|
+
$ cloud_datastore_emulator create tmp/test_datastore
|
468
|
+
$ ./start-local-datastore.sh
|
469
|
+
$ rails s
|
470
|
+
```
|
471
|
+
|
472
|
+
Navigate to http://localhost:3000.
|
473
|
+
|
474
|
+
## <a name="track_changes"></a>Track Changes
|
475
|
+
|
476
|
+
TODO: document the change tracking implementation.
|
477
|
+
|
255
478
|
## <a name="nested"></a>Nested Forms
|
256
479
|
|
257
480
|
Adds support for nested attributes to ActiveModel. Heavily inspired by
|
@@ -260,7 +483,7 @@ Rails ActiveRecord::NestedAttributes.
|
|
260
483
|
Nested attributes allow you to save attributes on associated records along with the parent.
|
261
484
|
It's used in conjunction with fields_for to build the nested form elements.
|
262
485
|
|
263
|
-
See Rails ActionView::Helpers::FormHelper::fields_for for more info.
|
486
|
+
See Rails [ActionView::Helpers::FormHelper::fields_for](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for) for more info.
|
264
487
|
|
265
488
|
*NOTE*: Unlike ActiveRecord, the way that the relationship is modeled between the parent and
|
266
489
|
child is not enforced. With NoSQL the relationship could be defined by any attribute, or with
|
@@ -329,12 +552,9 @@ Within the parent model `valid?` will validate the parent and associated childre
|
|
329
552
|
a truthy `_destroy` key, the appropriate nested_models will have `marked_for_destruction` set
|
330
553
|
to True.
|
331
554
|
|
332
|
-
## <a name="
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
TODO: document using the datastore emulator to generate the index.yaml.
|
339
|
-
|
340
|
-
TODO: document the change tracking implementation.
|
555
|
+
## <a name="gotchas"></a>Datastore Gotchas
|
556
|
+
#### Ordering of query results is undefined when no sort order is specified.
|
557
|
+
When a query does not specify a sort order, the results are returned in the order they are retrieved.
|
558
|
+
As Cloud Datastore implementation evolves (or if a project's indexes change), this order may change.
|
559
|
+
Therefore, if your application requires its query results in a particular order, be sure to specify
|
560
|
+
that sort order explicitly in the query.
|
@@ -9,21 +9,25 @@
|
|
9
9
|
# class User
|
10
10
|
# include ActiveModel::Datastore
|
11
11
|
#
|
12
|
-
# attr_accessor :email, :name, :
|
12
|
+
# attr_accessor :email, :enabled, :name, :role, :state
|
13
13
|
#
|
14
14
|
# before_validation :set_default_values
|
15
|
-
#
|
16
|
-
#
|
15
|
+
# after_validation :format_values
|
16
|
+
#
|
17
|
+
# before_save { puts '** something can happen before save **'}
|
18
|
+
# after_save { puts '** something can happen after save **'}
|
17
19
|
#
|
18
20
|
# validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
|
19
21
|
# validates :name, presence: true, length: { maximum: 30 }
|
22
|
+
# validates :role, presence: true
|
20
23
|
#
|
21
24
|
# def entity_properties
|
22
|
-
# %w[email name
|
25
|
+
# %w[email enabled name role]
|
23
26
|
# end
|
24
27
|
#
|
25
28
|
# def set_default_values
|
26
29
|
# default_property_value :enabled, true
|
30
|
+
# default_property_value :role, 1
|
27
31
|
# end
|
28
32
|
#
|
29
33
|
# def format_values
|
@@ -116,13 +120,20 @@ module ActiveModel::Datastore
|
|
116
120
|
included do
|
117
121
|
private_class_method :query_options, :query_sort, :query_property_filter, :find_all_entities
|
118
122
|
define_model_callbacks :save, :update, :destroy
|
119
|
-
attr_accessor :id
|
123
|
+
attr_accessor :id, :parent_key_id
|
120
124
|
end
|
121
125
|
|
122
126
|
def entity_properties
|
123
127
|
[]
|
124
128
|
end
|
125
129
|
|
130
|
+
##
|
131
|
+
# Used to determine if the ActiveModel object belongs to an entity group.
|
132
|
+
#
|
133
|
+
def parent?
|
134
|
+
parent_key_id.present?
|
135
|
+
end
|
136
|
+
|
126
137
|
##
|
127
138
|
# Used by ActiveModel for determining polymorphic routing.
|
128
139
|
#
|
@@ -177,11 +188,18 @@ module ActiveModel::Datastore
|
|
177
188
|
##
|
178
189
|
# Builds the Cloud Datastore entity with attributes from the Model object.
|
179
190
|
#
|
191
|
+
# @param [Google::Cloud::Datastore::Key] parent An optional parent Key of the entity.
|
192
|
+
#
|
180
193
|
# @return [Entity] The updated Google::Cloud::Datastore::Entity.
|
181
194
|
#
|
182
195
|
def build_entity(parent = nil)
|
183
196
|
entity = CloudDatastore.dataset.entity self.class.name, id
|
184
|
-
|
197
|
+
if parent.present?
|
198
|
+
raise ArgumentError, 'Must be a Key' unless parent.is_a? Google::Cloud::Datastore::Key
|
199
|
+
entity.key.parent = parent
|
200
|
+
elsif parent?
|
201
|
+
entity.key.parent = self.class.parent_key(parent_key_id)
|
202
|
+
end
|
185
203
|
entity_properties.each do |attr|
|
186
204
|
entity[attr] = instance_variable_get("@#{attr}")
|
187
205
|
end
|
@@ -194,17 +212,6 @@ module ActiveModel::Datastore
|
|
194
212
|
|
195
213
|
##
|
196
214
|
# For compatibility with libraries that require the bang method version (example, factory_girl).
|
197
|
-
# If you require a save! method that supports parents (ancestor queries), override this method
|
198
|
-
# in your own code with something like this:
|
199
|
-
#
|
200
|
-
# def save!
|
201
|
-
# parent = nil
|
202
|
-
# if account_id.present?
|
203
|
-
# parent = CloudDatastore.dataset.key 'Parent' + self.class.name, account_id.to_i
|
204
|
-
# end
|
205
|
-
# msg = 'Failed to save the entity'
|
206
|
-
# save_entity(parent) || raise(ActiveModel::Datastore::EntityNotSavedError, msg)
|
207
|
-
# end
|
208
215
|
#
|
209
216
|
def save!
|
210
217
|
save_entity || raise(EntityNotSavedError, 'Failed to save the entity')
|
@@ -222,6 +229,7 @@ module ActiveModel::Datastore
|
|
222
229
|
def destroy
|
223
230
|
run_callbacks :destroy do
|
224
231
|
key = CloudDatastore.dataset.key self.class.name, id
|
232
|
+
key.parent = self.class.parent_key(parent_key_id) if parent?
|
225
233
|
self.class.retry_on_exception? { CloudDatastore.dataset.delete key }
|
226
234
|
end
|
227
235
|
end
|
@@ -234,12 +242,20 @@ module ActiveModel::Datastore
|
|
234
242
|
entity = build_entity(parent)
|
235
243
|
success = self.class.retry_on_exception? { CloudDatastore.dataset.save entity }
|
236
244
|
self.id = entity.key.id if success
|
245
|
+
self.parent_key_id = entity.key.parent.id if entity.key.parent.present?
|
237
246
|
success
|
238
247
|
end
|
239
248
|
end
|
240
249
|
|
241
250
|
# Methods defined here will be class methods when 'include ActiveModel::Datastore'.
|
242
251
|
module ClassMethods
|
252
|
+
##
|
253
|
+
# A default parent key for specifying an ancestor path and creating an entity group.
|
254
|
+
#
|
255
|
+
def parent_key(parent_id)
|
256
|
+
CloudDatastore.dataset.key('Parent' + name, parent_id.to_i)
|
257
|
+
end
|
258
|
+
|
243
259
|
##
|
244
260
|
# Retrieves an entity by id or name and by an optional parent.
|
245
261
|
#
|
@@ -356,7 +372,7 @@ module ActiveModel::Datastore
|
|
356
372
|
#
|
357
373
|
# @example
|
358
374
|
# User.find_by(name: 'Joe')
|
359
|
-
# User.find_by(name: 'Bryce', ancestor:
|
375
|
+
# User.find_by(name: 'Bryce', ancestor: parent_key)
|
360
376
|
#
|
361
377
|
def find_by(args)
|
362
378
|
query = CloudDatastore.dataset.query name
|
@@ -391,6 +407,7 @@ module ActiveModel::Datastore
|
|
391
407
|
model_entity = new
|
392
408
|
model_entity.id = entity.key.id unless entity.key.id.nil?
|
393
409
|
model_entity.id = entity.key.name unless entity.key.name.nil?
|
410
|
+
model_entity.parent_key_id = entity.key.parent.id if entity.key.parent.present?
|
394
411
|
entity.properties.to_hash.each do |name, value|
|
395
412
|
model_entity.send "#{name}=", value if model_entity.respond_to? "#{name}="
|
396
413
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activemodel-datastore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bryce McLean
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-04-
|
11
|
+
date: 2017-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|