parse-stack 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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