parse-stack 1.5.1 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changes.md +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
|