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
@@ -0,0 +1,121 @@
|
|
1
|
+
require_relative '../pointer'
|
2
|
+
require_relative 'collection_proxy'
|
3
|
+
require_relative 'pointer_collection_proxy'
|
4
|
+
require_relative 'relation_collection_proxy'
|
5
|
+
|
6
|
+
# BelongsTo relation is the simplies association in which the local
|
7
|
+
# table constains a column that points to a foreign table record using
|
8
|
+
# a given Parse Pointer. The key of the property is implied to be the
|
9
|
+
# name of the class/parse table that contains the foreign associated record.
|
10
|
+
# All belongs to relationship column types have the special data type of :pointer.
|
11
|
+
module Parse
|
12
|
+
module Associations
|
13
|
+
|
14
|
+
module BelongsTo
|
15
|
+
|
16
|
+
def self.included(base)
|
17
|
+
base.extend(ClassMethods)
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
attr_accessor :references
|
22
|
+
# We can keep references to all "belong_to" properties
|
23
|
+
def references
|
24
|
+
@references ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
# belongs_to :featured_song, as: :song, field: :featuredSong, through: :reference
|
28
|
+
# belongs_to :artist
|
29
|
+
# belongs_to :manager, as: :user
|
30
|
+
# These items are added as attributes with the special data type of :pointer
|
31
|
+
def belongs_to(key, opts = {})
|
32
|
+
opts = {as: key, field: key.to_s.camelize(:lower), required: false}.merge(opts)
|
33
|
+
className = opts[:as].to_parse_class
|
34
|
+
parse_field = opts[:field].to_sym
|
35
|
+
if self.fields[key].present? && Parse::Properties::BASE_FIELD_MAP[key].nil?
|
36
|
+
raise Parse::Properties::DefinitionError, "Belongs relation #{self}##{key} already defined with type #{className}"
|
37
|
+
end
|
38
|
+
if self.fields[parse_field].present?
|
39
|
+
raise Parse::Properties::DefinitionError, "Alias belongs_to #{self}##{parse_field} conflicts with previously defined property."
|
40
|
+
end
|
41
|
+
# store this attribute in the attributes hash with the proper remote column name.
|
42
|
+
# we know the type is pointer.
|
43
|
+
self.attributes.merge!( parse_field => :pointer )
|
44
|
+
# Add them to our list of pointer references
|
45
|
+
self.references.merge!( parse_field => className )
|
46
|
+
# Add them to the list of fields in our class model
|
47
|
+
self.fields.merge!( key => :pointer, parse_field => :pointer )
|
48
|
+
# Mapping between local attribute name and the remote column name
|
49
|
+
self.field_map.merge!( key => parse_field )
|
50
|
+
|
51
|
+
# used for dirty tracking
|
52
|
+
define_attribute_methods key
|
53
|
+
|
54
|
+
# validations
|
55
|
+
validates_presence_of(key) if opts[:required]
|
56
|
+
|
57
|
+
# We generate the getter method
|
58
|
+
define_method(key) do
|
59
|
+
ivar = :"@#{key}"
|
60
|
+
val = instance_variable_get ivar
|
61
|
+
# We provide autofetch functionality. If the value is nil and the
|
62
|
+
# current Parse::Object is a pointer, then let's auto fetch it
|
63
|
+
if val.nil? && pointer?
|
64
|
+
autofetch!(key)
|
65
|
+
val = instance_variable_get ivar
|
66
|
+
end
|
67
|
+
|
68
|
+
# if for some reason we retrieved either from store or fetching a
|
69
|
+
# hash, lets try to buid a Pointer of that type.
|
70
|
+
|
71
|
+
if val.is_a?(Hash) && ( val["__type"].freeze == "Pointer".freeze || val["__type"].freeze == "Object".freeze )
|
72
|
+
val = Parse::Object.build val, ( val["className"] || className )
|
73
|
+
instance_variable_set ivar, val
|
74
|
+
end
|
75
|
+
val
|
76
|
+
end
|
77
|
+
|
78
|
+
define_method("#{key}?") do
|
79
|
+
self.send(key).is_a?(Parse::Pointer)
|
80
|
+
end
|
81
|
+
|
82
|
+
# A proxy setter method that has dirty tracking enabled.
|
83
|
+
define_method("#{key}=") do |val|
|
84
|
+
send "#{key}_set_attribute!", val, true
|
85
|
+
end
|
86
|
+
|
87
|
+
# We only support pointers, either by object or by transforming a hash.
|
88
|
+
define_method("#{key}_set_attribute!") do |val, track = true|
|
89
|
+
if val.is_a?(Hash) && ( val["__type"].freeze == "Pointer".freeze || val["__type"].freeze == "Object".freeze )
|
90
|
+
val = Parse::Object.build val, ( val["className"] || className )
|
91
|
+
end
|
92
|
+
if track == true
|
93
|
+
send :"#{key}_will_change!" unless val == instance_variable_get( :"@#{key}" )
|
94
|
+
end
|
95
|
+
# Never set an object that is not a Parse::Pointer
|
96
|
+
if val.nil? || val.is_a?(Parse::Pointer)
|
97
|
+
instance_variable_set(:"@#{key}", val)
|
98
|
+
else
|
99
|
+
warn "[#{self.class}] Invalid value #{val} set for belongs_to field #{key}"
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
# don't create method aliases if the fields are the same
|
104
|
+
return if parse_field.to_sym == key.to_sym
|
105
|
+
|
106
|
+
if self.method_defined?(parse_field) == false
|
107
|
+
alias_method parse_field, key
|
108
|
+
alias_method "#{parse_field}=", "#{key}="
|
109
|
+
alias_method "#{parse_field}_set_attribute!", "#{key}_set_attribute!"
|
110
|
+
elsif parse_field.to_sym != :objectId
|
111
|
+
warn "Alias belongs_to method #{self}##{parse_field} already defined."
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end # ClassMethod
|
117
|
+
|
118
|
+
end #BelongsTo
|
119
|
+
end #Associations
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/inflector'
|
4
|
+
require 'active_support/core_ext/object'
|
5
|
+
require_relative '../pointer'
|
6
|
+
|
7
|
+
# A collection proxy is a special type of array wrapper that will allow us to
|
8
|
+
# notify the parent object about changes to the array. We use a delegate pattern
|
9
|
+
# to send notifications to the parent whenever the content of the internal array changes.
|
10
|
+
# The main requirement to using the proxy is to provide the list of initial items if any,
|
11
|
+
# the owner to be notified and the name of the attribute 'key'. With that, anytime the array
|
12
|
+
# will change, we will notify the delegate by sending :'key'_will_change! . The proxy can also
|
13
|
+
# be lazy when fetching the contents of the collection. Whenever the collection is accessed and
|
14
|
+
# the list is in a "not loaded" state (empty and loaded == false), we will send :'key_fetch!' to the delegate in order to
|
15
|
+
# populate the collection.
|
16
|
+
module Parse
|
17
|
+
|
18
|
+
class CollectionProxy
|
19
|
+
include ::ActiveModel::Model
|
20
|
+
include ::ActiveModel::Dirty
|
21
|
+
|
22
|
+
attr_accessor :collection, :delegate, :loaded
|
23
|
+
attr_reader :delegate, :key
|
24
|
+
attr_accessor :parse_class
|
25
|
+
# This is to use dirty tracking within the proxy
|
26
|
+
define_attribute_methods :collection
|
27
|
+
include Enumerable
|
28
|
+
|
29
|
+
# To initialize a collection, you need to pass the following named parameters
|
30
|
+
# collection - the initial items to add to the collection.
|
31
|
+
# :delegate - the owner of the object that will receive the notifications.
|
32
|
+
# :key - the name of the key to use when sending notifications for _will_change! and _fetch!
|
33
|
+
# :parse_class - what Parse class type are the items of the collection.
|
34
|
+
# This is used to typecast the objects in the array to a particular Parse Object type.
|
35
|
+
def initialize(collection = nil, delegate: nil, key: nil, parse_class: nil)
|
36
|
+
@delegate = delegate
|
37
|
+
@key = key.to_sym if key.present?
|
38
|
+
@collection = collection.is_a?(Array) ? collection : []
|
39
|
+
@loaded = @collection.count > 0
|
40
|
+
@parse_class = parse_class
|
41
|
+
end
|
42
|
+
|
43
|
+
def loaded?
|
44
|
+
@loaded
|
45
|
+
end
|
46
|
+
|
47
|
+
# helper method to forward a message to the delegate
|
48
|
+
def forward(method, params = nil)
|
49
|
+
return unless @delegate.present? && @delegate.respond_to?(method)
|
50
|
+
params.nil? ? @delegate.send(method) : @delegate.send(method, params)
|
51
|
+
end
|
52
|
+
|
53
|
+
def reset!
|
54
|
+
@loaded = false
|
55
|
+
clear
|
56
|
+
end
|
57
|
+
|
58
|
+
def ==(other_list)
|
59
|
+
if other_list.is_a?(Array)
|
60
|
+
return @collection == other_list
|
61
|
+
elsif other_list.is_a?(Parse::CollectionProxy)
|
62
|
+
return @collection == other_list.instance_variable_get(:@collection)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def reload!
|
67
|
+
reset!
|
68
|
+
collection #force reload
|
69
|
+
end
|
70
|
+
|
71
|
+
def clear
|
72
|
+
@collection.clear
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_ary
|
76
|
+
collection.to_a
|
77
|
+
end; alias_method :to_a, :to_ary
|
78
|
+
|
79
|
+
# lazy loading of a collection. If empty and not loaded, then forward _fetch!
|
80
|
+
# to the delegate
|
81
|
+
def collection
|
82
|
+
if @collection.empty? && @loaded == false
|
83
|
+
@collection = forward( :"#{@key}_fetch!" ) || @collection || []
|
84
|
+
@loaded = true
|
85
|
+
end
|
86
|
+
|
87
|
+
@collection
|
88
|
+
end
|
89
|
+
|
90
|
+
def collection=(c)
|
91
|
+
notify_will_change!
|
92
|
+
@collection = c
|
93
|
+
end
|
94
|
+
|
95
|
+
# Method to add items to the collection.
|
96
|
+
def add(*items)
|
97
|
+
notify_will_change! if items.count > 0
|
98
|
+
items.each do |item|
|
99
|
+
collection.push item
|
100
|
+
end
|
101
|
+
@collection
|
102
|
+
end; alias_method :push, :add
|
103
|
+
|
104
|
+
# Remove items from the collection
|
105
|
+
def remove(*items)
|
106
|
+
notify_will_change! if items.count > 0
|
107
|
+
items.each do |item|
|
108
|
+
collection.delete item
|
109
|
+
end
|
110
|
+
@collection
|
111
|
+
end; alias_method :delete, :remove
|
112
|
+
|
113
|
+
def add!(*items)
|
114
|
+
return false unless @delegate.respond_to?(:op_add!)
|
115
|
+
@delegate.send :op_add!, @key, items
|
116
|
+
reset!
|
117
|
+
end
|
118
|
+
|
119
|
+
def add_unique!(*items)
|
120
|
+
return false unless @delegate.respond_to?(:op_add_unique!)
|
121
|
+
@delegate.send :op_add_unique!, @key, items
|
122
|
+
reset!
|
123
|
+
end
|
124
|
+
|
125
|
+
def remove!(*items)
|
126
|
+
return false unless @delegate.respond_to?(:op_remove!)
|
127
|
+
@delegate.send :op_remove!, @key, items
|
128
|
+
reset!
|
129
|
+
end
|
130
|
+
|
131
|
+
def destroy!
|
132
|
+
return false unless @delegate.respond_to?(:op_destroy!)
|
133
|
+
@delegate.send :op_destroy!, @key
|
134
|
+
collection_will_change!
|
135
|
+
@collection.clear
|
136
|
+
reset!
|
137
|
+
end
|
138
|
+
|
139
|
+
def rollback!
|
140
|
+
restore_attributes
|
141
|
+
end
|
142
|
+
|
143
|
+
def clear_changes!
|
144
|
+
clear_changes_information
|
145
|
+
end
|
146
|
+
|
147
|
+
def changes_applied!
|
148
|
+
changes_applied
|
149
|
+
end
|
150
|
+
|
151
|
+
def first(*args)
|
152
|
+
collection.first(*args)
|
153
|
+
end
|
154
|
+
|
155
|
+
def second
|
156
|
+
collection.second
|
157
|
+
end
|
158
|
+
|
159
|
+
def last(*args)
|
160
|
+
collection.last(*args)
|
161
|
+
end
|
162
|
+
|
163
|
+
def count
|
164
|
+
collection.count
|
165
|
+
end
|
166
|
+
|
167
|
+
def as_json(*args)
|
168
|
+
collection.as_json(args)
|
169
|
+
end
|
170
|
+
|
171
|
+
def empty?
|
172
|
+
collection.empty?
|
173
|
+
end
|
174
|
+
|
175
|
+
# append items to the array
|
176
|
+
def <<(*list)
|
177
|
+
if list.count > 0
|
178
|
+
notify_will_change!
|
179
|
+
list.flatten.each { |e| collection.push(e) }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
# we call our own dirty tracking and also forward the call
|
183
|
+
def notify_will_change!
|
184
|
+
collection_will_change!
|
185
|
+
forward "#{@key}_will_change!"
|
186
|
+
end
|
187
|
+
|
188
|
+
# supported iterator
|
189
|
+
def each
|
190
|
+
return collection.enum_for(:each) unless block_given?
|
191
|
+
collection.each &Proc.new
|
192
|
+
end
|
193
|
+
|
194
|
+
def inspect
|
195
|
+
"#<#{self.class} changed?=#{changed?} @collection=#{@collection.inspect} >"
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
|
202
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require_relative '../pointer'
|
2
|
+
require_relative 'collection_proxy'
|
3
|
+
require_relative 'pointer_collection_proxy'
|
4
|
+
require_relative 'relation_collection_proxy'
|
5
|
+
|
6
|
+
module Parse
|
7
|
+
|
8
|
+
module Associations
|
9
|
+
|
10
|
+
# This module provides has_many functionality to defining Parse::Object classes.
|
11
|
+
# There are two main types of a has_many association - array and relation.
|
12
|
+
# A has_many array relation, uses a PointerCollectionProxy to store a list of Parse::Object (or pointers)
|
13
|
+
# that are stored in the column of the local table. This means we expect a the remote Parse table to contain
|
14
|
+
# a column of type array which would contain a set of hash-like Pointers.
|
15
|
+
# In the relation case, the object's Parse table has a column, but it points to a separate
|
16
|
+
# relational table (join table) which maps both the local class and the foreign class. In this case
|
17
|
+
# the type of the column is of "Relation" with a specific class name. This then means that it contains a set of
|
18
|
+
# object Ids that we will treat as being part of the foreign table.
|
19
|
+
# Ex. If a Artist defines a has_many relation to a Song class through a column called 'favoriteSongs'.
|
20
|
+
# Then the Parse type of the favoriteSongs column, contained in the Artist table,
|
21
|
+
# would be Relation<Song>. Any objectIds listed in that relation would then
|
22
|
+
# be Song object Ids.
|
23
|
+
# One thing to note is that when querying relations, the foreign table is the one that needs to be
|
24
|
+
# queried in order to retrive the associated object to the local object. For example,
|
25
|
+
# if an Artist has a relation to many Song objects, and we wanted to get the list of songs
|
26
|
+
# this artist is related to, we would query the Song table passing the specific artist record
|
27
|
+
# we are constraining to.
|
28
|
+
module HasMany
|
29
|
+
def self.included(base)
|
30
|
+
base.extend(ClassMethods)
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
attr_accessor :relations
|
35
|
+
def relations
|
36
|
+
@relations ||= {}
|
37
|
+
end
|
38
|
+
# Examples:
|
39
|
+
# has_many :fans, as: :users, through: :relation, field: "awesomeFans"
|
40
|
+
# has_many :songs
|
41
|
+
# has_many :likes, as: :users, through: :relation
|
42
|
+
# has_many :artists, field: "managedArtists"
|
43
|
+
# The first item in the has_many is the name of the local attribute. This will create
|
44
|
+
# several methods for accessing the relation type. By default, the remote column name
|
45
|
+
# relating to this attribute will be the lower-first-camelcase version of this key.
|
46
|
+
# Ex. a relation to :my_songs, would imply that the remote column name is "mySongs". This behavior
|
47
|
+
# can be overriden by using the field: option and passing the literal field name in the Parse table.
|
48
|
+
# This allows you to use a local attribute name while still having a different remote column name.
|
49
|
+
# Since these types of collections are of a particular "type", we will assume that the name of the
|
50
|
+
# key is the plural version of the name of the local camelized-named class. Ex. If the property is named :songs, then
|
51
|
+
# we will assume there is a local class name defined as 'Song'. This can be overriden by using the as: parameter.
|
52
|
+
# This allows you to name your local attribute differently to what the responsible class for this association.
|
53
|
+
# Ex. You could define a has_many :favorite_songs property that points to the User class by using the 'as: :songs'. This would
|
54
|
+
# imply that the instance object has a set of Song objects through the attribute :favorite_songs.
|
55
|
+
# By default, all associations are stored in 'through: :array' form. If you are working with a Parse Relation, you
|
56
|
+
# should specify the 'through: :relation' property instead. This will switch the internal storage mechanisms
|
57
|
+
# from using a PointerCollectionProxy to a RelationCollectionProxy.
|
58
|
+
|
59
|
+
def has_many(key, opts = {})
|
60
|
+
opts = {through: :array,
|
61
|
+
field: key.to_s.camelize(:lower),
|
62
|
+
required: false,
|
63
|
+
as: key}.merge(opts)
|
64
|
+
|
65
|
+
klassName = opts[:as].to_parse_class
|
66
|
+
parse_field = opts[:field].to_sym
|
67
|
+
access_type = opts[:through].to_sym
|
68
|
+
# verify that the user did not duplicate properties or defined different properties with the same name
|
69
|
+
if self.fields[key].present? && Parse::Properties::BASE_FIELD_MAP[key].nil?
|
70
|
+
raise Parse::Properties::DefinitionError, "Has_many property #{self}##{key} already defined with type #{klassName}"
|
71
|
+
end
|
72
|
+
if self.fields[parse_field].present?
|
73
|
+
raise Parse::Properties::DefinitionError, "Alias has_many #{self}##{parse_field} conflicts with previously defined property."
|
74
|
+
end
|
75
|
+
# validations
|
76
|
+
validates_presence_of(key) if opts[:required]
|
77
|
+
|
78
|
+
# default proxy class.
|
79
|
+
proxyKlass = Parse::PointerCollectionProxy
|
80
|
+
|
81
|
+
#if this is a relation type, use this proxy instead. Relations are stored
|
82
|
+
# in the relations hash. If a PointerCollectionProxy is used, we store those
|
83
|
+
# as we would normal properties.
|
84
|
+
if access_type == :relation
|
85
|
+
proxyKlass = Parse::RelationCollectionProxy
|
86
|
+
self.relations[key] = klassName
|
87
|
+
else
|
88
|
+
self.attributes.merge!( parse_field => :array )
|
89
|
+
# Add them to the list of fields in our class model
|
90
|
+
self.fields.merge!( key => :array, parse_field => :array )
|
91
|
+
end
|
92
|
+
|
93
|
+
self.field_map.merge!( key => parse_field )
|
94
|
+
# dirty tracking
|
95
|
+
define_attribute_methods key
|
96
|
+
|
97
|
+
# The first method to be defined is a getter.
|
98
|
+
define_method(key) do
|
99
|
+
ivar = :"@#{key}"
|
100
|
+
val = instance_variable_get(ivar)
|
101
|
+
# if the value for this is nil and we are a pointer, then autofetch
|
102
|
+
if val.nil? && pointer?
|
103
|
+
autofetch!(key)
|
104
|
+
val = instance_variable_get ivar
|
105
|
+
end
|
106
|
+
|
107
|
+
# if the result is not a collection proxy, then create a new one.
|
108
|
+
unless val.is_a?(Parse::PointerCollectionProxy)
|
109
|
+
results = []
|
110
|
+
#results = val.parse_objects if val.respond_to?(:parse_objects)
|
111
|
+
val = proxyKlass.new results, delegate: self, key: key
|
112
|
+
instance_variable_set(ivar, val)
|
113
|
+
end
|
114
|
+
val
|
115
|
+
end
|
116
|
+
|
117
|
+
# proxy setter that forwards with dirty tracking
|
118
|
+
define_method("#{key}=") do |val|
|
119
|
+
send "#{key}_set_attribute!", val, true
|
120
|
+
end
|
121
|
+
|
122
|
+
# This will set the content of the proxy.
|
123
|
+
define_method("#{key}_set_attribute!") do |val, track = true|
|
124
|
+
# If it is a hash, with a __type of Relation, createa a new RelationCollectionProxy, regardless
|
125
|
+
# of what is defined because we must have gotten this from Parse.
|
126
|
+
val = [] if val.nil?
|
127
|
+
|
128
|
+
if val.is_a?(Hash) && val["__type"] == "Relation"
|
129
|
+
relation_objects = val["objects"] || []
|
130
|
+
val = Parse::RelationCollectionProxy.new relation_objects, delegate: self, key: key, parse_class: (val["className"] || klassName)
|
131
|
+
elsif val.is_a?(Hash) && val["__op"] == "AddRelation" && val["objects"].present?
|
132
|
+
_collection = proxyKlass.new [], delegate: self, key: key, parse_class: (val["className"] || klassName)
|
133
|
+
_collection.loaded = true
|
134
|
+
_collection.add val["objects"].parse_objects
|
135
|
+
val = _collection
|
136
|
+
elsif val.is_a?(Hash) && val["__op"] == "RemoveRelation" && val["objects"].present?
|
137
|
+
_collection = proxyKlass.new [], delegate: self, key: key, parse_class: (val["className"] || klassName)
|
138
|
+
_collection.loaded = true
|
139
|
+
_collection.remove val["objects"].parse_objects
|
140
|
+
val = _collection
|
141
|
+
elsif val.is_a?(Array)
|
142
|
+
# Otherwise create a new collection based on what the user defined.
|
143
|
+
val = proxyKlass.new val.parse_objects, delegate: self, key: key, parse_class: klassName
|
144
|
+
end
|
145
|
+
|
146
|
+
# send dirty tracking if set
|
147
|
+
if track == true
|
148
|
+
send :"#{key}_will_change!" unless val == instance_variable_get( :"@#{key}" )
|
149
|
+
end
|
150
|
+
# TODO: Only allow empty proxy collection class as a value or nil.
|
151
|
+
if val.is_a?(Parse::CollectionProxy)
|
152
|
+
instance_variable_set(:"@#{key}", val)
|
153
|
+
else
|
154
|
+
warn "[#{self.class}] Invalid value #{val} for :has_many field #{key}. Should be an Array or a CollectionProxy"
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
data_type = opts[:through]
|
160
|
+
# if the type is a relation association, add these methods to the delegate
|
161
|
+
# that will be used when creating the collection proxies. See Collection proxies
|
162
|
+
# for more information.
|
163
|
+
if data_type == :relation
|
164
|
+
# return a query given the foreign table class name.
|
165
|
+
define_method("#{key}_relation_query") do
|
166
|
+
Parse::Query.new(klassName, key.to_sym.related_to => self.pointer, limit: :max)
|
167
|
+
end
|
168
|
+
# fetch the contents of the relation
|
169
|
+
define_method("#{key}_fetch!") do
|
170
|
+
q = self.send :"#{key}_relation_query"
|
171
|
+
q.results || []
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
# if the remote field name and the local field name are the same
|
177
|
+
# don't create alias methods
|
178
|
+
return if parse_field.to_sym == key.to_sym
|
179
|
+
|
180
|
+
if self.method_defined?(parse_field) == false
|
181
|
+
alias_method parse_field, key
|
182
|
+
alias_method "#{parse_field}=", "#{key}="
|
183
|
+
alias_method "#{parse_field}_set_attribute!", "#{key}_set_attribute!"
|
184
|
+
elsif parse_field.to_sym != :objectId
|
185
|
+
warn "Alias has_many method #{self}##{parse_field} already defined."
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
end # has_many_array
|
190
|
+
end #ClassMethods
|
191
|
+
|
192
|
+
# provides a hash list of all relations to this class.
|
193
|
+
def relations
|
194
|
+
self.class.relations
|
195
|
+
end
|
196
|
+
|
197
|
+
# returns a has of all the relation changes that have been performed on this
|
198
|
+
# instance.
|
199
|
+
def relation_updates
|
200
|
+
h = {}
|
201
|
+
changed.each do |key|
|
202
|
+
next unless relations[key.to_sym].present? && send(key).changed?
|
203
|
+
remote_field = self.field_map[key.to_sym] || key
|
204
|
+
h[remote_field] = send key
|
205
|
+
end
|
206
|
+
h
|
207
|
+
end
|
208
|
+
|
209
|
+
# true if this object has any relation changes
|
210
|
+
def relation_changes?
|
211
|
+
changed.any? { |key| relations[key.to_sym] }
|
212
|
+
end
|
213
|
+
|
214
|
+
end # HasMany
|
215
|
+
end #Associations
|
216
|
+
|
217
|
+
|
218
|
+
end # Parse
|