parse-stack 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +77 -0
- data/LICENSE +20 -0
- data/README.md +1281 -0
- data/Rakefile +12 -0
- data/bin/console +20 -0
- data/bin/server +10 -0
- data/bin/setup +7 -0
- data/lib/parse/api/all.rb +13 -0
- data/lib/parse/api/analytics.rb +16 -0
- data/lib/parse/api/apps.rb +37 -0
- data/lib/parse/api/batch.rb +148 -0
- data/lib/parse/api/cloud_functions.rb +18 -0
- data/lib/parse/api/config.rb +22 -0
- data/lib/parse/api/files.rb +21 -0
- data/lib/parse/api/hooks.rb +68 -0
- data/lib/parse/api/objects.rb +77 -0
- data/lib/parse/api/push.rb +16 -0
- data/lib/parse/api/schemas.rb +25 -0
- data/lib/parse/api/sessions.rb +11 -0
- data/lib/parse/api/users.rb +43 -0
- data/lib/parse/client.rb +225 -0
- data/lib/parse/client/authentication.rb +59 -0
- data/lib/parse/client/body_builder.rb +69 -0
- data/lib/parse/client/caching.rb +103 -0
- data/lib/parse/client/protocol.rb +15 -0
- data/lib/parse/client/request.rb +43 -0
- data/lib/parse/client/response.rb +116 -0
- data/lib/parse/model/acl.rb +182 -0
- data/lib/parse/model/associations/belongs_to.rb +121 -0
- data/lib/parse/model/associations/collection_proxy.rb +202 -0
- data/lib/parse/model/associations/has_many.rb +218 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +71 -0
- data/lib/parse/model/associations/relation_collection_proxy.rb +134 -0
- data/lib/parse/model/bytes.rb +50 -0
- data/lib/parse/model/core/actions.rb +499 -0
- data/lib/parse/model/core/properties.rb +377 -0
- data/lib/parse/model/core/querying.rb +100 -0
- data/lib/parse/model/core/schema.rb +92 -0
- data/lib/parse/model/date.rb +50 -0
- data/lib/parse/model/file.rb +127 -0
- data/lib/parse/model/geopoint.rb +98 -0
- data/lib/parse/model/model.rb +120 -0
- data/lib/parse/model/object.rb +347 -0
- data/lib/parse/model/pointer.rb +106 -0
- data/lib/parse/model/push.rb +99 -0
- data/lib/parse/query.rb +378 -0
- data/lib/parse/query/constraint.rb +130 -0
- data/lib/parse/query/constraints.rb +176 -0
- data/lib/parse/query/operation.rb +66 -0
- data/lib/parse/query/ordering.rb +49 -0
- data/lib/parse/stack.rb +11 -0
- data/lib/parse/stack/version.rb +5 -0
- data/lib/parse/webhooks.rb +228 -0
- data/lib/parse/webhooks/payload.rb +115 -0
- data/lib/parse/webhooks/registration.rb +139 -0
- data/parse-stack.gemspec +45 -0
- 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
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).
|