Module: Parse::Core::Querying

Included in:
Object
Defined in:
lib/parse/model/core/querying.rb

Overview

Defines the querying methods applied to a Parse::Object.

Instance Method Summary collapse

Instance Method Details

#all(constraints = {limit: :max}) { ... } ⇒ Array<Parse::Object>

Note:

This method will continually query for records by automatically incrementing the :skip parameter until no more results are returned by the server.

Fetch all matching objects in this collection matching the constraints. This will be the most common way when querying Parse objects for a subclass. When no block is passed, all objects are returned. Using a block is more memory efficient as matching objects are fetched in batches and discarded after the iteration is completed.

Examples:


songs = Song.all( ... expressions ...) # => array of Parse::Objects
# memory efficient for large amounts of records.
Song.all( ... expressions ...) do |song|
    # ... do something with song..
end

Parameters:

  • constraints (Hash) (defaults to: {limit: :max})

    a set of Query constraints.

Yields:

  • a block to iterate with each matching object.

Returns:

  • (Array<Parse::Object>)

    an array of matching objects. If a block is passed, an empty array is returned.


213
214
215
216
217
218
# File 'lib/parse/model/core/querying.rb', line 213

def all(constraints = {limit: :max})
  constraints = constraints.reverse_merge({limit: :max})
  prepared_query = query(constraints)
  return prepared_query.results(&Proc.new) if block_given?
  prepared_query.results
end

#count(constraints = {}) ⇒ Interger

Creates a count request which is more performant when counting objects.

Examples:

# number of songs with a like count greater than 20.
count = Song.count( :like_count.gt => 20 )

Parameters:

  • constraints (Hash) (defaults to: {})

    a set of Query constraints.

Returns:

  • (Interger)

    the number of records matching the query.

See Also:


251
252
253
# File 'lib/parse/model/core/querying.rb', line 251

def count(constraints = {})
  query(constraints).count
end

#distinct(field, constraints = {}) ⇒ Array

Finds the distinct values for a specified field across a single collection or view and returns the results in an array.

Examples:

# get a list of unique city names for users who are older than 21.
cities = User.distinct(:city, :age.gt => 21 )

Parameters:

  • field

    The name of the field to use for unique aggregation.

  • constraints (Hash) (defaults to: {})

    a set of Query constraints.

Returns:

  • (Array)

    a list of distinct values

See Also:


264
265
266
# File 'lib/parse/model/core/querying.rb', line 264

def distinct(field, constraints = {})
  query(constraints).distinct(field)
end

#each(constraints = {}) { ... } ⇒ Parse::Object

Note:

You cannot use :created_at as a constraint.

This methods allow you to efficiently iterate over all the records in the collection (lower memory cost) at a minor cost of performance. This method utilizes the `created_at` field of Parse records to order and iterate over all matching records, therefore you should not use this method if you want to perform a query with constraints against the `created_at` field or need specific type of ordering. If you need to use `:created_at` in your constraints, consider using #all or Actions::ClassMethods#save_all

Examples:


post = Post.first
# iterate over all comments matching conditions
Comment.each(post: post) do |comment|
   # ...
end

Parameters:

  • constraints (Hash) (defaults to: {})

    a set of query constraints.

Yields:

  • a block which will iterate through each matching record.

Returns:

Raises:

  • ArgumentError if :created_at is detected in the constraints argument.

See Also:

  • all
  • Actions.save_all

152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/parse/model/core/querying.rb', line 152

def each(constraints = {}, &block)
  # verify we don't hvae created at as a constraint, otherwise this will not work
  invalid_constraints = constraints.keys.any? do |k|
    (k == :created_at || k == :createdAt) ||
    ( k.is_a?(Parse::Operation) && (k.operand == :created_at || k.operand == :createdAt) )
  end
  if invalid_constraints
    raise ArgumentError, "[#{self.class}.each] Special method each()" \
                         "cannot be used with a :created_at constraint."
  end
  batch_size = 250
  start_cursor = first( order: :created_at.asc, keys: :created_at )
  constraints.merge! cache: false, limit: batch_size, order: :created_at.asc
  all_query = query(constraints)
  cursor = start_cursor
  # the exclusion set is a set of ids not to include the next query.
  exclusion_set = []
  loop do
    _q = query(constraints.dup)
    _q.where(:created_at.on_or_after => cursor.created_at)
    # set of ids not to include in the next query. non-performant, but accurate.
    _q.where(:id.nin => exclusion_set) unless exclusion_set.empty?
    results = _q.results # get results

    break cursor if results.empty? # break if no results
    results.each(&block)
    next_cursor = results.last
    # break if we got less than the maximum requested
    break next_cursor if results.count < batch_size
    # break if the next object is the same as the current object.
    break next_cursor if cursor.id == next_cursor.id
    # The exclusion set is used in the case where multiple records have the exact
    # same created_at date (down to the microsecond). This prevents getting the same
    # record in the next query request.
    exclusion_set = results.select { |r| r.created_at == next_cursor.created_at }.map(&:id)
    results = nil
    cursor = next_cursor
  end

end

#find(*parse_ids, type: :parallel, compact: true) ⇒ Parse::Object+ Also known as: get

Find objects for a given objectId in this collection.The result is a list (or single item) of the objects that were successfully found.

Examples:

Object.find "<objectId>"
Object.find "<objectId>", "<objectId>"....
Object.find ["<objectId>", "<objectId>"]

Parameters:

  • parse_ids (String)

    the objectId to find.

  • type (Symbol)

    the fetching methodology to use if more than one id was passed.

    • :parallel : Utilizes parrallel HTTP requests to fetch all objects requested.

    • :batch : This uses a batch fetch request using a contained_in clause.

  • compact (Boolean)

    whether to remove nil items from the returned array for objects that were not found.

Returns:


302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/parse/model/core/querying.rb', line 302

def find(*parse_ids, type: :parallel, compact: true)
  # flatten the list of Object ids.
  parse_ids.flatten!
  parse_ids.compact!
  # determines if the result back to the call site is an array or a single result
  as_array = parse_ids.count > 1
  results = []

  if type == :batch
    # use a .in query with the given id as a list
    results = self.class.all(:id.in => parse_ids)
  else
    # use Parallel to make multiple threaded requests for finding these objects.
    # The benefit of using this as default is that each request goes to a specific URL
    # which is better than Query request (table scan). This in turn allows for caching of
    # individual objects.
    results = parse_ids.threaded_map do |parse_id|
      next nil unless parse_id.present?
      response = client.fetch_object(parse_class, parse_id)
      next nil if response.error?
      Parse::Object.build response.result, parse_class
    end
  end
  # removes any nil items in the array
  results.compact! if compact

  as_array ? results : results.first
end

#first(count = 1) ⇒ Parse::Object+ #first(constraints = {}) ⇒ Parse::Object

Returns the first item matching the constraint.

Overloads:

  • #first(count = 1) ⇒ Parse::Object+

    Examples:

    Object.first(2) # => an array of the first 2 objects in the collection.

    Parameters:

    • count (Interger) (defaults to: 1)

      The number of items to return.

    Returns:

  • #first(constraints = {}) ⇒ Parse::Object

    Returns the first matching object.

    Examples:

    Object.first( :name => "Anthony" )

    Parameters:

    • constraints (Hash) (defaults to: {})

      a set of Query constraints.

    Returns:


232
233
234
235
236
237
238
239
240
241
242
# File 'lib/parse/model/core/querying.rb', line 232

def first(constraints = {})
  fetch_count = 1
  if constraints.is_a?(Numeric)
    fetch_count = constraints.to_i
    constraints = {}
  end
  constraints.merge!( {limit: fetch_count} )
  res = query(constraints).results
  return res.first if fetch_count == 1
  return res.first fetch_count
end

#literal_where(conditions = {}) ⇒ self

Parameters:

  • conditions (Hash) (defaults to: {})

    a set of constraints for this query.

Returns:

  • (self)

See Also:


127
128
129
# File 'lib/parse/model/core/querying.rb', line 127

def literal_where(conditions = {})
  query.where(conditions)
end

#newest(constraints = {}) ⇒ Array<Parse::Object>

Find objects matching the constraint ordered by the descending created_at date.

Parameters:

  • constraints (Hash) (defaults to: {})

    a set of Query constraints.

Returns:


271
272
273
274
275
276
# File 'lib/parse/model/core/querying.rb', line 271

def newest(constraints = {})
  constraints.merge!(order: :created_at.desc)
  _q = query(constraints)
  _q.define_singleton_method(:method_missing) { |m, *args, &block| self.results.send(m, *args, &block) }
  _q
end

#oldest(constraints = {}) ⇒ Array<Parse::Object>

Find objects matching the constraint ordered by the ascending created_at date.

Parameters:

  • constraints (Hash) (defaults to: {})

    a set of Query constraints.

Returns:


281
282
283
284
285
286
# File 'lib/parse/model/core/querying.rb', line 281

def oldest(constraints = {})
  constraints.merge!(order: :created_at.asc)
  _q = query(constraints)
  _q.define_singleton_method(:method_missing) { |m, *args, &block| self.results.send(m, *args, &block) }
  _q
end

#query(constraints = {}) ⇒ Parse::Query Also known as: where

Creates a new Query with the given constraints for this class.

Examples:

# assume Post < Parse::Object
query = Post.query(:updated_at.before => DateTime.now)

Returns:

  • (Parse::Query)

    a new query with the given constraints for this Parse::Object subclass.


120
121
122
# File 'lib/parse/model/core/querying.rb', line 120

def query(constraints = {})
  Parse::Query.new self.parse_class, constraints
end

#scope(name, body) ⇒ Symbol

This feature is a small subset of the ActiveRecord named scopes feature. Scoping allows you to specify commonly-used queries which can be referenced as class method calls and are chainable with other scopes. You can use every Query method previously covered such as `where`, `includes` and `limit`.

class Article < Parse::Object
  property :published, :boolean
  scope :published, -> { query(published: true) }
end

This is the same as defining your own class method for the query.

class Article < Parse::Object
  def self.published
    query(published: true)
  end
end

You can also chain scopes and pass parameters. In addition, boolean and enumerated properties have automatically generated scopes for you to use.

class Article < Parse::Object
  scope :published, -> { query(published: true) }

  property :comment_count, :integer
  property :category
  property :approved, :boolean

  scope :published_and_commented, -> { published.where :comment_count.gt => 0 }
  scope :popular_topics, ->(name) { published_and_commented.where category: name }
end

# simple scope
Article.published # => where published is true

# chained scope
Article.published_and_commented # published is true and comment_count > 0

# scope with parameters
Article.popular_topic("music") # => popular music articles
# equivalent: where(published: true, :comment_count.gt => 0, category: name)

# automatically generated scope
Article.approved(category: "tour") # => where approved: true, category: 'tour'

If you would like to turn off automatic scope generation for property types, set the option `:scope` to false when declaring the property.

Parameters:

  • name (Symbol)

    the name of the scope.

  • body (Proc)

    the proc related to the scope.

Returns:

  • (Symbol)

    the name of the singleton method created.

Raises:

  • ArgumentError if body parameter does not respond to `call`


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/parse/model/core/querying.rb', line 64

def scope(name, body)
  unless body.respond_to?(:call)
    raise ArgumentError, 'The scope body needs to be callable.'
  end

  name = name.to_sym
  if respond_to?(name, true)
    puts "Creating scope :#{name}. Will overwrite existing method #{self}.#{name}."
  end

  define_singleton_method(name) do |*args, &block|

    if body.arity.zero?
      res = body.call
      res.conditions(*args) if args.present?
    else
      res = body.call(*args)
    end

    _q = res || query

    if _q.is_a?(Parse::Query)
      klass = self
      _q.define_singleton_method(:method_missing) do |m, *args, &chained_block|
        if klass.respond_to?(m, true)
          # must be a scope
          klass_scope = klass.send(m, *args)
          if klass_scope.is_a?(Parse::Query)
            # merge constraints
            add_constraints( klass_scope.constraints )
            # if a block was passed, execute the query, otherwise return the query
            return chained_block.present? ? results(&chained_block) : self
          end # if
          klass = nil # help clean up ruby gc
          return klass_scope
        end
        klass = nil # help clean up ruby gc
        return results.send(m, *args, &chained_block)
      end
    end

    Parse::Query.apply_auto_introspection!(_q)

    return _q if block.nil?
    _q.results(&block)
  end

end