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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +15 -1
  3. data/Gemfile.lock +10 -10
  4. data/README.md +23 -9
  5. data/bin/console +3 -0
  6. data/lib/parse/api/analytics.rb +1 -1
  7. data/lib/parse/api/objects.rb +1 -1
  8. data/lib/parse/api/users.rb +1 -1
  9. data/lib/parse/client.rb +77 -40
  10. data/lib/parse/client/caching.rb +9 -5
  11. data/lib/parse/client/protocol.rb +47 -0
  12. data/lib/parse/client/request.rb +66 -37
  13. data/lib/parse/client/response.rb +39 -21
  14. data/lib/parse/model/acl.rb +4 -9
  15. data/lib/parse/model/associations/belongs_to.rb +97 -9
  16. data/lib/parse/model/associations/collection_proxy.rb +89 -29
  17. data/lib/parse/model/associations/has_many.rb +301 -28
  18. data/lib/parse/model/associations/has_one.rb +98 -4
  19. data/lib/parse/model/associations/pointer_collection_proxy.rb +48 -16
  20. data/lib/parse/model/associations/relation_collection_proxy.rb +61 -36
  21. data/lib/parse/model/bytes.rb +11 -5
  22. data/lib/parse/model/classes/installation.rb +50 -3
  23. data/lib/parse/model/classes/role.rb +7 -2
  24. data/lib/parse/model/classes/session.rb +21 -4
  25. data/lib/parse/model/classes/user.rb +122 -22
  26. data/lib/parse/model/core/actions.rb +7 -3
  27. data/lib/parse/model/core/properties.rb +14 -13
  28. data/lib/parse/model/core/querying.rb +16 -10
  29. data/lib/parse/model/core/schema.rb +2 -3
  30. data/lib/parse/model/date.rb +18 -12
  31. data/lib/parse/model/file.rb +77 -19
  32. data/lib/parse/model/geopoint.rb +70 -12
  33. data/lib/parse/model/model.rb +84 -8
  34. data/lib/parse/model/object.rb +225 -94
  35. data/lib/parse/model/pointer.rb +94 -13
  36. data/lib/parse/model/push.rb +76 -4
  37. data/lib/parse/query.rb +356 -41
  38. data/lib/parse/query/constraints.rb +399 -29
  39. data/lib/parse/query/ordering.rb +21 -8
  40. data/lib/parse/stack.rb +1 -0
  41. data/lib/parse/stack/version.rb +2 -1
  42. data/lib/parse/webhooks.rb +0 -24
  43. data/lib/parse/webhooks/payload.rb +54 -1
  44. data/lib/parse/webhooks/registration.rb +13 -2
  45. 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
- attr_accessor :collection, :delegate, :loaded
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
- attr_accessor :parse_class
48
+
28
49
  # This is to use dirty tracking within the proxy
29
50
  define_attribute_methods :collection
30
- include Enumerable
31
-
32
- # To initialize a collection, you need to pass the following named parameters
33
- # collection - the initial items to add to the collection.
34
- # :delegate - the owner of the object that will receive the notifications.
35
- # :key - the name of the key to use when sending notifications for _will_change! and _fetch!
36
- # :parse_class - what Parse class type are the items of the collection.
37
- # This is used to typecast the objects in the array to a particular Parse Object type.
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
- # helper method to forward a message to the delegate
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
- def to_ary
108
+ # @return [Array]
109
+ def to_a
79
110
  collection.to_a
80
- end; alias_method :to_a, :to_ary
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
- # lazy loading of a collection. If empty and not loaded, then forward _fetch!
87
- # to the delegate
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
- # Method to add items to the collection.
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
- # append items to the array
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
- # we call our own dirty tracking and also forward the call
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
- # This module provides has_many functionality to defining Parse::Object classes.
14
- # There are two main types of a has_many association - array and relation.
15
- # A has_many array relation, uses a PointerCollectionProxy to store a list of Parse::Object (or pointers)
16
- # that are stored in the column of the local table. This means we expect a the remote Parse table to contain
17
- # a column of type array which would contain a set of hash-like Pointers.
18
- # In the relation case, the object's Parse table has a column, but it points to a separate
19
- # relational table (join table) which maps both the local class and the foreign class. In this case
20
- # the type of the column is of "Relation" with a specific class name. This then means that it contains a set of
21
- # object Ids that we will treat as being part of the foreign table.
22
- # Ex. If a Artist defines a has_many relation to a Song class through a column called 'favoriteSongs'.
23
- # Then the Parse type of the favoriteSongs column, contained in the Artist table,
24
- # would be Relation<Song>. Any objectIds listed in that relation would then
25
- # be Song object Ids.
26
- # One thing to note is that when querying relations, the foreign table is the one that needs to be
27
- # queried in order to retrive the associated object to the local object. For example,
28
- # if an Artist has a relation to many Song objects, and we wanted to get the list of songs
29
- # this artist is related to, we would query the Song table passing the specific artist record
30
- # we are constraining to.
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, &block|
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
- klass_scope = klass.send(m, *args, &block)
96
- return klass_scope.is_a?(Parse::Query) ?
97
- self.add_constraints( klass_scope.constraints ) :
98
- klass_scope
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
- self.results.send(m, *args, &block)
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
- # provides a hash list of all relations to this class.
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
- # returns a hash of all the relation changes that have been performed on this
264
- # instance.
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 this object has any relation changes
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