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.
-
#group_by(field, **opts) ⇒ Parse::GroupBy, Parse::SortableGroupBy
Groups records by a field and returns a GroupBy (or SortableGroupBy) aggregation object you can call .count, .sum, .average, etc.
-
#group_by_date(field, interval, **opts) ⇒ Parse::GroupByDate, Parse::SortableGroupByDate
Groups records by a date field truncated to the given interval and returns a GroupByDate (or SortableGroupByDate) aggregation object.
-
#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.
-
#pluralized_alias!(constant_name = nil) ⇒ self?
Define a pluralized constant alias for this class so the plural form can be used as a query entry point — e.g.
-
#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, use_master_key: false) {|subscription| ... } ⇒ 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.
281 282 283 284 285 286 |
# File 'lib/parse/model/core/querying.rb', line 281 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.
297 298 299 300 301 |
# File 'lib/parse/model/core/querying.rb', line 297 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.
409 410 411 |
# File 'lib/parse/model/core/querying.rb', line 409 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.
424 425 426 |
# File 'lib/parse/model/core/querying.rb', line 424 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.
508 509 510 |
# File 'lib/parse/model/core/querying.rb', line 508 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.
437 438 439 |
# File 'lib/parse/model/core/querying.rb', line 437 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
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/parse/model/core/querying.rb', line 221 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.
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 |
# File 'lib/parse/model/core/querying.rb', line 595 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.
665 666 667 |
# File 'lib/parse/model/core/querying.rb', line 665 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.
315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/parse/model/core/querying.rb', line 315 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.
333 334 335 336 337 338 339 340 341 342 |
# File 'lib/parse/model/core/querying.rb', line 333 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 |
#group_by(field, **opts) ⇒ Parse::GroupBy, Parse::SortableGroupBy
Groups records by a field and returns a GroupBy (or SortableGroupBy) aggregation object you can call .count, .sum, .average, etc. on.
451 452 453 |
# File 'lib/parse/model/core/querying.rb', line 451 def group_by(field, **opts) query.group_by(field, **opts) end |
#group_by_date(field, interval, **opts) ⇒ Parse::GroupByDate, Parse::SortableGroupByDate
Groups records by a date field truncated to the given interval and returns a GroupByDate (or SortableGroupByDate) aggregation object.
466 467 468 |
# File 'lib/parse/model/core/querying.rb', line 466 def group_by_date(field, interval, **opts) query.group_by_date(field, interval, **opts) end |
#last_updated(count = 1) ⇒ Parse::Object+ #last_updated(constraints = {}) ⇒ Parse::Object
Returns the most recently updated object (ordered by updated_at descending).
387 388 389 390 391 392 393 394 395 396 397 398 399 400 |
# File 'lib/parse/model/core/querying.rb', line 387 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).
358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/parse/model/core/querying.rb', line 358 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
196 197 198 |
# File 'lib/parse/model/core/querying.rb', line 196 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.
473 474 475 476 477 478 |
# File 'lib/parse/model/core/querying.rb', line 473 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.
483 484 485 486 487 488 |
# File 'lib/parse/model/core/querying.rb', line 483 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 |
#pluralized_alias!(constant_name = nil) ⇒ self?
Define a pluralized constant alias for this class so the plural form
can be used as a query entry point — e.g. Posts.where(...).count
for a class Post. The alias is the same class object, so every
class method (where, query, count, find, all, scopes)
works through it and Posts.parse_class still returns "Post".
This is the explicit counterpart to the automatic
Parse.pluralized_aliases behavior. Use it when automatic aliasing
is disabled, when the class name ends in s (which the automatic
path skips), when you want a custom plural, or for namespaced models
(the alias is defined on the enclosing module, not at top level).
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 159 def pluralized_alias!(constant_name = nil) base = name return nil if base.nil? parts = base.split("::") short = parts.last plural = (constant_name && constant_name.to_s) || short.pluralize return nil if plural == short # NOTE: bare `Object` here would lexically resolve to `Parse::Object` # (we are inside module Parse::Core::Querying), so the alias must be # anchored at the true top level with `::Object`. parent = parts.length > 1 ? parts[0..-2].join("::").constantize : ::Object if parent.const_defined?(plural.to_sym, false) existing = parent.const_get(plural.to_sym) return self if existing.equal?(self) # A code reloader (Zeitwerk in development) swaps `self` for a fresh # class object but does not clean up the alias constant we set — it # owns no autoload entry for it. On re-run of the class body the # plural still points at the now-orphaned previous class. Re-point it # to the current class instead of raising on every reload. Only a # genuinely foreign constant (not a Parse model mapping to the same # remote class) is treated as a conflict. stale_reload = existing.is_a?(Class) && existing < Parse::Object && existing.parse_class == parse_class unless stale_reload raise ArgumentError, "Cannot define pluralized alias #{plural} for #{base}: " \ "constant already defined as #{existing}." end parent.send(:remove_const, plural.to_sym) end parent.const_set(plural.to_sym, self) self 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, use_master_key: false) {|subscription| ... } ⇒ 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.
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 |
# File 'lib/parse/model/core/querying.rb', line 557 def subscribe(where: {}, fields: nil, session_token: nil, client: nil, use_master_key: false, &block) # 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, use_master_key: use_master_key, &block ) end |