parse-stack 1.5.1 → 1.5.2

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 (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