activemodel-datastore 0.1.0 → 0.2.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 +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
|
+
[](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
|