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
-
#all(constraints = { limit: :max }) { ... } ⇒ Array<Parse::Object>
Fetch all matching objects in this collection matching the constraints.
-
#all_as(token, constraints = { limit: :max }, &block) ⇒ Array<Parse::Object>
Convenience wrapper around #all that runs the query under a caller-supplied session token.
-
#count(constraints = {}) ⇒ Interger
Creates a count request which is more performant when counting objects.
-
#count_distinct(field, constraints = {}) ⇒ Integer
Counts the number of distinct values for a specified field.
-
#cursor(constraints = {}, limit: 100, order: nil) ⇒ Parse::Cursor
Create a cursor-based paginator for efficiently traversing large datasets.
-
#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.
-
#each(constraints = {}) { ... } ⇒ Parse::Object
This methods allow you to efficiently iterate over all the records in the collection (lower memory cost) at a minor cost of performance.
-
#find(*parse_ids, type: :parallel, compact: true, cache: nil, session_token: nil, use_master_key: nil) ⇒ Parse::Object+
(also: #get)
Find objects for a given objectId in this collection.
-
#find_cached(*parse_ids, type: :parallel, compact: true) ⇒ Parse::Object+
Find objects with caching enabled.
-
#first(constraints = {}) ⇒ Object
Returns the first item matching the constraint.
-
#first_as(token, constraints = {}) ⇒ Parse::Object, ...
Convenience wrapper around #first that runs the query under a caller-supplied session token.
-
#last_updated(constraints = {}) ⇒ Object
Returns the most recently updated object (ordered by updated_at descending).
-
#latest(constraints = {}) ⇒ Object
Returns the most recently created object (ordered by created_at descending).
- #literal_where(conditions = {}) ⇒ self
-
#newest(constraints = {}) ⇒ Array<Parse::Object>
Find objects matching the constraint ordered by the descending created_at date.
-
#oldest(constraints = {}) ⇒ Array<Parse::Object>
Find objects matching the constraint ordered by the ascending created_at date.
-
#query(constraints = {}) ⇒ Parse::Query
(also: #where)
Creates a new Query with the given constraints for this class.
-
#scope(name, body) ⇒ Symbol
This feature is a small subset of the ActiveRecord named scopes feature.
-
#subscribe(where: {}, fields: nil, session_token: nil, client: nil) ⇒ Parse::LiveQuery::Subscription
Subscribe to real-time updates for objects in this collection.
Instance Method Details
#all(constraints = { limit: :max }) { ... } ⇒ Array<Parse::Object>
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.
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 |
#all_as(token, constraints = { limit: :max }, &block) ⇒ Array<Parse::Object>
Convenience wrapper around #all that runs the query under a
caller-supplied session token. Equivalent to passing
session_token: in the constraints hash, surfaced as a named
kwarg so client-mode callers don't have to remember the
constraint-key form. Returns nil if token is blank.
227 228 229 230 231 |
# File 'lib/parse/model/core/querying.rb', line 227 def all_as(token, constraints = { limit: :max }, &block) tok = token.respond_to?(:session_token) ? token.session_token : token return nil if tok.nil? || tok.to_s.empty? all(constraints.merge(session_token: tok), &block) end |
#count(constraints = {}) ⇒ Interger
Creates a count request which is more performant when counting objects.
339 340 341 |
# File 'lib/parse/model/core/querying.rb', line 339 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.
354 355 356 |
# File 'lib/parse/model/core/querying.rb', line 354 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.
409 410 411 |
# File 'lib/parse/model/core/querying.rb', line 409 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.
367 368 369 |
# File 'lib/parse/model/core/querying.rb', line 367 def distinct(field, constraints = {}) query(constraints).distinct(field) end |
#each(constraints = {}) { ... } ⇒ Parse::Object
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
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, session_token: nil, use_master_key: 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.
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 |
# File 'lib/parse/model/core/querying.rb', line 472 def find(*parse_ids, type: :parallel, compact: true, cache: nil, session_token: nil, use_master_key: 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 } # Forward session-token / use_master_key when supplied so client-mode # callers can scope a `.find` to a logged-in user without dropping # down to the raw `client.fetch_object` form. client_opts[:session_token] = session_token unless session_token.nil? client_opts[:use_master_key] = use_master_key unless use_master_key.nil? # The parallel path spawns worker threads via `Parallel.map`. Worker # threads don't inherit fiber-local storage from the calling thread, # so `Parse.current_session_token` resolved inside the worker would # be nil even when the caller is inside a `Parse.with_session(...)` # block. Snapshot the ambient here (in the calling thread) and pass # it explicitly so each worker sends the right auth. if !client_opts.key?(:session_token) ambient = Parse.current_session_token client_opts[:session_token] = ambient if ambient.is_a?(String) && !ambient.empty? end 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 query.session_token = session_token unless session_token.nil? query.use_master_key = use_master_key unless use_master_key.nil? 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.
542 543 544 |
# File 'lib/parse/model/core/querying.rb', line 542 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.
245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/parse/model/core/querying.rb', line 245 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 |
#first_as(token, constraints = {}) ⇒ Parse::Object, ...
Convenience wrapper around #first that runs the query under a caller-supplied session token.
263 264 265 266 267 268 269 270 271 272 |
# File 'lib/parse/model/core/querying.rb', line 263 def first_as(token, constraints = {}) tok = token.respond_to?(:session_token) ? token.session_token : token return nil if tok.nil? || tok.to_s.empty? if constraints.is_a?(Numeric) # `first(2)` shape — surface kwarg via a synthetic constraints hash first({ limit: constraints.to_i, session_token: tok }) else first(constraints.merge(session_token: tok)) end end |
#last_updated(count = 1) ⇒ Parse::Object+ #last_updated(constraints = {}) ⇒ Parse::Object
Returns the most recently updated object (ordered by updated_at descending).
317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/parse/model/core/querying.rb', line 317 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).
288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
# File 'lib/parse/model/core/querying.rb', line 288 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
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.
374 375 376 377 378 379 |
# File 'lib/parse/model/core/querying.rb', line 374 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.
384 385 386 387 388 389 |
# File 'lib/parse/model/core/querying.rb', line 384 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.
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.
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.
441 442 443 444 445 446 447 448 449 450 |
# File 'lib/parse/model/core/querying.rb', line 441 def subscribe(where: {}, fields: nil, session_token: nil, client: nil) # Fall through to the ambient set by `Parse.with_session` / `Parse.login` # so a caller wrapping a region with `with_session(user) { Klass.subscribe ... }` # gets an ACL-aware subscription without re-threading the token. if session_token.nil? ambient = Parse.current_session_token session_token = ambient if ambient.is_a?(String) && !ambient.empty? end query(where).subscribe(fields: fields, session_token: session_token, client: client) end |