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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3557dd1c97f0f227d848732708916d3428563d22
4
- data.tar.gz: b71256968073f1318cb5c88665c551e6e6f7381c
3
+ metadata.gz: '0267822a7e96425e847f7218ad98a11115b511b4'
4
+ data.tar.gz: 5f942b6458ba0f0de2f5e9657fe9e135ea46fcc7
5
5
  SHA512:
6
- metadata.gz: 78d5ee17c4c2a9b8488ef6e37a8e6eeedf82e195b4369423e2694a784c4ff8cd41ba73d945cbdaa863ec094f6b1a1f4df5b26a6b8e28ae56401dfaf988b138d1
7
- data.tar.gz: 86e596121613d31532aee9cd67fa8e5691ccbdcba7de01f11cfc0e3731b61e43cfde29a91137665e537aa448897af37dc21ea1b83bdc7908d7617065de1a8b93
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.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- parse-stack (1.7.1)
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.4)
60
- jsonapi-renderer (0.1.2)
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`, is allowed to both read and write this record.
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 who have liked a specific band. *In the
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 `#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. You may modify this behavior by setting `Parse::Model.autosave_on_create = true`.
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, a Parse::Pointer. 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:
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
- # make sure global acls are set
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
- params = params
2340
- # ... do stuff ...
2341
- some_result
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, "Artist" do
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
 
@@ -23,7 +23,8 @@ module Parse
23
23
  # Fetch all defined cloud code functions.
24
24
  # @return [Parse::Response]
25
25
  def functions
26
- request :get, "#{HOOKS_PREFIX}functions"
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
- request :get, "#{HOOKS_PREFIX}triggers"
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.
@@ -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
- request :get, SCHEMAS_PATH
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
- request :get, "#{SCHEMAS_PATH}/#{className}"
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.
@@ -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
- response = request :get, SERVER_HEALTH_PATH
32
+ opts = {cache: false}
33
+ response = request :get, SERVER_HEALTH_PATH, opts: opts
33
34
  response.success?
34
35
  end
35
36
 
@@ -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
- # @return [Moneta::Transformer]
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} that will be used
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].is_a?(Moneta::Transformer)
281
- raise ArgumentError, "Parse::Client option :cache needs to be a type of Moneta::Transformer store."
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 }
@@ -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.is_a?(Moneta::Transformer)
85
- raise ArgumentError, "Caching store object must a Moneta key/value store (Moneta::Transformer)."
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? && @enabled && @expires > 0
118
+ return @app.call(env) unless @enabled && @store.present? && @expires > 0
119
119
 
120
120
  url = env.url
121
121
  method = env.method
@@ -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 *, which indicates the public access permissions.
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", is allowed to both read and write this record.
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
- # The instance object to be notified of changes. The delegate must support
114
- # receiving a `acl_will_change!` method.
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.present? && @delegate.respond_to?(method)
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
- # @!attribute [rw] collection
114
- # Set the internal collection of items without dirty tracking or
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(*args)
236
- collection.as_json(args)
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
- # @return [Array<Parse::Object>]
21
- # @see CollectionProxy#collection
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(*args)
100
- collection.parse_pointers.as_json
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..}, {.. resrouce_attrs ..})
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: false)
166
+ obj.apply_attributes!(resource_attrs, dirty_track: true)
167
167
  end
168
- obj.save if obj.new? && Parse::Model.autosave_on_create
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] whether there were any errors.
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
@@ -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
@@ -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.
@@ -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(*args)
278
- pointer? ? pointer : super(*args)
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/false depending whether the object
370
- # had existed before *its last save operation*. This implies
371
- # that the created_at and updated_at dates are exactly the same. This
372
- # is a helper method in a webhook afterSave to know if this object was recently
373
- # saved in the beforeSave webhook.
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 if the last beforeSave webhook successfully saved this object for the first time.
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 valudations for this object. If it validation fails,
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!
@@ -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?
@@ -333,7 +333,9 @@ module Parse
333
333
  end
334
334
  end # each
335
335
  self #chaining
336
- end; alias_method :query, :conditions
336
+ end
337
+ alias_method :query, :conditions
338
+ alias_method :append, :conditions
337
339
 
338
340
  def table=(t)
339
341
  @table = t.to_s.camelize
@@ -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
- d = { __type: Parse::Model::TYPE_DATE, iso: d.iso8601(3) } if d.respond_to?(:iso8601)
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
@@ -1,5 +1,4 @@
1
1
 
2
- # The Parse _Installation collection
3
2
  class Parse::Installation < Parse::Object
4
3
  # See: https://github.com/modernistik/parse-stack#parseinstallation
5
4
  # add additional properties here
@@ -1,5 +1,4 @@
1
1
 
2
- # The Parse _Role collection
3
2
  class Parse::Role < Parse::Object
4
3
  # See: https://github.com/modernistik/parse-stack#parserole
5
4
  # add additional properties here
@@ -1,5 +1,4 @@
1
1
 
2
- # The Parse _Session collection
3
2
  class Parse::Session < Parse::Object
4
3
  # See: https://github.com/modernistik/parse-stack#parsesession
5
4
  # add additional properties here
@@ -1,5 +1,4 @@
1
1
 
2
- # The Parse _User collection
3
2
  class Parse::User < Parse::Object
4
3
  # add additional properties
5
4
 
@@ -6,6 +6,6 @@ module Parse
6
6
  # The Parse Server SDK for Ruby
7
7
  module Stack
8
8
  # The current version.
9
- VERSION = "1.7.1"
9
+ VERSION = "1.7.2"
10
10
  end
11
11
  end
@@ -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.1
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-07-12 00:00:00.000000000 Z
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.6.12
278
+ rubygems_version: 2.5.2
279
279
  signing_key:
280
280
  specification_version: 4
281
281
  summary: Parse Server Ruby Client SDK