parse-stack 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changes.md +15 -1
- data/Gemfile.lock +10 -10
- data/README.md +23 -9
- data/bin/console +3 -0
- data/lib/parse/api/analytics.rb +1 -1
- data/lib/parse/api/objects.rb +1 -1
- data/lib/parse/api/users.rb +1 -1
- data/lib/parse/client.rb +77 -40
- data/lib/parse/client/caching.rb +9 -5
- data/lib/parse/client/protocol.rb +47 -0
- data/lib/parse/client/request.rb +66 -37
- data/lib/parse/client/response.rb +39 -21
- data/lib/parse/model/acl.rb +4 -9
- data/lib/parse/model/associations/belongs_to.rb +97 -9
- data/lib/parse/model/associations/collection_proxy.rb +89 -29
- data/lib/parse/model/associations/has_many.rb +301 -28
- data/lib/parse/model/associations/has_one.rb +98 -4
- data/lib/parse/model/associations/pointer_collection_proxy.rb +48 -16
- data/lib/parse/model/associations/relation_collection_proxy.rb +61 -36
- data/lib/parse/model/bytes.rb +11 -5
- data/lib/parse/model/classes/installation.rb +50 -3
- data/lib/parse/model/classes/role.rb +7 -2
- data/lib/parse/model/classes/session.rb +21 -4
- data/lib/parse/model/classes/user.rb +122 -22
- data/lib/parse/model/core/actions.rb +7 -3
- data/lib/parse/model/core/properties.rb +14 -13
- data/lib/parse/model/core/querying.rb +16 -10
- data/lib/parse/model/core/schema.rb +2 -3
- data/lib/parse/model/date.rb +18 -12
- data/lib/parse/model/file.rb +77 -19
- data/lib/parse/model/geopoint.rb +70 -12
- data/lib/parse/model/model.rb +84 -8
- data/lib/parse/model/object.rb +225 -94
- data/lib/parse/model/pointer.rb +94 -13
- data/lib/parse/model/push.rb +76 -4
- data/lib/parse/query.rb +356 -41
- data/lib/parse/query/constraints.rb +399 -29
- data/lib/parse/query/ordering.rb +21 -8
- data/lib/parse/stack.rb +1 -0
- data/lib/parse/stack/version.rb +2 -1
- data/lib/parse/webhooks.rb +0 -24
- data/lib/parse/webhooks/payload.rb +54 -1
- data/lib/parse/webhooks/registration.rb +13 -2
- metadata +2 -2
@@ -7,34 +7,56 @@ require 'active_support/inflector'
|
|
7
7
|
require 'active_support/core_ext/object'
|
8
8
|
require_relative '../pointer'
|
9
9
|
|
10
|
-
# A collection proxy is a special type of array wrapper that will allow us to
|
11
|
-
# notify the parent object about changes to the array. We use a delegate pattern
|
12
|
-
# to send notifications to the parent whenever the content of the internal array changes.
|
13
|
-
# The main requirement to using the proxy is to provide the list of initial items if any,
|
14
|
-
# the owner to be notified and the name of the attribute 'key'. With that, anytime the array
|
15
|
-
# will change, we will notify the delegate by sending :'key'_will_change! . The proxy can also
|
16
|
-
# be lazy when fetching the contents of the collection. Whenever the collection is accessed and
|
17
|
-
# the list is in a "not loaded" state (empty and loaded == false), we will send :'key_fetch!' to the delegate in order to
|
18
|
-
# populate the collection.
|
19
|
-
module Parse
|
20
10
|
|
11
|
+
module Parse
|
12
|
+
# We use a delegate pattern to send notifications to the parent whenever the content of the internal array changes.
|
13
|
+
# The main requirement to using the proxy is to provide the list of initial items if any,
|
14
|
+
# the owner to be notified and the name of the attribute 'key'. With that, anytime the array
|
15
|
+
# will change, we will notify the delegate by sending :'key'_will_change! . The proxy can also
|
16
|
+
# be lazy when fetching the contents of the collection. Whenever the collection is accessed and
|
17
|
+
# the list is in a "not loaded" state (empty and loaded == false), we will send :'key_fetch!' to the delegate in order to
|
18
|
+
# populate the collection.
|
19
|
+
|
20
|
+
# A CollectionProxy is a special type of array wrapper that notifies a delegate
|
21
|
+
# object about changes to the array in order to perform dirty tracking. This is
|
22
|
+
# used for all Array properties in Parse::Objects.
|
21
23
|
class CollectionProxy
|
22
24
|
include ::ActiveModel::Model
|
23
25
|
include ::ActiveModel::Dirty
|
24
26
|
include ::Enumerable
|
25
|
-
|
27
|
+
# @!attribute [rw] collection
|
28
|
+
# The internal backing store of the collection.
|
29
|
+
# @return [Array]
|
30
|
+
|
31
|
+
# @!attribute [r] delegate
|
32
|
+
# The object to be notified of changes to the collection.
|
33
|
+
# @return [Object]
|
34
|
+
|
35
|
+
# @!attribute [rw] loaded
|
36
|
+
# @return [Boolean] true/false whether the collection has been loaded.
|
37
|
+
|
38
|
+
# @!attribute [r] parse_class
|
39
|
+
# For some subclasses, this helps typecast the items in the collection.
|
40
|
+
# @return [String]
|
41
|
+
|
42
|
+
# @!attribute [r] key
|
43
|
+
# the name of the property key to use when sending notifications for _will_change! and _fetch!
|
44
|
+
# @return [String]
|
45
|
+
|
46
|
+
attr_accessor :collection, :delegate, :loaded, :parse_class
|
26
47
|
attr_reader :delegate, :key
|
27
|
-
|
48
|
+
|
28
49
|
# This is to use dirty tracking within the proxy
|
29
50
|
define_attribute_methods :collection
|
30
|
-
|
31
|
-
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
51
|
+
|
52
|
+
# Create a new CollectionProxy instance.
|
53
|
+
# @param collection [Array] the initial items to add to the collection.
|
54
|
+
# @param delegate [Object] the owner of the object that will receive the notifications.
|
55
|
+
# @param key [Symbol] the name of the key to use when sending notifications for _will_change! and _fetch!
|
56
|
+
# @param parse_class [String] (Optional) the Parse class type are the items of the collection.
|
57
|
+
# This is used to typecast the objects in the array to a particular Parse Object type.
|
58
|
+
# @see PointerCollectionProxy
|
59
|
+
# @see RelationCollectionProxy
|
38
60
|
def initialize(collection = nil, delegate: nil, key: nil, parse_class: nil)
|
39
61
|
@delegate = delegate
|
40
62
|
@key = key.to_sym if key.present?
|
@@ -43,21 +65,27 @@ module Parse
|
|
43
65
|
@parse_class = parse_class
|
44
66
|
end
|
45
67
|
|
68
|
+
# true if the collection has been loaded
|
46
69
|
def loaded?
|
47
70
|
@loaded
|
48
71
|
end
|
49
72
|
|
50
|
-
#
|
73
|
+
# Forward a method call to the delegate.
|
74
|
+
# @param method [Symbol] the name of the method to forward
|
75
|
+
# @param params [Object] method parameters
|
76
|
+
# @return [Object] the return value from the forwarded method.
|
51
77
|
def forward(method, params = nil)
|
52
78
|
return unless @delegate.present? && @delegate.respond_to?(method)
|
53
79
|
params.nil? ? @delegate.send(method) : @delegate.send(method, params)
|
54
80
|
end
|
55
81
|
|
82
|
+
# Reset the state of the collection.
|
56
83
|
def reset!
|
57
84
|
@loaded = false
|
58
85
|
clear
|
59
86
|
end
|
60
87
|
|
88
|
+
# @return [Boolean] true if two collection proxies have similar items.
|
61
89
|
def ==(other_list)
|
62
90
|
if other_list.is_a?(Array)
|
63
91
|
return @collection == other_list
|
@@ -66,25 +94,32 @@ module Parse
|
|
66
94
|
end
|
67
95
|
end
|
68
96
|
|
97
|
+
# Reload and restore the collection to its original set of items.
|
69
98
|
def reload!
|
70
99
|
reset!
|
71
100
|
collection #force reload
|
72
101
|
end
|
73
102
|
|
103
|
+
# clear all items in the collection
|
74
104
|
def clear
|
75
105
|
@collection.clear
|
76
106
|
end
|
77
107
|
|
78
|
-
|
108
|
+
# @return [Array]
|
109
|
+
def to_a
|
79
110
|
collection.to_a
|
80
|
-
end; alias_method :
|
111
|
+
end; alias_method :to_ary, :to_a
|
81
112
|
|
113
|
+
# @!attribute [rw] collection
|
114
|
+
# Set the internal collection of items without dirty tracking or
|
115
|
+
# change notifications.
|
82
116
|
def set_collection!(list)
|
83
117
|
@collection = list
|
84
118
|
end
|
85
119
|
|
86
|
-
#
|
87
|
-
#
|
120
|
+
# @!attribute [rw] collection
|
121
|
+
# The internal backing store of the collection.
|
122
|
+
# @return [Array] contents of the collection.
|
88
123
|
def collection
|
89
124
|
if @collection.empty? && @loaded == false
|
90
125
|
@collection = forward( :"#{@key}_fetch!" ) || @collection || []
|
@@ -99,7 +134,8 @@ module Parse
|
|
99
134
|
@collection = c
|
100
135
|
end
|
101
136
|
|
102
|
-
#
|
137
|
+
# Add items to the collection
|
138
|
+
# @param items [Array] items to add
|
103
139
|
def add(*items)
|
104
140
|
notify_will_change! if items.count > 0
|
105
141
|
items.each do |item|
|
@@ -109,6 +145,7 @@ module Parse
|
|
109
145
|
end; alias_method :push, :add
|
110
146
|
|
111
147
|
# Remove items from the collection
|
148
|
+
# @param items [Array] items to remove
|
112
149
|
def remove(*items)
|
113
150
|
notify_will_change! if items.count > 0
|
114
151
|
items.each do |item|
|
@@ -117,24 +154,37 @@ module Parse
|
|
117
154
|
@collection
|
118
155
|
end; alias_method :delete, :remove
|
119
156
|
|
157
|
+
# Atomically adds all items from the array.
|
158
|
+
# This request is sent directly to the Parse backend.
|
159
|
+
# @param items [Array] items to uniquely add
|
160
|
+
# @see #add_unique!
|
120
161
|
def add!(*items)
|
121
162
|
return false unless @delegate.respond_to?(:op_add!)
|
122
163
|
@delegate.send :op_add!, @key, items.flatten
|
123
164
|
reset!
|
124
165
|
end
|
125
166
|
|
167
|
+
# Atomically adds all items from the array that are not already part of the collection.
|
168
|
+
# This request is sent directly to the Parse backend.
|
169
|
+
# @param items [Array] items to uniquely add
|
170
|
+
# @see #add!
|
126
171
|
def add_unique!(*items)
|
127
172
|
return false unless @delegate.respond_to?(:op_add_unique!)
|
128
173
|
@delegate.send :op_add_unique!, @key, items.flatten
|
129
174
|
reset!
|
130
175
|
end
|
131
176
|
|
177
|
+
# Atomically deletes all items from the array. This request is sent
|
178
|
+
# directly to the Parse backend.
|
179
|
+
# @param items [Array] items to remove
|
132
180
|
def remove!(*items)
|
133
181
|
return false unless @delegate.respond_to?(:op_remove!)
|
134
182
|
@delegate.send :op_remove!, @key, items.flatten
|
135
183
|
reset!
|
136
184
|
end
|
137
185
|
|
186
|
+
# Atomically deletes all items in the array, and marks the field as `undefined` directly
|
187
|
+
# with the Parse server. This request is sent directly to the Parse backend.
|
138
188
|
def destroy!
|
139
189
|
return false unless @delegate.respond_to?(:op_destroy!)
|
140
190
|
@delegate.send :op_destroy!, @key
|
@@ -143,30 +193,39 @@ module Parse
|
|
143
193
|
reset!
|
144
194
|
end
|
145
195
|
|
196
|
+
# Locally restores previous attributes (not from the persistent store)
|
146
197
|
def rollback!
|
147
198
|
restore_attributes
|
148
199
|
end
|
149
200
|
|
201
|
+
# clears all dirty tracked information.
|
150
202
|
def clear_changes!
|
151
203
|
clear_changes_information
|
152
204
|
end
|
153
205
|
|
206
|
+
# mark that collection changes where applied, which clears dirty tracking.
|
154
207
|
def changes_applied!
|
155
208
|
changes_applied
|
156
209
|
end
|
157
210
|
|
211
|
+
# @param args [Hash] arguments to pass to Array#first.
|
212
|
+
# @return [Object] the first item in the collection
|
158
213
|
def first(*args)
|
159
214
|
collection.first(*args)
|
160
215
|
end
|
161
216
|
|
217
|
+
# @return [Object] the second item in the collection
|
162
218
|
def second
|
163
219
|
collection.second
|
164
220
|
end
|
165
221
|
|
222
|
+
# @param args [Hash] arguments to pass to Array#last.
|
223
|
+
# @return [Object] the last item in the collection
|
166
224
|
def last(*args)
|
167
225
|
collection.last(*args)
|
168
226
|
end
|
169
227
|
|
228
|
+
# @return [Integer] number of items in the collection.
|
170
229
|
def count
|
171
230
|
collection.count
|
172
231
|
end
|
@@ -175,24 +234,25 @@ module Parse
|
|
175
234
|
collection.as_json(args)
|
176
235
|
end
|
177
236
|
|
237
|
+
# true if the collection is empty.
|
178
238
|
def empty?
|
179
239
|
collection.empty?
|
180
240
|
end
|
181
241
|
|
182
|
-
#
|
242
|
+
# Append items to the collection
|
183
243
|
def <<(*list)
|
184
244
|
if list.count > 0
|
185
245
|
notify_will_change!
|
186
246
|
list.flatten.each { |e| collection.push(e) }
|
187
247
|
end
|
188
248
|
end
|
189
|
-
|
249
|
+
|
250
|
+
# Notifies the delegate that the collection changed.
|
190
251
|
def notify_will_change!
|
191
252
|
collection_will_change!
|
192
253
|
forward "#{@key}_will_change!"
|
193
254
|
end
|
194
255
|
|
195
|
-
# supported iterator
|
196
256
|
def each
|
197
257
|
return collection.enum_for(:each) unless block_given?
|
198
258
|
collection.each &Proc.new
|
@@ -10,29 +10,291 @@ module Parse
|
|
10
10
|
|
11
11
|
module Associations
|
12
12
|
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
13
|
+
# Parse has many ways to implement one-to-many and many-to-many
|
14
|
+
# associations: `Array`, `Parse Relation` or through a `Query`. How you decide
|
15
|
+
# to implement your associations, will affect how `has_many` works in
|
16
|
+
# Parse-Stack. Parse natively supports one-to-many and many-to-many
|
17
|
+
# relationships using `Array` and `Relations`, as described in
|
18
|
+
# {https://parseplatform.github.io/docs/js/guide/#relational-data Parse Relational Data}.
|
19
|
+
# Both of these methods require you define a specific column type in your
|
20
|
+
# Parse table that will be used to store information about the association.
|
21
|
+
#
|
22
|
+
# In addition to `Array` and `Relation`, Parse-Stack also implements the
|
23
|
+
# standard `has_many` behavior prevalent in other frameworks through a query
|
24
|
+
# where the associated class contains a foreign pointer to the local class,
|
25
|
+
# usually the inverse of a `belongs_to`. This requires that the associated
|
26
|
+
# class has a defined column that contains a pointer the refers to the
|
27
|
+
# defining class.
|
28
|
+
#
|
29
|
+
# *Query-Approach*
|
30
|
+
#
|
31
|
+
# In this `Query` implementation, a `has_many` association for a Parse class
|
32
|
+
# requires that another Parse class will have a foreign pointer that refers
|
33
|
+
# to instances of this class. This is the standard way that `has_many`
|
34
|
+
# relationships work in most databases systems. This is usually the case when
|
35
|
+
# you have a class that has a `belongs_to` relationship to instances of the
|
36
|
+
# local class.
|
37
|
+
#
|
38
|
+
# In the example below, many songs belong to a specific artist. We set this
|
39
|
+
# association by setting {Associations::BelongsTo.belongs_to :belongs_to} relationship from `Song` to `Artist`.
|
40
|
+
# Knowing there is a column in `Song` that points to instances of an `Artist`,
|
41
|
+
# we can setup a `has_many` association to `Song` instances in the `Artist`
|
42
|
+
# class. Doing so will generate a helper query method on the `Artist` instance
|
43
|
+
# objects.
|
44
|
+
#
|
45
|
+
# class Song < Parse::Object
|
46
|
+
# property :released, :date
|
47
|
+
# # this class will have a pointer column to an Artist
|
48
|
+
# belongs_to :artist
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# class Artist < Parse::Object
|
52
|
+
# has_many :songs
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# artist = Artist.first
|
56
|
+
#
|
57
|
+
# artist.songs # => [all songs belonging to artist]
|
58
|
+
# # equivalent: Song.all(artist: artist)
|
59
|
+
#
|
60
|
+
# # filter also by release date
|
61
|
+
# artist.songs(:released.after => 1.year.ago)
|
62
|
+
# # equivalent: Song.all(artist: artist, :released.after => 1.year.ago)
|
63
|
+
#
|
64
|
+
# In order to modify the associated objects (ex. `songs`), you must modify
|
65
|
+
# their corresponding `belongs_to` field (in this case `song.artist`), to
|
66
|
+
# another record and save it.
|
67
|
+
#
|
68
|
+
# Options for `has_many` using the `Query` approach are `:as` and `:field`. The
|
69
|
+
# `:as` option behaves similarly to the {Associations::BelongsTo.belongs_to :belongs_to} counterpart. The
|
70
|
+
# `:field` option can be used to override the derived column name located
|
71
|
+
# in the foreign class. The default value for `:field` is the columnized
|
72
|
+
# version of the Parse subclass `parse_class` method.
|
73
|
+
#
|
74
|
+
# class Parse::User
|
75
|
+
# # since the foreign column name is :agent
|
76
|
+
# has_many :artists, field: :agent
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# class Artist < Parse::Object
|
80
|
+
# belongs_to :manager, as: :user, field: :agent
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# artist.manager # => Parse::User object
|
84
|
+
#
|
85
|
+
# user.artists # => [artists where :agent column is user]
|
86
|
+
#
|
87
|
+
#
|
88
|
+
# When using this approach, you may also employ the use of scopes to filter the particular data from the `has_many` association.
|
89
|
+
#
|
90
|
+
# class Artist
|
91
|
+
# has_many :songs, ->(timeframe) { where(:created_at.after => timeframe) }
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# artist.songs(6.months.ago)
|
95
|
+
# # => [artist's songs created in the last 6 months]
|
96
|
+
#
|
97
|
+
# You may also call property methods in your scopes related to the instance.
|
98
|
+
# You also have access to the instance object for the local class
|
99
|
+
# through a special "*i*" method in the scope.
|
100
|
+
#
|
101
|
+
# class Concert
|
102
|
+
# property :city
|
103
|
+
# belongs_to :artist
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# class Artist
|
107
|
+
# property :hometown
|
108
|
+
# has_many :local_concerts, -> { where(:city => hometown) }, as: :concerts
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# # assume
|
112
|
+
# artist.hometown = "San Diego"
|
113
|
+
#
|
114
|
+
# # artist's concerts in their hometown of 'San Diego'
|
115
|
+
# artist.local_concerts
|
116
|
+
# # equivalent: Concert.all(artist: artist, city: artist.hometown)
|
117
|
+
#
|
118
|
+
# You may also omit the association completely, as rely on the scope to fetch the
|
119
|
+
# associated records. This makes the `has_many` work as a macro query setting the :scope_only
|
120
|
+
# option to true:
|
121
|
+
#
|
122
|
+
# class Author < Parse::Object
|
123
|
+
# property :name
|
124
|
+
# has_many :posts, ->{ where :tags.in => name.downcase }, scope_only: true
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# class Post < Parse::Object
|
128
|
+
# property :tags, :array
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# author.posts # => Posts where author's name is a tag
|
132
|
+
# # equivalent: Post.all( :tags.in => artist.name.downcase )
|
133
|
+
#
|
134
|
+
# *Array-Approach*
|
135
|
+
#
|
136
|
+
# In the `Array` implemenatation, you can designate a column to be of `Array`
|
137
|
+
# type that contains a list of Parse pointers. Parse-Stack supports this by
|
138
|
+
# passing the option `through: :array` to the `has_many` method. If you use
|
139
|
+
# this approach, it is recommended that this is used for associations where
|
140
|
+
# the quantity is less than 100 in order to maintain query and fetch
|
141
|
+
# performance. You would be in charge of maintaining the array with the proper
|
142
|
+
# list of Parse pointers that are associated to the object. Parse-Stack does
|
143
|
+
# help by wrapping the array in a {Parse::PointerCollectionProxy} which provides dirty tracking.
|
144
|
+
#
|
145
|
+
# class Artist < Parse::Object
|
146
|
+
# end
|
147
|
+
#
|
148
|
+
# class Band < Parse::Object
|
149
|
+
# has_many :artists, through: :array
|
150
|
+
# end
|
151
|
+
#
|
152
|
+
# artist = Artist.first
|
153
|
+
#
|
154
|
+
# # find all bands that contain this artist
|
155
|
+
# bands = Band.all( :artists.in => [artist.pointer] )
|
156
|
+
#
|
157
|
+
# band = bands.first
|
158
|
+
# band.artists # => [array of Artist pointers]
|
159
|
+
#
|
160
|
+
# # remove artists
|
161
|
+
# band.artists.remove artist
|
162
|
+
#
|
163
|
+
# # add artist
|
164
|
+
# band.artists.add artist
|
165
|
+
#
|
166
|
+
# # save changes
|
167
|
+
# band.save
|
168
|
+
#
|
169
|
+
# *ParseRelation-Approach*
|
170
|
+
#
|
171
|
+
# Other than the use of arrays, Parse supports native one-to-many and many-to-many
|
172
|
+
# associations through what is referred to as a {https://parseplatform.github.io/docs/js/guide/#many-to-many-relationships Parse Relation}.
|
173
|
+
# This is implemented by defining a column to be of type `Relation` which
|
174
|
+
# refers to a foreign class. Parse-Stack supports this by passing the
|
175
|
+
# `through: :relation` option to the `has_many` method. Designating a column
|
176
|
+
# as a Parse relation to another class type, will create a one-way intermediate
|
177
|
+
# "join-list" between the local class and the foreign class. One important
|
178
|
+
# distinction of this compared to other types of data stores (ex. PostgresSQL) is that:
|
179
|
+
#
|
180
|
+
# *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 by adding two Parse relation columns in each class, or by creating a separate class (ex. `ArtistBands`) that is used as a join table.
|
181
|
+
#
|
182
|
+
# *2*. Querying the relation is actually performed against the implicit join table, not the local one.
|
183
|
+
#
|
184
|
+
# *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.
|
185
|
+
#
|
186
|
+
# The Parse documentation provides more details on associations, see {http://parseplatform.github.io/docs/ios/guide/#relations Parse Relations Guide}.
|
187
|
+
# Parse-Stack will handle the work for (2) and (3) automatically.
|
188
|
+
#
|
189
|
+
# In the example below, a `Band` can have thousands of `Fans`. We setup a
|
190
|
+
# `Relation<Fan>` column in the `Band` class that references the `Fan` class.
|
191
|
+
# Parse-Stack provides methods to manage the relationship under the {Parse::RelationCollectionProxy}
|
192
|
+
# class.
|
193
|
+
#
|
194
|
+
# class Fan < Parse::Object
|
195
|
+
# # .. lots of properties ...
|
196
|
+
# property :location, :geopoint
|
197
|
+
# end
|
198
|
+
#
|
199
|
+
# class Band < Parse::Object
|
200
|
+
# has_many :fans, through: :relation
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# band = Band.first
|
204
|
+
#
|
205
|
+
# # the number of fans in the relation
|
206
|
+
# band.fans.count
|
207
|
+
#
|
208
|
+
# # get the first object in relation
|
209
|
+
# fan = bands.fans.first # => Parse::User object
|
210
|
+
#
|
211
|
+
# # use `add` or `remove` to modify relations
|
212
|
+
# band.fans.add user
|
213
|
+
# bands.fans.remove user
|
214
|
+
#
|
215
|
+
# # updates the relation as well as changes to `band`
|
216
|
+
# band.fans.save
|
217
|
+
#
|
218
|
+
# # Find 50 fans who are near San Diego, CA
|
219
|
+
# downtown = Parse::GeoPoint.new(32.82, -117.23)
|
220
|
+
# fans = band.fans.all :location.near => downtown
|
221
|
+
#
|
222
|
+
# You can perform atomic additions and removals of objects from `has_many`
|
223
|
+
# relations. Parse allows this by providing a specific atomic operation
|
224
|
+
# request. You can use the methods below to perform these types of atomic
|
225
|
+
# operations. The operation is performed directly on Parse server
|
226
|
+
# and not on your instance object.
|
227
|
+
#
|
228
|
+
# # atomically add/remove
|
229
|
+
# band.artists.add! objects # { __op: :AddUnique }
|
230
|
+
# band.artists.remove! objects # { __op: :AddUnique }
|
231
|
+
#
|
232
|
+
# # atomically add unique Artist
|
233
|
+
# band.artists.add_unique! objects # { __op: :AddUnique }
|
234
|
+
#
|
235
|
+
# # atomically add/remove relations
|
236
|
+
# band.fans.add! users # { __op: :Add }
|
237
|
+
# band.fans.remove! users # { __op: :Remove }
|
238
|
+
#
|
239
|
+
# # atomically perform a delete operation on this field name
|
240
|
+
# # this should set it as `undefined`.
|
241
|
+
# band.op_destroy!("category") # { __op: :Delete }
|
242
|
+
#
|
31
243
|
module HasMany
|
244
|
+
|
245
|
+
# @!attribute [rw] self.relations
|
246
|
+
# A hash mapping of all has_many associations that use the ParseRelation implementation.
|
247
|
+
# @return [Hash]
|
248
|
+
|
249
|
+
# Define a one-to-many or many-to-many association between the local model and a foreign class.
|
250
|
+
# Options for `has_many` are the same as the {Associations::BelongsTo.belongs_to} counterpart with
|
251
|
+
# support for `:required`, `:as` and `:field`. It has additional options.
|
252
|
+
#
|
253
|
+
# @!method self.has_many(key, scope = nil, opts = {})
|
254
|
+
# @param [Symbol] key The pluralized version of the foreign class. Using the :query method,
|
255
|
+
# this implies the name of the foreign column that a pointer to this record.
|
256
|
+
# Using the :array or :relation method, this implies the name of the local
|
257
|
+
# column that contains either an array of Parse::Pointers in the case of :array,
|
258
|
+
# or the Parse Relation, in the case of :relation.
|
259
|
+
# @param [Proc] scope Only applicable using :query. A proc that can customize the query by applying
|
260
|
+
# additional constraints when fetching the associated records. Works similarly as
|
261
|
+
# ActiveModel associations described in section {http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html Customizing the Query}
|
262
|
+
# @option opts [Symbol] :through The type of implementation to use: :query (default), :array or :relation.
|
263
|
+
# If set to `:array`, it defines the column in Parse as being an array of
|
264
|
+
# Parse pointer objects and will be managed locally using a {Parse::PointerCollectionProxy}.
|
265
|
+
# If set to `:relation`, it defines a column of type Parse Relation with
|
266
|
+
# the foreign class and will be managed locally using a {Parse::RelationCollectionProxy}.
|
267
|
+
# If set to `:query`, no storage is required on the local class as the
|
268
|
+
# associated records will be fetched using a Parse query.
|
269
|
+
# @option opts [Symbol] :field override the name of the remote column to use when fetching the association.
|
270
|
+
# When using through :query, this is the column name of the remote column
|
271
|
+
# of the foreign class that will be used for matching. When using :array,
|
272
|
+
# this is the name of the remote column of the local class that contains
|
273
|
+
# an array of pointers to the foreign class. When using :relation, this
|
274
|
+
# is the name of the remote column of the local class that contains the Parse Relation.
|
275
|
+
# @option opts [Symbol] :as override the inferred Parse::Object subclass of the association.
|
276
|
+
# By default this is inferred as the singularized camel case version of
|
277
|
+
# the key parameter. This option allows you to override the typecast of
|
278
|
+
# foreign Parse model of the association, while allowing you to have a
|
279
|
+
# different accessor name.
|
280
|
+
# @example
|
281
|
+
# has_many :fans, as: :users, through: :relation, field: "awesomeFans"
|
282
|
+
# has_many :songs
|
283
|
+
# has_many :likes, as: :users, through: :relation
|
284
|
+
# has_many :artists, field: "managedArtists"
|
285
|
+
#
|
286
|
+
# @return [Array<Parse::Object>] if through :query
|
287
|
+
# @return [PointerCollectionProxy] if through :array
|
288
|
+
# @return [RelationCollectionProxy] if through :relation
|
289
|
+
# @see PointerCollectionProxy
|
290
|
+
# @see RelationCollectionProxy
|
291
|
+
|
292
|
+
# @!visibility private
|
32
293
|
def self.included(base)
|
33
294
|
base.extend(ClassMethods)
|
34
295
|
end
|
35
296
|
|
297
|
+
# @!visibility private
|
36
298
|
module ClassMethods
|
37
299
|
attr_accessor :relations
|
38
300
|
def relations
|
@@ -89,17 +351,24 @@ module Parse
|
|
89
351
|
query.conditions(*args)
|
90
352
|
end
|
91
353
|
|
92
|
-
query.define_singleton_method(:method_missing) do |m, *args, &
|
354
|
+
query.define_singleton_method(:method_missing) do |m, *args, &chained_block|
|
93
355
|
klass = Parse::Model.find_class klassName
|
356
|
+
|
94
357
|
if klass.present? && klass.respond_to?(m)
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
358
|
+
|
359
|
+
klass_scope = klass.send(m, *args) # blocks only passed to final result set
|
360
|
+
return klass_scope unless klass_scope.is_a?(Parse::Query)
|
361
|
+
# merge constraints
|
362
|
+
add_constraints( klass_scope.constraints )
|
363
|
+
# if a block was passed, execute the query, otherwise return the query
|
364
|
+
return chained_block.present? ? results(&chained_block) : self
|
99
365
|
end
|
100
|
-
|
366
|
+
results.send(m, *args, &chained_block)
|
101
367
|
end
|
102
368
|
|
369
|
+
query.define_singleton_method(:to_s) { self.results.to_s }
|
370
|
+
query.define_singleton_method(:inspect) { self.results.to_a.inspect }
|
371
|
+
|
103
372
|
return query if block.nil?
|
104
373
|
query.results(&block)
|
105
374
|
end
|
@@ -255,13 +524,16 @@ module Parse
|
|
255
524
|
end # has_many_array
|
256
525
|
end #ClassMethods
|
257
526
|
|
258
|
-
#
|
527
|
+
# A hash list of all has_many associations that use a Parse Relation.
|
528
|
+
# @return [Hash]
|
529
|
+
# @see Associations::HasMany.relations
|
259
530
|
def relations
|
260
531
|
self.class.relations
|
261
532
|
end
|
262
533
|
|
263
|
-
#
|
264
|
-
#
|
534
|
+
# A hash of all the relation changes that have been performed on this
|
535
|
+
# instance. This is only used when the association uses Parse Relations.
|
536
|
+
# @return [Hash]
|
265
537
|
def relation_updates
|
266
538
|
h = {}
|
267
539
|
changed.each do |key|
|
@@ -272,7 +544,8 @@ module Parse
|
|
272
544
|
h
|
273
545
|
end
|
274
546
|
|
275
|
-
# true if
|
547
|
+
# @return [Boolean] true if there are pending relational changes for
|
548
|
+
# associations defined using Parse Relations.
|
276
549
|
def relation_changes?
|
277
550
|
changed.any? { |key| relations[key.to_sym] }
|
278
551
|
end
|