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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +6 -0
  3. data/Gemfile.lock +77 -0
  4. data/LICENSE +20 -0
  5. data/README.md +1281 -0
  6. data/Rakefile +12 -0
  7. data/bin/console +20 -0
  8. data/bin/server +10 -0
  9. data/bin/setup +7 -0
  10. data/lib/parse/api/all.rb +13 -0
  11. data/lib/parse/api/analytics.rb +16 -0
  12. data/lib/parse/api/apps.rb +37 -0
  13. data/lib/parse/api/batch.rb +148 -0
  14. data/lib/parse/api/cloud_functions.rb +18 -0
  15. data/lib/parse/api/config.rb +22 -0
  16. data/lib/parse/api/files.rb +21 -0
  17. data/lib/parse/api/hooks.rb +68 -0
  18. data/lib/parse/api/objects.rb +77 -0
  19. data/lib/parse/api/push.rb +16 -0
  20. data/lib/parse/api/schemas.rb +25 -0
  21. data/lib/parse/api/sessions.rb +11 -0
  22. data/lib/parse/api/users.rb +43 -0
  23. data/lib/parse/client.rb +225 -0
  24. data/lib/parse/client/authentication.rb +59 -0
  25. data/lib/parse/client/body_builder.rb +69 -0
  26. data/lib/parse/client/caching.rb +103 -0
  27. data/lib/parse/client/protocol.rb +15 -0
  28. data/lib/parse/client/request.rb +43 -0
  29. data/lib/parse/client/response.rb +116 -0
  30. data/lib/parse/model/acl.rb +182 -0
  31. data/lib/parse/model/associations/belongs_to.rb +121 -0
  32. data/lib/parse/model/associations/collection_proxy.rb +202 -0
  33. data/lib/parse/model/associations/has_many.rb +218 -0
  34. data/lib/parse/model/associations/pointer_collection_proxy.rb +71 -0
  35. data/lib/parse/model/associations/relation_collection_proxy.rb +134 -0
  36. data/lib/parse/model/bytes.rb +50 -0
  37. data/lib/parse/model/core/actions.rb +499 -0
  38. data/lib/parse/model/core/properties.rb +377 -0
  39. data/lib/parse/model/core/querying.rb +100 -0
  40. data/lib/parse/model/core/schema.rb +92 -0
  41. data/lib/parse/model/date.rb +50 -0
  42. data/lib/parse/model/file.rb +127 -0
  43. data/lib/parse/model/geopoint.rb +98 -0
  44. data/lib/parse/model/model.rb +120 -0
  45. data/lib/parse/model/object.rb +347 -0
  46. data/lib/parse/model/pointer.rb +106 -0
  47. data/lib/parse/model/push.rb +99 -0
  48. data/lib/parse/query.rb +378 -0
  49. data/lib/parse/query/constraint.rb +130 -0
  50. data/lib/parse/query/constraints.rb +176 -0
  51. data/lib/parse/query/operation.rb +66 -0
  52. data/lib/parse/query/ordering.rb +49 -0
  53. data/lib/parse/stack.rb +11 -0
  54. data/lib/parse/stack/version.rb +5 -0
  55. data/lib/parse/webhooks.rb +228 -0
  56. data/lib/parse/webhooks/payload.rb +115 -0
  57. data/lib/parse/webhooks/registration.rb +139 -0
  58. data/parse-stack.gemspec +45 -0
  59. 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
@@ -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