parse-stack 1.7.1 → 1.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changes.md +23 -0
- data/Gemfile.lock +3 -3
- data/README.md +105 -14
- data/lib/parse/api/hooks.rb +4 -2
- data/lib/parse/api/schema.rb +4 -2
- data/lib/parse/api/server.rb +2 -1
- data/lib/parse/client.rb +19 -7
- data/lib/parse/client/caching.rb +5 -5
- data/lib/parse/model/acl.rb +163 -12
- data/lib/parse/model/associations/collection_proxy.rb +93 -11
- data/lib/parse/model/associations/pointer_collection_proxy.rb +6 -4
- data/lib/parse/model/classes/user.rb +13 -0
- data/lib/parse/model/core/actions.rb +24 -8
- data/lib/parse/model/core/properties.rb +12 -5
- data/lib/parse/model/date.rb +9 -0
- data/lib/parse/model/model.rb +0 -13
- data/lib/parse/model/object.rb +15 -9
- data/lib/parse/model/pointer.rb +11 -0
- data/lib/parse/query.rb +3 -1
- data/lib/parse/query/constraint.rb +7 -1
- data/lib/parse/query/constraints.rb +7 -0
- data/lib/parse/stack/generators/templates/model_installation.rb +0 -1
- data/lib/parse/stack/generators/templates/model_role.rb +0 -1
- data/lib/parse/stack/generators/templates/model_session.rb +0 -1
- data/lib/parse/stack/generators/templates/model_user.rb +0 -1
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/webhooks/payload.rb +11 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0267822a7e96425e847f7218ad98a11115b511b4'
|
4
|
+
data.tar.gz: 5f942b6458ba0f0de2f5e9657fe9e135ea46fcc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7978c0a43c5f46217e7fcc3f0ff600c548571f9094f91b7716bf8ce8881e47edd49bc579d803853213c2890ee3e1f8aec6b23453f9f396b17bedfae7b173f9fa
|
7
|
+
data.tar.gz: 3a29aac5e5c251424023426498cd4e98a6751189e752a932f011becfb3f1f1806e1988088c97e4f21b7d1b2bd235d32e5bd912478da45919928439b20dd33c0e
|
data/Changes.md
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
## Parse-Stack Changelog
|
2
2
|
|
3
|
+
### 1.7.2
|
4
|
+
- NEW: `Parse::Model.autosave_on_create` has been removed in favor of `first_or_create!`.
|
5
|
+
- NEW: Webhook Triggers and Functions now have a `wlog` method, similar to `puts`, but allows easier tracing of
|
6
|
+
single requests in a multi-request threaded environment. (See Parse::Webhooks::Payload)
|
7
|
+
- NEW: `:id` constraints also safely supports pointers by skipping class matching.
|
8
|
+
- NEW: Support for `add_unique` and the set union operator `|` in collection proxies.
|
9
|
+
- NEW: Support for `uniq` and `uniq!` in collection proxies.
|
10
|
+
- NEW: `uniq` and `uniq!` for collection proxies utilize `eql?` for determining uniqueness.
|
11
|
+
- NEW: Updated override behavior for the `hash` method in Parse::Pointer and subclasses.
|
12
|
+
- NEW: Support for additional array methods in collection proxies (+,-,& and |)
|
13
|
+
- NEW: Additional methods for Parse::ACL class for setting read/write privileges.
|
14
|
+
- NEW: Expose the shared cache store through `Parse.cache`.
|
15
|
+
- NEW: `User#any_session!` method, see documentation.
|
16
|
+
- NEW: Extension to support `Date#parse_date`.
|
17
|
+
- NEW: Added `Parse::Query#append` as alias to `Parse::Query#conditions`
|
18
|
+
- CHANGED: `save_all` now returns true if there were no errors.
|
19
|
+
- FIXED: first_or_create will now apply dirty tracking to newly created fields.
|
20
|
+
- FIXED: Properties of :array type will always return a Parse::CollectionProxy if their internal value is nil. The object will not be marked dirty until something is added to the array.
|
21
|
+
- FIXED: Encoding a Parse::Object into JSON will remove any values that are `nil`
|
22
|
+
which were not explicitly changed to that value.
|
23
|
+
- [PR#39](https://github.com/modernistik/parse-stack/pull/39): Allow Moneta::Expires as cache object to allow for non-native expiring caches by [GrahamW](https://github.com/GrahamW)
|
24
|
+
|
25
|
+
|
3
26
|
### 1.7.1
|
4
27
|
- NEW: `:timezone` datatype that maps to `Parse::TimeZone` (which mimics `ActiveSupport::TimeZone`)
|
5
28
|
- NEW: Installation `:time_zone` field is now a `Parse::TimeZone` instance.
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
parse-stack (1.7.
|
4
|
+
parse-stack (1.7.2)
|
5
5
|
active_model_serializers (>= 0.9, < 1)
|
6
6
|
activemodel (>= 4.2.1, < 6)
|
7
7
|
activesupport (>= 4.2.1, < 6)
|
@@ -56,8 +56,8 @@ GEM
|
|
56
56
|
multipart-post (>= 1.2, < 3)
|
57
57
|
faraday_middleware (0.11.0.1)
|
58
58
|
faraday (>= 0.7.4, < 1.0)
|
59
|
-
i18n (0.8.
|
60
|
-
jsonapi-renderer (0.1.
|
59
|
+
i18n (0.8.6)
|
60
|
+
jsonapi-renderer (0.1.3)
|
61
61
|
loofah (2.0.3)
|
62
62
|
nokogiri (>= 1.5.9)
|
63
63
|
method_source (0.8.2)
|
data/README.md
CHANGED
@@ -587,14 +587,15 @@ event.time_zone.valid? # => false
|
|
587
587
|
|
588
588
|
### [Parse::ACL](https://www.modernistik.com/gems/parse-stack/Parse/ACL.html)
|
589
589
|
The `ACL` class represents the access control lists for each record. An ACL is represented by a JSON object with the keys being `Parse::User` object ids or the special key of `*`, which indicates the public access permissions.
|
590
|
-
The value of each key in the hash is a `Parse::ACL::Permission` object which defines the boolean permission state for `read` and `write`.
|
590
|
+
The value of each key in the hash is a [`Parse::ACL::Permission`](https://www.modernistik.com/gems/parse-stack/Parse/ACL/Permission.html) object which defines the boolean permission state for `read` and `write`.
|
591
591
|
|
592
|
-
The example below illustrates a Parse ACL JSON object where there is a public read permission, but public write is prevented. In addition, the user with id `3KmCvT7Zsb
|
592
|
+
The example below illustrates a Parse ACL JSON object where there is a public read permission, but public write is prevented. In addition, the user with id `3KmCvT7Zsb` and the `Admins` role, are allowed to both read and write on this record.
|
593
593
|
|
594
594
|
```json
|
595
595
|
{
|
596
596
|
"*": { "read": true },
|
597
|
-
"3KmCvT7Zsb": { "read": true, "write": true }
|
597
|
+
"3KmCvT7Zsb": { "read": true, "write": true },
|
598
|
+
"role:Admins": { "read": true, "write": true }
|
598
599
|
}
|
599
600
|
```
|
600
601
|
|
@@ -609,7 +610,6 @@ All `Parse::Object` subclasses have an `acl` property by default. With this prop
|
|
609
610
|
# apply public read, but no public write
|
610
611
|
artist.acl.everyone true, false
|
611
612
|
|
612
|
-
|
613
613
|
# allow user to have read and write access
|
614
614
|
artist.acl.apply user.id, true, true
|
615
615
|
|
@@ -619,6 +619,12 @@ All `Parse::Object` subclasses have an `acl` property by default. With this prop
|
|
619
619
|
# allow the 'Admins' role read and write
|
620
620
|
artist.acl.apply_role "Admins", true, true
|
621
621
|
|
622
|
+
# remove write from all attached privileges
|
623
|
+
artist.acl.no_write!
|
624
|
+
|
625
|
+
# remove all attached privileges
|
626
|
+
artist.acl.master_key_only!
|
627
|
+
|
622
628
|
artist.save
|
623
629
|
```
|
624
630
|
You may also set default ACLs for newly created insatnces of your subclasses using `set_default_acl`:
|
@@ -1260,6 +1266,7 @@ fan = bands.fans.first # => Parse::User object
|
|
1260
1266
|
|
1261
1267
|
# use `add` or `remove` to modify relations
|
1262
1268
|
band.fans.add user
|
1269
|
+
band.fans.add_unique user # no op
|
1263
1270
|
bands.fans.remove user
|
1264
1271
|
|
1265
1272
|
# updates the relation as well as changes to `band`
|
@@ -1305,7 +1312,7 @@ specific band.
|
|
1305
1312
|
end
|
1306
1313
|
```
|
1307
1314
|
|
1308
|
-
You can now find all `Parse::User` records
|
1315
|
+
You can now find all `Parse::User` records that have "liked" a specific band. *In the
|
1309
1316
|
example below, the `:likes` key refers to the `likes` column defined in the `Band`
|
1310
1317
|
collection which contains the set of user records.*
|
1311
1318
|
|
@@ -1314,6 +1321,10 @@ collection which contains the set of user records.*
|
|
1314
1321
|
# find all users who have liked this band, where :likes is a column
|
1315
1322
|
# in the Band collection - NOT in the User collection.
|
1316
1323
|
users = Parse::User.all :likes.related_to => band
|
1324
|
+
|
1325
|
+
# or use the relation accessor in band. It is equivalent since Band is
|
1326
|
+
# declared with a :has_many association.
|
1327
|
+
band.likes.all # => array of Parse::Users who liked the band
|
1317
1328
|
```
|
1318
1329
|
You can also find all bands that a specific user has liked.
|
1319
1330
|
|
@@ -1394,7 +1405,7 @@ song.changed # ['name']
|
|
1394
1405
|
|
1395
1406
|
```
|
1396
1407
|
|
1397
|
-
If you want to either find the first resource matching some given criteria or just create that resource if it can't be found, you can use
|
1408
|
+
If you want to either find the first resource matching some given criteria or just create that resource if it can't be found, you can use `first_or_create`. Note that if a match is not found, the object will not be saved to Parse automatically, since the framework provides support for heterogeneous object batch saving. This means you can group different object classes together and save them all at once through the `Array#save` method to reduce API requests. If you want to truly want to find a first or create (save) the object, you may use `first_or_create!`.
|
1398
1409
|
|
1399
1410
|
```ruby
|
1400
1411
|
# Finds matching song or creates a new unsaved object
|
@@ -1408,6 +1419,9 @@ song = Song.first_or_create(name: "Awesome Song", available: true)
|
|
1408
1419
|
song.id # 'xyz1122df`
|
1409
1420
|
song.save # noop since nothing changed
|
1410
1421
|
|
1422
|
+
# first_or_create! : Return an existing OR newly saved object
|
1423
|
+
song = Song.first_or_create!(name: "Awesome Song", available: true)
|
1424
|
+
|
1411
1425
|
```
|
1412
1426
|
|
1413
1427
|
If the constraints you use for the query differ from the attributes you want to set for the new object, you can pass the attributes for creating a new resource as the second parameter to `#first_or_create`, also in the form of a `#Hash`.
|
@@ -1801,6 +1815,23 @@ Song.all limit: 500, cache: false
|
|
1801
1815
|
Song.all limit: 500, cache: 1.minute
|
1802
1816
|
```
|
1803
1817
|
|
1818
|
+
You may access the shared cache for the default client connection through `Parse.cache`. This is useful if you
|
1819
|
+
want to utilize the same cache store for other purposes.
|
1820
|
+
|
1821
|
+
```ruby
|
1822
|
+
# Access the cache instance for other uses
|
1823
|
+
Parse.cache["key"] = "value"
|
1824
|
+
Parse.cache["key"] # => "value"
|
1825
|
+
|
1826
|
+
# or with Parse queries and objects
|
1827
|
+
Parse.cache.fetch("all:song:records") do |key|
|
1828
|
+
results = Song.all # or other complex query or operation
|
1829
|
+
# store it in the cache, but expires in 30 seconds
|
1830
|
+
Parse.cache.store(key, results, expires: 30)
|
1831
|
+
end
|
1832
|
+
|
1833
|
+
```
|
1834
|
+
|
1804
1835
|
#### :use_master_key
|
1805
1836
|
A true/false value. If you provided a master key as part of `Parse.setup()`, it will be sent on every request. However, if you wish to disable sending the master key on a particular request in order for the record ACLs to be enforced, you may pass `false`. If `false` is passed, caching will be disabled for this request.
|
1806
1837
|
|
@@ -2000,15 +2031,28 @@ q.where :field.not_in_query => query # alias
|
|
2000
2031
|
```
|
2001
2032
|
|
2002
2033
|
#### Matches Object Id
|
2003
|
-
Sometimes you want to find rows where a particular Parse object exists. You can do so by passing a the Parse::Object subclass
|
2034
|
+
Sometimes you want to find rows where a particular Parse object exists. You can do so by passing a the Parse::Object subclass or a Parse::Pointer. In some cases you may only have the "objectId" of the record you are looking for. For convenience, you can also use the `id` constraint. This will assume that the name of the field matches a particular Parse class you have defined. Assume the following:
|
2004
2035
|
|
2005
2036
|
```ruby
|
2006
2037
|
# where this Parse object equals the object in the column `field`.
|
2007
2038
|
q.where :field => Parse::Pointer("Field", "someObjectId")
|
2039
|
+
# => "field":{"__type":"Pointer","className":"Field","objectId":"someObjectId"}}
|
2040
|
+
|
2008
2041
|
# alias, shorthand when we infer `:field` maps to `Field` parse class.
|
2009
2042
|
q.where :field.id => "someObjectId"
|
2010
|
-
# "field":{"__type":"Pointer","className":"Field","objectId":"someObjectId"}}
|
2043
|
+
# => "field":{"__type":"Pointer","className":"Field","objectId":"someObjectId"}}
|
2011
2044
|
|
2045
|
+
```
|
2046
|
+
It is always important to be thoughtful in naming column names in associations as
|
2047
|
+
close to their foreign Parse class names. This enables more expressive syntax while reducing
|
2048
|
+
code. The `id` also supports any object or pointer object. These are all equivalent:
|
2049
|
+
|
2050
|
+
```ruby
|
2051
|
+
q.where :user => User.pointer("xyx123")
|
2052
|
+
q.where :user.id => "xyx123"
|
2053
|
+
q.where :user.id => User.pointer("xyx123")
|
2054
|
+
# All produce
|
2055
|
+
# => "user":{"__type":"Pointer","className":"_User","objectId":"xyx123"}}
|
2012
2056
|
```
|
2013
2057
|
|
2014
2058
|
##### Additional Examples
|
@@ -2039,6 +2083,17 @@ Song.all :artist.id => artist_id
|
|
2039
2083
|
# other approaches, same result
|
2040
2084
|
Song.all :artist => Artist.pointer(artist_id)
|
2041
2085
|
Song.all :artist => Parse::Pointer.new("Artist", artist_id)
|
2086
|
+
|
2087
|
+
# "id" safely pointers and strings for supporting these types of API patterns
|
2088
|
+
def find_songs(artist)
|
2089
|
+
Song.all :artist.id => artist
|
2090
|
+
end
|
2091
|
+
|
2092
|
+
# all ok
|
2093
|
+
songs = find_songs artist_id # by a string ObjectId
|
2094
|
+
songs = find_songs artist # or by an object or pointer
|
2095
|
+
songs = find_songs Artist.pointer(artist_id)
|
2096
|
+
|
2042
2097
|
```
|
2043
2098
|
|
2044
2099
|
### [Geo Queries](https://www.modernistik.com/gems/parse-stack/Parse/Constraint/NearSphereQueryConstraint.html)
|
@@ -2119,9 +2174,12 @@ end
|
|
2119
2174
|
|
2120
2175
|
post = Post.first
|
2121
2176
|
# find all Users who have liked this post object,
|
2122
|
-
# where likes is a column on the Post class.
|
2177
|
+
# where 'likes' is a column on the Post class.
|
2123
2178
|
users = Parse::User.all :likes.rel => post
|
2124
2179
|
|
2180
|
+
# or use the relation accessor declared in Post
|
2181
|
+
users = post.likes.all # same result
|
2182
|
+
|
2125
2183
|
# or find posts that a certain user has liked
|
2126
2184
|
user = Parse::User.first
|
2127
2185
|
# likes is a Parse relation in the Post collection that contains User records
|
@@ -2236,7 +2294,7 @@ class Song < Parse::Object
|
|
2236
2294
|
# ex. before save callback
|
2237
2295
|
before_save do
|
2238
2296
|
self.name = self.name.titleize
|
2239
|
-
|
2297
|
+
# make sure global acls are set
|
2240
2298
|
acl.everyone(true, false) if new?
|
2241
2299
|
end
|
2242
2300
|
|
@@ -2336,15 +2394,31 @@ class Song < Parse::Object
|
|
2336
2394
|
|
2337
2395
|
webhook :function, :mySongFunction do
|
2338
2396
|
the_user = user # available if a Parse user made the call
|
2339
|
-
|
2340
|
-
|
2341
|
-
|
2397
|
+
str = params["str"]
|
2398
|
+
|
2399
|
+
# ... get the list of matching songs the user has access to.
|
2400
|
+
results = Songs.all(:name.like => /#{str}/, :session => the_user)
|
2401
|
+
# Helper method for logging
|
2402
|
+
wlog "Found #{results.count} for #{the_user.username}"
|
2403
|
+
|
2404
|
+
results
|
2342
2405
|
end
|
2343
2406
|
|
2344
2407
|
end
|
2345
2408
|
|
2346
2409
|
```
|
2347
2410
|
|
2411
|
+
You may optionally, register these functions outside of classes (recommended).
|
2412
|
+
|
2413
|
+
```ruby
|
2414
|
+
Parse::Webhooks.route :function, :mySongFunction do
|
2415
|
+
# .. do stuff ..
|
2416
|
+
str = params["str"]
|
2417
|
+
results = Songs.all(:name.like => /#{str}/, :session => user)
|
2418
|
+
results
|
2419
|
+
end
|
2420
|
+
```
|
2421
|
+
|
2348
2422
|
### Cloud Code Triggers
|
2349
2423
|
You can register webhooks to handle the different object triggers: `:before_save`, `:after_save`, `:before_delete` and `:after_delete`. The `payload` object, which is an instance of `Parse::Webhooks::Payload`, contains several properties that represent the payload. One of the most important ones is `parse_object`, which will provide you with the instance of your specific Parse object. In `:before_save` triggers, this object already contains dirty tracking information of what has been changed.
|
2350
2424
|
|
@@ -2363,7 +2437,7 @@ You can register webhooks to handle the different object triggers: `:before_save
|
|
2363
2437
|
end
|
2364
2438
|
|
2365
2439
|
# or the explicit way
|
2366
|
-
Parse::Webhooks.route :after_save,
|
2440
|
+
Parse::Webhooks.route :after_save, :Artist do
|
2367
2441
|
puts "User: #{user.username}" if user.present? # Parse::User
|
2368
2442
|
artist = parse_object # Artist
|
2369
2443
|
# no need for return in after save
|
@@ -2395,6 +2469,7 @@ class Artist < Parse::Object
|
|
2395
2469
|
error!("Name cannot be empty") if artist.name.blank?
|
2396
2470
|
|
2397
2471
|
if artist.name_changed?
|
2472
|
+
wlog "The artist name changed!"
|
2398
2473
|
# .. do something if `name` has changed
|
2399
2474
|
end
|
2400
2475
|
|
@@ -2519,6 +2594,22 @@ Parse::Client.client.clear_cache!
|
|
2519
2594
|
|
2520
2595
|
# or through the client accessor of a model
|
2521
2596
|
Song.client.clear_cache!
|
2597
|
+
```
|
2598
|
+
|
2599
|
+
You can always access the default shared cache through `Parse.cache` and utilize it
|
2600
|
+
for other purposes in your application:
|
2601
|
+
|
2602
|
+
```ruby
|
2603
|
+
# Access the cache instance for other uses
|
2604
|
+
Parse.cache["key"] = "value"
|
2605
|
+
Parse.cache["key"] # => "value"
|
2606
|
+
|
2607
|
+
# or with Parse queries and objects
|
2608
|
+
Parse.cache.fetch("all:records") do |key|
|
2609
|
+
results = Song.all # or other complex query or operation
|
2610
|
+
# store it in the cache, but expires in 30 seconds
|
2611
|
+
Parse.cache.store(key, results, expires: 30)
|
2612
|
+
end
|
2522
2613
|
|
2523
2614
|
```
|
2524
2615
|
|
data/lib/parse/api/hooks.rb
CHANGED
@@ -23,7 +23,8 @@ module Parse
|
|
23
23
|
# Fetch all defined cloud code functions.
|
24
24
|
# @return [Parse::Response]
|
25
25
|
def functions
|
26
|
-
|
26
|
+
opts = {cache: false}
|
27
|
+
request :get, "#{HOOKS_PREFIX}functions", opts: opts
|
27
28
|
end
|
28
29
|
|
29
30
|
# Fetch information about a specific registered cloud function.
|
@@ -61,7 +62,8 @@ module Parse
|
|
61
62
|
# Get the set of registered triggers.
|
62
63
|
# @return [Parse::Response]
|
63
64
|
def triggers
|
64
|
-
|
65
|
+
opts = {cache: false}
|
66
|
+
request :get, "#{HOOKS_PREFIX}triggers", opts: opts
|
65
67
|
end
|
66
68
|
|
67
69
|
# Fetch information about a registered webhook trigger.
|
data/lib/parse/api/schema.rb
CHANGED
@@ -12,14 +12,16 @@ module Parse
|
|
12
12
|
# Get all the schemas for the application.
|
13
13
|
# @return [Parse::Response]
|
14
14
|
def schemas
|
15
|
-
|
15
|
+
opts = {cache: false}
|
16
|
+
request :get, SCHEMAS_PATH, opts: opts
|
16
17
|
end
|
17
18
|
|
18
19
|
# Get the schema for a collection.
|
19
20
|
# @param className [String] the name of the remote Parse collection.
|
20
21
|
# @return [Parse::Response]
|
21
22
|
def schema(className)
|
22
|
-
|
23
|
+
opts = {cache: false}
|
24
|
+
request :get, "#{SCHEMAS_PATH}/#{className}", opts: opts
|
23
25
|
end
|
24
26
|
|
25
27
|
# Create a new collection with the specific schema.
|
data/lib/parse/api/server.rb
CHANGED
@@ -29,7 +29,8 @@ module Parse
|
|
29
29
|
# Fetches the status of the server based on the health check.
|
30
30
|
# @return [Boolean] whether the server is 'OK'.
|
31
31
|
def server_health
|
32
|
-
|
32
|
+
opts = {cache: false}
|
33
|
+
response = request :get, SERVER_HEALTH_PATH, opts: opts
|
33
34
|
response.success?
|
34
35
|
end
|
35
36
|
|
data/lib/parse/client.rb
CHANGED
@@ -82,12 +82,22 @@ module Parse
|
|
82
82
|
end
|
83
83
|
|
84
84
|
# Helper method to get the default Parse client.
|
85
|
-
# @param conn [Symbol] the name of the client connection to use.
|
85
|
+
# @param conn [Symbol] the name of the client connection to use. Defaults to :default
|
86
86
|
# @return [Parse::Client] a client object for the connection name.
|
87
87
|
def self.client(conn = :default)
|
88
88
|
Parse::Client.client(conn)
|
89
89
|
end
|
90
90
|
|
91
|
+
# The shared cache for the default client connection. This is useful if you want to
|
92
|
+
# also utilize the same cache store for other purposes in your application.
|
93
|
+
# This should normally be a {https://github.com/minad/moneta Moneta} unified
|
94
|
+
# cache interface.
|
95
|
+
# @return [Moneta::Transformer,Moneta::Expires] the cache instance
|
96
|
+
# @see Parse::Client#cache
|
97
|
+
def self.cache
|
98
|
+
@shared_cache ||= Parse::Client.client(:default).cache
|
99
|
+
end
|
100
|
+
|
91
101
|
# This class is the core and low level API for the Parse SDK REST interface that
|
92
102
|
# is used by the other components. It can manage multiple sessions, which means
|
93
103
|
# you can have multiple client instances pointing to different Parse Applications
|
@@ -122,7 +132,8 @@ module Parse
|
|
122
132
|
|
123
133
|
# @!attribute cache
|
124
134
|
# The underlying cache store for caching API requests.
|
125
|
-
# @
|
135
|
+
# @see Parse.cache
|
136
|
+
# @return [Moneta::Transformer,Moneta::Expires]
|
126
137
|
# @!attribute [r] application_id
|
127
138
|
# The Parse application identifier to be sent in every API request.
|
128
139
|
# @return [String]
|
@@ -206,8 +217,9 @@ module Parse
|
|
206
217
|
# the logging performed by {Parse::Middleware::BodyBuilder}.
|
207
218
|
# @option opts [Object] :adapter The connection adapter. By default it uses
|
208
219
|
# the `Faraday.default_adapter` which is Net/HTTP.
|
209
|
-
# @option opts [Moneta::Transformer] :cache A caching adapter of type
|
210
|
-
# {https://github.com/minad/moneta Moneta::Transformer}
|
220
|
+
# @option opts [Moneta::Transformer,Moneta::Expires] :cache A caching adapter of type
|
221
|
+
# {https://github.com/minad/moneta Moneta::Transformer} or
|
222
|
+
# {https://github.com/minad/moneta Moneta::Expires} that will be used
|
211
223
|
# by the caching middleware {Parse::Middleware::Caching}.
|
212
224
|
# Caching queries and object fetches can help improve the performance of
|
213
225
|
# your application, even if it is for a few seconds. Only successful GET
|
@@ -223,7 +235,7 @@ module Parse
|
|
223
235
|
# @option opts [Hash] :faraday You may pass a hash of options that will be
|
224
236
|
# passed to the Faraday constructor.
|
225
237
|
# @raise Parse::Error::ConnectionError if the client was not properly configured with required keys or url.
|
226
|
-
# @raise ArgumentError if the cache instance passed to the :cache option is not of Moneta::Transformer
|
238
|
+
# @raise ArgumentError if the cache instance passed to the :cache option is not of Moneta::Transformer or Moneta::Expires
|
227
239
|
# @see Parse::Middleware::BodyBuilder
|
228
240
|
# @see Parse::Middleware::Caching
|
229
241
|
# @see Parse::Middleware::Authentication
|
@@ -277,8 +289,8 @@ module Parse
|
|
277
289
|
end
|
278
290
|
end
|
279
291
|
|
280
|
-
unless opts[:cache].
|
281
|
-
raise ArgumentError, "Parse::Client option :cache needs to be a type of Moneta
|
292
|
+
unless [:key?, :[], :delete, :store].all? { |method| opts[:cache].respond_to?(method) }
|
293
|
+
raise ArgumentError, "Parse::Client option :cache needs to be a type of Moneta store"
|
282
294
|
end
|
283
295
|
self.cache = opts[:cache]
|
284
296
|
conn.use Parse::Middleware::Caching, self.cache, {expires: opts[:expires].to_i }
|
data/lib/parse/client/caching.rb
CHANGED
@@ -59,7 +59,7 @@ module Parse
|
|
59
59
|
|
60
60
|
# @!attribute [rw] store
|
61
61
|
# The internal moneta cache store instance.
|
62
|
-
# @return [Moneta::Transformer]
|
62
|
+
# @return [Moneta::Transformer,Moneta::Expires]
|
63
63
|
attr_accessor :store
|
64
64
|
|
65
65
|
# @!attribute [rw] expires
|
@@ -73,7 +73,7 @@ module Parse
|
|
73
73
|
# @param store [Moneta] An instance of the Moneta cache store to use.
|
74
74
|
# @param opts [Hash] additional options.
|
75
75
|
# @option opts [Integer] :expires the default expiration for a cache entry.
|
76
|
-
# @raise ArgumentError, if `store` is not a Moneta::Transformer instance.
|
76
|
+
# @raise ArgumentError, if `store` is not a Moneta::Transformer or Moneta::Expires instance.
|
77
77
|
def initialize(adapter, store, opts = {})
|
78
78
|
super(adapter)
|
79
79
|
@store = store
|
@@ -81,8 +81,8 @@ module Parse
|
|
81
81
|
@opts.merge!(opts) if opts.is_a?(Hash)
|
82
82
|
@expires = @opts[:expires]
|
83
83
|
|
84
|
-
unless @store.
|
85
|
-
raise ArgumentError, "Caching store object must a Moneta key/value store
|
84
|
+
unless [:key?, :[], :delete, :store].all? { |method| @store.respond_to?(method) }
|
85
|
+
raise ArgumentError, "Caching store object must a Moneta key/value store."
|
86
86
|
end
|
87
87
|
|
88
88
|
end
|
@@ -115,7 +115,7 @@ module Parse
|
|
115
115
|
|
116
116
|
# if caching is enabled and we have a valid cache duration, use cache
|
117
117
|
# otherwise work as a passthrough.
|
118
|
-
return @app.call(env) unless @store.present? && @
|
118
|
+
return @app.call(env) unless @enabled && @store.present? && @expires > 0
|
119
119
|
|
120
120
|
url = env.url
|
121
121
|
method = env.method
|
data/lib/parse/model/acl.rb
CHANGED
@@ -49,15 +49,18 @@ module Parse
|
|
49
49
|
# means denial.
|
50
50
|
#
|
51
51
|
# An ACL is represented by a JSON object with the keys being Parse::User object
|
52
|
-
# ids or the special key of
|
52
|
+
# ids or the special key of "*", which indicates the public access permissions.
|
53
53
|
# The value of each key in the hash is a {Parse::ACL::Permission} object which
|
54
54
|
# defines the boolean permission state for read and write.
|
55
|
-
# The example below illustrates a Parse ACL JSON object where there is a public
|
55
|
+
# The example below illustrates a Parse ACL JSON object where there is a *public*
|
56
56
|
# read permission, but public write is prevented. In addition, the user with
|
57
|
-
# id "3KmCvT7Zsb"
|
57
|
+
# id "*3KmCvT7Zsb*" is allowed to both read and write this record, and the "*Admins*"
|
58
|
+
# role is also allowed write access.
|
59
|
+
#
|
58
60
|
# {
|
59
61
|
# "*": { "read": true },
|
60
|
-
# "3KmCvT7Zsb": { "read": true, "write": true }
|
62
|
+
# "3KmCvT7Zsb": { "read": true, "write": true },
|
63
|
+
# "role:Admins": { "write": true }
|
61
64
|
# }
|
62
65
|
#
|
63
66
|
# All Parse::Object subclasses have an acl property by default. With this
|
@@ -81,6 +84,12 @@ module Parse
|
|
81
84
|
# # allow the 'Admins' role read and write
|
82
85
|
# artist.acl.apply_role "Admins", true, true
|
83
86
|
#
|
87
|
+
# # remove write from all attached privileges
|
88
|
+
# artist.acl.no_write!
|
89
|
+
#
|
90
|
+
# # remove all attached privileges
|
91
|
+
# artist.acl.master_key_only!
|
92
|
+
#
|
84
93
|
# artist.save
|
85
94
|
#
|
86
95
|
# You may also set default ACLs for your subclasses by using {Parse::Object.set_default_acl}.
|
@@ -104,17 +113,21 @@ module Parse
|
|
104
113
|
# {http://docs.parseplatform.org/rest/guide/#security Security}.
|
105
114
|
class ACL < DataType
|
106
115
|
|
107
|
-
# @!attribute permissions
|
108
|
-
# Contains a hash structure of permissions, with keys mapping to either Public '*',
|
109
|
-
# a role name or an objectId for a user and values of type {ACL::Permission}.
|
110
|
-
# @return [Hash] a hash of permissions.
|
111
|
-
|
112
116
|
# @!attribute delegate
|
113
|
-
#
|
114
|
-
#
|
117
|
+
# The instance object to be notified of changes. The delegate must support
|
118
|
+
# receiving a {Parse::Object#acl_will_change!} method.
|
115
119
|
# @return [Parse::Object]
|
116
120
|
attr_accessor :permissions, :delegate
|
117
121
|
|
122
|
+
# @!attribute [rw] permissions
|
123
|
+
# Contains a hash structure of permissions, with keys mapping to either Public '*',
|
124
|
+
# a role name or an objectId for a user and values of type {ACL::Permission}. If you
|
125
|
+
# modify this attribute directly, you should call {Parse::Object#acl_will_change!}
|
126
|
+
# on the target object in order for dirty tracking to register changes.
|
127
|
+
# @example
|
128
|
+
# object.acl.permissions
|
129
|
+
# # => { "*": { "read": true }, "3KmCvT7Zsb": { "read": true, "write": true } }
|
130
|
+
# @return [Hash] a hash of permissions.
|
118
131
|
def permissions
|
119
132
|
@permissions ||= {}
|
120
133
|
end
|
@@ -301,16 +314,124 @@ module Parse
|
|
301
314
|
permissions.values.any? { |v| v.present? }
|
302
315
|
end
|
303
316
|
|
317
|
+
# Removes all ACLs, which only allows requests using the Parse Server master key
|
318
|
+
# to query and modify the object.
|
319
|
+
# @example
|
320
|
+
# object.acl
|
321
|
+
# # { "*": { "read" : true },
|
322
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
323
|
+
# # "role:Admins": { "write": true }
|
324
|
+
# # }
|
325
|
+
# object.acl.master_key_only!
|
326
|
+
# # Outcome:
|
327
|
+
# # { }
|
328
|
+
# @version 1.7.2
|
329
|
+
# @return [Hash] The cleared permissions hash
|
330
|
+
def master_key_only!
|
331
|
+
will_change!
|
332
|
+
@permissions = {}
|
333
|
+
end; alias_method :clear!, :master_key_only!
|
334
|
+
|
335
|
+
# Grants read permission on all existing users and roles attached to this object.
|
336
|
+
# @example
|
337
|
+
# object.acl
|
338
|
+
# # { "*": { "read" : true },
|
339
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
340
|
+
# # "role:Admins": { "write": true }
|
341
|
+
# # }
|
342
|
+
# object.acl.all_read!
|
343
|
+
# # Outcome:
|
344
|
+
# # { "*": { "read" : true },
|
345
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
346
|
+
# # "role:Admins": { "read" : true, "write": true}
|
347
|
+
# # }
|
348
|
+
# @version 1.7.2
|
349
|
+
# @return [Array] list of ACL keys
|
350
|
+
def all_read!
|
351
|
+
will_change!
|
352
|
+
permissions.keys.each do |perm|
|
353
|
+
permissions[perm].read! true
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Grants write permission on all existing users and roles attached to this object.
|
358
|
+
# @example
|
359
|
+
# object.acl
|
360
|
+
# # { "*": { "read" : true },
|
361
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
362
|
+
# # "role:Admins": { "write": true }
|
363
|
+
# # }
|
364
|
+
# object.acl.all_write!
|
365
|
+
# # Outcome:
|
366
|
+
# # { "*": { "read" : true, "write": true },
|
367
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
368
|
+
# # "role:Admins": { "write": true }
|
369
|
+
# # }
|
370
|
+
# @version 1.7.2
|
371
|
+
# @return [Array] list of ACL keys
|
372
|
+
def all_write!
|
373
|
+
will_change!
|
374
|
+
permissions.keys.each do |perm|
|
375
|
+
permissions[perm].write! true
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
# Denies read permission on all existing users and roles attached to this object.
|
380
|
+
# @example
|
381
|
+
# object.acl
|
382
|
+
# # { "*": { "read" : true },
|
383
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
384
|
+
# # "role:Admins": { "write": true }
|
385
|
+
# # }
|
386
|
+
# object.acl.no_read!
|
387
|
+
# # Outcome:
|
388
|
+
# # { "*": nil,
|
389
|
+
# # "3KmCvT7Zsb": { "write": true },
|
390
|
+
# # "role:Admins": { "write": true }
|
391
|
+
# # }
|
392
|
+
# @version 1.7.2
|
393
|
+
# @return [Array] list of ACL keys
|
394
|
+
def no_read!
|
395
|
+
will_change!
|
396
|
+
permissions.keys.each do |perm|
|
397
|
+
permissions[perm].read! false
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# Denies write permission on all existing users and roles attached to this object.
|
402
|
+
# @example
|
403
|
+
# object.acl
|
404
|
+
# # { "*": { "read" : true },
|
405
|
+
# # "3KmCvT7Zsb": { "read" : true, "write": true },
|
406
|
+
# # "role:Admins": { "write": true }
|
407
|
+
# # }
|
408
|
+
# object.acl.no_write!
|
409
|
+
# # Outcome:
|
410
|
+
# # { "*": { "read" : true },
|
411
|
+
# # "3KmCvT7Zsb": { "read" : true },
|
412
|
+
# # "role:Admins": nil
|
413
|
+
# # }
|
414
|
+
# @version 1.7.2
|
415
|
+
# @return [Array] list of ACL keys
|
416
|
+
def no_write!
|
417
|
+
will_change!
|
418
|
+
permissions.keys.each do |perm|
|
419
|
+
permissions[perm].write! false
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
304
423
|
# The Permission class tracks the read and write permissions for a specific
|
305
424
|
# ACL entry. The value of an Parse-ACL hash only contains two keys: "read" and "write".
|
306
425
|
#
|
307
426
|
# # Example of the ACL format
|
308
427
|
# { "*": { "read": true },
|
309
|
-
# "3KmCvT7Zsb": { "read": true, "write": true }
|
428
|
+
# "3KmCvT7Zsb": { "read": true, "write": true },
|
429
|
+
# "role:Admins": { "write": true }
|
310
430
|
# }
|
311
431
|
# This would be managed as:
|
312
432
|
# { "*": ACL::Permission.new(true),
|
313
433
|
# "3KmCvT7Zsb": ACL::Permission.new(true, true)
|
434
|
+
# "role:Amdins": ACL::Permission.new(false,true)
|
314
435
|
# }
|
315
436
|
#
|
316
437
|
class Permission
|
@@ -380,6 +501,36 @@ module Parse
|
|
380
501
|
@read.present? || @write.present?
|
381
502
|
end
|
382
503
|
|
504
|
+
# Sets the *read* value of the permission. Defaults to true.
|
505
|
+
# @note Setting the value in this manner is not dirty tracked.
|
506
|
+
# @version 1.7.2
|
507
|
+
# @return [void]
|
508
|
+
def read!(value = true)
|
509
|
+
@read = value
|
510
|
+
end
|
511
|
+
|
512
|
+
# Sets the *write* value of the permission. Defaults to true.
|
513
|
+
# @note Setting the value in this manner is not dirty tracked.
|
514
|
+
# @version 1.7.2
|
515
|
+
# @return [void]
|
516
|
+
def write!(value = true)
|
517
|
+
@write = value
|
518
|
+
end
|
519
|
+
|
520
|
+
# Sets the *read* value of the permission to false.
|
521
|
+
# @version 1.7.2
|
522
|
+
# @return [void]
|
523
|
+
def no_read!
|
524
|
+
@write = false
|
525
|
+
end
|
526
|
+
|
527
|
+
# Sets the *write* value of the permission to false.
|
528
|
+
# @version 1.7.2
|
529
|
+
# @return [void]
|
530
|
+
def no_write!
|
531
|
+
@write = false
|
532
|
+
end
|
533
|
+
|
383
534
|
end
|
384
535
|
end
|
385
536
|
end
|
@@ -19,14 +19,15 @@ module Parse
|
|
19
19
|
|
20
20
|
# A CollectionProxy is a special type of array wrapper that notifies a delegate
|
21
21
|
# object about changes to the array in order to perform dirty tracking. This is
|
22
|
-
# used for all Array properties in Parse::Objects.
|
22
|
+
# used for all Array properties in Parse::Objects. Subclasses of {CollectionProxy} are
|
23
|
+
# also available for supporting different association types such as an array of Parse pointers
|
24
|
+
# and Parse relations.
|
25
|
+
# @see PointerCollectionProxy
|
26
|
+
# @see RelationCollectionProxy
|
23
27
|
class CollectionProxy
|
24
28
|
include ::ActiveModel::Model
|
25
29
|
include ::ActiveModel::Dirty
|
26
30
|
include ::Enumerable
|
27
|
-
# @!attribute [rw] collection
|
28
|
-
# The internal backing store of the collection.
|
29
|
-
# @return [Array]
|
30
31
|
|
31
32
|
# @!attribute [r] delegate
|
32
33
|
# The object to be notified of changes to the collection.
|
@@ -75,7 +76,7 @@ module Parse
|
|
75
76
|
# @param params [Object] method parameters
|
76
77
|
# @return [Object] the return value from the forwarded method.
|
77
78
|
def forward(method, params = nil)
|
78
|
-
return unless @delegate
|
79
|
+
return unless @delegate && @delegate.respond_to?(method)
|
79
80
|
params.nil? ? @delegate.send(method) : @delegate.send(method, params)
|
80
81
|
end
|
81
82
|
|
@@ -110,16 +111,18 @@ module Parse
|
|
110
111
|
collection.to_a
|
111
112
|
end; alias_method :to_ary, :to_a
|
112
113
|
|
113
|
-
#
|
114
|
-
#
|
115
|
-
# change notifications.
|
114
|
+
# Set the internal collection of items *without* dirty tracking or
|
115
|
+
# change notifications.
|
116
116
|
# @return [Array] the collection
|
117
117
|
def set_collection!(list)
|
118
118
|
@collection = list
|
119
119
|
end
|
120
120
|
|
121
121
|
# @!attribute [rw] collection
|
122
|
-
# The internal backing store of the collection.
|
122
|
+
# The internal backing store of the collection containing the content. This value is lazily
|
123
|
+
# loaded for some subclasses.
|
124
|
+
# @note If you modify this directly, it is highly recommended that you
|
125
|
+
# call {CollectionProxy#notify_will_change!} to notify the dirty tracking system.
|
123
126
|
# @return [Array] contents of the collection.
|
124
127
|
def collection
|
125
128
|
if @collection.empty? && @loaded == false
|
@@ -145,6 +148,72 @@ module Parse
|
|
145
148
|
@collection
|
146
149
|
end; alias_method :push, :add
|
147
150
|
|
151
|
+
# Add items to the collection if they don't already exist
|
152
|
+
# @param items [Array] items to uniquely add
|
153
|
+
# @return [Array] the collection.
|
154
|
+
def add_unique(*items)
|
155
|
+
return unless items.count > 0
|
156
|
+
notify_will_change!
|
157
|
+
@collection = collection | items.flatten
|
158
|
+
@collection
|
159
|
+
end; alias_method :push_unique, :add_unique
|
160
|
+
|
161
|
+
# Set Union - Returns a new array by joining two arrays, excluding
|
162
|
+
# any duplicates and preserving the order from the original array.
|
163
|
+
# It compares elements using their hash and eql? methods for efficiency.
|
164
|
+
# See {https://docs.ruby-lang.org/en/2.0.0/Array.html#method-i-hash Array#|}
|
165
|
+
# @example
|
166
|
+
# [ "a", "b", "c" ] | [ "c", "d", "a" ] #=> [ "a", "b", "c", "d" ]
|
167
|
+
# @param items [Array] items to uniquely add
|
168
|
+
# @see #add_unique
|
169
|
+
# @return [Array] array with unique items
|
170
|
+
def |(items)
|
171
|
+
collection | [items].flatten
|
172
|
+
end
|
173
|
+
|
174
|
+
# Set Intersection - Returns a new array containing unique elements common
|
175
|
+
# to the two arrays. The order is preserved from the original array.
|
176
|
+
#
|
177
|
+
# It compares elements using their hash and eql? methods for efficiency.
|
178
|
+
# See {https://ruby-doc.org/core-2.4.1/Array.html#method-i-26 Array#&}
|
179
|
+
# @example
|
180
|
+
# [ 1, 1, 3, 5 ] & [ 3, 2, 1 ] #=> [ 1, 3 ]
|
181
|
+
# [ 'a', 'b', 'b', 'z' ] & [ 'a', 'b', 'c' ] #=> [ 'a', 'b' ]
|
182
|
+
# @param other_ary [Array]
|
183
|
+
# @return [Array] intersection array
|
184
|
+
def &(other_ary)
|
185
|
+
collection & [other_ary].flatten
|
186
|
+
end
|
187
|
+
|
188
|
+
# Alias {https://ruby-doc.org/core-2.4.1/Array.html#method-i-2D Array Difference}.
|
189
|
+
# Returns a new array that is a copy of the original array, removing any
|
190
|
+
# items that also appear in other_ary. The order is preserved from the
|
191
|
+
# original array.
|
192
|
+
# @example
|
193
|
+
# [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
|
194
|
+
# @param other_ary [Array]
|
195
|
+
# @return [Array] delta array
|
196
|
+
def -(other_ary)
|
197
|
+
collection - [other_ary].flatten
|
198
|
+
end
|
199
|
+
|
200
|
+
# Alias {https://ruby-doc.org/core-2.4.1/Array.html#method-i-2B Array Concatenation}.
|
201
|
+
# Returns a new array built by concatenating the two arrays together to
|
202
|
+
# produce a third array.
|
203
|
+
# @example
|
204
|
+
# [ 1, 2, 3 ] + [ 4, 5 ] #=> [ 1, 2, 3, 4, 5 ]
|
205
|
+
# @param other_ary [Array]
|
206
|
+
# @return [Array] concatenated array
|
207
|
+
def +(other_ary)
|
208
|
+
collection + [other_ary].flatten.to_a
|
209
|
+
end
|
210
|
+
|
211
|
+
# Alias {https://ruby-doc.org/core-2.4.1/Array.html#method-i-flatten Array flattening}.
|
212
|
+
# @return [Array] a flattened one-dimensional array
|
213
|
+
def flatten
|
214
|
+
collection.flatten
|
215
|
+
end
|
216
|
+
|
148
217
|
# Remove items from the collection
|
149
218
|
# @param items [Array] items to remove
|
150
219
|
def remove(*items)
|
@@ -232,8 +301,8 @@ module Parse
|
|
232
301
|
end
|
233
302
|
|
234
303
|
# @return [Hash] a JSON representation
|
235
|
-
def as_json(
|
236
|
-
collection.as_json(
|
304
|
+
def as_json(opts = nil)
|
305
|
+
collection.as_json(opts)
|
237
306
|
end
|
238
307
|
|
239
308
|
# true if the collection is empty.
|
@@ -273,6 +342,19 @@ module Parse
|
|
273
342
|
collection.select &Proc.new
|
274
343
|
end
|
275
344
|
|
345
|
+
# Alias for Array#uniq
|
346
|
+
def uniq
|
347
|
+
return collection.uniq(&Proc.new) if block_given?
|
348
|
+
return collection.uniq
|
349
|
+
end
|
350
|
+
|
351
|
+
# Alias for Array#uniq!
|
352
|
+
def uniq!
|
353
|
+
notify_will_change!
|
354
|
+
return collection.uniq!(&Proc.new) if block_given?
|
355
|
+
return collection.uniq!
|
356
|
+
end
|
357
|
+
|
276
358
|
# @!visibility private
|
277
359
|
def inspect
|
278
360
|
"#<#{self.class} changed?=#{changed?} @collection=#{@collection.inspect} >"
|
@@ -17,8 +17,10 @@ module Parse
|
|
17
17
|
|
18
18
|
# @!attribute [rw] collection
|
19
19
|
# The internal backing store of the collection.
|
20
|
-
#
|
21
|
-
#
|
20
|
+
# @note If you modify this directly, it is highly recommended that you
|
21
|
+
# call {CollectionProxy#notify_will_change!} to notify the dirty tracking system.
|
22
|
+
# @return [Array<Parse::Object>]
|
23
|
+
# @see CollectionProxy#collection
|
22
24
|
def collection=(c)
|
23
25
|
notify_will_change!
|
24
26
|
@collection = c
|
@@ -96,8 +98,8 @@ module Parse
|
|
96
98
|
end
|
97
99
|
|
98
100
|
# Encode the collection as a JSON object of Parse::Pointers.
|
99
|
-
def as_json(
|
100
|
-
|
101
|
+
def as_json(opts = nil)
|
102
|
+
parse_pointers.as_json(opts)
|
101
103
|
end
|
102
104
|
|
103
105
|
# @return [Array<Parse::Pointer>] an array of pointers representing this collection.
|
@@ -415,6 +415,19 @@ module Parse
|
|
415
415
|
response.success? ? Parse::User.build(response.result) : nil
|
416
416
|
end
|
417
417
|
|
418
|
+
# If the current session token for this instance is nil, this method finds
|
419
|
+
# the most recent active Parse::Session token for this user and applies it to the instance.
|
420
|
+
# The user instance will now be authenticated and logged in with the selected session token.
|
421
|
+
# Useful if you need to call save or destroy methods on behalf of a logged in user.
|
422
|
+
# @return [String] The session token or nil if no session was found for this user.
|
423
|
+
def any_session!
|
424
|
+
unless @session_token.present?
|
425
|
+
_active_session = active_sessions(restricted: false, order: :updated_at.desc).first
|
426
|
+
self.session_token = _active_session.session_token if _active_session.present?
|
427
|
+
end
|
428
|
+
@session_token
|
429
|
+
end
|
430
|
+
|
418
431
|
end
|
419
432
|
|
420
433
|
end
|
@@ -149,12 +149,12 @@ module Parse
|
|
149
149
|
# Finds the first object matching the query conditions, or creates a new
|
150
150
|
# unsaved object with the attributes. This method takes the possibility of two hashes,
|
151
151
|
# therefore make sure you properly wrap the contents of the input with `{}`.
|
152
|
+
# @example
|
152
153
|
# Parse::User.first_or_create({ ..query conditions..})
|
153
|
-
# Parse::User.first_or_create({ ..query conditions..}, {..
|
154
|
+
# Parse::User.first_or_create({ ..query conditions..}, {.. resource_attrs ..})
|
154
155
|
# @param query_attrs [Hash] a set of query constraints that also are applied.
|
155
156
|
# @param resource_attrs [Hash] a set of attribute values to be applied if an object was not found.
|
156
157
|
# @return [Parse::Object] a Parse::Object, whether found by the query or newly created.
|
157
|
-
# @see Parse::Model.autosave_on_create
|
158
158
|
def first_or_create(query_attrs = {}, resource_attrs = {})
|
159
159
|
|
160
160
|
query_attrs = query_attrs.symbolize_keys
|
@@ -163,9 +163,25 @@ module Parse
|
|
163
163
|
|
164
164
|
if obj.blank?
|
165
165
|
obj = self.new query_attrs
|
166
|
-
obj.apply_attributes!(resource_attrs, dirty_track:
|
166
|
+
obj.apply_attributes!(resource_attrs, dirty_track: true)
|
167
167
|
end
|
168
|
-
obj
|
168
|
+
obj
|
169
|
+
end
|
170
|
+
|
171
|
+
# Finds the first object matching the query conditions, or creates a new
|
172
|
+
# *saved* object with the attributes. This method is similar to {first_or_create}
|
173
|
+
# but will also {save!} the object if it was newly created.
|
174
|
+
# @example
|
175
|
+
# obj = Parse::User.first_or_create!({ ..query conditions..})
|
176
|
+
# obj = Parse::User.first_or_create!({ ..query conditions..}, {.. resource_attrs ..})
|
177
|
+
# @param query_attrs [Hash] a set of query constraints that also are applied.
|
178
|
+
# @param resource_attrs [Hash] a set of attribute values to be applied if an object was not found.
|
179
|
+
# @return [Parse::Object] a Parse::Object, whether found by the query or newly created.
|
180
|
+
# @raise {Parse::RecordNotSaved} if the save fails
|
181
|
+
# @see #first_or_create
|
182
|
+
def first_or_create!(query_attrs = {}, resource_attrs = {})
|
183
|
+
obj = first_or_create(query_attrs, resource_attrs)
|
184
|
+
obj.save! if obj.new?
|
169
185
|
obj
|
170
186
|
end
|
171
187
|
|
@@ -185,7 +201,7 @@ module Parse
|
|
185
201
|
# # it will automatically be saved
|
186
202
|
# end
|
187
203
|
# @note You cannot use *:updated_at* as a constraint.
|
188
|
-
# @return [Boolean]
|
204
|
+
# @return [Boolean] true if all saves succeeded and there were no errors.
|
189
205
|
def save_all(constraints = {})
|
190
206
|
invalid_constraints = constraints.keys.any? do |k|
|
191
207
|
(k == :updated_at || k == :updatedAt) ||
|
@@ -256,7 +272,7 @@ module Parse
|
|
256
272
|
|
257
273
|
has_errors ||= batch.error?
|
258
274
|
end
|
259
|
-
has_errors
|
275
|
+
not has_errors
|
260
276
|
end
|
261
277
|
|
262
278
|
end # ClassMethods
|
@@ -479,7 +495,7 @@ module Parse
|
|
479
495
|
#
|
480
496
|
# You can define before and after :save callbacks
|
481
497
|
# autoraise: set to true will automatically raise an exception if the save fails
|
482
|
-
# @raise Parse::RecordNotSaved if the save fails
|
498
|
+
# @raise {Parse::RecordNotSaved} if the save fails
|
483
499
|
# @raise ArgumentError if a non-nil value is passed to `session` that doesn't provide a session token string.
|
484
500
|
# @param session [String] a session token in order to apply ACLs to this operation.
|
485
501
|
# @param autoraise [Boolean] whether to raise an exception if the save fails.
|
@@ -519,7 +535,7 @@ module Parse
|
|
519
535
|
end
|
520
536
|
|
521
537
|
# Save this object and raise an exception if it fails.
|
522
|
-
# @raise Parse::RecordNotSaved if the save fails
|
538
|
+
# @raise {Parse::RecordNotSaved} if the save fails
|
523
539
|
# @raise ArgumentError if a non-nil value is passed to `session` that doesn't provide a session token string.
|
524
540
|
# @param session (see #save)
|
525
541
|
# @return (see #save)
|
@@ -88,17 +88,18 @@ module Parse
|
|
88
88
|
# required: (data_type)
|
89
89
|
# default: (value or Proc)
|
90
90
|
# alias: Whether to create the remote field alias getter/setters for this attribute
|
91
|
+
|
92
|
+
# symbolize: Makes sure the saved and return value locally is in symbol format. useful
|
93
|
+
# for enum type fields that are string columns in Parse. Ex. a booking_status for a field
|
94
|
+
# could be either "submitted" or "completed" in Parse, however with symbolize, these would
|
95
|
+
# be available as :submitted or :completed.
|
96
|
+
|
91
97
|
# This is the class level property method to be used when declaring properties. This helps builds specific methods, formatters
|
92
98
|
# and conversion handlers for property storing and saving data for a particular parse class.
|
93
99
|
# The first parameter is the name of the local attribute you want to declare with its corresponding data type.
|
94
100
|
# Declaring a `property :my_date, :date`, would declare the attribute my_date with a corresponding remote column called
|
95
101
|
# "myDate" (lower-first-camelcase) with a Parse data type of Date.
|
96
102
|
# You can override the implicit naming behavior by passing the option :field to override.
|
97
|
-
|
98
|
-
# symbolize: Makes sure the saved and return value locally is in symbol format. useful
|
99
|
-
# for enum type fields that are string columns in Parse. Ex. a booking_status for a field
|
100
|
-
# could be either "submitted" or "completed" in Parse, however with symbolize, these would
|
101
|
-
# be available as :submitted or :completed.
|
102
103
|
def property(key, data_type = :string, **opts)
|
103
104
|
|
104
105
|
key = key.to_sym
|
@@ -300,6 +301,12 @@ module Parse
|
|
300
301
|
# lets set the variable with the updated value
|
301
302
|
instance_variable_set ivar, value
|
302
303
|
send will_change_method
|
304
|
+
elsif value.nil? && data_type == :array
|
305
|
+
value = Parse::CollectionProxy.new [], delegate: self, key: key
|
306
|
+
instance_variable_set ivar, value
|
307
|
+
# don't send the notification yet until they actually add something
|
308
|
+
# which will be handled by the collection proxy.
|
309
|
+
# send will_change_method
|
303
310
|
end
|
304
311
|
|
305
312
|
# if the value is a String (like an iso8601 date) and the data type of
|
data/lib/parse/model/date.rb
CHANGED
@@ -60,6 +60,7 @@ class Time
|
|
60
60
|
end
|
61
61
|
|
62
62
|
end
|
63
|
+
|
63
64
|
# Adds extensions to DateTime class to be compatible with {Parse::Date}.
|
64
65
|
class DateTime
|
65
66
|
# @return [Parse::Date] Converts object to Parse::Date
|
@@ -78,3 +79,11 @@ module ActiveSupport
|
|
78
79
|
end
|
79
80
|
end
|
80
81
|
end
|
82
|
+
|
83
|
+
# Adds extensions to Date class to be compatible with {Parse::Date}.
|
84
|
+
class Date
|
85
|
+
# @return [Parse::Date] Converts object to Parse::Date
|
86
|
+
def parse_date
|
87
|
+
Parse::Date.parse iso8601
|
88
|
+
end
|
89
|
+
end
|
data/lib/parse/model/model.rb
CHANGED
@@ -86,19 +86,6 @@ module Parse
|
|
86
86
|
# @!visibility private
|
87
87
|
@@model_cache = {}
|
88
88
|
|
89
|
-
# @!attribute self.autosave_on_create
|
90
|
-
# If set to true, a call to {Parse::Object.first_or_create} will automatically save the object.
|
91
|
-
# Default is false.
|
92
|
-
# @return [Boolean]
|
93
|
-
def self.autosave_on_create
|
94
|
-
@@autosave_on_create ||= false
|
95
|
-
end
|
96
|
-
|
97
|
-
# @!visibility private
|
98
|
-
def self.autosave_on_create=(bool)
|
99
|
-
@@autosave_on_create = bool
|
100
|
-
end
|
101
|
-
|
102
89
|
class << self
|
103
90
|
# @!attribute self.raise_on_save_failure
|
104
91
|
# By default, we return `true` or `false` for save and destroy operations.
|
data/lib/parse/model/object.rb
CHANGED
@@ -274,8 +274,10 @@ module Parse
|
|
274
274
|
end
|
275
275
|
|
276
276
|
# @return [Hash] a json-hash representing this object.
|
277
|
-
def as_json(
|
278
|
-
pointer
|
277
|
+
def as_json(opts = nil)
|
278
|
+
return pointer if pointer?
|
279
|
+
changed_fields = changed_attributes
|
280
|
+
super(opts).delete_if { |k, v| v.nil? && !changed_fields.has_key?(k) }
|
279
281
|
end
|
280
282
|
|
281
283
|
# The main constructor for subclasses. It can take different parameter types
|
@@ -366,13 +368,17 @@ module Parse
|
|
366
368
|
@id.blank?
|
367
369
|
end
|
368
370
|
|
369
|
-
# Existed returns true
|
370
|
-
#
|
371
|
-
#
|
372
|
-
#
|
373
|
-
#
|
371
|
+
# Existed returns true if the object had existed before *its last save
|
372
|
+
# operation*. This method returns false if the {Parse::Object#created_at}
|
373
|
+
# and {Parse::Object#updated_at} dates of an object are equal, implyiny this
|
374
|
+
# object has been newly created and saved (especially in an afterSave hook).
|
375
|
+
#
|
376
|
+
# This is a helper method in a webhook afterSave to know
|
377
|
+
# if this object was recently saved in the beforeSave webhook. Checking for
|
378
|
+
# {Parse::Object#existed?} == false in an afterSave hook, is equivalent to using
|
379
|
+
# {Parse::Object#new?} in a beforeSave hook.
|
374
380
|
# @note You should not use this method inside a beforeSave webhook.
|
375
|
-
# @return [Boolean] true
|
381
|
+
# @return [Boolean] true iff the last beforeSave successfully saved this object for the first time.
|
376
382
|
def existed?
|
377
383
|
if @id.blank? || @created_at.blank? || @updated_at.blank?
|
378
384
|
return false
|
@@ -409,7 +415,7 @@ module Parse
|
|
409
415
|
end
|
410
416
|
|
411
417
|
# Overrides ActiveModel::Validations#validate! instance method.
|
412
|
-
# It runs all
|
418
|
+
# It runs all validations for this object. If validation fails,
|
413
419
|
# it raises ActiveModel::ValidationError otherwise it returns the object.
|
414
420
|
# @raise ActiveModel::ValidationError
|
415
421
|
# @see ActiveModel::Validations#validate!
|
data/lib/parse/model/pointer.rb
CHANGED
@@ -156,6 +156,17 @@ module Parse
|
|
156
156
|
end
|
157
157
|
alias_method :eql?, :==
|
158
158
|
|
159
|
+
# Compute a hash-code for this object. It is calculated
|
160
|
+
# by combining the Parse class name, the {Parse::Object#id} field and
|
161
|
+
# any pending changes.
|
162
|
+
#
|
163
|
+
# Two objects with the same content will have the same hash code
|
164
|
+
# (and will compare using eql?).
|
165
|
+
# @return [Fixnum]
|
166
|
+
def hash
|
167
|
+
"#{parse_class}#{id}#{changes.to_s}".hash
|
168
|
+
end
|
169
|
+
|
159
170
|
# @return [Boolean] true if instance has a Parse class and an id.
|
160
171
|
def present?
|
161
172
|
parse_class.present? && @id.present?
|
data/lib/parse/query.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require_relative 'operation'
|
5
5
|
require 'time'
|
6
|
+
require 'date'
|
6
7
|
|
7
8
|
module Parse
|
8
9
|
# Constraints are the heart of the Parse::Query system.
|
@@ -104,7 +105,12 @@ module Parse
|
|
104
105
|
def formatted_value(value)
|
105
106
|
d = value
|
106
107
|
d = { __type: Parse::Model::TYPE_DATE, iso: d.utc.iso8601(3) } if d.respond_to?(:utc)
|
107
|
-
|
108
|
+
# if it responds to parse_date (most likely a time/date object), then call the conversion
|
109
|
+
d = d.parse_date if d.respond_to?(:parse_date)
|
110
|
+
# if it's not a Parse::Date, but still responds to iso8601, then do it manually
|
111
|
+
if d.is_a?(Parse::Date) == false && d.respond_to?(:iso8601)
|
112
|
+
d = { __type: Parse::Model::TYPE_DATE, iso: d.iso8601(3) }
|
113
|
+
end
|
108
114
|
d = d.pointer if d.respond_to?(:pointer) #simplified query object
|
109
115
|
d = d.to_s if d.is_a?(Regexp)
|
110
116
|
# d = d.pointer if d.is_a?(Parse::Object) #simplified query object
|
@@ -33,6 +33,7 @@ module Parse
|
|
33
33
|
#
|
34
34
|
# # find all songs for this artist object
|
35
35
|
# Song.all :artist => artist
|
36
|
+
#
|
36
37
|
# In some cases, you do not have the Parse object, but you have its `objectId`.
|
37
38
|
# You can use the objectId in the query as follows:
|
38
39
|
#
|
@@ -40,6 +41,7 @@ module Parse
|
|
40
41
|
# Song.all :artist.id => artist_id
|
41
42
|
#
|
42
43
|
# # other approaches, same result
|
44
|
+
# Song.all :artist.id => artist # safely supported Parse::Pointer
|
43
45
|
# Song.all :artist => Artist.pointer(artist_id)
|
44
46
|
# Song.all :artist => Parse::Pointer.new("Artist", artist_id)
|
45
47
|
#
|
@@ -48,6 +50,7 @@ module Parse
|
|
48
50
|
# A registered method on a symbol to create the constraint.
|
49
51
|
# @example
|
50
52
|
# q.where :field.id => "someObjectId"
|
53
|
+
# q.where :field.id => pointer # safely supported
|
51
54
|
# @return [ObjectIdConstraint]
|
52
55
|
register :id
|
53
56
|
|
@@ -55,6 +58,10 @@ module Parse
|
|
55
58
|
def build
|
56
59
|
className = operand.to_parse_class
|
57
60
|
value = formatted_value
|
61
|
+
# if it is already a pointer value, just return the constraint. Allows for
|
62
|
+
# supporting strings, symbols and pointers.
|
63
|
+
return { @operation.operand => value } if value.is_a?(Parse::Pointer)
|
64
|
+
|
58
65
|
begin
|
59
66
|
klass = className.constantize
|
60
67
|
rescue NameError => e
|
data/lib/parse/stack/version.rb
CHANGED
@@ -98,6 +98,17 @@ module Parse
|
|
98
98
|
ATTRIBUTES
|
99
99
|
end
|
100
100
|
|
101
|
+
# Method to print to standard that utilizes the an internal id to make it easier
|
102
|
+
# to trace incoming requests.
|
103
|
+
def wlog(s)
|
104
|
+
# generates a unique random number in order to be used in logging. This
|
105
|
+
# is useful when debugging issues in production where one server instance
|
106
|
+
# may be running multiple threads and you want to trace the incoming call.
|
107
|
+
@rid ||= rand(999).to_s.rjust(3)
|
108
|
+
puts "[> #{@rid}] #{s}"
|
109
|
+
@rid
|
110
|
+
end
|
111
|
+
|
101
112
|
# true if this is a webhook function request.
|
102
113
|
def function?
|
103
114
|
@function_name.present?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parse-stack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.7.
|
4
|
+
version: 1.7.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anthony Persaud
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -275,7 +275,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
275
275
|
version: '0'
|
276
276
|
requirements: []
|
277
277
|
rubyforge_project:
|
278
|
-
rubygems_version: 2.
|
278
|
+
rubygems_version: 2.5.2
|
279
279
|
signing_key:
|
280
280
|
specification_version: 4
|
281
281
|
summary: Parse Server Ruby Client SDK
|