parse-stack 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +6 -0
  3. data/Gemfile.lock +77 -0
  4. data/LICENSE +20 -0
  5. data/README.md +1281 -0
  6. data/Rakefile +12 -0
  7. data/bin/console +20 -0
  8. data/bin/server +10 -0
  9. data/bin/setup +7 -0
  10. data/lib/parse/api/all.rb +13 -0
  11. data/lib/parse/api/analytics.rb +16 -0
  12. data/lib/parse/api/apps.rb +37 -0
  13. data/lib/parse/api/batch.rb +148 -0
  14. data/lib/parse/api/cloud_functions.rb +18 -0
  15. data/lib/parse/api/config.rb +22 -0
  16. data/lib/parse/api/files.rb +21 -0
  17. data/lib/parse/api/hooks.rb +68 -0
  18. data/lib/parse/api/objects.rb +77 -0
  19. data/lib/parse/api/push.rb +16 -0
  20. data/lib/parse/api/schemas.rb +25 -0
  21. data/lib/parse/api/sessions.rb +11 -0
  22. data/lib/parse/api/users.rb +43 -0
  23. data/lib/parse/client.rb +225 -0
  24. data/lib/parse/client/authentication.rb +59 -0
  25. data/lib/parse/client/body_builder.rb +69 -0
  26. data/lib/parse/client/caching.rb +103 -0
  27. data/lib/parse/client/protocol.rb +15 -0
  28. data/lib/parse/client/request.rb +43 -0
  29. data/lib/parse/client/response.rb +116 -0
  30. data/lib/parse/model/acl.rb +182 -0
  31. data/lib/parse/model/associations/belongs_to.rb +121 -0
  32. data/lib/parse/model/associations/collection_proxy.rb +202 -0
  33. data/lib/parse/model/associations/has_many.rb +218 -0
  34. data/lib/parse/model/associations/pointer_collection_proxy.rb +71 -0
  35. data/lib/parse/model/associations/relation_collection_proxy.rb +134 -0
  36. data/lib/parse/model/bytes.rb +50 -0
  37. data/lib/parse/model/core/actions.rb +499 -0
  38. data/lib/parse/model/core/properties.rb +377 -0
  39. data/lib/parse/model/core/querying.rb +100 -0
  40. data/lib/parse/model/core/schema.rb +92 -0
  41. data/lib/parse/model/date.rb +50 -0
  42. data/lib/parse/model/file.rb +127 -0
  43. data/lib/parse/model/geopoint.rb +98 -0
  44. data/lib/parse/model/model.rb +120 -0
  45. data/lib/parse/model/object.rb +347 -0
  46. data/lib/parse/model/pointer.rb +106 -0
  47. data/lib/parse/model/push.rb +99 -0
  48. data/lib/parse/query.rb +378 -0
  49. data/lib/parse/query/constraint.rb +130 -0
  50. data/lib/parse/query/constraints.rb +176 -0
  51. data/lib/parse/query/operation.rb +66 -0
  52. data/lib/parse/query/ordering.rb +49 -0
  53. data/lib/parse/stack.rb +11 -0
  54. data/lib/parse/stack/version.rb +5 -0
  55. data/lib/parse/webhooks.rb +228 -0
  56. data/lib/parse/webhooks/payload.rb +115 -0
  57. data/lib/parse/webhooks/registration.rb +139 -0
  58. data/parse-stack.gemspec +45 -0
  59. metadata +340 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b38fa6b8bbbe4913023493342be2d72d1c14f57c
4
+ data.tar.gz: a9aa594b35887afd223b775f8f842a3149ef5ab1
5
+ SHA512:
6
+ metadata.gz: d7eb6d9ee361c9cf988a0bd11c34988b91a55866d36f0bc246ff24d2bad6989f025951b871841bbee2fcb2f585673a84f03129ffa2f6b4eed06d380a25ef7dee
7
+ data.tar.gz: a81733fd6f44fc451c1b9d856903fc3640f0d099b3d659f4f1c1c91bc96c9e48f652add587a84bbf76e046ffb93f4a84ba1ab07e2c5fb5923ed211883afc166a
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in parse-stack.gemspec
4
+ gemspec
5
+ gem 'byebug'
6
+ gem 'puma'
data/Gemfile.lock ADDED
@@ -0,0 +1,77 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ parse-stack (1.0.0)
5
+ active_model_serializers (>= 0.9, < 1)
6
+ activemodel (>= 4, < 5)
7
+ activesupport (>= 4, < 5)
8
+ faraday (>= 0.8, < 1)
9
+ faraday_middleware (>= 0.9, < 1)
10
+ moneta (>= 0.7, < 1)
11
+ parallel (>= 1.6, < 2)
12
+ rack (< 2)
13
+
14
+ GEM
15
+ remote: https://rubygems.org/
16
+ specs:
17
+ active_model_serializers (0.9.4)
18
+ activemodel (>= 3.2)
19
+ activemodel (4.2.5.1)
20
+ activesupport (= 4.2.5.1)
21
+ builder (~> 3.1)
22
+ activesupport (4.2.5.1)
23
+ i18n (~> 0.7)
24
+ json (~> 1.7, >= 1.7.7)
25
+ minitest (~> 5.1)
26
+ thread_safe (~> 0.3, >= 0.3.4)
27
+ tzinfo (~> 1.1)
28
+ binding_of_caller (0.7.2)
29
+ debug_inspector (>= 0.0.1)
30
+ builder (3.2.2)
31
+ byebug (8.2.1)
32
+ coderay (1.1.0)
33
+ debug_inspector (0.0.2)
34
+ faraday (0.9.2)
35
+ multipart-post (>= 1.2, < 3)
36
+ faraday_middleware (0.10.0)
37
+ faraday (>= 0.7.4, < 0.10)
38
+ i18n (0.7.0)
39
+ json (1.8.3)
40
+ method_source (0.8.2)
41
+ minitest (5.8.4)
42
+ moneta (0.7.20)
43
+ multipart-post (2.0.0)
44
+ parallel (1.6.1)
45
+ pry (0.10.3)
46
+ coderay (~> 1.1.0)
47
+ method_source (~> 0.8.1)
48
+ slop (~> 3.4)
49
+ pry-nav (0.2.4)
50
+ pry (>= 0.9.10, < 0.11.0)
51
+ pry-stack_explorer (0.4.9.2)
52
+ binding_of_caller (>= 0.7)
53
+ pry (>= 0.9.11)
54
+ puma (2.15.3)
55
+ rack (1.6.4)
56
+ rake (10.5.0)
57
+ slop (3.6.0)
58
+ thread_safe (0.3.5)
59
+ tzinfo (1.2.2)
60
+ thread_safe (~> 0.1)
61
+
62
+ PLATFORMS
63
+ ruby
64
+
65
+ DEPENDENCIES
66
+ bundler (~> 1)
67
+ byebug
68
+ minitest (~> 5)
69
+ parse-stack!
70
+ pry (< 1)
71
+ pry-nav (< 1)
72
+ pry-stack_explorer (< 1)
73
+ puma
74
+ rake (~> 10)
75
+
76
+ BUNDLED WITH
77
+ 1.11.2
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014-2016 Anthony Persaud
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,1281 @@
1
+ # Parse::Stack
2
+ Parse::Stack is an opinionated framework for larger scale ruby applications that utilize the [Parse Platform](http://www.parse.com). It provides a client adapter, a query engine, an object relational mapper (ORM) and a Cloud Code Webhooks rack application.
3
+
4
+ ## Table Of Contents
5
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
6
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
7
+
8
+
9
+ - [Overview](#overview)
10
+ - [Main Features](#main-features)
11
+ - [Architecture](#architecture)
12
+ - [Parse::Client](#parseclient)
13
+ - [Parse::Query](#parsequery)
14
+ - [Parse::Object](#parseobject)
15
+ - [Parse::Webhooks](#parsewebhooks)
16
+ - [Modeling](#modeling)
17
+ - [Subclassing](#subclassing)
18
+ - [Other Core Classes](#other-core-classes)
19
+ - [Parse::Pointer](#parsepointer)
20
+ - [Parse::File](#parsefile)
21
+ - [Parse::Date](#parsedate)
22
+ - [Parse::GeoPoint](#parsegeopoint)
23
+ - [Parse::Bytes](#parsebytes)
24
+ - [Properties](#properties)
25
+ - [Accessor Aliasing](#accessor-aliasing)
26
+ - [Property Options](#property-options)
27
+ - [`:required => (true|false)`](#required--truefalse)
28
+ - [`:field => (string)`](#field--string)
29
+ - [`:default => (value|proc)`](#default--valueproc)
30
+ - [`:alias => (true|false)`](#alias--truefalse)
31
+ - [`:symbolize => (true|false)`](#symbolize--truefalse)
32
+ - [Overriding Property Accessors](#overriding-property-accessors)
33
+ - [Associations](#associations)
34
+ - [Belongs To](#belongs-to)
35
+ - [Options](#options)
36
+ - [`:required => (true|false)`](#required--truefalse-1)
37
+ - [`:as => (string)`](#as--string)
38
+ - [`:field => (string)`](#field--string-1)
39
+ - [Has Many (Array or Relation)](#has-many-array-or-relation)
40
+ - [Options](#options-1)
41
+ - [`:through => (:array|:relation)`](#through--arrayrelation)
42
+ - [Creating, Saving and Destroying Records](#creating-saving-and-destroying-records)
43
+ - [Examples](#examples)
44
+ - [Raising an exception when save fails](#raising-an-exception-when-save-fails)
45
+ - [Create](#create)
46
+ - [Save and Update](#save-and-update)
47
+ - [Modifying Associations](#modifying-associations)
48
+ - [Magic `save_all`](#magic-save_all)
49
+ - [Destroy](#destroy)
50
+ - [Fetching, Finding and Counting Records](#fetching-finding-and-counting-records)
51
+ - [Auto-Fetching Associations](#auto-fetching-associations)
52
+ - [Advanced Querying](#advanced-querying)
53
+ - [Counting](#counting)
54
+ - [Compound Queries (or)](#compound-queries-or)
55
+ - [Results Caching](#results-caching)
56
+ - [Expressions](#expressions)
57
+ - [:order](#order)
58
+ - [:keys](#keys)
59
+ - [:includes](#includes)
60
+ - [:limit](#limit)
61
+ - [:skip](#skip)
62
+ - [:where](#where)
63
+ - [Where Query Constraints](#where-query-constraints)
64
+ - [Hooks and Callbacks](#hooks-and-callbacks)
65
+ - [Push Notifications](#push-notifications)
66
+ - [Webhooks](#webhooks)
67
+ - [Setup Cloud Code functions](#setup-cloud-code-functions)
68
+ - [Setup Cloud Code Triggers](#setup-cloud-code-triggers)
69
+ - [Mounting Webhooks Application](#mounting-webhooks-application)
70
+ - [Register Webhooks](#register-webhooks)
71
+ - [Cloud Code Functions](#cloud-code-functions)
72
+ - [Cloud Code Background Jobs](#cloud-code-background-jobs)
73
+ - [Parse REST API Client](#parse-rest-api-client)
74
+ - [Options](#options-2)
75
+ - [Request Caching](#request-caching)
76
+ - [Installation](#installation)
77
+ - [Development](#development)
78
+
79
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
80
+
81
+ ## Overview
82
+ Parse::Stack is a full stack framework that utilizes several ideas behind [DataMapper](http://datamapper.org/docs/find.html) and [ActiveModel](https://github.com/rails/rails/tree/master/activemodel) to manage and maintain larger scale ruby applications and tools that utilize the Parse Platform. If you are familiar with these technologies, the framework should feel familiar to you.
83
+
84
+ ```ruby
85
+
86
+ require 'parse/stack'
87
+
88
+ Parse.setup application_id: APP_ID, api_key: REST_API_KEY
89
+
90
+ # Object Mapper
91
+ class Song < Parse::Object
92
+ property :name
93
+ property :play, :integer
94
+ property :audio_file, :file
95
+ property :tags, :array
96
+ property :released, :date
97
+ belongs_to :artist
98
+ # `like` is a Parse Relation to User class
99
+ has_many :likes, as: :user, through: :relation
100
+ end
101
+
102
+ # Optional schema updates (requires master key)
103
+ Song.auto_upgrade!
104
+
105
+ artist = Artist.first(:name.like => /Sinatra/, :genres.in => ['swing'])
106
+
107
+ song = Song.new name: "Fly Me to the Moon"
108
+ song.artist = artist
109
+ # Parse files
110
+ song.audio_file = Parse::File.create("http://path_to.mp3")
111
+ # relations
112
+ song.likes.add Parse::User.first(username: "persaud")
113
+
114
+ # saves both attributes and relations
115
+ song.save
116
+
117
+ # find songs
118
+ songs = Song.all(artist: artist, :plays.gt => 100, :released.lte => 10.years.ago)
119
+
120
+ songs.each { |s| s.tags.add "awesome" }
121
+ # batch saves
122
+ songs.save
123
+
124
+ # Call Cloud Code functions
125
+ result = Parse.call_function :myFunctionName, {param: value}
126
+
127
+ # Trigger a Parse Job
128
+ Parse.trigger_job :myBackgroundJob, {param: value}
129
+
130
+ ```
131
+
132
+ ## Main Features
133
+ While there are many additional features of the framework, these are the main points.
134
+
135
+ - Object Relational Mapping with dirty tracking.
136
+ - Easy management of Parse GeoPoints, Files and ACLs.
137
+ - Queries support with caching middleware. (Reduces API usage)
138
+ - Support for all Parse data types.
139
+ - One-to-One, One-to-Many and Many-to-Many relations.
140
+ - Inegration with Parse Cloud Code Webhooks.
141
+ - Send Push notifications with advanced targeting.
142
+ - Schema upgrades and migrations.
143
+
144
+ ## Architecture
145
+ The architecture of `Parse::Stack` is broken into four main components.
146
+
147
+ #### Parse::Client
148
+ This class is the core and low level API for the Parse SDK REST interface that is used by the other components. It can manage multiple sessions, which means you can have multiple client instances pointing to different Parse Applications at the same time. It handles sending raw requests as well as providing Request/Response objects for all API handlers. The connection engine is Faraday, which means it is open to add any additional middleware for features you'd like to implement.
149
+
150
+ #### Parse::Query
151
+ This class implements the [Parse REST Querying](https://parse.com/docs/rest/guide#queries) interface in the [DataMapper finder syntax style](http://datamapper.org/docs/find.html). It compiles a set of query constraints and utilizes `Parse::Client` to send the request and provide the raw results. This class can be used without the need to define models.
152
+
153
+ #### Parse::Object
154
+ This component is main class for all object relational mapping subclasses for your application. It provides features in order to map your remote Parse records to a local ruby object. It implements the Active::Model interface to provide a lot of additional features, CRUD operations, querying, including dirty tracking, JSON serialization, save/destroy callbacks and others. While we are overlooking some functionality, for simplicity, you will mainly be working with Parse::Object as your superclass. While not required, it is highly recommended that you define a model (Parse::Object subclass) for all the Parse classes in your application.
155
+
156
+ #### Parse::Webhooks
157
+ Parse provides a feature called [Cloud Code Webhooks](http://blog.parse.com/announcements/introducing-cloud-code-webhooks/). For most applications, save/delete triggers and cloud functions tend to be implemented by Parse's own hosted Javascript solution called Cloud Code. However, Parse provides the ability to have these hooks utilize your hosted solution instead of their own, since their environment is limited in terms of resources and tools.
158
+
159
+ ## Modeling
160
+ For the general case, your Parse classes should inherit from `Parse::Object`. `Parse::Object` utilizes features from `ActiveModel` to add several features to each instance of your subclass. These include `Dirty`, `Conversion`, `Callbacks`, `Naming` and `Serializers::JSON`.
161
+
162
+ To get started use the `property` and `has_many` methods to setup declarations for your fields. Properties define literal values that are columns in your Parse class. These can be any of the base Parse data types. You will not need to define classes for the basic Parse class types - this includes "\_User", "\_Installation", "\_Session" and "\_Role". These are mapped to `Parse::User`, `Parse::Installation`, `Parse::Session` and `Parse::Role` respectively.
163
+
164
+ ### Subclassing
165
+ To get started, you define your classes based on `Parse::Object`. By default, the name of the class is used as the name of the remote Parse class. For a class `Post`, we will assume there is a remote camel-cased Parse table called `Post`. If you need to map the local class name to a different remote class, use the `parse_class` method.
166
+
167
+ ```ruby
168
+ class Post < Parse::Object
169
+ # assumes Parse class "Post"
170
+ end
171
+
172
+ class Commentary < Parse::Object
173
+ # set remote class "Comment"
174
+ parse_class "Comment"
175
+ end
176
+ ```
177
+
178
+ ### Other Core Classes
179
+ While some native data types are similar to the ones supported by Ruby natively, other ones are more complex and require their dedicated classes.
180
+
181
+ #### Parse::Pointer
182
+ An important concept is the `Parse::Pointer` class. This is the superclass of `Parse::Object` and represents the pointer type in Parse. A `Parse::Pointer` only contains data about the specific Parse class and the `id` for the object. Therefore, creating an instance of any Parse::Object subclass with only the `:id` field set will be considered in "pointer" state even though its specific class is not `Parse::Pointer` type. The only case that you may have a Parse::Pointer is in the case where an object was received for one of your classes and the framework has no registered class handler for it. Using the example above, assume you have the tables `Post`, `Comment` and `Author` defined in your remote Parse application, but have only defined `Post` and `Commentary` locally.
183
+
184
+ ```ruby
185
+ # assume the following
186
+ class Post < Parse::Object
187
+ end
188
+
189
+ class Commentary < Parse::Object
190
+ parse_class "Comment"
191
+ belongs_to :post
192
+ #'Author' class not defined locally
193
+ belongs_to :author
194
+ end
195
+
196
+ comment = Commentary.first
197
+ comment.post.pointer? # true
198
+ comment.author.pointer? # true
199
+
200
+ # we have defined a Post class handler
201
+ comment.post # <Post @parse_class="Post", @id="xdqcCqfngz">
202
+
203
+ # we have not defined an Author class handler
204
+ comment.author # <Parse::Pointer @parse_class="Author", @id="hZLbW6ofKC">
205
+ ```
206
+
207
+ The effect is that for any unknown classes that the framework encounters, it will generate Parse::Pointer instances until you define those classes with valid properties and associations. While this might be ok for some classes you do not use, we still recommend defining all your Parse classes locally in the framework.
208
+
209
+ #### Parse::File
210
+ This class represents a Parse file pointer. `Parse::File` has helper methods to upload Parse files directly to Parse and manage file associations with your classes. Using our Song class example:
211
+
212
+ ```ruby
213
+ song = Song.first
214
+ file = song.audio_file # Parse::File
215
+ file.url # URL in the Parse file storage system
216
+
217
+ contents = File.open("file_path.jpg").read
218
+ file = Parse::File.new("myimage.jpg", contents , "image/jpeg")
219
+ file.saved? # false. Hasn't been uploaded to Parse
220
+ file.save # uploads to Parse.
221
+
222
+ file.url # https://files.parsetfss.com/....
223
+
224
+ # or create and upload a remote file (auto-detected mime type)
225
+ file = Parse::File.create(some_url)
226
+ song.file = file
227
+ song.save
228
+
229
+ ```
230
+
231
+ #### Parse::Date
232
+ This class manages dates in the special JSON format it requires for properties of type `:date`. `Parse::Date` subclasses `DateTime`, which allows you to use any features or methods available to `DateTime` with `Parse::Date`. While the conversion between `Time` and `DateTime` objects to a `Parse::Date` object is done implicitly for you, you can use the added special methods, `DateTime#parse_date` and `Time#parse_date`, for special occasions.
233
+
234
+ ```ruby
235
+ song = Song.first
236
+ song.released = DateTime.now # converted to Parse::Date
237
+ song.save # ok
238
+ ```
239
+
240
+ One important note with dates, is that `created_at` and `updated_at` columns do not follow this convention all the time. Depending on the Cloud Code SDK, they can be the Parse ISO hash date format or the `iso8601` string format. By default, these are serialized as `iso8601` when sent as responses to Parse for backwards compatibility with some clients. To use the Parse ISO hash format for these fields instead, set `Parse::Object.disable_serialized_string_date = true`.
241
+
242
+ #### Parse::GeoPoint
243
+ This class manages the GeoPoint data type that Parse provides to support geo-queries. To define a GeoPoint property, use the `:geopoint` data type.
244
+
245
+ ```ruby
246
+ class Song < Parse::Object
247
+ property :location, :geopoint
248
+ end
249
+
250
+ san_diego = Parse::GeoPoint.new(32.8233, -117.6542)
251
+ los_angeles = Parse::GeoPoint.new [34.0192341, -118.970792]
252
+ san_diego == los_angeles # false
253
+
254
+ song.location = san_diego
255
+
256
+ # Haversine calculations
257
+ san_diego.distance_in_miles(los_angeles) # ~112.33 miles
258
+ san_diego.distance_in_km(los_angeles) # ~180.793 km
259
+
260
+ ```
261
+
262
+ #### Parse::Bytes
263
+ The `Bytes` data type represents the storage format for binary content in a Parse column. The content is needs to be encoded into a base64 string.
264
+
265
+ ```ruby
266
+ bytes = Parse::Bytes.new( base64_string )
267
+ # or use helper method
268
+ bytes = Parse::Bytes.new
269
+ bytes.encode( content ) # same as Base64.encode64
270
+
271
+ decoded = bytes.decoded # same as Base64.decode64
272
+ ```
273
+
274
+ ### Properties
275
+ Properties are considered a literal-type of association. This means that a defined local property maps directly to a column name for that remote Parse class which contain the value. **All properties are implicitly formatted to map to a lower-first camelcase version in Parse (remote).** Therefore a local property defined as `like_count`, would be mapped to the remote column of `likeCount` automatically. The only special behavior to this rule is the `:id` property which maps to `objectId` in Parse. This implicit conversion mapping is the default behavior, but can be changed on a per-property basis. All Parse data types are supported and all Parse::Object subclasses already provide definitions for `:id` (objectId), `:created_at` (createdAt), `:updated_at` (updatedAt) and `:acl` (ACL) properties.
276
+
277
+ - **:string** (_default_) - a generic string.
278
+ - **:integer** - basic number.
279
+ - **:float** - a floating numeric value.
280
+ - **:boolean** - true/false value.
281
+ - **:date** - a Parse date type. Maps to `Parse::Date`.
282
+ - **:array** - a collection of heterogeneous items. Maps to `Parse::CollectionProxy`.
283
+ - **:file** - a Parse file type. Maps to `Parse::File`.
284
+ - **:geopoint** - a GeoPoint type. Maps to `Parse::GeoPoint`.
285
+ - **:bytes** - a Parse bytes data type managed as base64. Maps to `Parse::Bytes`.
286
+ - **:object** - an object Hash data type.
287
+
288
+ For completeness, the `:id` and `:acl` data types are also defined in order to handle the Parse `objectId` field and the `ACL` object. Those are special and should not be used in your class (unless you know what you are doing). New data types can be implemented through the internal `typecast` interface. **TODO: discuss `typecast` interface in the future**
289
+
290
+ Using the example above, we can add the base properties to our classes.
291
+
292
+ ```ruby
293
+ class Post < Parse::Object
294
+ property :title
295
+ property :content, :string # explicit
296
+
297
+ # treat the values of this field as symbols instead of strings.
298
+ property :category, :string, symbolize: true
299
+
300
+ # maybe a count of comments.
301
+ property :comment_count, :integer, default: 0
302
+
303
+ # the published date. Maps to "publishDate"
304
+ property :publish_date, :date, ->{ DateTime.now }
305
+
306
+ # maybe whether it is currently visible
307
+ property :visible, :boolean
308
+
309
+ # a list using
310
+ property :tags, :array
311
+
312
+ # Maps to "featuredImage" column representing a File.
313
+ property :featured_image, :file
314
+
315
+ property :location, :geopoint
316
+
317
+ # Support bytes
318
+ property :data, :bytes
319
+
320
+ # store SEO information. Make sure we map it to the column
321
+ # "SEO", otherwise it would have implicitly used "seo"
322
+ # as the remote column name
323
+ property :seo, :object, field: "SEO"
324
+ end
325
+ ```
326
+
327
+ After properties are defined, you can use appropriate getter and setter methods to modify the values. As properties become modified, the model will keep track of the changes using the [dirty tracking feature of ActiveModel](http://api.rubyonrails.org/classes/ActiveModel/Dirty.html). If an attribute is modified in-place then make use of **[attribute_name]_will_change!** to mark that the attribute is changing. Otherwise ActiveModel can't track changes to in-place attributes.
328
+
329
+ To support dirty tracking on properties of data type of `:array`, we utilize a proxy class called `Parse::CollectionProxy`. This class has special functionality which allows lazy loading of content as well and keeping track of the changes that are made. While you are able to access the internal array on the collection through the `#collection` method, it is important not to make in-place edits to the object. You should use the preferred methods of `#add` and `#remove` to modify the contents of the collection. When `#save` is called on the object, the changes will be commited to Parse.
330
+
331
+ ```ruby
332
+ post = Post.first
333
+ post.tags.each do |tag|
334
+ puts tag
335
+ end
336
+ post.tags.empty? # false
337
+ post.tags.count # 3
338
+ array = post.tags.to_a # get array
339
+
340
+ # Add
341
+ post.tags.add "music", "tech"
342
+ post.tags.remove "stuff"
343
+ post.save # commit changes
344
+ ```
345
+
346
+ ###### Accessor Aliasing
347
+ To enable easy conversion between incoming Parse attributes, which may be different than the locally labeled attribute, we make use of aliasing accessors with their remote field names. As an example, for a `Post` instance and its `publish_date` property, it would have an accessor defined for both `publish_date` and `publishDate` (or whatever value you passed as the `:field` option) that map to the same attribute. We highly discourage turning off this feature, but if you need to, you can pass the value of `false` to the `:alias` option when defining the property.
348
+
349
+ ```ruby
350
+ # These are equivalent
351
+ post.publish_date = DateTime.now
352
+ post.publishDate = DateTime.now
353
+ post.publish_date == post.publishDate
354
+
355
+ post.seo # ok
356
+ post.SEO # the alias method since 'field: "SEO"'
357
+ ```
358
+
359
+ #### Property Options
360
+ These are the supported options when defining properties. Parse::Objects are backed by `ActiveModel`, which means you can add additional validations and features supported by that library.
361
+
362
+ ###### `:required => (true|false)`
363
+ This option provides information to the property builder that it is a required property. The requirement is not strongly enforced for a save, which means even though the value for the property may not be present, saves and updates can be successfully performed. However, the setting `required` to true, it will set some ActiveModel validations on the property to be used when calling `valid?`. By default it will add a `validates_presence_of` for the property key. If the data type of the property is either `:integer` or `:float`, it will also add a `validates_numericality_of` validation. Default `false`.
364
+
365
+ ###### `:field => (string)`
366
+ This option allows you to set the name of the remote column for the Parse table. Using this will explicitly set the remote property name to the value of this option. The value provided for this option will affect the name of the alias method that is generated when `alias` option is used. **By default, the name of the remote column is the lower-first camelcase version of the property name. As an example, for a property with key `:my_property_name`, the framework will implicitly assume that the remote column is `myPropertyName`.**
367
+
368
+ ###### `:default => (value|proc)`
369
+ This option provides you to set a default value for a specific property when the getter accessor method is used and the internal value of the instance object's property is nil. It can either take a literal value or a Proc/lambda.
370
+
371
+ ```ruby
372
+ class SomeClass < Parse::Object
373
+ # default value
374
+ property :category, default: "myValue"
375
+ # default value Proc style
376
+ property :date, default: ->{ DateTime.now }
377
+ end
378
+ ```
379
+ ###### `:alias => (true|false)`
380
+ It is highly recommended that this is set to true, which is the default. This option allows for the generation of the additional accessors with the value of `:field`. By allowing two accessors methods, aliased to each other, allows for easier importing and automatic object instantiation based on Parse object JSON data into the Parse::Object subclass.
381
+
382
+ ###### `:symbolize => (true|false)`
383
+ This option is only available for fields with data type of `:string`. This allows you to utilize the values for this property as symbols instead of the literal strings, which is Parse's storage format. This feature is useful if a particular property represents a set of enumerable states described in string form. As an example, if you have a `Post` object which has a set of publish states stored in Parse as "draft","scheduled", and "published" - we can use ruby symbols to make our code easier.
384
+
385
+ ```ruby
386
+ class Post < Parse::Object
387
+ property :state, :string, symbolize: true
388
+ end
389
+
390
+ post = Post.first
391
+ # the value returned is auto-symbolized
392
+ if post.state == :draft
393
+ # will be converted to string when updated in Parse
394
+ post.state = :published
395
+ post.save
396
+ end
397
+ ```
398
+
399
+ #### Overriding Property Accessors
400
+ When a `property` is defined, special accessors are created for it. It is not recommended that you override the generated accessors for the properties you have defined.
401
+
402
+ ### Associations
403
+ Parse supports a three main types of relational associations. One type of relation is the `One-to-One` association. This is implemented through a specific column in Parse with a Pointer data type. This pointer column, contains a local value that refers to a different record in a separate Parse table. This association is implemented using the `:belongs_to` feature. The second association is of `One-to-Many`. This is implemented is in Parse as a Array type column that contains a list of of Parse pointer objects. It is recommended by Parse that this array does not exceed 100 items for performance reasons. This feature is implemented using the `:has_many` operation with the plural name of the local Parse class. The last association type is a Parse Relation. These can be used to implement a large `Many-to-Many` association without requiring an explicit intermediary Parse table or class. This feature is also implemented using the `:has_many` method but passing the option of `:relation`.
404
+
405
+ #### Belongs To
406
+ Utilizing the `belongs_to` method in defining a property in a Parse::Object subclass sets up an association between the local table and a foreign table. Specifying the `belongs_to` in the class, tells the framework that the Parse table contains a local column in its schema that has a reference to a record in a foreign table. The argument to `belongs_to` should be the singularized version of the foreign Parse::Object class. you should specify the foreign table as the snake_case singularized version of the foreign table class. It is important to note that the reverse relationship is not generated automatically.
407
+
408
+ ```ruby
409
+ class Author < Parse::Object
410
+ property :name
411
+ end
412
+
413
+ class Comment < Parse::Object
414
+ belongs_to :user # Parse::User
415
+ end
416
+
417
+ class Post < Parse::Object
418
+ belongs_to :author
419
+ end
420
+
421
+ post = Post.first
422
+ # Follow the author pointer and get name
423
+ post.author.name
424
+
425
+ other_author = Author.first
426
+ # change author by setting new pointer
427
+ post.author = other_author
428
+ post.save
429
+ ```
430
+
431
+ ##### Options
432
+ You can override some of the default functionality when creating both `belongs_to` and `has_many` associations.
433
+
434
+ ###### `:required => (true|false)`
435
+ Setting the requirement, automatically creates an ActiveModel validation of `validates_presence_of` for the association. This will not prevent the save, but affects the validation check when `valid?` is called on an instance. Default is false.
436
+
437
+ ###### `:as => (string)`
438
+ This option allows you to override the foreign Parse class that this association refers while allowing you to have a different accessor name. As an example, you may have a class `Band` which has a `manager` who is of type `Parse::User` and a set of band members, represented by the class `Artist`. You can override the default casting class as follows:
439
+
440
+ ```ruby
441
+ # represents a member of a band or group
442
+ class Artist < Parse::Object
443
+ end
444
+
445
+ class Band < Parse::Object
446
+ belongs_to :manager, as: :user
447
+ belongs_to :lead_singer, as: :artist
448
+ belongs_to :drummer, as: :artist
449
+ end
450
+
451
+ band = Band.first
452
+ band.manager # Parse::User object
453
+ band.lead_singer # Artist object
454
+ band.drummer # Artist object
455
+ ```
456
+
457
+ ###### `:field => (string)`
458
+ This option allows you to set the name of the remote Parse column for this property. Using this will explicitly set the remote property name to the value of this option. The value provided for this option will affect the name of the alias method that is generated when `alias` option is used. **By default, the name of the remote column is the lower-first camel case version of the property name. As an example, for a property with key `:my_property_name`, the framework will implicitly assume that the remote column is `myPropertyName`.**
459
+
460
+ #### Has Many (Array or Relation)
461
+ Parse has two ways of implementing a `has_many` association. The first type is where you can designate a column to be of Array type that contains a list of Parse pointers. It is recommended that this is used for associations where the quantity is less than 100 in order to maintain query and fetch performance. The second implementation is through a Parse Relation. This is done by passing the option `:through => :relation` to the `has_many` method. Designating a column as a Parse relation to another class type, will create a one-way intermediate "join" table between the local table class and the foreign one. One important distinction of this compared to other types of data stores (ex. PostgresSQL) is that:
462
+
463
+ 1. The inverse relationship association is not available automatically. Therefore, having a column of `artists` in a `Band` class that relates to members of the band (as `Artist` class), does not automatically make a set of `Band` records available to `Artist` records for which they have been related. If you need to maintain both the inverse relationship between a foreign class to its associations, you will need to manually manage that.
464
+ 2. Querying the relation is actually performed against the implicit join table, not the local one.
465
+ 3. Applying query constraints for a set of records within a relation is performed against the foreign table class, not the class having the relational column.
466
+
467
+ The Parse documentation provides more details on associations, see [Parse Relations Guide](https://parse.com/docs/ios/guide#relations). The good news is that the framework will handle the work for (2) and (3) automatically.
468
+
469
+ To define a `has_many` association, provide the name of the foreign relation class in plural form. The framework will use the camelcase singular form of the property name as being the name of the foreign table class.
470
+
471
+ ```ruby
472
+
473
+ class Artist < Parse::Object
474
+ end
475
+
476
+ class Fan < Parse::Object
477
+ property :location, :geopoint
478
+ end
479
+
480
+ class Band < Parse::Object
481
+ property :category, :integer, default: 1
482
+ # assume any band as < 100 members
483
+ has_many :artists # assumes `through: :array`
484
+ # bands can have millions of fans, we use relations instead
485
+ has_many :fans, through: :relation 
486
+ end
487
+
488
+ # Find all bands which have a category in this array.
489
+ bands = Band.all( :category.in => [1,3,5,7,9] )
490
+
491
+ # Find all bands which have Joe as an artist.
492
+ banjoe = Artist.first name: "Joe Banjoe"
493
+ bands = Band.all( :artists.in => [banjoe.pointer] )
494
+ band = bands.first
495
+
496
+ # the number of fans in the relation
497
+ band.fans.count
498
+
499
+ # Find 50 fans who are near San Diego, CA
500
+ downtown = Parse::GeoPoint.new(32.82, -117.23)
501
+ fans = band.fans.all(:location.near => downtown, :limit => 50)
502
+
503
+ ```
504
+
505
+ ##### Options
506
+ Options for `has_many` are the same as the `belongs_to` counterpart with support for `:required`, `:as` and `:field`. It has this additional option of `:through` which helps specify whether it is an Array or Relation association type.
507
+
508
+ ###### `:through => (:array|:relation)`
509
+ This sets the type of the `has_many` relation. If `:relation` is set, it tells the framework that the column defined is of type Parse Relation. The default value is `:array`, which defines the column in Parse as being an array of Parse pointer objects.
510
+
511
+ ## Creating, Saving and Destroying Records
512
+ This section provides some of the basic methods when creating, updating and deleting objects from Parse. To illustrate the various methods available for saving Parse records, we use this example class:
513
+
514
+ #### Examples
515
+
516
+ ```ruby
517
+
518
+ class Artist < Parse::Object
519
+ property :name
520
+ belongs_to :manager, as: :user
521
+ end
522
+
523
+ class Song < Parse::Object
524
+ property :name
525
+ property :audio_file, :file
526
+ property :released, :date
527
+ property :available, :boolean, default: true
528
+ belongs_to :artist
529
+ has_many :fans, as: :user, through: :relation
530
+ end
531
+ ```
532
+
533
+ #### Raising an exception when save fails
534
+ By default, we return `true` or `false` for save and destroy operations. If you prefer to have `Parse::Object` raise an exception instead, you can tell to do so either globally or on a per-model basis.
535
+
536
+ ```ruby
537
+ Parse::Model.raise_on_save_failure = true # globally across all models
538
+ Song.raise_on_save_failure = true # per-model
539
+
540
+ ```
541
+
542
+ When enabled, if an error is returned by Parse due to saving or destroying a record, due to your `before_save` or `before_delete` validation cloud code triggers, `Parse::Object` will return the a `Parse::SaveFailureError` exception type. This exception has an instance method of `#object` which contains the object that failed to save.
543
+
544
+ #### Create
545
+ To create a new object you can call `#new` while passing a hash of attributes you want to set. You can then use the property accessors to also modify individual properties. As you modify properties, you can access dirty tracking state and data using the generated [`ActiveModel::Dirty`](http://api.rubyonrails.org/classes/ActiveModel/Dirty.html) features. When you are ready to commit the new object to Parse, you can call `#save`.
546
+
547
+ ```ruby
548
+ song = Song.new(name: "My Old Song")
549
+ song.new? # true
550
+ song.id # nil
551
+ song.released = DateTime.now
552
+ song.changed? # true
553
+ song.changed # ['name', 'released']
554
+ song.name_changed? # true
555
+
556
+ # commit changes
557
+ song.save
558
+
559
+ song.new? # false
560
+ song.id # 'hZLbW6ofKC'
561
+ song.name = "My New Song"
562
+ song.name_was # "My Old Song"
563
+ song.changed # ['name']
564
+
565
+ ```
566
+
567
+ 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`.
568
+
569
+ ```ruby
570
+ # Finds matching song or creates a new unsaved object
571
+ song = Song.first_or_create(name: "Awesome Song", available: true)
572
+ song.id # nil since it wasn't found, and autosave is off.
573
+ song.released = 1.day.from_now
574
+ song.save
575
+ song.id # now has a valid objectId ex. 'xyz1122df'
576
+
577
+ song = Song.first_or_create(name: "Awesome Song", available: true)
578
+ song.id # 'xyz1122df`
579
+ song.save # noop since nothing changed
580
+
581
+ ```
582
+
583
+ 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`.
584
+
585
+ ```ruby
586
+ song = Song.first_or_create({ name: "Long Way Home" }, { released: DateTime.now })
587
+ ```
588
+
589
+ The above will search for a Song with name 'Long Way Home'. If it does not find a match, it will create a new instance with `name` set to 'Long Way Home' and the `released` date field to the current time, at time of execution. In this scenario, both hash arguments are merged to create a new instance with the second set of arguments overriding the first set.
590
+
591
+ ```ruby
592
+ song = Song.first_or_create({ name: "Long Way Home" }, {
593
+ name: "Other Way Home",
594
+ released: DateTime.now # Time.now ok too
595
+ })
596
+ ```
597
+
598
+ In the above case, if a Song is not found with name 'Long Way Home', the new instance will be created with `name` set to 'Other Way Home' and `released` set to `DateTime.now`.
599
+
600
+ #### Save and Update
601
+ To commit a new record or changes to an existing record to Parse, use the `#save` method. The method will automatically detect whether it is a new object or an existing one and call the appropriate workflow. The use of ActiveModel dirty tracking allows us to send only the changes that were made to the object when saving. **Saving a record will take care of both saving all the changed properties, and associations. However, any modified linked objects (ex. belongs_to) need to be saved independently.**
602
+
603
+ ```ruby
604
+ song = Song.new(name: "Awesome Song") # Pass in a hash to the new method
605
+ song.name = "Super Song" # Set individual property
606
+
607
+ # Set multiple properties at once
608
+ song.attributes = { name: "Best Song", released: DateTime.now }
609
+
610
+ song.artist = Artist.first
611
+ song.artist.name = "New Band Name"
612
+ # add a fan to this song. Note this is a Parse Relation
613
+ song.fans.add = Parse::User.first
614
+
615
+ # saves changed properties, associations and relations.
616
+ song.save
617
+
618
+ song.artist.save # to commit the changes made to 'name'.
619
+
620
+ songs = Song.all( :available => false)
621
+ songs.each { |song| song.available = true }
622
+
623
+ # uses a Parse batch operation for efficiency
624
+ songs.save # save the rest of the items
625
+ ```
626
+
627
+ The save operation can handle both creating and updating existing objects. If you do not want to update the association data of a changed object, you may use the `#update` method to only save the changed property values. In the case where you want to force update an object even though it has not changed, to possibly trigger your `before_save` hooks, you can use the `#update!` method.
628
+
629
+ ###### Modifying Associations
630
+ Similar to `:array` types of properties, a `has_many` association is backed by a collection proxy class and requires the use of `#add` and `#remove` to modify the contents of the association in order for it to correctly manage changes and updates with Parse. Using `has_many` for associations has the additional functionality that we will only add items to the association if they are of a `Parse::Pointer` or `Parse::Object` type. By default, these associations are fetched with only pointer data. To fetch all the objects in the association, you can call `#fetch` or `#fetch!` on the collection. Note that because the framework supports chaining, it is better to only request the objects you need by utilizing their accessors.
631
+
632
+ ```ruby
633
+ class Artist < Parse::Object
634
+ has_many :songs # array association
635
+ end
636
+
637
+ artist = Artist.first
638
+ artist.songs # Song pointers
639
+
640
+ # fetch all the objects in this association
641
+ artist.songs.fetch # fetches with parallel requests
642
+
643
+ # add another song
644
+ artist.songs.add Song.first
645
+ artist.songs.remove other_song
646
+ artist.save # commits changes
647
+ ```
648
+
649
+ For the cases when you want to modify the items in this association without having to fetch all the objects in the association, we provide the methods `#add!`, `#add_unique!`, `#remove!` and `#destroy` that perform atomic Parse operations. These Parse operations are made directly to Parse compared to the non-bang versions which are batched with the rest of the pending object changes.
650
+
651
+ ```ruby
652
+ artist = Artist.first
653
+ artist.songs.add! song # Add operation
654
+ artist.songs.add_unique! other_song # AddUnique operation
655
+ artist.songs.remove! another_song # Remove operation
656
+ artist.save # noop. operations were sent directly to Parse
657
+
658
+ artist.songs.destroy! # Delete operation of all Songs
659
+ ```
660
+
661
+ The `has_many` Parse Relation associations are handled similarly as in the array cases above. However, since a Parse Relation represents a separate table, there are additional methods provided in order to query the intermediate relational table.
662
+
663
+ ```ruby
664
+ song = Song.first
665
+
666
+ # Standard methods, but through relation table
667
+ song.fans.count # efficient counting
668
+ song.fans.add user
669
+ song.fans.remove another_user
670
+ song.save # commit changes
671
+
672
+ # OR use to commit ONLY relational changes
673
+ song.fans.save
674
+
675
+ # Additional filtering methods
676
+
677
+ # Find objects within the relation that match query constraints
678
+ song.fans.all( ... constraints ... )
679
+
680
+ # get a foreign relational query, related to this object
681
+ query = song.fans.query
682
+
683
+ # Atomic operations
684
+ song.fans.add! user # AddRelation operation
685
+ song.fans.remove! user # RemoveRelation operation
686
+ song.fans.destroy! #noop since Relations cannot be emptied.
687
+
688
+ ```
689
+
690
+ ##### Magic `save_all`
691
+ By default, all Parse queries have a maximum fetch limit of 1000. While using the `:max` option, `Parse::Stack` can increase this up to 11,000. In the cases where you need to update a large number of objects, you can utilize the `Parse::Object#save_all` method
692
+ to fetch, modify and save objects.
693
+
694
+ This methodology works by continually fetching and saving older records related to the time you begin a `save_all` request (called an "anchor date"), until there are no records left to update. To enable this to work, you must have confidence that any modifications you make to the records will successfully save through you validations that may be present in your `before_save`. This is important, as saving a record will set its `updated_at` date to one newer than the "anchor date" of when the `save_all` started. This `save_all` process will stop whenever no more records match the provided constraints that are older than the "anchor date", or when an object that was previously updated, is seen again in a future fetch (_which means the object failed to save_). Note that `save_all` will automatically manage the correct `updated_at` constraints in the query, so it is recommended that you do not use it as part of the initial constraints.
695
+
696
+ ```ruby
697
+ # Add any constraints except `updated_at`.
698
+ Song.save_all( available: false) do |song|
699
+ song.available = true # make all songs available
700
+ # only objects that were modified will be updated
701
+ # do not call save. We will batch objects for saving.
702
+ end
703
+ ```
704
+
705
+ #### Destroy
706
+ You can destroy a Parse record, just call the `#destroy` method. It will return a boolean value whether it was successful.
707
+
708
+ ```ruby
709
+ song = Song.first
710
+ song.destroy
711
+
712
+ # or in a batch
713
+ songs = Song.all(:limit => 10)
714
+ songs.destroy # uses batch operation
715
+ ```
716
+
717
+ ## Fetching, Finding and Counting Records
718
+
719
+ ```ruby
720
+ song = Song.find "<objectId>"
721
+ Song.get "<objectId>" # alias
722
+
723
+ song1, song2 = Song.find("<objectId>", "<objectId2>", ...) # fetches in parallel with threads
724
+
725
+ count = Song.count( constraints ) # performs a count operation
726
+
727
+ query = Song.where( constraints ) # returns a Parse::Query with where clauses
728
+ song = Song.first( ... constraints ... ) # first Song matching constraints
729
+ s1, s2, s3 = Song.first(3) # get first 3 records from Parse.
730
+
731
+ songs = Song.all( ... expressions ...) # get matching Song records. See Advanced Querying
732
+
733
+ # memory efficient for large amounts of records if you don't need all the objects.
734
+ # Does not return results after loop.
735
+ Song.all( ... expressions ...) do |song|
736
+ # ... do something with song..
737
+ end
738
+
739
+ ```
740
+
741
+ ### Auto-Fetching Associations
742
+ All associations in `Parse::Stack` are fetched lazily by default. If you wish to include objects as part of your query results you can use the `:includes` expression.
743
+
744
+ ```ruby
745
+ song = Song.first
746
+ song.artist.pointer? # true, not fetched
747
+
748
+ # find songs and include the full artist object for each
749
+ song = Song.first(:includes => :artist)
750
+ song.artist.pointer? # false (Full object already available)
751
+
752
+ ```
753
+
754
+ However, `Parse::Stack` performs automatic fetching of associations when the associated classes and their properties are locally defined. Using our Artist and Song examples. In this example, the Song object fetched only has a pointer object in its `#artist` field. However, because the framework knows there is a `Artist#name` property, calling `#name` on the artist pointer will automatically go to Parse to fetch the associated object and provide you with the value.
755
+
756
+ ```ruby
757
+ song = Song.first
758
+ # artist is automatically fetched
759
+ song.artist.name
760
+
761
+ # You can manually do the same with `fetch` and `fetch!`
762
+ song.artist.fetch # considered "fetch if needed". Noop if not needed.
763
+ song.artist.fetch! # force fetch regardless of state.
764
+ ```
765
+
766
+ This also works for all associations types.
767
+
768
+ ```ruby
769
+ song = Song.first
770
+ # automatically fetches all pointers in the chain
771
+ song.artist.manager.username # Parse::User's username
772
+
773
+ # Fetches Parse Relation objects
774
+ song.fans.first.username # the fan's username
775
+ ```
776
+
777
+ ## Advanced Querying
778
+ The `Parse::Query` class provides the lower-level querying interface for your Parse tables using the default `Parse::Client` session created when `setup()` was called. This component can be used on its own without defining your models as all results are provided in hash form. By convention in Ruby (see [Style Guide](https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars)), symbols and variables are expressed in lower_snake_case form. Parse, however, prefers column names in **lower-first camel case** (ex. `objectId`, `createdAt` and `updatedAt`). To keep in line with the style guides between the languages, we do the automatic conversion of the field names when compiling the query. As an additional exception to this rule, the field key of `id` will automatically be converted to the `objectId` field when used. This feature can be overridden by changing the value of `Parse::Query.field_formatter`.
779
+
780
+ ```ruby
781
+ # default
782
+ Parse::Query.field_formatter = :columnize
783
+
784
+ # turn off
785
+ Parse::Query.field_formatter = nil
786
+
787
+ # force everything camel case
788
+ Parse::Query.field_formatter = :camelize
789
+ ```
790
+
791
+ Simplest way to perform query, is to pass the Parse class as the first parameter and the set of expressions.
792
+
793
+ ```ruby
794
+ query = Parse::Query.new("Song", {.... expressions ....})
795
+ # or with Object classes
796
+ query = Song.query({ .. expressions ..})
797
+
798
+ # Examples
799
+ query.results # get results as Parse::Object(s)
800
+ query.results(raw: true) # get the raw hash results
801
+
802
+ query.first # first results matching constraints
803
+ query.first(3) # gets first 3 results matching constraints
804
+
805
+ query.count # perform a count operation instead
806
+ ```
807
+
808
+ For large results set where you may want to operate on objects and may not need to keep all the objects in memory, you can use the block version of the API to iterate through all the records more efficiently.
809
+
810
+ ```ruby
811
+
812
+ # For large results set, you can use the block version to iterate over each matching record
813
+ query.each do |record|
814
+ # ... do something with record ...
815
+ # block version does not return results
816
+ end
817
+
818
+ ```
819
+
820
+ #### Counting
821
+ If you only need to know the result count for a query, provide count a non-zero value. However, if you need to perform a count query, use `count()` method instead. As a reminder, there are a few [caveats to counting records as detailed by Parse](https://parse.com/docs/rest/guide#queries-counting-objects).
822
+
823
+ ```ruby
824
+ # get number of songs with a play_count > 10
825
+ Song.count(:play_count.gt => 10)
826
+
827
+ # same
828
+ query = Parse::Query.new("Song")
829
+ query.where play_count.gt => 10
830
+ query.count
831
+
832
+ ```
833
+
834
+ #### Compound Queries (or)
835
+ If you want to find objects that are from one of several queries, you can combine them in an "or" clause using the `|` operator.
836
+
837
+ ```ruby
838
+ # use | for combining queries
839
+ or_query = query1 | query2 | query3.....
840
+
841
+ # Find songs whose like count is < 10 OR greater than 100
842
+ or_query = Song.query(:like_count.gt < 10) | Song.query(:like_count.gt > 100)
843
+ results = or_query.results
844
+
845
+ ```
846
+
847
+ #### Results Caching
848
+ When a query API is made, the results are cached in the query object in case you need access to the results multiple times. This is only true as long as no modifications to the query parameters are made. You can force clear the locally stored results by calling `clear()` on the query instance.
849
+
850
+ ```ruby
851
+ query = Parse::Query.new("Song")
852
+ query.where :field => value
853
+
854
+ query.results # makes request
855
+ # no query parameters changed, therefore same results
856
+ query.results # no API request
857
+
858
+ # if you modify the query or call 'clear'
859
+ query.clear
860
+ query.results # makes API request
861
+
862
+ ```
863
+
864
+
865
+ ### Expressions
866
+ The set of supported expressions based on what is available through the Parse REST API. _For those who don't prefer the DataMapper style syntax, we have provided method accessors for each of the expressions._
867
+
868
+ ##### :order
869
+ Specify a field to sort by.
870
+
871
+ ```ruby
872
+ # order updated_at ascending order
873
+ Song.all( :order => :updated_at )
874
+
875
+ # first order by highest like_count, then by ascending name.
876
+ # Note that ascending is the default if not specified (ex. `:name.asc`)
877
+ Song.all( :order => [:like_count.desc, :name])
878
+ ```
879
+
880
+ ##### :keys
881
+ Restrict the fields returned by the query. This is useful for larger query results set where some of the data will not be used, which reduces network traffic and deserialization performance. _Use this feature with caution when working with the results, as values for the fields not specified in the query will be omitted in the resulting object._
882
+
883
+ ```ruby
884
+ # results only contain :name field
885
+ Song.all(:keys => :name)
886
+
887
+ # multiple keys
888
+ Song.all(:keys => [:name,:artist])
889
+ ```
890
+
891
+ ##### :includes
892
+ Use on Pointer columns to return the full object. You may chain multiple columns with the `.` operator.
893
+
894
+ ```ruby
895
+ # assuming an 'Artist' has a pointer column for a 'Manager'
896
+ # and a Song has a pointer column for an 'Artist'.
897
+
898
+ # include the full artist object
899
+ Song.all(:includes => :artist)
900
+
901
+ # Chaining
902
+ Song.all(:includes => [:artist, 'artist.manager'])
903
+
904
+ ```
905
+
906
+ ##### :limit
907
+ Limit the number of objects returned by the query. The default is 100, with Parse allowing a maximum of 1000. The framework also allows a value of `:max`. Utilizing this will have the framework continually intelligently utilize `:skip` to continue to paginate through results until an empty result set is received or the `:skip` limit is reached (10,000). When utilizing `all()`, `:max` is the default option for `:limit`.
908
+
909
+ ```ruby
910
+ Song.all(:limit => 1) # same as Song.first
911
+ Song.all(:limit => 1000) # maximum allowed by Parse
912
+ Song.all(:limit => :max) # up to 11,000 records (theoretical).
913
+ ```
914
+
915
+ ##### :skip
916
+ Use with limit to paginate through results. Default is 0 with maximum value being 10,000.
917
+
918
+ ```ruby
919
+ # get the next 3 songs after the first 10
920
+ Song.all(:limit => 3, :skip => 10)
921
+ ```
922
+
923
+ ##### :where
924
+ The `where` clause is based on utilizing a set of constraints on the defined column names in your Parse classes. The constraints are implemented as method operators on field names that are tied to a value. Any symbol/string that is not one of the main expression keywords described here will be considered as a type of query constraint for the `where` clause in the query. See the section `Where Constraints` for examples of available query constraints.
925
+
926
+ ```ruby
927
+ # parts of a single where constraint
928
+ { :column.constraint => value }
929
+ ```
930
+
931
+ ### Where Query Constraints
932
+ Most of the constraints supported by Parse are available to `Parse::Query`. Assuming you have a column named `field`, here are some examples. For an explanation of the constraints, please see [Parse Query Constraints documentation](https://parse.com/docs/rest/guide#queries-query-constraints). You can build your own custom query constraints by creating a `Parse::Constraint` subclass.
933
+
934
+ ```ruby
935
+ q = Song.query # or Parse::Query.new("Song")
936
+
937
+
938
+ # equals (default)
939
+ q.where :field => value
940
+
941
+ # less than
942
+ q.where :field.lt => value
943
+
944
+ # less than or equal to
945
+ q.where :field.lte => value
946
+
947
+ # greater than
948
+ q.where :field.gt => value
949
+
950
+ # greater than or equal to
951
+ q.where :field.gte => value
952
+
953
+ # Not equal to
954
+ q.where :field.not => value
955
+
956
+ # is null
957
+ q.where :field.null => true|false
958
+
959
+ # exists
960
+ q.where :field.exists => true|false
961
+
962
+ # contained in
963
+ q.where :field.in => [item1,item2,...]
964
+ q.where :field.contained_in => [item1,item2,...] # alias
965
+
966
+ # not contained in
967
+ q.where :field.not_in => [item1,item2,...]
968
+
969
+ # contains all
970
+ q.where :field.all => [item1, item2,...]
971
+ q.where :field.contains_all => [item1,item2,...]
972
+
973
+ # regular expression
974
+ q.where :field.like => /ruby_regex/
975
+ q.where :field.regex => /abc/ # alias
976
+
977
+ # select (TODO: improve API)
978
+ q.where :field.select => query
979
+
980
+ # don't select (TODO: improve API)
981
+ q.where :field.reject => query
982
+
983
+ # matches inQuery (TODO: improve API)
984
+ q.where :field.join => query
985
+ q.where :field.in_query => query # alias
986
+
987
+ # notInQuery (inverse of `join`)
988
+ q.where :field.excludes => query
989
+
990
+ # near GeoPoint
991
+ q.where :field.near => geopoint
992
+
993
+ # near GeoPoint within max distance (miles)
994
+ q.where :field.near => geopoint.max_miles(5)
995
+ # or provide a triplet includes max miles constraint
996
+ q.where :field.near => [lat,lng,miles]
997
+
998
+ # relational query
999
+ q.where :field.related_to => pointer
1000
+ q.where :field.rel => pointer # alias
1001
+
1002
+ # OR query
1003
+ or_query = query1 | query2 | query3 ...
1004
+ ```
1005
+
1006
+ ## Hooks and Callbacks
1007
+ All `Parse::Object` subclasses extend [`ActiveModel::Callbacks`](http://api.rubyonrails.org/classes/ActiveModel/Callbacks.html) for `#save` and `#destroy` operations. You can setup internal hooks for `before`, `during` and `after`. See
1008
+
1009
+ ```ruby
1010
+
1011
+ class Song < Parse::Object
1012
+ # ex. before save callback
1013
+ before_save do
1014
+ self.name = self.name.titleize
1015
+ # make sure global acls are set
1016
+ acl.everyone(true, false) if new?
1017
+ end
1018
+
1019
+ end
1020
+
1021
+ song = Song.new name: "my title"
1022
+ puts song.name # 'my title'
1023
+ song.save
1024
+ puts song.name # 'My Title'
1025
+
1026
+ ```
1027
+
1028
+ ## Push Notifications
1029
+ Push notifications are implemented through the `Parse::Push` class. To send push notifications through the REST API, you must enable `REST push enabled?` option in the `Push Notification Settings` section of the `Settings` page in your Parse application. Push notifications targeting uses the Installation Parse class to determine which devices receive the notification. You can provide any query constraint, similar to using `Parse::Query`, in order to target the specific set of devices you want given the columns you have configured in your `Installation` class. The `Parse::Push` class supports many other options not listed here.
1030
+
1031
+ ```ruby
1032
+
1033
+ push = Parse::Push.new
1034
+ push.send( "Hello World!") # to everyone
1035
+
1036
+ # simple channel push
1037
+ push = Parse::Push.new
1038
+ push.channels = ["addicted2salsa"]
1039
+ push.send "You are subscribed to Addicted2Salsa!"
1040
+
1041
+ # advanced targeting
1042
+ push = Parse::Push.new( {..where query constraints..} )
1043
+ # or use `where()`
1044
+ push.where :device_type.in => ['ios','android'], :location.near => some_geopoint
1045
+ push.alert = "Hello World!"
1046
+ push.sound = "soundfile.caf"
1047
+
1048
+ # additional payload data
1049
+ push.data = { uri: "app://deep_link_path" }
1050
+
1051
+ # Send the push
1052
+ push.send
1053
+
1054
+ ```
1055
+
1056
+ ## Webhooks
1057
+ Parse Parse allows you to receive Cloud Code webhooks on your own hosted server. The `Parse::Webhooks` class is a lightweight Rack application that routes incoming Cloud Code webhook requests and payloads to locally registered handlers. The payloads are `Parse::Payload` type of objects that represent that data that Parse sends webhook handlers. You can register any of the Cloud Code webhook trigger hooks (`beforeSave`, `afterSave`, `beforeDelete`, `afterDelete`) and function hooks.
1058
+
1059
+ ### Setup Cloud Code functions
1060
+ You can use the `route()` method to register handler blocks.
1061
+
1062
+ If a function block returns any value that is true for `blank?`, we will automatically return `true` as part of the response to the webhook. If an exception is raised inside the block, we will return the correct Parse error response with the value you provided.
1063
+
1064
+ ```ruby
1065
+ # Register handling the 'helloWorld' function.
1066
+ Parse::Webhooks.route(:function, :helloWorld) do |payload|
1067
+ # use the Parse::Payload payload object
1068
+ params = payload.params #function params
1069
+ name = params['name'].to_s
1070
+
1071
+ # will return proper error response
1072
+ raise "Missing argument 'name'." unless name.present?
1073
+ # return early
1074
+ "Hello #{name}!"
1075
+ end
1076
+
1077
+ # Advanced: you can register handlers through classes if you prefer
1078
+ Parse::Webhooks.route :function, :myFunc, MyClass.method(:my_func)
1079
+ ```
1080
+
1081
+ If you are creating `Parse::Object` subclasses, you may also register them there to keep common code and functionality centralized.
1082
+
1083
+ ```ruby
1084
+ class Song < Parse::Object
1085
+
1086
+ webhook :function, :mySongFunction do |payload|
1087
+ user = payload.user
1088
+ params = payload.params
1089
+ # ... do stuff ...
1090
+ true
1091
+ end
1092
+
1093
+ end
1094
+
1095
+ ```
1096
+
1097
+ ### Setup Cloud Code Triggers
1098
+ You can register webhooks to handle the different object triggers: `:before_save`, `:after_save`, `:before_delete` and `:after_delete`. While you can use `Parse::Webhooks.route` to register the trigger, we recommend keeping the code inside you model. The `payload` object, which is an instance of `Parse::Payload`
1099
+
1100
+ ```ruby
1101
+ # recommended way
1102
+ class Artist < Parse::Object
1103
+ # ... properties ...
1104
+
1105
+ # setup after save for Artist
1106
+ webhook :after_save do |payload|
1107
+ user = payload.user # Parse::User
1108
+ artist = payload.parse_object # Artist
1109
+ end
1110
+
1111
+ end
1112
+
1113
+ # or the explicit way
1114
+ Parse::Webhooks.route :after_save, "Artist" do |payload|
1115
+ user = payload.user # Parse::User
1116
+ artist = payload.parse_object # Artist
1117
+ end
1118
+ ```
1119
+
1120
+ For any `after_*` hook, return values are not needed since Parse does not utilize them. You may also register as many `after_save` or `after_delete` handlers as you prefer, all of them will be called.
1121
+
1122
+ `before_save` and `before_delete` hooks have special functionality. When an exception is thrown inside the provided block, the framework will return the correct error response to Parse with value provided to raise. Returning an error will prevent Parse from saving the object in the case of `before_save` and will prevent Parse from deleting the object when in a `before_delete`. In addition, for a `before_save`, the last value returned by the block will be the value returned in the success response. If the block returns nil or an `empty?` value, it will return `true` as the default response. You can also return a JSON object in a hash format to override the values that will be saved for the object. For more details, see [Cloud Code BeforeSave Webhooks](https://parse.com/docs/cloudcode/guide#cloud-code-advanced-beforesave-webhooks)
1123
+
1124
+ ```ruby
1125
+ # recommended way
1126
+ class Artist < Parse::Object
1127
+ property :name
1128
+ property :location, :geopoint
1129
+
1130
+ # setup after save for Artist
1131
+ webhook :before_save do |payload|
1132
+ user = payload.user # Parse::User
1133
+ artist = payload.parse_object # Artist
1134
+ # artist object will have dirty tracking information
1135
+
1136
+ # default San Diego
1137
+ artist.location ||= Parse::GeoPoint.new(32.82, -117.23)
1138
+
1139
+ if artist.name_changed?
1140
+ # .. do something if `name` has changed
1141
+ end
1142
+
1143
+ # return a special hash of changed values
1144
+ artist.payload_update
1145
+ end
1146
+
1147
+ webhook :before_delete do |payload|
1148
+ # prevent deleting Artist records
1149
+ raise "You can't delete an Artist"
1150
+ end
1151
+
1152
+ end
1153
+
1154
+ ```
1155
+
1156
+ ### Mounting Webhooks Application
1157
+ The app can be mounted like any regular Rack-based application.
1158
+
1159
+ ```ruby
1160
+ # Rack in config.ru
1161
+ map "/webhooks" do
1162
+ run Parse::Webhooks
1163
+ end
1164
+
1165
+ # Padrino (in apps.rb)
1166
+ Padrino.mount('Parse::Webhooks', :cascade => true).to('/webhooks')
1167
+
1168
+ # Rails
1169
+ RailsApp::Application.routes.draw do
1170
+ mount Parse::Webhooks, :at => '/webhooks'
1171
+ end
1172
+ ```
1173
+
1174
+ ### Register Webhooks
1175
+ Once you have locally setup all your trigger and function routes, you can write a small rake task to automatically register these hooks with your Parse application. To do this, you can configure a `HOOKS_URL` variable to be used as the endpoint. If you are using a service like Heroku, this would be the name of the heroku app url followed by your configured mount point.
1176
+
1177
+ ```ruby
1178
+ # ex. https://12345678.ngrok.com/webhooks
1179
+ HOOKS_URL = ENV["HOOKS_URL"]
1180
+
1181
+ # Register locally setup handlers with Parse
1182
+ task :register_hooks do
1183
+ # Parse.setup(....) if needed
1184
+ Parse::Webhooks.register_functions! HOOKS_URL
1185
+ Parse::Webhooks.register_triggers! HOOKS_URL
1186
+ end
1187
+
1188
+ # Remove all webhooks!
1189
+ task :remove_hooks do
1190
+ # Parse.setup(....) if needed
1191
+ Parse::Webhooks.remove_all_functions!
1192
+ Parse::Webhooks.remove_all_triggers!
1193
+ end
1194
+
1195
+ ```
1196
+
1197
+ ## Cloud Code Functions
1198
+ You can call on your defined Cloud Code functions using the `call_function()` method. The result will be `nil` in case of errors or the value of the `result` field in the Parse response.
1199
+
1200
+ ```ruby
1201
+ params = {}
1202
+ # use the explicit name of the function
1203
+ result = Parse.call_function 'functionName', params
1204
+
1205
+ # to get the raw Response object
1206
+ response = Parse.call_function 'functionName', params, raw: true
1207
+ response.result unless response.error?
1208
+ ```
1209
+
1210
+ ## Cloud Code Background Jobs
1211
+ You can trigger background jobs that you have configured in your Parse application as follows.
1212
+
1213
+ ```ruby
1214
+ params = {}
1215
+ # use explicit name of the job
1216
+ result = Parse.trigger_job :myJobName, params
1217
+
1218
+ # to get the raw Response object
1219
+ response = Parse.trigger_job :myJobName, params, raw: true
1220
+ response.result unless response.error?
1221
+ ```
1222
+
1223
+ ## Parse REST API Client
1224
+ While in most cases you do not have to work with `Parse::Client` directly, you can still utilize it for any raw requests that are not supported by the framework. We provide support for most of the [Parse REST API](https://parse.com/docs/rest/guide#quick-reference) endpoints as helper methods, however you can use the `request()` method to make your own API requests. Parse::Client will handle header authentication, request/response generation and caching.
1225
+
1226
+ ```ruby
1227
+ client = Parse::Client.new(application_id: <string>, api_key: <string>) do |conn|
1228
+ # .. optional: configure additional middleware
1229
+ end
1230
+
1231
+ # Use API helper methods...
1232
+ client.config
1233
+ client.create_object "Artist", {name: "Hector Lavoe"}
1234
+ client.call_function "myCloudFunction", { key: "value"}
1235
+
1236
+ # or use low-level request method
1237
+ client.request :get, "/1/users", query: {} , headers: {}
1238
+ client.request :post, "/1/users/<objectId>", body: {} , headers: {}
1239
+
1240
+ ```
1241
+
1242
+ ##### Options
1243
+ - **application_id**: Your Parse application identifier.
1244
+ - **api_key**: Your REST API key corresponding to the provided `application_id`.
1245
+ - **master_key**: The master secret key for the application. If this is provided, `api_key` may be unnecessary.
1246
+ - **logging**: A boolean value to add additional logging messages.
1247
+ - **cache**: A [Moneta](https://github.com/minad/moneta) cache store that can be used to cache API requests. We recommend use a cache store that supports native expires like [Redis](http://redis.io). For more information see `Parse::Middleware::Caching`. Disabled by default.
1248
+ - **expires**: When used with the `cache` option, sets the expiration time of cached API responses. The default is [3 seconds](https://parse.com/docs/cloudcode/guide#cloud-code-timeouts).
1249
+ - **adapter**: The connection adapter to use. Defaults to `Faraday.default_adapter`.
1250
+
1251
+ ### Request Caching
1252
+ For high traffic applications that may be performing several server tasks on similar objects, you may utilize request caching. Caching is provided by a the `Parse::Middleware::Caching` class which utilizes a [Moneta store](https://github.com/minad/moneta) object to cache GET url requests that have allowable status codes (ex. HTTP 200, 410, 301, etc). The cache entry for the url will be removed when it is either considered expired (based on the `expires` option) or if a non-GET request is made with the same url. Using this feature appropriately can dramatically reduce your API request usage.
1253
+
1254
+ ```ruby
1255
+ store = Moneta.new :Redis, url: 'redis://localhost:6379'
1256
+ # use a Redis cache store with an automatic expire of 10 seconds.
1257
+ Parse.setup(cache: store, expires: 10, ...)
1258
+
1259
+ user = Parse::User.first # request made
1260
+ same_user = Parse::User.first # cached result
1261
+
1262
+ ```
1263
+
1264
+ ## Installation
1265
+
1266
+ Add this line to your application's Gemfile:
1267
+
1268
+ ```ruby
1269
+ gem 'parse-stack'
1270
+ ```
1271
+
1272
+ or install it locally
1273
+
1274
+ ```ruby
1275
+ $ gem install parse-stack
1276
+ ```
1277
+
1278
+ ## Development
1279
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
1280
+
1281
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).