parse-stack 1.0.0
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 +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,99 @@
|
|
1
|
+
require_relative '../query.rb'
|
2
|
+
require_relative '../client.rb'
|
3
|
+
|
4
|
+
module Parse
|
5
|
+
|
6
|
+
class Push
|
7
|
+
include Client::Connectable
|
8
|
+
attr_accessor :query, :alert, :badge, :sound, :title, :data
|
9
|
+
attr_accessor :expiration_time, :expiration_interval, :push_time, :channels
|
10
|
+
|
11
|
+
alias_method :message, :alert
|
12
|
+
alias_method :message=, :alert=
|
13
|
+
|
14
|
+
def self.send(payload)
|
15
|
+
client.push payload.as_json
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(constraints = {})
|
19
|
+
self.where constraints
|
20
|
+
end
|
21
|
+
|
22
|
+
def query
|
23
|
+
@query ||= Parse::Query.new(Parse::Model::CLASS_INSTALLATION)
|
24
|
+
end
|
25
|
+
|
26
|
+
def where=(where_clausees)
|
27
|
+
query.where where_clauses
|
28
|
+
end
|
29
|
+
|
30
|
+
def where(constraints = nil)
|
31
|
+
return query.compile_where unless constraints.is_a?(Hash)
|
32
|
+
query.where constraints
|
33
|
+
query
|
34
|
+
end
|
35
|
+
|
36
|
+
def channels=(list)
|
37
|
+
@channels = [list].flatten
|
38
|
+
end
|
39
|
+
|
40
|
+
def data=(h)
|
41
|
+
if h.is_a?(String)
|
42
|
+
@alert = h
|
43
|
+
else
|
44
|
+
@data = h.symbolize_keys
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def as_json(*args)
|
49
|
+
payload.as_json
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_json(*args)
|
53
|
+
as_json.to_json
|
54
|
+
end
|
55
|
+
|
56
|
+
def payload
|
57
|
+
msg = {
|
58
|
+
data: {
|
59
|
+
alert: alert,
|
60
|
+
badge: badge || "Increment".freeze
|
61
|
+
}
|
62
|
+
}
|
63
|
+
msg[:data][:sound] = sound if sound.present?
|
64
|
+
msg[:data][:title] = title if title.present?
|
65
|
+
msg[:data].merge! @data if @data.is_a?(Hash)
|
66
|
+
|
67
|
+
|
68
|
+
if @expiration_time.present?
|
69
|
+
msg[:expiration_time] = @expiration_time.respond_to?(:iso8601) ? @expiration_time.iso8601(3) : @expiration_time
|
70
|
+
end
|
71
|
+
if @push_time.present?
|
72
|
+
msg[:push_time] = @push_time.respond_to?(:iso8601) ? @push_time.iso8601(3) : @push_time
|
73
|
+
end
|
74
|
+
|
75
|
+
if @expiration_interval.is_a?(Numeric)
|
76
|
+
msg[:expiration_interval] = @expiration_interval.to_i
|
77
|
+
end
|
78
|
+
|
79
|
+
if query.where.present?
|
80
|
+
q = @query.dup
|
81
|
+
if @channels.is_a?(Array) && @channels.empty? == false
|
82
|
+
q.where :channels.in => @channels
|
83
|
+
end
|
84
|
+
msg[:where] = q.compile_where unless q.where.empty?
|
85
|
+
elsif @channels.is_a?(Array) && @channels.empty? == false
|
86
|
+
msg[:channels] = @channels
|
87
|
+
end
|
88
|
+
msg
|
89
|
+
end
|
90
|
+
|
91
|
+
def send(message = nil)
|
92
|
+
@alert = message if message.is_a?(String)
|
93
|
+
@data = message if message.is_a?(Hash)
|
94
|
+
client.push( payload.as_json )
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
data/lib/parse/query.rb
ADDED
@@ -0,0 +1,378 @@
|
|
1
|
+
require_relative "client"
|
2
|
+
require_relative "query/operation"
|
3
|
+
require_relative "query/constraints"
|
4
|
+
require_relative "query/ordering"
|
5
|
+
|
6
|
+
|
7
|
+
module Parse
|
8
|
+
# This is the main engine behind making Parse queries on tables. It takes
|
9
|
+
# a set of constraints and generatse the proper hash parameters that are passed
|
10
|
+
# to a client :get request in order to retrive the results.
|
11
|
+
# The design of querying is based on ruby DataMapper orm where we define
|
12
|
+
# symbols with specific methos attached to values.
|
13
|
+
# At the core of each item is a Parse::Operation. An operation is
|
14
|
+
# made up of a field name and an operator. Therefore calling
|
15
|
+
# something like :name.eq, defines an equality operator on the field
|
16
|
+
# name. Using Parse::Operations with values, we can build different types of
|
17
|
+
# constraints - as Parse::Constraint
|
18
|
+
|
19
|
+
class Query
|
20
|
+
include Parse::Client::Connectable
|
21
|
+
# A query needs to be tied to a Parse table name (Parse class)
|
22
|
+
# The client object is of type Parse::Client in order to send query requests.
|
23
|
+
# You can modify the default client being used by all Parse::Query objects by setting
|
24
|
+
# Parse::Query.client. You can override individual Parse::Query object clients
|
25
|
+
# by changing their client variable to a different Parse::Client object.
|
26
|
+
attr_accessor :table, :client
|
27
|
+
|
28
|
+
# We have a special class method to handle field formatting. This turns
|
29
|
+
# the symbol keys in an operand from one key to another. For example, we can
|
30
|
+
# have the keys like :cost_rate in a query be translated to "costRate" when we
|
31
|
+
# build the query string before sending to Parse. This would allow you to have
|
32
|
+
# underscore case for your ruby code, while still maintaining camelCase in Parse.
|
33
|
+
# The default field formatter method is :columnize (which is camel case with the first letter
|
34
|
+
# in lower case). You can specify a different method to call by setting the Parse::Query.field_formatter
|
35
|
+
# variable with the symbol name of the method to call on the object. You can set this to nil
|
36
|
+
# if you do not want any field formatting to be performed.
|
37
|
+
class << self
|
38
|
+
#field formatter getters and setters.
|
39
|
+
attr_accessor :field_formatter
|
40
|
+
|
41
|
+
def field_formatter
|
42
|
+
#default
|
43
|
+
@field_formatter ||= :columnize
|
44
|
+
end
|
45
|
+
|
46
|
+
def format_field(str)
|
47
|
+
res = str.to_s
|
48
|
+
if field_formatter.present?
|
49
|
+
formatter = field_formatter.to_sym
|
50
|
+
# don't format if object d
|
51
|
+
res = res.send(formatter) if res.respond_to?(formatter)
|
52
|
+
end
|
53
|
+
res.strip
|
54
|
+
end
|
55
|
+
|
56
|
+
# Simple way to create a query.
|
57
|
+
def all(table, constraints = {})
|
58
|
+
self.new(table, {limit: :max}.merge(constraints) )
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def client
|
64
|
+
# use the set client or the default client.
|
65
|
+
@client ||= self.class.client
|
66
|
+
end
|
67
|
+
|
68
|
+
def clear(item = :results)
|
69
|
+
case item
|
70
|
+
when :where
|
71
|
+
# an array of Parse::Constraint subclasses
|
72
|
+
@where = []
|
73
|
+
when :order
|
74
|
+
# an array of Parse::Order objects
|
75
|
+
@order = []
|
76
|
+
when :includes
|
77
|
+
@includes = []
|
78
|
+
when :skip
|
79
|
+
@skip = 0
|
80
|
+
when :limit
|
81
|
+
@limit = 100
|
82
|
+
when :count
|
83
|
+
@count = 0
|
84
|
+
when :keys
|
85
|
+
@keys = []
|
86
|
+
end
|
87
|
+
@results = nil
|
88
|
+
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
def initialize(table, constraints = {})
|
93
|
+
raise "First parameter should be the name of the Parse class (table)" unless table.is_a?(String)
|
94
|
+
@count = 0 #non-zero/1 implies a count query request
|
95
|
+
@where = []
|
96
|
+
@order = []
|
97
|
+
@keys = []
|
98
|
+
@includes = []
|
99
|
+
@limit = 100
|
100
|
+
@skip = 0
|
101
|
+
@table = table
|
102
|
+
conditions constraints
|
103
|
+
self # chaining
|
104
|
+
end # initialize
|
105
|
+
|
106
|
+
def conditions(expressions = {})
|
107
|
+
expressions.each do |expression, value|
|
108
|
+
if expression == :order
|
109
|
+
order value
|
110
|
+
elsif expression == :keys
|
111
|
+
keys value
|
112
|
+
elsif expression == :skip
|
113
|
+
skip value
|
114
|
+
elsif expression == :limit
|
115
|
+
limit value
|
116
|
+
elsif expression == :include || expression == :includes
|
117
|
+
includes(value)
|
118
|
+
else
|
119
|
+
add_constraint(expression, value)
|
120
|
+
end
|
121
|
+
end # each
|
122
|
+
end
|
123
|
+
|
124
|
+
def table=(t)
|
125
|
+
@table = t.to_s.camelize
|
126
|
+
end
|
127
|
+
|
128
|
+
# returns the query parameter for the particular clause
|
129
|
+
def clause(clause_name = :where)
|
130
|
+
return unless [:keys, :where, :order, :includes, :limit, :skip].include?(clause_name)
|
131
|
+
instance_variable_get "@#{clause_name}".to_sym
|
132
|
+
end
|
133
|
+
|
134
|
+
def keys(*fields)
|
135
|
+
@keys ||= []
|
136
|
+
fields.flatten.each do |field|
|
137
|
+
if field.nil? == false && field.respond_to?(:to_s)
|
138
|
+
@keys.push Query.format_field(field).to_sym
|
139
|
+
end
|
140
|
+
end
|
141
|
+
@keys.uniq!
|
142
|
+
@results = nil if fields.count > 0
|
143
|
+
self # chaining
|
144
|
+
end
|
145
|
+
|
146
|
+
def order(*ordering)
|
147
|
+
@order ||= []
|
148
|
+
ordering.flatten.each do |order|
|
149
|
+
order = Order.new(order) if order.respond_to?(:to_sym)
|
150
|
+
if order.is_a?(Order)
|
151
|
+
order.field = Query.format_field(order.field)
|
152
|
+
@order.push order
|
153
|
+
end
|
154
|
+
end #value.each
|
155
|
+
@results = nil if ordering.count > 0
|
156
|
+
self #chaining
|
157
|
+
end #order
|
158
|
+
|
159
|
+
def skip(count)
|
160
|
+
# min <= count <= max
|
161
|
+
@skip = [ 0, count.to_i, 10_000].sort[1]
|
162
|
+
@results = nil
|
163
|
+
self #chaining
|
164
|
+
end
|
165
|
+
|
166
|
+
def limit(count)
|
167
|
+
if count == :max || count == :all
|
168
|
+
@limit = 11_000
|
169
|
+
elsif count.is_a?(Numeric)
|
170
|
+
@limit = [ 0, count.to_i, 11_000].sort[1]
|
171
|
+
end
|
172
|
+
|
173
|
+
@results = nil
|
174
|
+
self #chaining
|
175
|
+
end
|
176
|
+
|
177
|
+
def related_to(field, pointer)
|
178
|
+
raise "Object value must be a Parse::Pointer type" unless pointer.is_a?(Parse::Pointer)
|
179
|
+
add_constraint field.to_sym.related_to, pointer
|
180
|
+
self
|
181
|
+
end
|
182
|
+
|
183
|
+
def includes(*fields)
|
184
|
+
@includes ||= []
|
185
|
+
fields.flatten.each do |field|
|
186
|
+
if field.nil? == false && field.respond_to?(:to_s)
|
187
|
+
@includes.push Query.format_field(field).to_sym
|
188
|
+
end
|
189
|
+
end
|
190
|
+
@includes.uniq!
|
191
|
+
@results = nil if fields.count > 0
|
192
|
+
self # chaining
|
193
|
+
end
|
194
|
+
alias_method :include, :includes
|
195
|
+
|
196
|
+
def add_constraint(operator, value, opts = {})
|
197
|
+
@where ||= []
|
198
|
+
constraint = Parse::Constraint.create operator, value
|
199
|
+
return unless constraint.is_a?(Parse::Constraint)
|
200
|
+
unless opts[:filter] == false
|
201
|
+
constraint.operand = Query.format_field(constraint.operand)
|
202
|
+
end
|
203
|
+
@where.push constraint
|
204
|
+
@results = nil
|
205
|
+
self #chaining
|
206
|
+
end
|
207
|
+
def constraints; @where; end;
|
208
|
+
|
209
|
+
def where(conditions = nil, opts = {})
|
210
|
+
return @where if conditions.nil?
|
211
|
+
if conditions.is_a?(Hash)
|
212
|
+
conditions.each do |operator, value|
|
213
|
+
add_constraint(operator, value, opts)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
self #chaining
|
217
|
+
end
|
218
|
+
|
219
|
+
def or_where(where_clauses = [])
|
220
|
+
where_clauses = where_clauses.where if where_clauses.is_a?(Parse::Query)
|
221
|
+
where_clauses = Parse::Query.new(@table, where_clauses ).where if where_clauses.is_a?(Hash)
|
222
|
+
return self if where_clauses.blank?
|
223
|
+
# we can only have one compound query constraint. If we need to add another OR clause
|
224
|
+
# let's find the one we have (if any)
|
225
|
+
compound = @where.find { |f| f.is_a?(Parse::CompoundQueryConstraint) }
|
226
|
+
# create a set of clauses that are not an OR clause.
|
227
|
+
remaining_clauses = @where.select { |f| f.is_a?(Parse::CompoundQueryConstraint) == false }
|
228
|
+
# if we don't have a OR clause to reuse, then create a new one with then
|
229
|
+
# current set of constraints
|
230
|
+
if compound.blank?
|
231
|
+
compound = Parse::CompoundQueryConstraint.new :or, [ Parse::Query.compile_where(remaining_clauses) ]
|
232
|
+
end
|
233
|
+
# then take the where clauses from the second query and append them.
|
234
|
+
compound.value.push Parse::Query.compile_where(where_clauses)
|
235
|
+
#compound = Parse::CompoundQueryConstraint.new :or, [remaining_clauses, or_where_query.where]
|
236
|
+
@where = [compound]
|
237
|
+
self #chaining
|
238
|
+
end
|
239
|
+
|
240
|
+
def |(other_query)
|
241
|
+
raise "Parse queries must be of the same class #{@table}." unless @table == other_query.table
|
242
|
+
copy_query = self.clone
|
243
|
+
copy_query.or_where other_query.where
|
244
|
+
copy_query
|
245
|
+
end
|
246
|
+
|
247
|
+
def count
|
248
|
+
@results = nil
|
249
|
+
old_value = @count
|
250
|
+
@count = 1
|
251
|
+
res = client.find_objects(@table, compile.as_json ).count
|
252
|
+
@count = old_value
|
253
|
+
res
|
254
|
+
end
|
255
|
+
|
256
|
+
def each
|
257
|
+
return results.enum_for(:each) unless block_given? # Sparkling magic!
|
258
|
+
results.each(&Proc.new)
|
259
|
+
end
|
260
|
+
|
261
|
+
def first(limit = 1)
|
262
|
+
@results = nil
|
263
|
+
@limit = limit
|
264
|
+
results.first(limit)
|
265
|
+
end
|
266
|
+
|
267
|
+
def max_results(raw: false)
|
268
|
+
compiled_query = compile
|
269
|
+
query_limit = compiled_query[:limit] ||= 1_000
|
270
|
+
query_skip = compiled_query[:skip] ||= 0
|
271
|
+
compiled_query[:limit] = 1_000
|
272
|
+
iterations = (query_limit/1000.0).ceil
|
273
|
+
results = []
|
274
|
+
|
275
|
+
iterations.times do |idx|
|
276
|
+
#puts "Fetching 1000 after #{compiled_query[:skip]}"
|
277
|
+
response = fetch!( compiled_query )
|
278
|
+
break if response.error? || response.results.empty?
|
279
|
+
#puts "Appending #{response.results.count} results..."
|
280
|
+
items = response.results
|
281
|
+
items = decode(items) unless raw
|
282
|
+
|
283
|
+
if block_given?
|
284
|
+
items.each(&Proc.new)
|
285
|
+
else
|
286
|
+
results += items
|
287
|
+
end
|
288
|
+
# if we get less than the maximum set of results, most likely the next
|
289
|
+
# query will return emtpy results - no need to perform it.
|
290
|
+
break if items.count < compiled_query[:limit]
|
291
|
+
# add to the skip count for the next iteration
|
292
|
+
compiled_query[:skip] += 1_000
|
293
|
+
break if compiled_query[:skip] > 10_000
|
294
|
+
end
|
295
|
+
results
|
296
|
+
end
|
297
|
+
|
298
|
+
def fetch!(compiled_query)
|
299
|
+
response = client.find_objects(@table, compiled_query.as_json )
|
300
|
+
if response.error?
|
301
|
+
puts "[ParseQuery] #{response.error}"
|
302
|
+
end
|
303
|
+
response
|
304
|
+
end
|
305
|
+
|
306
|
+
def results(raw: false)
|
307
|
+
if @results.nil?
|
308
|
+
if @limit <= 1_000
|
309
|
+
response = fetch!( compile )
|
310
|
+
return [] if response.error?
|
311
|
+
items = raw ? response.results : decode(response.results)
|
312
|
+
return items.each(&Proc.new) if block_given?
|
313
|
+
@results = items
|
314
|
+
elsif block_given?
|
315
|
+
return max_results(raw: raw, &Proc.new)
|
316
|
+
else
|
317
|
+
@results = max_results(raw: raw)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
@results
|
321
|
+
end
|
322
|
+
alias_method :result, :results
|
323
|
+
|
324
|
+
def decode(list)
|
325
|
+
list.map { |m| Parse::Object.build(m, @table) }.compact
|
326
|
+
end
|
327
|
+
|
328
|
+
def as_json(*args)
|
329
|
+
compile.as_json
|
330
|
+
end
|
331
|
+
|
332
|
+
def compile(encode = true)
|
333
|
+
q = {} #query
|
334
|
+
q[:limit] = 11_000 if @limit == :max || @limit == :all
|
335
|
+
q[:limit] = @limit if @limit.is_a?(Numeric) && @limit > 0
|
336
|
+
q[:skip] = @skip if @skip > 0
|
337
|
+
|
338
|
+
q[:include] = @includes.join(',') unless @includes.empty?
|
339
|
+
q[:keys] = @keys.join(',') unless @keys.empty?
|
340
|
+
q[:order] = @order.join(',') unless @order.empty?
|
341
|
+
unless @where.empty?
|
342
|
+
q[:where] = Parse::Query.compile_where(@where)
|
343
|
+
q[:where] = q[:where].to_json if encode
|
344
|
+
end
|
345
|
+
|
346
|
+
if @count && @count > 0
|
347
|
+
# if count is requested
|
348
|
+
q[:limit] = 0
|
349
|
+
q[:count] = 1
|
350
|
+
end
|
351
|
+
q
|
352
|
+
end
|
353
|
+
|
354
|
+
def compile_where
|
355
|
+
self.class.compile_where( @where || [] )
|
356
|
+
end
|
357
|
+
|
358
|
+
def self.compile_where(where)
|
359
|
+
constraint_reduce( where )
|
360
|
+
end
|
361
|
+
|
362
|
+
def self.constraint_reduce(clauses)
|
363
|
+
# TODO: Need to add proper constraint merging
|
364
|
+
clauses.reduce({}) do |clause, subclause|
|
365
|
+
#puts "Merging Subclause: #{subclause.as_json}"
|
366
|
+
|
367
|
+
clause.deep_merge!( subclause.as_json || {} )
|
368
|
+
clause
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def print
|
373
|
+
puts JSON.pretty_generate( as_json )
|
374
|
+
end
|
375
|
+
|
376
|
+
end # Query
|
377
|
+
|
378
|
+
end # Parse
|