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,37 +7,97 @@ require 'active_support/inflector'
7
7
  require 'active_support/core_ext'
8
8
  require 'active_model_serializers'
9
9
  require_relative 'model'
10
- require 'active_model_serializers'
11
10
  module Parse
12
11
 
13
- # A Parse Pointer is the superclass of Parse::Object types. A pointer can be considered
14
- # a type of Parse::Object in which only the class name and id is known. In most cases,
15
- # you may not see Parse::Pointer object be used if you have defined all your Parse::Object subclasses
16
- # based on your Parse application tables - however they are used for when a class is found that cannot be
17
- # associated with a defined ruby class or used when specifically saving Parse relation types.
12
+ # The Pointer class represents the pointer type in Parse and is the superclass
13
+ # of Parse::Object types. A pointer can be considered a type of Parse::Object
14
+ # in which only the class name and id is known. In most cases, you may not
15
+ # deal with Parse::Pointer objects directly if you have defined all your
16
+ # Parse::Object subclasses.
17
+ #
18
+ # A `Parse::Pointer` only contains data about the specific Parse class and
19
+ # the `id` for the object. Therefore, creating an instance of any
20
+ # Parse::Object subclass with only the `:id` field set will be
21
+ # considered in "pointer" state even though its specific class is not
22
+ # `Parse::Pointer` type. The only case that you may have a Parse::Pointer
23
+ # is in the case where an object was received for one of your classes and
24
+ # the framework has no registered class handler for it.
25
+ # Assume you have the tables `Post`, `Comment` and `Author` defined in your
26
+ # remote Parse database, but have only defined `Post` and `Commentary`
27
+ # locally.
28
+ # @example
29
+ # class Post < Parse::Object
30
+ # end
31
+ #
32
+ # class Commentary < Parse::Object
33
+ # belongs_to :post
34
+ # belongs_to :author
35
+ # end
36
+ #
37
+ # comment = Commentary.first
38
+ # comment.post? # true because it is non-nil
39
+ # comment.artist? # true because it is non-nil
40
+ #
41
+ # # both are true because they are in a Pointer state
42
+ # comment.post.pointer? # true
43
+ # comment.author.pointer? # true
44
+ #
45
+ # # we have defined a Post class handler
46
+ # comment.post # <Post @parse_class="Post", @id="xdqcCqfngz">
47
+ #
48
+ # # we have not defined an Author class handler
49
+ # comment.author # <Parse::Pointer @parse_class="Author", @id="hZLbW6ofKC">
50
+ #
51
+ #
52
+ # comment.post.fetch # fetch the relation
53
+ # comment.post.pointer? # false, it is now a full object.
54
+ #
55
+ # The effect is that for any unknown classes that the framework encounters,
56
+ # it will generate Parse::Pointer instances until you define those classes
57
+ # with valid properties and associations. While this might be ok for some
58
+ # classes you do not use, we still recommend defining all your Parse classes
59
+ # locally in the framework.
60
+ #
61
+ # Once you have a subclass, you may also create a Parse::Pointer object using
62
+ # the _pointer_ method.
63
+ # @example
64
+ # Parse::User.pointer("123456") # => Parse::Pointer for "_User" class
65
+ #
66
+ # @see Parse::Object
18
67
  class Pointer < Model
19
68
  ATTRIBUTES = { __type: :string, className: :string, objectId: :string}.freeze
20
- attr_accessor :parse_class, :id
69
+ # @return [String] the name of the Parse class for this pointer.
70
+ attr_accessor :parse_class
71
+ # @return [String] the objectId field
72
+ attr_accessor :id
21
73
 
22
- def __type; "Pointer"; end;
74
+ # @return [Model::TYPE_POINTER]
75
+ def __type; Parse::Model::TYPE_POINTER; end;
23
76
  alias_method :className, :parse_class
24
77
  # A Parse object as a className field and objectId. In ruby, we will use the
25
78
  # id attribute method, but for usability, we will also alias it to objectId
26
79
  alias_method :objectId, :id
27
80
 
81
+ # A Parse pointer only requires the name of the remote Parse collection name,
82
+ # and the `objectId` of the record.
83
+ # @param table [String] The Parse class name in the Parse database.
84
+ # @param oid [String] The objectId
28
85
  def initialize(table, oid)
29
86
  @parse_class = table.to_s
30
87
  @id = oid.to_s
31
88
  end
32
89
 
90
+ # @return [String] the name of the collection for this Pointer.
33
91
  def parse_class
34
92
  @parse_class
35
93
  end
36
94
 
95
+ # @return [Hash]
37
96
  def attributes
38
97
  ATTRIBUTES
39
98
  end
40
99
 
100
+ # @return [Hash] serialized JSON structure
41
101
  def json_hash
42
102
  JSON.parse to_json
43
103
  end
@@ -45,32 +105,44 @@ module Parse
45
105
  # Create a new pointer with the current class name and id. While this may not make sense
46
106
  # for a pointer instance, Parse::Object subclasses use this inherited method to turn themselves into
47
107
  # pointer objects.
108
+ # @example
109
+ # user = Parse::User.first
110
+ # user.pointer # => Parse::Pointer("_User", user.id)
111
+ #
112
+ # @return [Pointer] a new Pointer for this object.
113
+ # @see Parse::Object
48
114
  def pointer
49
115
  Pointer.new parse_class, @id
50
116
  end
51
117
 
52
- # determines if an object (or subclass) is a pointer type. A pointer is determined
53
- # if we have a parse class and an id, but no timestamps, then we probably are a pointer.
118
+ # Whether this instance is in pointer state. A pointer is determined
119
+ # if we have a parse class and an id, but no created_at or updated_at fields.
120
+ # @return [Boolean] true if instance is in pointer state.
54
121
  def pointer?
55
122
  present? && @created_at.blank? && @updated_at.blank?
56
123
  end
57
124
 
58
- # boolean whether this object has data and is not a pointer. Because of some autofetching
125
+ # Returns true if the data for this instance has been fetched. Because of some autofetching
59
126
  # mechanisms, this is useful to know whether the object already has data without actually causing
60
127
  # a fetch of the data.
128
+ # @return [Boolean] true if not in pointer state.
61
129
  def fetched?
62
130
  present? && pointer? == false
63
131
  end
64
132
 
65
133
  # This method is a general implementation that gets overriden by Parse::Object subclass.
66
134
  # Given the class name and the id, we will go to Parse and fetch the actual record, returning the
67
- # JSON object. Note that the subclass implementation does something a bit different.
135
+ # JSON object.
136
+ # @return [Parse::Object] the fetched Parse::Object, nil otherwise.
68
137
  def fetch
69
138
  response = client.fetch_object(parse_class, id)
70
139
  return nil if response.error?
71
140
  response.result
72
141
  end
73
142
 
143
+ # Two Parse::Pointers (or Parse::Objects) are equal if both of them have
144
+ # the same Parse class and the same id.
145
+ # @return [Boolean]
74
146
  def ==(o)
75
147
  return false unless o.is_a?(Pointer)
76
148
  #only equal if the Parse class and object ID are the same.
@@ -78,6 +150,7 @@ module Parse
78
150
  end
79
151
  alias_method :eql?, :==
80
152
 
153
+ # @return [Boolean] true if instance has a Parse class and an id.
81
154
  def present?
82
155
  parse_class.present? && @id.present?
83
156
  end
@@ -87,14 +160,22 @@ end
87
160
 
88
161
  # extensions
89
162
  class Array
163
+ # This method maps all the ids (String) of all Parse::Objects in the array.
164
+ # @return [Array<String>] an array of strings of ids.
90
165
  def objectIds
91
- map { |m| m.is_?(Parse::Pointer) ? m.id : nil }.reject { |r| r.nil? }
166
+ map { |m| m.is_?(Parse::Pointer) ? m.id : nil }.compact
92
167
  end
93
168
 
169
+ # Filter all objects in the array that do not inherit from Parse::Pointer or
170
+ # Parse::Object.
171
+ # @return [Array<Parse::Object,Parse::Pointer>] an array of Parse::Objects.
94
172
  def valid_parse_objects
95
173
  select { |s| s.is_a?(Parse::Pointer) }
96
174
  end
97
175
 
176
+ # Convert all potential objects in the array to a list of Parse::Pointer instances.
177
+ # The array can contain a mixture of objects types including JSON Parse-like hashes.
178
+ # @return [Array<Parse::Pointer>] an array of Parse::Pointer objects.
98
179
  def parse_pointers(table = nil)
99
180
  self.map do |m|
100
181
  #if its an exact Parse::Pointer
@@ -5,19 +5,82 @@ require_relative '../query.rb'
5
5
  require_relative '../client.rb'
6
6
  require 'active_model_serializers'
7
7
  module Parse
8
-
8
+ # This class represents the API to send push notification to devices that are
9
+ # available in the Installation table. Push notifications are implemented
10
+ # through the `Parse::Push` class. To send push notifications through the
11
+ # REST API, you must enable `REST push enabled?` option in the `Push
12
+ # Notification Settings` section of the `Settings` page in your Parse
13
+ # application. Push notifications targeting uses the Installation Parse
14
+ # class to determine which devices receive the notification. You can provide
15
+ # any query constraint, similar to using `Parse::Query`, in order to target
16
+ # the specific set of devices you want given the columns you have configured
17
+ # in your `Installation` class. The `Parse::Push` class supports many other
18
+ # options not listed here.
19
+ # @example
20
+ #
21
+ # push = Parse::Push.new
22
+ # push.send( "Hello World!") # to everyone
23
+ #
24
+ # # simple channel push
25
+ # push = Parse::Push.new
26
+ # push.channels = ["addicted2salsa"]
27
+ # push.send "You are subscribed to Addicted2Salsa!"
28
+ #
29
+ # # advanced targeting
30
+ # push = Parse::Push.new( {..where query constraints..} )
31
+ # # or use `where()`
32
+ # push.where :device_type.in => ['ios','android'], :location.near => some_geopoint
33
+ # push.alert = "Hello World!"
34
+ # push.sound = "soundfile.caf"
35
+ #
36
+ # # additional payload data
37
+ # push.data = { uri: "app://deep_link_path" }
38
+ #
39
+ # # Send the push
40
+ # push.send
41
+ #
42
+ #
9
43
  class Push
10
44
  include Client::Connectable
11
- attr_accessor :query, :alert, :badge, :sound, :title, :data
12
- attr_accessor :expiration_time, :expiration_interval, :push_time, :channels
45
+
46
+ # @!attribute [rw] query
47
+ # Sending a push notification is done by performing a query against the Installation
48
+ # collection with a Parse::Query. This query contains the constraints that will be
49
+ # sent to Parse with the push payload.
50
+ # @return [Parse::Query] the query containing Installation constraints.
51
+
52
+ # @!attribute [rw] alert
53
+ # @return [String]
54
+ # @!attribute [rw] badge
55
+ # @return [Integer]
56
+ # @!attribute [rw] sound
57
+ # @return [String] the name of the sound file
58
+ # @!attribute [rw] title
59
+ # @return [String]
60
+ # @!attribute [rw] data
61
+ # @return [Hash] specific payload data.
62
+ # @!attribute [rw] expiration_time
63
+ # @return [Parse::Date]
64
+ # @!attribute [rw] expiration_interval
65
+ # @return [Integer]
66
+ # @!attribute [rw] push_time
67
+ # @return [Parse::Date]
68
+ # @!attribute [rw] channels
69
+ # @return [Array] an array of strings for subscribed channels.
70
+ attr_accessor :query, :alert, :badge, :sound, :title, :data,
71
+ :expiration_time, :expiration_interval, :push_time, :channels
13
72
 
14
73
  alias_method :message, :alert
15
74
  alias_method :message=, :alert=
16
75
 
76
+ # Send a push notification using a push notification hash
77
+ # @param payload [Hash] a push notification hash payload
17
78
  def self.send(payload)
18
79
  client.push payload.as_json
19
80
  end
20
81
 
82
+ # Initialize a new push notification request.
83
+ # @param constraints [Hash] a set of query constraints
21
84
  def initialize(constraints = {})
22
85
  self.where constraints
23
86
  end
@@ -26,6 +89,8 @@ module Parse
26
89
  @query ||= Parse::Query.new(Parse::Model::CLASS_INSTALLATION)
27
90
  end
28
91
 
92
+ # @!attribute [rw] where
93
+ # @return [Hash] a hash of 'where' query constraints
29
94
  def where=(where_clausees)
30
95
  query.where where_clauses
31
96
  end
@@ -37,7 +102,7 @@ module Parse
37
102
  end
38
103
 
39
104
  def channels=(list)
40
- @channels = [list].flatten
105
+ @channels = Array.wrap(list)
41
106
  end
42
107
 
43
108
  def data=(h)
@@ -48,14 +113,19 @@ module Parse
48
113
  end
49
114
  end
50
115
 
116
+ # @return [Hash] a JSON encoded hash.
51
117
  def as_json(*args)
52
118
  payload.as_json
53
119
  end
54
120
 
121
+ # @return [String] a JSON encoded string.
55
122
  def to_json(*args)
56
123
  as_json.to_json
57
124
  end
58
125
 
126
+ # This method takes all the parameters of the instance and creates a proper
127
+ # hash structure, required by Parse, in order to process the push notification.
128
+ # @return [Hash] the prepared push payload to be used in the request.
59
129
  def payload
60
130
  msg = {
61
131
  data: {
@@ -91,6 +161,8 @@ module Parse
91
161
  msg
92
162
  end
93
163
 
164
+ # helper method to send a message
165
+ # @param message [String] the message to send
94
166
  def send(message = nil)
95
167
  @alert = message if message.is_a?(String)
96
168
  @data = message if message.is_a?(Hash)
data/lib/parse/query.rb CHANGED
@@ -12,27 +12,131 @@ require 'active_support/inflector'
12
12
  require 'active_support/core_ext'
13
13
 
14
14
  module Parse
15
- # This is the main engine behind making Parse queries on tables. It takes
16
- # a set of constraints and generatse the proper hash parameters that are passed
17
- # to a client :get request in order to retrive the results.
18
- # The design of querying is based on ruby DataMapper orm where we define
19
- # symbols with specific methos attached to values.
20
- # At the core of each item is a Parse::Operation. An operation is
15
+ # The {Parse::Query} class provides the lower-level querying interface for
16
+ # your Parse collections by utilizing the {http://parseplatform.github.io/docs/rest/guide/#queries
17
+ # REST Querying interface}. This is the main engine behind making Parse queries
18
+ # on remote collections. It takes a set of constraints and generates the
19
+ # proper hash parameters that are passed to an API request in order to retrive
20
+ # matching results. The querying design pattern is inspired from
21
+ # {http://datamapper.org/ DataMapper} where symbols are overloaded with
22
+ # specific methods with attached values.
23
+ #
24
+ # At the core of each item is a {Parse::Operation}. An operation is
21
25
  # made up of a field name and an operator. Therefore calling
22
26
  # something like :name.eq, defines an equality operator on the field
23
- # name. Using Parse::Operations with values, we can build different types of
24
- # constraints - as Parse::Constraint
25
-
27
+ # name. Using {Parse::Operation}s with values, we can build different types of
28
+ # constraints, known as {Parse::Constraint}s.
29
+ #
30
+ # This component can be used on its own without defining your models as all
31
+ # results are provided in hash form.
32
+ #
33
+ # *Field-Formatter*
34
+ #
35
+ # By convention in Ruby (see
36
+ # {https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars Style Guide}),
37
+ # symbols and variables are expressed in lower_snake_case form. Parse, however,
38
+ # prefers column names in {String#columnize} format (ex. `objectId`,
39
+ # `createdAt` and `updatedAt`). To keep in line with the style
40
+ # guides between the languages, we do the automatic conversion of the field
41
+ # names when compiling the query. This feature can be overridden by changing the
42
+ # value of {Parse::Query.field_formatter}.
43
+ #
44
+ # # default uses :columnize
45
+ # query = Parse::User.query :field_one => 1, :FieldTwo => 2, :Field_Three => 3
46
+ # query.compile_where # => {"fieldOne"=>1, "fieldTwo"=>2, "fieldThree"=>3}
47
+ #
48
+ # # turn off
49
+ # Parse::Query.field_formatter = nil
50
+ # query = Parse::User.query :field_one => 1, :FieldTwo => 2, :Field_Three => 3
51
+ # query.compile_where # => {"field_one"=>1, "FieldTwo"=>2, "Field_Three"=>3}
52
+ #
53
+ # # force everything camel case
54
+ # Parse::Query.field_formatter = :camelize
55
+ # query = Parse::User.query :field_one => 1, :FieldTwo => 2, :Field_Three => 3
56
+ # query.compile_where # => {"FieldOne"=>1, "FieldTwo"=>2, "FieldThree"=>3}
57
+ #
58
+ # Most of the constraints supported by Parse are available to `Parse::Query`.
59
+ # Assuming you have a column named `field`, here are some examples. For an
60
+ # explanation of the constraints, please see
61
+ # {http://parseplatform.github.io/docs/rest/guide/#queries Parse Query Constraints documentation}.
62
+ # You can build your own custom query constraints by creating a `Parse::Constraint`
63
+ # subclass. For all these `where` clauses assume `q` is a `Parse::Query` object.
26
64
  class Query
27
65
  extend ::ActiveModel::Callbacks
28
66
  include Parse::Client::Connectable
29
67
  include Enumerable
68
+ # @!group Callbacks
69
+ #
70
+ # @!method before_prepare
71
+ # A callback called before the query is compiled
72
+ # @yield A block to execute for the callback.
73
+ # @see ActiveModel::Callbacks
74
+ # @!method after_prepare
75
+ # A callback called after the query is compiled
76
+ # @yield A block to execute for the callback.
77
+ # @see ActiveModel::Callbacks
78
+ # @!endgroup
30
79
  define_model_callbacks :prepare, only: [:after, :before]
31
80
  # A query needs to be tied to a Parse table name (Parse class)
32
81
  # The client object is of type Parse::Client in order to send query requests.
33
82
  # You can modify the default client being used by all Parse::Query objects by setting
34
83
  # Parse::Query.client. You can override individual Parse::Query object clients
35
84
  # by changing their client variable to a different Parse::Client object.
85
+
86
+ # @!attribute [rw] table
87
+ # @return [String] the name of the Parse collection to query against.
88
+ # @!attribute [rw] client
89
+ # @return [Parse::Client] the client to use for the API Query request.
90
+ # @!attribute [rw] key
91
+ # This parameter is used to support `select` queries where you have to
92
+ # pass a `key` parameter for matching different tables.
93
+ # @return [String] the foreign key to match against.
94
+ # @!attribute [rw] cache
95
+ # Set whether this query should be cached and for how long. This parameter
96
+ # is used to cache queries when using {Parse::Middleware::Caching}. If
97
+ # the caching middleware is configured, all queries will be cached for the
98
+ # duration allowed by the cache, and therefore some queries could return
99
+ # cached results. To disable caching and cached results for this specific query,
100
+ # you may set this field to `false`. To specify the specific amount of time
101
+ # you want this query to be cached, set a duration (in number of seconds) that
102
+ # the caching middleware should cache this request.
103
+ # @example
104
+ # # find all users with name "Bob"
105
+ # query = Parse::Query.new("_User", :name => "Bob")
106
+ #
107
+ # query.cache = true # (default) cache using default cache duration.
108
+ #
109
+ # query.cache = 1.day # cache for 86400 seconds
110
+ #
111
+ # query.cache = false # do not cache or use cache results
112
+ #
113
+ # # You may optionally pass this into the constraint hash.
114
+ # query = Parse::Query.new("_User", :name => "Bob", :cache => 1.day)
115
+ #
116
+ # @return [Boolean] if set to true or false on whether it should use the default caching
117
+ # length set when configuring {Parse::Middleware::Caching}.
118
+ # @return [Integer] if set to a number of seconds to cache this specific request
119
+ # with the {Parse::Middleware::Caching}.
120
+ # @!attribute [rw] use_master_key
121
+ # True or false on whether we should send the master key in this request. If
122
+ # You have provided the master_key when initializing Parse, then all requests
123
+ # will send the master key by default. This feature is useful when you want to make
124
+ # a particular query be performed with public credentials, or on behalf of a user using
125
+ # a {session_token}. Default is set to true.
126
+ # @see #session_token
127
+ # @example
128
+ # # disable use of the master_key in the request.
129
+ # query = Parse::Query.new("_User", :name => "Bob", :master_key => false)
130
+ # @return [Boolean] whether we should send the master key in this request.
131
+ # @!attribute [rw] session_token
132
+ # Set the session token to send with this API request. A session token is tied to
133
+ # a logged in {Parse::User}. When sending a session_token in the request,
134
+ # this performs the query on behalf of the user (with their allowed priviledges).
135
+ # @example
136
+ # # perform this query as user represented by session_token
137
+ # query = Parse::Query.new("_User", :name => "Bob", :session_token => "r:XyX123...")
138
+ # @note Using a session_token automatically disables sending the master key in the request.
139
+ # @return [String] the session token to send with this API request.
36
140
  attr_accessor :table, :client, :key, :cache, :use_master_key, :session_token
37
141
 
38
142
  # We have a special class method to handle field formatting. This turns
@@ -44,11 +148,18 @@ module Parse
44
148
  # in lower case). You can specify a different method to call by setting the Parse::Query.field_formatter
45
149
  # variable with the symbol name of the method to call on the object. You can set this to nil
46
150
  # if you do not want any field formatting to be performed.
151
+
47
152
  @field_formatter = :columnize
48
153
  class << self
49
- #field formatter getters and setters.
154
+ # The method to use when converting field names to Parse column names. Default is {String#columnize}.
155
+ # By convention Parse uses lowercase-first camelcase syntax for field/column names, but ruby
156
+ # uses snakecase. To support this methodology we process all field constraints through the method
157
+ # defined by the field formatter. You may set this to nil to turn off this functionality.
158
+ # @return [Symbol] The filter method to process column and field names. Default {String#columnize}.
50
159
  attr_accessor :field_formatter
51
160
 
161
+ # @param str [String] the string to format
162
+ # @return [String] formatted string using {Parse::Query.field_formatter}.
52
163
  def format_field(str)
53
164
  res = str.to_s.strip
54
165
  if field_formatter.present? && res.respond_to?(field_formatter)
@@ -57,18 +168,48 @@ module Parse
57
168
  res
58
169
  end
59
170
 
60
- # Simple way to create a query.
61
- def all(table, constraints = {})
62
- self.new(table, {limit: :max}.merge(constraints) )
171
+ # Helper method to create a query with constraints for a specific Parse collection.
172
+ # Also sets the default limit count to `:max`.
173
+ # @param table [String] the name of the Parse collection to query. (ex. "_User")
174
+ # @param constraints [Hash] a set of query constraints.
175
+ # @return [Query] a new query for the Parse collection with the passed in constraints.
176
+ def all(table, constraints = {limit: :max})
177
+ self.new(table, constraints.reverse_merge({limit: :max}))
178
+ end
179
+
180
+ # This methods takes a set of constraints and merges them to build a final
181
+ # `where` constraint clause for sending to the Parse backend.
182
+ # @param where [Array] an array of {Parse::Constraint} objects.
183
+ # @return [Hash] a hash representing the compiled query
184
+ def compile_where(where)
185
+ constraint_reduce( where )
186
+ end
187
+
188
+ # @!visibility private
189
+ def constraint_reduce(clauses)
190
+ # @todo Need to add proper constraint merging
191
+ clauses.reduce({}) do |clause, subclause|
192
+ #puts "Merging Subclause: #{subclause.as_json}"
193
+
194
+ clause.deep_merge!( subclause.as_json || {} )
195
+ clause
196
+ end
63
197
  end
64
198
 
65
199
  end
66
200
 
201
+ # @!attribute [r] client
202
+ # @return [Parse::Client] the client to use for making the API request.
203
+ # @see Parse::Client::Connectable
67
204
  def client
68
205
  # use the set client or the default client.
69
206
  @client ||= self.class.client
70
207
  end
71
208
 
209
+ # Clear a specific clause of this query. This can be one of: :where, :order,
210
+ # :includes, :skip, :limit, :count, :keys or :results.
211
+ # @param item [:Symbol] the clause to clear.
212
+ # @return [self]
72
213
  def clear(item = :results)
73
214
  case item
74
215
  when :where
@@ -89,10 +230,27 @@ module Parse
89
230
  @keys = []
90
231
  end
91
232
  @results = nil
92
-
93
- self
233
+ self # chaining
94
234
  end
95
235
 
236
+ # Constructor method to create a query with constraints for a specific Parse collection.
237
+ # Also sets the default limit count to `:max`.
238
+ # @overload new(table)
239
+ # Create a query for this Parse collection name.
240
+ # @example
241
+ # Parse::Query.new "_User"
242
+ # Parse::Query.new "_Installation", :device_type => 'ios'
243
+ # @param table [String] the name of the Parse collection to query. (ex. "_User")
244
+ # @param constraints [Hash] a set of query constraints.
245
+ # @overload new(parseSubclass)
246
+ # Create a query for this Parse model (or anything that responds to {Parse::Object.parse_class}).
247
+ # @example
248
+ # Parse::Query.new Parse::User
249
+ # # assume Post < Parse::Object
250
+ # Parse::Query.new Post, like_count.gt => 0
251
+ # @param parseSubclass [Parse::Object] the Parse model constant
252
+ # @param constraints [Hash] a set of query constraints.
253
+ # @return [Query] a new query for the Parse collection with the passed in constraints.
96
254
  def initialize(table, constraints = {})
97
255
  table = table.to_s.to_parse_class if table.is_a?(Symbol)
98
256
  table = table.parse_class if table.respond_to?(:parse_class)
@@ -108,9 +266,11 @@ module Parse
108
266
  @cache = true
109
267
  @use_master_key = true
110
268
  conditions constraints
111
- self # chaining
112
269
  end # initialize
113
270
 
271
+ # Add a set of query expressions and constraints.
272
+ # @param expressions
273
+ # @return [self]
114
274
  def conditions(expressions = {})
115
275
  expressions.each do |expression, value|
116
276
  if expression == :order
@@ -144,12 +304,29 @@ module Parse
144
304
  @table = t.to_s.camelize
145
305
  end
146
306
 
147
- # returns the query parameter for the particular clause
307
+ # returns the query clause for the particular clause
308
+ # @param clause_name [Symbol] One of supported clauses to return: :keys,
309
+ # :where, :order, :includes, :limit, :skip
310
+ # @return [Object] the content of the clause for this query.
148
311
  def clause(clause_name = :where)
149
312
  return unless [:keys, :where, :order, :includes, :limit, :skip].include?(clause_name)
150
313
  instance_variable_get "@#{clause_name}".to_sym
151
314
  end
152
315
 
316
+ # Restrict the fields returned by the query. This is useful for larger query
317
+ # results set where some of the data will not be used, which reduces network
318
+ # traffic and deserialization performance.
319
+ # @example
320
+ # # results only contain :name field
321
+ # Song.all :keys => :name
322
+ #
323
+ # # multiple keys
324
+ # Song.all :keys => [:name,:artist]
325
+ # @note Use this feature with caution when working with the results, as
326
+ # values for the fields not specified in the query will be omitted in
327
+ # the resulting object.
328
+ # @param fields [Array] the name of the fields to return.
329
+ # @return [self]
153
330
  def keys(*fields)
154
331
  @keys ||= []
155
332
  fields.flatten.each do |field|
@@ -162,6 +339,16 @@ module Parse
162
339
  self # chaining
163
340
  end
164
341
 
342
+ # Add a sorting order for the query.
343
+ # @example
344
+ # # order updated_at ascending order
345
+ # Song.all :order => :updated_at
346
+ #
347
+ # # first order by highest like_count, then by ascending name.
348
+ # # Note that ascending is the default if not specified (ex. `:name.asc`)
349
+ # Song.all :order => [:like_count.desc, :name]
350
+ # @param ordering [Parse::Order] an ordering
351
+ # @return [self]
165
352
  def order(*ordering)
166
353
  @order ||= []
167
354
  ordering.flatten.each do |order|
@@ -175,6 +362,13 @@ module Parse
175
362
  self #chaining
176
363
  end #order
177
364
 
365
+ # Use with limit to paginate through results. Default is 0 with
366
+ # maximum value being 10,000.
367
+ # @example
368
+ # # get the next 3 songs after the first 10
369
+ # Song.all :limit => 3, :skip => 10
370
+ # @param count [Integer] The number of records to skip.
371
+ # @return [self]
178
372
  def skip(count)
179
373
  # min <= count <= max
180
374
  @skip = [ 0, count.to_i, 10_000].sort[1]
@@ -182,6 +376,19 @@ module Parse
182
376
  self #chaining
183
377
  end
184
378
 
379
+ # Limit the number of objects returned by the query. The default is 100, with
380
+ # Parse allowing a maximum of 1000. The framework also allows a value of
381
+ # `:max`. Utilizing this will have the framework continually intelligently
382
+ # utilize `:skip` to continue to paginate through results until an empty
383
+ # result set is received or the `:skip` limit is reached (10,000). When
384
+ # utilizing `all()`, `:max` is the default option for `:limit`.
385
+ # @example
386
+ # Song.all :limit => 1 # same as Song.first
387
+ # Song.all :limit => 1000 # maximum allowed by Parse
388
+ # Song.all :limit => :max # up to 11,000 records (theoretical).
389
+ # @param count [Integer,Symbol] The number of records to return. You may pass :max
390
+ # to get as many as 11_000 records with the aid if skipping.
391
+ # @return [self]
185
392
  def limit(count)
186
393
  if count == :max || count == :all
187
394
  @limit = 11_000
@@ -201,6 +408,19 @@ module Parse
201
408
  self #chaining
202
409
  end
203
410
 
411
+ # Set a list of Parse Pointer columns to be fetched for matching records.
412
+ # You may chain multiple columns with the `.` operator.
413
+ # @example
414
+ # # assuming an 'Artist' has a pointer column for a 'Manager'
415
+ # # and a Song has a pointer column for an 'Artist'.
416
+ #
417
+ # # include the full artist object
418
+ # Song.all(:includes => [:artist])
419
+ #
420
+ # # Chaining - fetches the artist and the artist's manager for matching songs
421
+ # Song.all :includes => ['artist.manager']
422
+ # @param fields [Array] the list of Pointer columns to fetch.
423
+ # @return [self]
204
424
  def includes(*fields)
205
425
  @includes ||= []
206
426
  fields.flatten.each do |field|
@@ -212,14 +432,27 @@ module Parse
212
432
  @results = nil if fields.count > 0
213
433
  self # chaining
214
434
  end
215
- alias_method :include, :includes
216
435
 
436
+ # Combine a list of {Parse::Constraint} objects
437
+ # @param list [Array<Parse::Constraint>] an array of Parse::Constraint subclasses.
438
+ # @return [self]
217
439
  def add_constraints(list)
218
440
  list = Array.wrap(list).select { |m| m.is_a?(Parse::Constraint) }
219
441
  @where = @where + list
220
442
  self
221
443
  end
222
444
 
445
+ # Add a constraint to the query. This is mainly used internally for compiling constraints.
446
+ # @example
447
+ # # add where :field equals "value"
448
+ # query.add_constraint(:field.eq, "value")
449
+ #
450
+ # # add where :like_count is greater than 20
451
+ # query.add_constraint(:like_count.gt, 20)
452
+ #
453
+ # @param operator [Parse::Operator] an operator object containing the operation and operand.
454
+ # @param value [Object] the value for the constraint.
455
+ # @return [self]
223
456
  def add_constraint(operator, value = nil, **opts)
224
457
  @where ||= []
225
458
  constraint = operator # assume Parse::Constraint
@@ -242,10 +475,26 @@ module Parse
242
475
  self #chaining
243
476
  end
244
477
 
478
+ # @return [Array<Parse::Constraint>] an array of constraints
479
+ # composing the :where clause for this query.
245
480
  def constraints
246
481
  @where
247
482
  end
248
483
 
484
+ # Add additional query constraints to the `where` clause. The `where` clause
485
+ # is based on utilizing a set of constraints on the defined column names in
486
+ # your Parse classes. The constraints are implemented as method operators on
487
+ # field names that are tied to a value. Any symbol/string that is not one of
488
+ # the main expression keywords described here will be considered as a type of
489
+ # query constraint for the `where` clause in the query.
490
+ # @example
491
+ # # parts of a single where constraint
492
+ # { :column.constraint => value }
493
+ # @see Parse::Constraint
494
+ # @param conditions [Hash] a set of constraints for this query.
495
+ # @param opts [Hash] a set of options when adding the constraints. This is
496
+ # specific for each Parse::Constraint.
497
+ # @return [self]
249
498
  def where(conditions = nil, opts = {})
250
499
  return @where if conditions.nil?
251
500
  if conditions.is_a?(Hash)
@@ -256,6 +505,22 @@ module Parse
256
505
  self #chaining
257
506
  end
258
507
 
508
+ # Combine two where clauses into an OR constraint. Equivalent to the `$or`
509
+ # Parse query operation. This is useful if you want to find objects that
510
+ # match several queries. We overload the `|` operator in order to have a
511
+ # clean syntax for joining these `or` operations.
512
+ # @example
513
+ # query = Player.where(:wins.gt => 150)
514
+ # query.or_where(:wins.lt => 5)
515
+ # # where wins > 150 || wins < 5
516
+ # results = query.results
517
+ #
518
+ # # or_query = query1 | query2 | query3 ...
519
+ # # ex. where wins > 150 || wins < 5
520
+ # query = Player.where(:wins.gt => 150) | Player.where(:wins.lt => 5)
521
+ # results = query.results
522
+ # @param where_clauses [Array<Parse::Constraint>] a list of Parse::Constraint objects to combine.
523
+ # @return [Query] the combined query with an OR clause.
259
524
  def or_where(where_clauses = [])
260
525
  where_clauses = where_clauses.where if where_clauses.is_a?(Parse::Query)
261
526
  where_clauses = Parse::Query.new(@table, where_clauses ).where if where_clauses.is_a?(Hash)
@@ -277,6 +542,8 @@ module Parse
277
542
  self #chaining
278
543
  end
279
544
 
545
+ # @see #or_where
546
+ # @return [Query] the combined query with an OR clause.
280
547
  def |(other_query)
281
548
  raise ArgumentError, "Parse queries must be of the same class #{@table}." unless @table == other_query.table
282
549
  copy_query = self.clone
@@ -284,6 +551,16 @@ module Parse
284
551
  copy_query
285
552
  end
286
553
 
554
+ # Perform a count query.
555
+ # @example
556
+ # # get number of songs with a play_count > 10
557
+ # Song.count :play_count.gt => 10
558
+ #
559
+ # # same
560
+ # query = Parse::Query.new("Song")
561
+ # query.where :play_count.gt => 10
562
+ # query.count
563
+ # @return [Integer] the count result
287
564
  def count
288
565
  old_value = @count
289
566
  @count = 1
@@ -292,36 +569,47 @@ module Parse
292
569
  res
293
570
  end
294
571
 
572
+ # @yield a block yield for each object in the result
573
+ # @return [Array]
574
+ # @see Array#each
295
575
  def each
296
576
  return results.enum_for(:each) unless block_given? # Sparkling magic!
297
577
  results.each(&Proc.new)
298
578
  end
299
579
 
580
+ # @yield a block yield for each object in the result
581
+ # @return [Array]
582
+ # @see Array#map
300
583
  def map
301
584
  return results.enum_for(:map) unless block_given? # Sparkling magic!
302
585
  results.map(&Proc.new)
303
586
  end
304
587
 
588
+ # @yield a block yield for each object in the result
589
+ # @return [Array]
590
+ # @see Array#select
305
591
  def select
306
592
  return results.enum_for(:select) unless block_given? # Sparkling magic!
307
593
  results.select(&Proc.new)
308
594
  end
309
595
 
596
+ # @return [Array]
597
+ # @see Array#to_a
310
598
  def to_a
311
599
  results.to_a
312
600
  end
313
601
 
314
- def select
315
- return results.enum_for(:select) unless block_given? # Sparkling magic!
316
- results.select(&Proc.new)
317
- end
318
-
602
+ # @param limit [Integer] the number of first items to return.
603
+ # @return [Parse::Object] the first object from the result.
319
604
  def first(limit = 1)
320
605
  @results = nil if @limit != limit
321
606
  @limit = limit
322
607
  limit == 1 ? results.first : results.first(limit)
323
608
  end
324
609
 
610
+ # max_results is used to iterate through as many API requests as possible using
611
+ # :skip and :limit paramter.
612
+ # @!visibility private
325
613
  def max_results(raw: false)
326
614
  compiled_query = compile
327
615
  query_limit = compiled_query[:limit] ||= 1_000
@@ -331,10 +619,9 @@ module Parse
331
619
  results = []
332
620
 
333
621
  iterations.times do |idx|
334
- #puts "Fetching 1000 after #{compiled_query[:skip]}"
335
622
  response = fetch!( compiled_query )
336
623
  break if response.error? || response.results.empty?
337
- #puts "Appending #{response.results.count} results..."
624
+
338
625
  items = response.results
339
626
  items = decode(items) unless raw
340
627
 
@@ -353,6 +640,7 @@ module Parse
353
640
  results
354
641
  end
355
642
 
643
+ # @!visibility private
356
644
  def _opts
357
645
  opts = {}
358
646
  opts[:cache] = self.cache || false
@@ -365,6 +653,9 @@ module Parse
365
653
  opts
366
654
  end
367
655
 
656
+ # Performs the fetch request for the query.
657
+ # @param compiled_query [Hash] the compiled query
658
+ # @return [Parse::Response] a response for a query request.
368
659
  def fetch!(compiled_query)
369
660
 
370
661
  response = client.find_objects(@table, compiled_query.as_json, _opts )
@@ -375,6 +666,27 @@ module Parse
375
666
  end
376
667
  alias_method :execute!, :fetch!
377
668
 
669
+ # Executes the query and builds the result set of Parse::Objects that matched.
670
+ # When this method is passed a block, the block is yielded for each matching item
671
+ # in the result, and the items are not returned. This methodology is more performant
672
+ # as large quantifies of objects are fetched in batches and all of them do
673
+ # not have to be kept in memory after the query finishes executing. This is the recommended
674
+ # method of processing large result sets.
675
+ # @example
676
+ # query = Parse::Query.new("_User", :created_at.before => DateTime.now)
677
+ # users = query.results # => Array of Parse::User objects.
678
+ #
679
+ # query = Parse::Query.new("_User", limit: :max)
680
+ #
681
+ # query.results do |user|
682
+ # # recommended; more memory efficient
683
+ # end
684
+ #
685
+ # @param raw [Boolean] whether to get the raw hash results of the query instead of
686
+ # a set of Parse::Object subclasses.
687
+ # @yield a block to iterate for each object that matched the query.
688
+ # @return [Array<Hash>] if raw is set to true, a set of Parse JSON hashes.
689
+ # @return [Array<Parse::Object>] if raw is set to false, a list of matching Parse::Object subclasses.
378
690
  def results(raw: false)
379
691
  if @results.nil?
380
692
  if @limit.nil? || @limit.to_i <= 1_000
@@ -393,18 +705,32 @@ module Parse
393
705
  end
394
706
  alias_method :result, :results
395
707
 
708
+ # Builds objects based on the set of Parse JSON hashes in an array.
709
+ # @param list [Array<Hash>] a list of Parse JSON hashes
710
+ # @return [Array<Parse::Object>] an array of Parse::Object subclasses.
396
711
  def decode(list)
397
712
  list.map { |m| Parse::Object.build(m, @table) }.compact
398
713
  end
399
714
 
715
+ # @return [Hash]
400
716
  def as_json(*args)
401
717
  compile.as_json
402
718
  end
403
719
 
720
+ # Returns a compiled query without encoding the where clause.
721
+ # @param includeClassName [Boolean] whether to include the class name of the collection
722
+ # in the resulting compiled query.
723
+ # @return [Hash] a hash representing the prepared query request.
404
724
  def prepared(includeClassName: false)
405
725
  compile(encode: false, includeClassName: includeClassName)
406
726
  end
407
727
 
728
+ # Complies the query and runs all prepare callbacks.
729
+ # @param encode [Boolean] whether to encode the `where` clause to a JSON string.
730
+ # @param includeClassName [Boolean] whether to include the class name of the collection.
731
+ # @return [Hash] a hash representing the prepared query request.
732
+ # @see #before_prepare
733
+ # @see #after_prepare
408
734
  def compile(encode: true, includeClassName: false)
409
735
  run_callbacks :prepare do
410
736
  q = {} #query
@@ -432,26 +758,15 @@ module Parse
432
758
  end
433
759
  end
434
760
 
761
+ # @return [Hash] a hash representing just the `where` clause of this query.
435
762
  def compile_where
436
763
  self.class.compile_where( @where || [] )
437
764
  end
438
765
 
439
- def self.compile_where(where)
440
- constraint_reduce( where )
441
- end
442
-
443
- def self.constraint_reduce(clauses)
444
- # TODO: Need to add proper constraint merging
445
- clauses.reduce({}) do |clause, subclause|
446
- #puts "Merging Subclause: #{subclause.as_json}"
447
-
448
- clause.deep_merge!( subclause.as_json || {} )
449
- clause
450
- end
451
- end
452
-
453
- def print
454
- puts JSON.pretty_generate( as_json )
766
+ # Retruns a formatted JSON string representing the query, useful for debugging.
767
+ # @return [String]
768
+ def pretty
769
+ JSON.pretty_generate( as_json )
455
770
  end
456
771
 
457
772
  end # Query