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.



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

def all(constraints = { limit: :max }, &block)
  constraints = constraints.reverse_merge({ limit: :max })
  prepared_query = query(constraints)
  return prepared_query.results(&block) 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:



307
308
309
# File 'lib/parse/model/core/querying.rb', line 307

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

#count_distinct(field, constraints = {}) ⇒ Integer

Counts the number of distinct values for a specified field. Uses MongoDB aggregation pipeline to efficiently count unique values.

Examples:

# get count of unique genres for songs with play_count > 100
distinct_genres_count = Song.count_distinct(:genre, :play_count.gt => 100)
# get total number of unique users
unique_users = User.count_distinct(:objectId)

Parameters:

  • field (Symbol|String)

    The name of the field to count distinct values for.

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

    a set of Query constraints.

Returns:

  • (Integer)

    the number of distinct values

See Also:



322
323
324
# File 'lib/parse/model/core/querying.rb', line 322

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

#cursor(constraints = {}, limit: 100, order: nil) ⇒ Parse::Cursor

Create a cursor-based paginator for efficiently traversing large datasets. This is more efficient than skip/offset pagination for large result sets.

Examples:

Basic usage

cursor = Song.cursor(limit: 100, order: :created_at.desc)
cursor.each_page do |page|
  process(page)
end

With constraints

cursor = Song.cursor(artist: "Artist Name", limit: 50)
cursor.each { |song| puts song.title }

Parameters:

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

    query constraints to apply

  • limit (Integer) (defaults to: 100)

    number of items per page (default: 100)

  • order (Symbol, Parse::Order) (defaults to: nil)

    the ordering for pagination

Returns:

See Also:



377
378
379
# File 'lib/parse/model/core/querying.rb', line 377

def cursor(constraints = {}, limit: 100, order: nil)
  query(constraints).cursor(limit: limit, order: order)
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:



335
336
337
# File 'lib/parse/model/core/querying.rb', line 335

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


151
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
# File 'lib/parse/model/core/querying.rb', line 151

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) # used for reference in loop below
  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, cache: nil) ⇒ 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. By default, bypasses the cache to ensure fresh data from the server.

Examples:

Object.find "<objectId>"
Object.find "<objectId>", "<objectId>"....
Object.find ["<objectId>", "<objectId>"]
Object.find "<objectId>", cache: true  # opt-in to cache

Parameters:

  • parse_ids (String)

    the objectId to find.

  • type (Symbol) (defaults to: :parallel)

    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) (defaults to: true)

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

  • cache (Boolean, Symbol) (defaults to: nil)

    caching mode. Defaults to :write_only when Parse.cache_write_on_fetch is true.

    • :write_only (default) - skip cache read, but update cache with fresh data

    • true - read from and write to cache

    • false - completely bypass cache (no read or write)

Returns:



433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'lib/parse/model/core/querying.rb', line 433

def find(*parse_ids, type: :parallel, compact: true, cache: nil)
  # 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 = []

  # Default to write-only cache mode - find always gets fresh data
  # but updates cache for future cached reads. Controlled by feature flag.
  if cache.nil?
    cache = Parse.cache_write_on_fetch ? :write_only : false
  end

  # Extract cache option for client requests
  client_opts = { cache: cache }

  if type == :batch
    # use a .in query with the given id as a list
    query = self.class.query(:id.in => parse_ids)
    query.cache = cache
    results = query.results
  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, **client_opts)
      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

#find_cached(*parse_ids, type: :parallel, compact: true) ⇒ Parse::Object+

Find objects with caching enabled. This is a convenience method that calls find with cache: true.

Examples:

Object.find_cached "<objectId>"
Object.find_cached "<objectId>", "<objectId>"....

Parameters:

  • parse_ids (String)

    the objectId(s) to find.

  • type (Symbol) (defaults to: :parallel)

    the fetching methodology (:parallel or :batch).

  • compact (Boolean) (defaults to: true)

    whether to remove nil items from the returned array.

Returns:

See Also:



486
487
488
# File 'lib/parse/model/core/querying.rb', line 486

def find_cached(*parse_ids, type: :parallel, compact: true)
  find(*parse_ids, type: type, compact: compact, cache: true)
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:



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

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

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

Returns the most recently updated object (ordered by updated_at descending).

Overloads:

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

    Examples:

    Object.last_updated(5) # => an array of the 5 most recently updated objects.

    Parameters:

    • count (Integer) (defaults to: 1)

      The number of items to return.

    Returns:

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

    Returns the most recently updated object matching constraints.

    Examples:

    Object.last_updated(status: "active") # => most recently updated active object
    Object.last_updated(:user.eq => user, limit: 3) # => 3 most recently updated for user

    Parameters:

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

      a set of Query constraints. Supports a :limit key to override the default limit of 1.

    Returns:

    • (Parse::Object)

      the most recently updated object matching constraints.



285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/parse/model/core/querying.rb', line 285

def last_updated(constraints = {})
  fetch_count = 1
  if constraints.is_a?(Numeric)
    fetch_count = constraints.to_i
    constraints = {}
  else
    # Allow limit to be specified in constraints hash
    fetch_count = constraints.delete(:limit) || 1
  end
  constraints.merge!({ limit: fetch_count, order: :updated_at.desc })
  res = query(constraints).results
  return res.first if fetch_count == 1
  return res.first fetch_count
end

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

Returns the most recently created object (ordered by created_at descending).

Overloads:

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

    Examples:

    Object.latest(3) # => an array of the 3 most recently created objects.

    Parameters:

    • count (Integer) (defaults to: 1)

      The number of items to return.

    Returns:

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

    Returns the most recently created object matching constraints.

    Examples:

    Object.latest(category: "news") # => most recent object in news category
    Object.latest(:user.eq => user, limit: 5) # => 5 most recent for user

    Parameters:

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

      a set of Query constraints. Supports a :limit key to override the default limit of 1.

    Returns:

    • (Parse::Object)

      the most recently created object matching constraints.



256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/parse/model/core/querying.rb', line 256

def latest(constraints = {})
  fetch_count = 1
  if constraints.is_a?(Numeric)
    fetch_count = constraints.to_i
    constraints = {}
  else
    # Allow limit to be specified in constraints hash
    fetch_count = constraints.delete(:limit) || 1
  end
  constraints.merge!({ limit: fetch_count, order: :created_at.desc })
  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:



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

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:



342
343
344
345
346
347
# File 'lib/parse/model/core/querying.rb', line 342

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:



352
353
354
355
356
357
# File 'lib/parse/model/core/querying.rb', line 352

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.



117
118
119
# File 'lib/parse/model/core/querying.rb', line 117

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

#subscribe(where: {}, fields: nil, session_token: nil, client: nil) ⇒ Parse::LiveQuery::Subscription

Subscribe to real-time updates for objects in this collection. Uses Parse LiveQuery WebSocket connection to receive push notifications when objects are created, updated, deleted, or enter/leave the query results.

Examples:

Basic subscription (all objects)

subscription = Song.subscribe
subscription.on(:create) { |song| puts "New song: #{song.title}" }
subscription.on(:update) { |song, original| puts "Updated!" }
subscription.on(:delete) { |song| puts "Deleted!" }

Subscribe with query constraints

subscription = Song.subscribe(where: { artist: "Beatles" })
subscription.on_create { |song| puts "New Beatles song!" }

With field filtering

subscription = User.subscribe(where: { status: "online" }, fields: ["name", "avatar"])
subscription.on_update { |user| puts "User changed: #{user.name}" }

With session token for ACL-aware subscriptions

subscription = PrivateData.subscribe(session_token: current_user.session_token)

Parameters:

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

    query constraints for the subscription

  • fields (Array<String>) (defaults to: nil)

    specific fields to watch for changes (nil = all fields)

  • session_token (String) (defaults to: nil)

    session token for ACL-aware subscriptions

  • client (Parse::LiveQuery::Client) (defaults to: nil)

    custom LiveQuery client (optional)

Returns:

See Also:



409
410
411
# File 'lib/parse/model/core/querying.rb', line 409

def subscribe(where: {}, fields: nil, session_token: nil, client: nil)
  query(where).subscribe(fields: fields, session_token: session_token, client: client)
end