Class: Parse::Object

Overview

This is the core class for all app specific Parse table subclasses. This class in herits from Parse::Pointer since an Object is a Parse::Pointer with additional fields, at a minimum, created_at, updated_at and ACLs. This class also handles all the relational types of associations in a Parse application and handles the main CRUD operations.

As the parent class to all custom subclasses, this class provides the default property schema:

class Parse::Object # All subclasses will inherit these properties by default.

 # the objectId column of a record.
 property :id, :string, field: :objectId

 # The the last updated date for a record (Parse::Date)
 property :updated_at, :date

 # The original creation date of a record (Parse::Date)
 property :created_at, :date

 # The Parse::ACL field
 property :acl, :acl, field: :ACL

end

Most Pointers and Object subclasses are treated the same. Therefore, defining a class Artist < Parse::Object that only has id set, will be treated as a pointer. Therefore a Parse::Object can be in a "pointer" state based on the data that it contains. Becasue of this, it is possible to take a Artist instance (in this example), that is in a pointer state, and fetch the rest of the data for that particular record without having to create a new object. Doing so would now mark it as not being a pointer anymore. This is important to the understanding on how relations and properties are handled.

The implementation of this class is large and has been broken up into several modules.

Properties:

All columns in a Parse object are considered a type of property (ex. string, numbers, arrays, etc) except in two cases - Pointers and Relations. For a detailed discussion of properties, see The Defining Properties section.

Associations:

Parse supports a three main types of relational associations. One type of relation is the One-to-One association. This is implemented through a specific column in Parse with a Pointer data type. This pointer column, contains a local value that refers to a different record in a separate Parse table. This association is implemented using the :belongs_to feature. The second association is of One-to-Many. This is implemented is in Parse as a Array type column that contains a list of of Parse pointer objects. It is recommended by Parse that this array does not exceed 100 items for performance reasons. This feature is implemented using the :has_many operation with the plural name of the local Parse class. The last association type is a Parse Relation. These can be used to implement a large Many-to-Many association without requiring an explicit intermediary Parse table or class. This feature is also implemented using the :has_many method but passing the option of :relation.

Defined Under Namespace

Modules: ValidationCallbackOnSupport

Constant Summary collapse

VALID_ACL_POLICIES =

Valid ACL policies that can be passed to acl_policy.

[:public, :public_read, :private, :owner_else_public, :owner_else_private, :owner_but_public_read].freeze
BUILTIN_PARSE_CLASS_NAMES =

SDK-provided Parse model class names that the policy resolver and init-time default-ACL stamp both skip. Parse Server applies its own per-class defaults for these classes when the save body omits the ACL field — most importantly, _User gets {"<user-id>": R/W, "*": R} so the newly created user can edit their own profile. Stamping any ACL from the SDK side (even {}) overrides those server-side defaults and is almost always wrong.

%w[
  Parse::User Parse::Installation Parse::Session Parse::Role
  Parse::Product Parse::PushStatus Parse::Audience
  Parse::JobStatus Parse::JobSchedule
].freeze
IDENTIFICATION_FIELDS =

Core identification fields that are always included in serialization unless strict: true is specified

%w[id objectId __type className].freeze

Constants included from Core::Schema

Core::Schema::DEFAULT_PUBLIC_CLP, Core::Schema::SCHEMA_READONLY_CLASSES

Constants included from Core::Describe

Core::Describe::ALL_SECTIONS, Core::Describe::CORE_FIELD_KEYS, Core::Describe::LOCAL_SECTIONS, Core::Describe::NETWORK_SECTIONS

Constants included from Core::Indexing

Core::Indexing::INDEX_REGISTRY_MUTEX, Core::Indexing::MAX_INDEXES_PER_COLLECTION, Core::Indexing::PARSE_MANAGED_ARRAY_FIELDS, Core::Indexing::SENSITIVE_FIELDS

Constants included from Core::SearchIndexing

Core::SearchIndexing::ALLOWED_INDEX_TYPES, Core::SearchIndexing::INDEX_NAME_PATTERN

Constants included from Core::VectorSearchable

Core::VectorSearchable::VECTOR_VISIBILITY_MODES

Constants included from Core::Fetching

Core::Fetching::NON_SERIALIZABLE_IVARS

Constants included from Core::EmbedManaged

Core::EmbedManaged::WRITER_KEY

Constants included from Core::ParseReference

Core::ParseReference::OBJECT_ID_LENGTH, Core::ParseReference::SEPARATOR

Constants included from Core::FieldGuards

Core::FieldGuards::GUARD_MODES

Constants included from Properties

Properties::BASE, Properties::BASE_FIELD_MAP, Properties::BASE_KEYS, Properties::CORE_FIELDS, Properties::DELETE_OP, Properties::PROTECTED_INITIALIZE_KEYS, Properties::PROTECTED_MASS_ASSIGNMENT_KEYS, Properties::TYPES

Constants inherited from Pointer

Pointer::ATTRIBUTES, Pointer::OBJECT_ID_FORMAT

Constants inherited from Model

Model::CLASS_AUDIENCE, Model::CLASS_INSTALLATION, Model::CLASS_JOB_SCHEDULE, Model::CLASS_JOB_STATUS, Model::CLASS_PRODUCT, Model::CLASS_PUSH_STATUS, Model::CLASS_ROLE, Model::CLASS_SCHEMA, Model::CLASS_SESSION, Model::CLASS_USER, Model::ID, Model::KEY_CLASS_NAME, Model::KEY_CREATED_AT, Model::KEY_OBJECT_ID, Model::KEY_UPDATED_AT, Model::OBJECT_ID, Model::SYSTEM_CLASS_MAP, Model::TYPE_ACL, Model::TYPE_BYTES, Model::TYPE_DATE, Model::TYPE_FIELD, Model::TYPE_FILE, Model::TYPE_GEOPOINT, Model::TYPE_NUMBER, Model::TYPE_OBJECT, Model::TYPE_POINTER, Model::TYPE_POLYGON, Model::TYPE_RELATION

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class-Level Permissions (CLP) collapse

Field Filtering (CLP) collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Core::Querying

all, all_as, count, count_distinct, cursor, distinct, each, find, find_cached, first, first_as, group_by, group_by_date, last_updated, latest, literal_where, newest, oldest, pluralized_alias!, query, scope, subscribe

Methods included from Core::Schema

_default_class_level_permissions_for_upgrade, auto_upgrade!, create_schema, fetch_schema, reset_clp!, update_schema

Methods included from Core::Describe

describe

Methods included from Core::Indexing

apply_indexes!, indexes_plan, mongo_geo_index, mongo_index, mongo_index_declarations, mongo_relation_index, unique_index_on

Methods included from Core::SearchIndexing

apply_search_indexes!, mongo_search_index, mongo_search_index_declarations, search_indexes_plan

Methods included from Core::VectorSearchable

find_similar, hybrid_search, vector_visibility, vectors_public_by_default?

Methods included from Agent::MetadataDSL

#agent_description, #agent_methods, #property_descriptions, #property_enum_descriptions

Methods included from Core::Actions

#_deleted?, #change_requests, #changes_applied!, #changes_payload, #create, #destroy, #destroy_request, #op_add!, #op_add_relation!, #op_add_unique!, #op_destroy!, #op_increment!, #op_remove!, #op_remove_relation!, #operate_field!, #prepare_save!, #relation_change_operations, #save, #save!, #set_attributes!, #update, #update!, #update_relations, #uri_path

Methods included from Core::Fetching

#autofetch!, #fetch, #fetch!, #fetch_cache!, #fetch_json, #fetch_object, #prepare_for_dirty_tracking!

Methods included from Core::EmbedManaged

#compute_embedding!

Methods included from Associations::HasMany

has_many, #relation_changes?, #relation_updates, #relations

Methods included from Associations::BelongsTo

belongs_to, #key?

Methods included from Associations::HasOne

has_one

Methods included from Core::ParseReference

format, generate_object_id, parse

Methods included from Core::FieldGuards

#apply_field_guards!

Methods included from Properties

#apply_attributes!, #attribute_changes?, #attribute_updates, #attributes, #attributes=, #field_map, #fields, #format_operation, #format_value

Methods inherited from Pointer

#==, #attributes, #fetch, #fetch_cache!, #fetch_json, #fetch_object, #hash, #json_hash, #method_missing, #pointer, #pointer?, #present?, #respond_to_missing?, #sig

Methods inherited from Model

#dirty?, find_class, same_parse_class?

Methods included from Client::Connectable

#client

Constructor Details

#new(id) ⇒ Parse::Object #new(hash = {}) ⇒ Parse::Object

Note:

Should only be called with Parse::Object subclasses.

The main constructor for subclasses. It can take different parameter types including a String and a JSON hash. Assume a Post class that inherits from Parse::Object:

Overloads:

  • #new(id) ⇒ Parse::Object

    Create a new object with an objectId. This method is useful for creating an unfetched object (pointer-state).

    Examples:

    Post.new "1234"

    Parameters:

    • id (String)

      The object id.

  • #new(hash = {}) ⇒ Parse::Object

    Create a new object with Parse JSON hash.

    Examples:

    # JSON hash from Parse
    Post.new({"className" => "Post", "objectId" => "1234", "title" => "My Title"})
    
    post = Post.new title: "My Title"
    post.title # => "My Title"

    Parameters:

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

      the hash representing the object. Untrusted by default: keys in Properties::PROTECTED_INITIALIZE_KEYS (+sessionToken+, +_rperm+, +_wperm+, +_hashed_password+, +authData+, +roles+) are filtered out even when an +objectId+ is present. This closes the mass-assignment hole where +klass.new(attacker_params)+ on a hash that happens to include +objectId+ would overwrite session tokens, ACLs, and auth data. Use build for trusted hydration from server JSON; it bypasses the filter.



1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
# File 'lib/parse/model/object.rb', line 1340

def initialize(opts = {})
  # Trusted hydration is signalled by the +@_trusted_init+ instance
  # variable rather than by a +trusted:+ keyword argument. Using a
  # keyword would break subclasses that override +initialize(*args)+
  # and call +super+ — Ruby 3 keyword-arg semantics would convert the
  # kwarg into a positional Hash through the variadic +*args+ splat
  # and the subsequent +super+ would arrive at this method with two
  # positional args. The internal hydration paths
  # ({Parse::Object.build}, {Parse::Pointer} autofetch,
  # {Parse::User#session}) +allocate+ the object, set the ivar, then
  # invoke +initialize+ so subclass overrides still fire and pick up
  # the trust signal here.
  trusted = @_trusted_init == true
  @_trusted_init = nil
  acl_owner_override = nil
  if opts.is_a?(String) #then it's the objectId
    @id = opts.to_s
  elsif opts.is_a?(Hash)
    # Pop the `:as` option (also accepts string key) before applying
    # attributes so it is not mistaken for a model property. This holds
    # the caller-supplied owner user for save-time ACL resolution.
    acl_owner_override = opts.delete(:as) || opts.delete("as")
    #if the objectId is provided we will consider the object pristine
    #and not track dirty items
    dirty_track = opts[Parse::Model::OBJECT_ID] || opts[:objectId] || opts[:id]
    # Always filter the narrow PROTECTED_INITIALIZE_KEYS set unless
    # the caller is a trusted hydration path. Decoupled from
    # dirty_track so an objectId-bearing hash from a controller,
    # JSON params, or cache rehydrator cannot mass-assign
    # sessionToken / _rperm / _wperm / _hashed_password / authData /
    # roles. The narrow list deliberately allows createdAt /
    # updatedAt / className / __type through so the legitimate
    # +Klass.new("objectId" => id, "createdAt" => ts, …)+
    # cache-rehydrate pattern keeps working.
    apply_attributes!(opts,
                      dirty_track: !dirty_track,
                      filter_protected: !trusted,
                      protected_set: Parse::Properties::PROTECTED_INITIALIZE_KEYS)
  end

  # If the caller did not set an ACL via opts, stamp the class default ACL
  # (the policy's fallback half) so `obj.acl` reads sensibly pre-save.
  # We mark the object as "ACL-pristine": the save-time resolver
  # (#_resolve_default_acl) may upgrade this to an owner-only ACL if an
  # `as:` user or owner field is resolvable. Any explicit caller change
  # via `acl=` flips pristine off via #acl_will_change!.
  #
  # Built-in Parse classes (User, Installation, Session, Role, …) are
  # exempt: the SDK leaves their `acl` untouched (nil) so the save body
  # omits the `ACL` field and Parse Server applies its own per-class
  # defaults. Most importantly this lets `_User` get the standard
  # self-write-plus-public-read ACL on signup; stamping any value from
  # the SDK side (even `{}`) overrides that and locks the new user out
  # of editing their own profile without the master key.
  acl_was_user_supplied = !self.acl.nil?
  unless self.class.builtin_acl_default_active?
    self.acl = self.class.default_acls.as_json if self.acl.nil?
  end
  @_acl_pristine = !acl_was_user_supplied
  @_acl_owner_override = acl_owner_override

  # One-time per-class permissive-default warning. Fires only when the
  # effective policy is :public or :owner_else_public.
  self.class._warn_permissive_acl_default_once

  # do not apply defaults on a pointer because it will stop it from being
  # a pointer and will cause its field to be autofetched (for sync).
  # Note: apply_defaults! already skips unfetched fields on selectively fetched objects.
  if !pointer?
    apply_defaults!
  end

  # clear changes AFTER applying defaults, so fields set by defaults
  # are not marked dirty when fetching with specific keys
  clear_changes! if @id.present? #then it was an import
  # do not call super since it is Pointer subclass
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Parse::Pointer

Class Attribute Details

.default_acl_privateBoolean

When set to true, new instances of this class will have a private ACL (no public access, master key only) instead of the default public read/write.

Examples:

class PrivateDocument < Parse::Object
  self.default_acl_private = true
end

doc = PrivateDocument.new
doc.acl.as_json # => {} (no permissions, master key only)

Returns:

  • (Boolean)

    whether new objects default to private ACLs.

Version:

  • 3.1.3



358
359
360
# File 'lib/parse/model/object.rb', line 358

def default_acl_private
  @default_acl_private
end

.parse_class(remoteName = nil) ⇒ String

The class method to override the implicitly assumed Parse collection name in your Parse database. The default Parse collection name is the singular form of the ruby Parse::Object subclass name. The Parse class value should match to the corresponding remote table in your database in order to properly store records and perform queries.

Examples:

class Song < Parse::Object; end;
class Artist < Parse::Object
  parse_class "Musician" # remote collection name
end

Parse::User.parse_class # => '_User'
Song.parse_class # => 'Song'
Artist.parse_class # => 'Musician'

Parameters:

  • remoteName (String) (defaults to: nil)

    the name of the remote collection

Returns:

  • (String)

    the name of the Parse collection for this model.



388
389
390
391
392
# File 'lib/parse/model/object.rb', line 388

def parse_class(remoteName = nil)
  @parse_class ||= model_name.name
  @parse_class = remoteName.to_s unless remoteName.nil?
  @parse_class
end

.suppress_permissive_acl_warningBoolean

When set on Parse::Object itself, suppresses the one-time per-class warning emitted when a class's effective acl_policy_setting is :public or :owner_else_public. Useful in test suites or apps that have deliberately reviewed and accepted permissive defaults. Defaults to true when ENV["PARSE_SUPPRESS_PERMISSIVE_ACL_WARNING"] is set to a truthy value (1, true, yes).

Returns:

  • (Boolean)

Version:

  • 4.1.0



338
# File 'lib/parse/model/object.rb', line 338

attr_writer :suppress_permissive_acl_warning

Instance Attribute Details

#aclACL

Returns the access control list (permissions) object for this record.

Returns:

  • (ACL)

    the access control list (permissions) object for this record.



1856
# File 'lib/parse/model/object.rb', line 1856

property :acl, :acl, field: :ACL

#created_atDate (readonly)

Returns the created_at date of the record in UTC Zulu iso 8601 with 3 millisecond format.

Returns:

  • (Date)

    the created_at date of the record in UTC Zulu iso 8601 with 3 millisecond format.



1848
# File 'lib/parse/model/object.rb', line 1848

property :created_at, :date

#idString

Returns the value of Parse "objectId" field.

Returns:

  • (String)

    the value of Parse "objectId" field.



1844
# File 'lib/parse/model/object.rb', line 1844

property :id, field: :objectId

#updated_atDate (readonly)

Returns the updated_at date of the record in UTC Zulu iso 8601 with 3 millisecond format.

Returns:

  • (Date)

    the updated_at date of the record in UTC Zulu iso 8601 with 3 millisecond format.



1852
# File 'lib/parse/model/object.rb', line 1852

property :updated_at, :date

Class Method Details

.acl_owner_fieldSymbol?

The name of the property/belongs_to designating the owner user for :owner_else_* ACL policies. Inherited from the superclass when not explicitly declared via acl_policy.

Returns:

Version:

  • 4.1.0



587
588
589
590
591
592
593
594
# File 'lib/parse/model/object.rb', line 587

def acl_owner_field
  return @acl_owner_field if defined?(@acl_owner_field) && @acl_owner_field
  if self != Parse::Object && superclass.respond_to?(:acl_owner_field)
    superclass.acl_owner_field
  else
    nil
  end
end

.acl_policy(policy, owner: nil) ⇒ Object

Declarative ACL policy applied to newly-created instances of this class. The policy is resolved at save time so that explicit ACL changes by the caller (obj.acl = …, as: kwarg, owner-field assignment after .new) always take precedence over the default.

Resolution order at save (only when caller has not overridden):

  1. Explicit as: user passed at construction → owner R/W only
  2. Owner pointer resolved from the declared owner: field → owner R/W only
  3. The else-half of the policy: :public → public R/W, :private → master-key only

Examples:

class Post < Parse::Object
  acl_policy :owner_else_private, owner: :author
end

# server-side: no owner resolvable → master-key-only fallback
Post.create!(title: "draft")

# owner pointer set → ACL granting R/W to that user only
Post.create!(title: "live", author: current_user)

# explicit caller override (works regardless of `author` field)
Post.create!({ title: "x" }, as: current_user)

Parameters:

  • policy (Symbol)

    one of :public, :public_read, :private, :owner_else_public, :owner_else_private, :owner_but_public_read. :public_read stamps {"*": {"read": true}} — anyone can read, no one can write through ACL (only the master key can mutate). Useful for catalog/lookup tables. :owner_but_public_read stamps the resolved owner with R/W AND grants public read in the same ACL — useful for publicly-viewable content with a single authoring user; falls back to :public_read semantics when no owner resolves.

  • owner (Symbol, nil) (defaults to: nil)

    the name of the property/belongs_to whose pointer designates the owner user. Only meaningful for :owner_* policies.

Raises:

See Also:

Version:

  • 4.1.0



514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
# File 'lib/parse/model/object.rb', line 514

def acl_policy(policy, owner: nil)
  unless VALID_ACL_POLICIES.include?(policy)
    raise ArgumentError, "Invalid acl_policy #{policy.inspect}; must be one of #{VALID_ACL_POLICIES.inspect}"
  end
  # Symmetric to the guard in set_default_acl: pick one API per class.
  if defined?(@acl_default_customized_by_set_default_acl) && @acl_default_customized_by_set_default_acl
    raise ArgumentError,
      "#{self}: cannot combine `acl_policy` with `set_default_acl`. " \
      "This class already calls `set_default_acl`. Use the declarative " \
      "DSL for the entire ACL configuration, or remove `acl_policy` and " \
      "use only `set_default_acl`."
  end
  # `owner: :self` is a special marker meaning "the record itself is
  # its own owner" — only meaningful for Parse::User and subclasses,
  # where the record IS a user. The save-time resolver pre-generates
  # `@id` via Parse::Core::ParseReference.generate_object_id when
  # blank so the ACL can grant R/W to the record's own objectId in
  # a single roundtrip. Non-User classes have no sensible
  # interpretation (a Post's objectId is not a user id).
  if owner == :self && !(self <= Parse::User)
    raise ArgumentError,
      "#{self}: `owner: :self` is only supported on Parse::User and " \
      "its subclasses (the record IS the owner). For other classes, " \
      "declare a belongs_to pointer to the owning user."
  end
  if owner && !policy.to_s.start_with?("owner_")
    warn "[#{self}] `owner:` is ignored when acl_policy is #{policy.inspect}; only :owner_else_public, :owner_else_private, and :owner_but_public_read use it."
  end
  if owner.nil? && policy.to_s.start_with?("owner_")
    fallback = case policy
               when :owner_else_public then "public R/W"
               when :owner_but_public_read then "public read only"
               else "master-key-only"
               end
    warn "[#{self}] acl_policy #{policy.inspect} declared without `owner:` field; ACL resolution will always use the fallback (#{fallback}). Pass `as:` at construction to override."
  end
  @acl_policy_setting = policy
  @acl_owner_field = owner
  # Reset materialized default_acls so it picks up the new policy's fallback half.
  @default_acls = nil
  # Re-arm the permissive-default warning so a subsequent change is re-evaluated.
  @_permissive_default_warned = nil
  policy
end

.acl_policy_settingSymbol

The effective ACL policy for this class. Inherits from the superclass when not explicitly declared. The gem-wide default is :owner_else_private — records grant read/write to the resolved owner (from as: or the class's owner: field) when one is supplied, and fall back to master-key-only when no owner is resolvable. default_acl_private = true is honored as :private. Classes that need public access for new records should declare acl_policy :public or :owner_else_public explicitly, or use the legacy set_default_acl additive API.

Returns:

Version:

  • 4.1.0



570
571
572
573
574
575
576
577
578
579
580
# File 'lib/parse/model/object.rb', line 570

def acl_policy_setting
  return @acl_policy_setting if defined?(@acl_policy_setting) && @acl_policy_setting
  return :private if default_acl_private
  if self == Parse::Object
    :owner_else_private
  elsif superclass.respond_to?(:acl_policy_setting)
    superclass.acl_policy_setting
  else
    :owner_else_private
  end
end

.build(json, table = nil, fetched_keys: nil, nested_fetched_keys: nil) ⇒ Parse::Object

Note:

If a Parse class object hash is encoutered for which we don't have a corresponding Parse::Object subclass for, a Parse::Pointer will be returned instead.

Method used for decoding JSON objects into their corresponding Object subclasses. The first parameter is a hash containing the object data and the second parameter is the name of the table / class if it is known. If it is not known, we we try and determine it by checking the "className" or :className entries in the hash.

Examples:

# assume you have defined Post subclass
post = Parse::Object.build({"className" => "Post", "objectId" => '1234'})
post # => #<Post:....>

# if you know the table name
post = Parse::Object.build({"title" => "My Title"}, "Post")
# or
post = Post.build({"title" => "My Title"})

Parameters:

  • json (Hash)

    a JSON hash that contains a Parse object.

  • table (String) (defaults to: nil)

    the Parse class for this hash. If not passed it will be detected.

  • fetched_keys (Array) (defaults to: nil)

    optional array of keys that were fetched (for partial fetch tracking).

  • nested_fetched_keys (Hash) (defaults to: nil)

    optional map of field names to their fetched keys for nested objects.

Returns:



1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
# File 'lib/parse/model/object.rb', line 1773

def self.build(json, table = nil, fetched_keys: nil, nested_fetched_keys: nil)
  # Precedence (most → least authoritative):
  # 1. Caller-supplied +table+ — caller knows the expected class
  #    (e.g. webhook payload routed to a typed handler, has_many that
  #    knows its declared target class).
  # 2. The subclass +parse_class+ when invoked on a Parse::Object
  #    subclass directly (Song.build(json)).
  # 3. The className inside the JSON — only trusted when neither of
  #    the above is available (e.g. base-class +Parse::Object.build+
  #    on untyped JSON).
  # Warn on mismatch between an explicit caller class and the
  # payload-supplied className so type-confusion attacks surface in
  # logs.
  incoming_class = nil
  if json.is_a?(Hash)
    incoming_class = json[Parse::Model::KEY_CLASS_NAME] || json[:className]
  end
  className = table
  if className.nil? && parse_class != BASE_OBJECT_CLASS
    className = parse_class
  end
  className ||= incoming_class
  if className && incoming_class && !Parse::Model.same_parse_class?(incoming_class, className)
    warn "[Parse::Object.build] expected className=#{className.inspect}, ignoring incoming className=#{incoming_class.inspect}"
  end
  if json.is_a?(Hash) && json["error"].present? && json["code"].present?
    warn "[Parse::Object] Detected object hash with 'error' and 'code' set. : #{json}"
  end
  return if className.nil?
  # we should do a reverse lookup on who is registered for a different class type
  # than their name with parse_class
  klass = Parse::Model.find_class className
  o = nil
  if klass.present?
    # when creating objects from Parse JSON data, don't use dirty tracking since
    # we are considering these objects as "pristine"
    o = klass.allocate

    # Set BOTH nested_fetched_keys AND fetched_keys BEFORE initialize
    # to ensure partially_fetched? returns correct value during attribute application
    o.instance_variable_set(:@_nested_fetched_keys, nested_fetched_keys) if nested_fetched_keys.present?
    if fetched_keys.present?
      # Process fetched_keys like the setter does - convert to symbols and include :id
      processed_keys = fetched_keys.map { |k| Parse::Query.format_field(k).to_sym }
      processed_keys << :id unless processed_keys.include?(:id)
      processed_keys << :objectId unless processed_keys.include?(:objectId)
      processed_keys.uniq!
      o.instance_variable_set(:@_fetched_keys, processed_keys)
    end

    # Trusted hydration: this path runs on server-side JSON (response
    # bodies, webhook payloads that have already been scrubbed,
    # autofetch results). Server responses legitimately include
    # protected keys like +sessionToken+, +_rperm+ that must populate
    # the in-memory object. Untrusted +klass.new(hash)+ callers
    # default to filter those keys. The +@_trusted_init+ ivar is the
    # signal — see {#initialize} for why we don't use a kwarg.
    o.instance_variable_set(:@_trusted_init, true)
    o.send(:initialize, json)
  else
    o = Parse::Pointer.new className, (json[Parse::Model::OBJECT_ID] || json[:objectId])
  end
  return o
  # rescue NameError => e
  #   puts "Parse::Object.build constant class error: #{e}"
  # rescue Exception => e
  #   puts "Parse::Object.build error: #{e}"
end

.class_permissionsParse::CLP Also known as: clp

The Class-Level Permissions for this model. CLPs control access to the class at the schema level.

Returns:

  • (Parse::CLP)

    the CLP instance for this class

See Also:



660
661
662
# File 'lib/parse/model/object.rb', line 660

def class_permissions
  @class_permissions ||= Parse::CLP.new
end

.default_aclsParse::ACL

The set of default ACLs to be applied on newly created instances of this class. The result follows the class's acl_policy_setting: the shipped default policy is :owner_else_private, whose fallback half is ACL.private (an empty ACL — readable only by the master key until an owner is resolved at save time). Classes that opt into a :public* policy, or set default_acl_private / set_default_acl, get the corresponding permissions instead.

Returns:

  • (Parse::ACL)

    the current default ACLs for this class.

See Also:



404
405
406
407
408
409
410
411
# File 'lib/parse/model/object.rb', line 404

def default_acls
  @default_acls ||= case acl_policy_setting
                    when :public, :owner_else_public then Parse::ACL.everyone
                    when :public_read, :owner_but_public_read then Parse::ACL.everyone(true, false)
                    when :private, :owner_else_private then Parse::ACL.private
                    else Parse::ACL.everyone
                    end
end

.describe_accessHash

Introspect the locally-configured access surface for this class. Combines the CLP operations, protectedFields read-side hiding, and the write-side protections installed via the field_guards DSL into a single hash, so it's easy to audit who can do what to which fields without reading three separate parts of the class body.

The hash is built from the Parse-Stack model declarations only. It does NOT round-trip the Parse Server schema; if you've configured CLPs on the server side that haven't been mirrored locally, those won't appear here. Conversely, calling update_clp! pushes what this method reflects.

Examples:

class Post < Parse::Object
  property :title, :string
  property :owner, :string
  guard :owner, :master_only
  parse_reference
  set_class_access(find: :public, create: :authenticated, update: "Admin")
end

Post.describe_access
# =>
# {
#   operations: {
#     find:   { "*" => true },
#     create: { "requiresAuthentication" => true },
#     update: { "role:Admin" => true },
#     ...
#   },
#   read_user_fields:  [],
#   write_user_fields: [],
#   fields: {
#     title:           { write: :open,        read: :open },
#     owner:           { write: :master_only, read: :open },
#     parse_reference: { write: :set_once,    read: { hidden_from: ["*"] } },
#   },
# }

Returns:



1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
# File 'lib/parse/model/object.rb', line 1021

def describe_access
  perms = class_permissions
  protected_by_pattern = perms.respond_to?(:protected_fields) ? perms.protected_fields : {}
  guards_map = respond_to?(:field_guards) && field_guards ? field_guards : {}

  # Per-field access summary. Iterate `field_map` (local -> remote)
  # rather than `fields`, because `fields` redundantly stores BOTH
  # the local key (e.g. :full_name) and the remote key (:fullName)
  # for every property. That redundancy would cause multi-word
  # properties to appear twice in the output.
  per_field = {}
  field_map.each do |local_sym, remote_sym|
    local_sym = local_sym.to_sym
    next if Parse::Properties::CORE_FIELDS.key?(local_sym)
    data_type = fields[local_sym]
    remote = remote_sym.to_s

    # Read protection -- collect every protectedFields pattern that
    # lists this field (under either its local or remote name).
    hidden_from = protected_by_pattern.each_with_object([]) do |(pattern, hidden_fields), acc|
      acc << pattern if hidden_fields.include?(remote) || hidden_fields.include?(local_sym.to_s)
    end

    per_field[local_sym] = {
      write: guards_map[local_sym] || :open,
      read:  hidden_from.empty? ? :open : { hidden_from: hidden_from },
      type:  data_type,
    }
  end

  # Deep-copy the operations hash so callers mutating the result
  # don't accidentally mutate the live class_permissions state.
  operations = if perms.respond_to?(:permissions)
      perms.permissions.transform_values { |v| v.is_a?(Hash) ? v.dup : v }
    else
      {}
    end

  {
    operations:        operations,
    read_user_fields:  perms.respond_to?(:read_user_fields)  ? perms.read_user_fields  : [],
    write_user_fields: perms.respond_to?(:write_user_fields) ? perms.write_user_fields : [],
    fields:            per_field,
  }
end

.fetch_clp(client: nil) ⇒ Parse::CLP Also known as: fetch_class_permissions

Fetch the current CLP from the Parse Server for this class.

Parameters:

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

    optional client to use

Returns:



1070
1071
1072
1073
1074
1075
1076
1077
# File 'lib/parse/model/object.rb', line 1070

def fetch_clp(client: nil)
  client ||= self.client
  response = client.schema(parse_class)
  return Parse::CLP.new unless response.success?

  clp_data = response.result["classLevelPermissions"] || {}
  Parse::CLP.new(clp_data)
end

.filter_results_for_user(objects, user, roles: [], authenticated: nil, clp: nil) ⇒ Array<Hash>

Filter an array of Parse objects or hashes for a user. Class method that applies CLP filtering to multiple results.

Examples:

Filter query results for a user

songs = Song.query(artist: "Beatles").results
filtered = Song.filter_results_for_user(songs, current_user, roles: user_roles)

Parameters:

  • objects (Array<Parse::Object, Hash>)

    array of objects or hashes to filter

  • user (Parse::User, String, nil)

    the user or user ID

  • roles (Array<String>) (defaults to: [])

    role names the user belongs to

  • authenticated (Boolean) (defaults to: nil)

    whether the user is authenticated

  • clp (Parse::CLP, nil) (defaults to: nil)

    optional CLP to use (defaults to class CLP)

Returns:

  • (Array<Hash>)

    filtered data hashes with protected fields removed

See Also:



1173
1174
1175
1176
1177
1178
1179
1180
1181
# File 'lib/parse/model/object.rb', line 1173

def self.filter_results_for_user(objects, user, roles: [], authenticated: nil, clp: nil)
  clp ||= class_permissions
  return objects.map { |o| o.is_a?(Parse::Object) ? o.as_json : o } unless clp.present?

  objects.map do |obj|
    data = obj.is_a?(Parse::Object) ? obj.as_json : obj
    clp.filter_fields(data, user: user, roles: roles, authenticated: authenticated)
  end
end

.master_only_class!

This method returns an undefined value.

Lock every CLP operation to master-key access only. Use as a starting point when a class should be entirely hidden from clients; you can then selectively open specific operations with set_clp or set_class_access afterward.

Examples:

Hide a class entirely from clients

class AuditLog < Parse::Object
  master_only_class!
end

Hide everything, then open create+get for clients

class Invitation < Parse::Object
  master_only_class!
  set_clp :create, public: true
  set_clp :get, public: true
end


816
817
818
819
# File 'lib/parse/model/object.rb', line 816

def master_only_class!
  Parse::CLP::OPERATIONS.each { |op| set_clp(op) }
  nil
end

.pointer(id) ⇒ Parse::Pointer

Helper method to create a Parse::Pointer object for a given id.

Parameters:

  • id (String)

    The objectId

Returns:

  • (Parse::Pointer)

    a pointer object corresponding to this class and id.



1433
1434
1435
1436
# File 'lib/parse/model/object.rb', line 1433

def self.pointer(id)
  return nil if id.nil?
  Parse::Pointer.new self.parse_class, id
end

.private_acl!Object

Convenience method to set default ACL to private (no public access). Equivalent to self.default_acl_private = true.

Examples:

class PrivateDocument < Parse::Object
  private_acl!
end

Version:

  • 3.1.3



367
368
369
# File 'lib/parse/model/object.rb', line 367

def private_acl!
  self.default_acl_private = true
end

.protect_fields(pattern, fields) ⇒ Object Also known as: set_protected_fields

Define protected fields that should be hidden from certain users/roles. This is used to implement field-level security.

Field names are automatically converted from snake_case (Ruby convention) to camelCase (Parse Server convention). You can use either format.

Examples:

Hide fields from public but allow admins to see everything

class User < Parse::Object
  property :email, :string
  property :phone, :string
  property :internal_notes, :string

  # Hide sensitive fields from public (use snake_case Ruby names)
  protect_fields "*", [:email, :phone, :internal_notes]

  # Admins can see everything (empty array = no restrictions)
  protect_fields "role:Admin", []

  # Users can see their own data
  protect_fields "userField:objectId", []
end

Hide metadata from non-owners

class Image < Parse::Object
  property :url, :string
  property :metadata, :object  # GPS, camera info, etc.
  belongs_to :owner, as: :user

  # Hide metadata from everyone (auto-converts to "metadata" in Parse)
  protect_fields "*", [:metadata]

  # But owners can see their own image metadata
  protect_fields "userField:owner", []
end

Master key only fields

class SensitiveDoc < Parse::Object
  property :admin_notes, :string
  property :internal_score, :integer

  # Only master key can see these fields
  # (converts to ["adminNotes", "internalScore"] for Parse Server)
  protect_fields "*", [:admin_notes, :internal_score]
end

Parameters:

  • pattern (String, Symbol)

    the pattern to apply protection for:

    • "*" or :public - applies to all users (public)
    • "role:RoleName" - applies to users in a specific role
    • "userField:fieldName" - applies to users referenced in a pointer field
    • user objectId - applies to a specific user
  • fields (Array<String, Symbol>)

    field names to hide from this pattern. Use Ruby property names (snake_case) - they will be auto-converted. An empty array means the user can see all fields.

See Also:



959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
# File 'lib/parse/model/object.rb', line 959

def protect_fields(pattern, fields)
  pattern = "*" if pattern.to_sym == :public rescue pattern

  # Convert userField:field_name pattern to use camelCase field name
  if pattern.to_s.start_with?("userField:")
    field_name = pattern.to_s.sub("userField:", "")
    field_sym = field_name.to_sym
    converted_field = field_map[field_sym] || field_name.camelize(:lower)
    pattern = "userField:#{converted_field}"
  end

  # Convert snake_case Ruby property names to camelCase Parse field names
  converted_fields = Array(fields).map do |field|
    field_sym = field.to_sym
    # Use field_map if available, otherwise convert to camelCase
    field_map[field_sym] || field.to_s.camelize(:lower)
  end
  class_permissions.set_protected_fields(pattern, converted_fields)
end

.roles_for_user(user) ⇒ Array<String>

Fetch a user's roles for use with field filtering. Convenience method to get role names that can be passed to filter methods.

Examples:

Get roles and filter

roles = Song.roles_for_user(current_user)
filtered = song.filter_for_user(current_user, roles: roles)

Parameters:

Returns:

  • (Array<String>)

    role names (without "role:" prefix)



1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
# File 'lib/parse/model/object.rb', line 1192

def self.roles_for_user(user)
  return [] unless user.is_a?(Parse::User) || user.is_a?(Parse::Pointer)
  return [] unless defined?(Parse::Role)

  user_id = user.respond_to?(:id) ? user.id : user.to_s
  return [] if user_id.blank?

  Parse::Role.all(users: user).map(&:name)
rescue => e
  warn "[Parse] Error fetching roles for user: #{e.message}"
  []
end

.set_class_access(**ops_to_access)

This method returns an undefined value.

Set CLP for multiple operations in one call, choosing a coarse access mode per operation. Each value can be:

  • :master / :master_only / nil / false -- master key only (Parse Server's empty {} permission for that op)
  • :public / true -- wildcard * access
  • :authenticated -- requiresAuthentication
  • a String or Symbol -- a single role name (the role: prefix is added automatically)
  • an Array of Strings/Symbols -- multiple role names

Operations not listed in the hash are left at their current setting. For finer control (mixed roles, users, pointer-fields, requires_authentication) use set_clp directly.

Examples:

The _Installation pattern -- get-by-id and create, but no listing

class Invitation < Parse::Object
  set_class_access(
    find:     :master,        # nobody can list
    count:    :master,        # nobody can count
    get:      :public,        # anyone with the id can fetch
    create:   :authenticated, # logged-in users may create
    update:   :master,        # only server may update
    delete:   :master,        # only server may delete
  )
end

Admin-only writes, public reads

class Article < Parse::Object
  set_class_access(
    find: :public, get: :public,
    create: "Admin", update: "Admin", delete: "Admin",
  )
end

Parameters:



877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
# File 'lib/parse/model/object.rb', line 877

def set_class_access(**ops_to_access)
  ops_to_access.each do |op, access|
    op = op.to_sym
    unless Parse::CLP::OPERATIONS.include?(op)
      raise ArgumentError,
            "Unknown CLP operation #{op.inspect}. Allowed: #{Parse::CLP::OPERATIONS.inspect}"
    end
    case access
    when :master, :master_only, nil, false
      set_clp(op)
    when :public, true
      set_clp(op, public: true)
    when :authenticated
      set_clp(op, requires_authentication: true)
    when Array
      set_clp(op, roles: access.map(&:to_s))
    when String, Symbol
      set_clp(op, roles: [access.to_s])
    else
      raise ArgumentError,
            "Unknown class_access value for :#{op}: #{access.inspect}. " \
            "Use :master, :public, :authenticated, a role name, or an array of roles."
    end
  end
  nil
end

.set_clp(operation, public: nil, roles: [], users: [], pointer_fields: [], requires_authentication: false) ⇒ Object Also known as: set_class_permission

Set a class-level permission for a specific operation. This is the main DSL method for configuring CLPs in your model.

Examples:

Basic usage

class Song < Parse::Object
  # Allow public read
  set_clp :find, public: true
  set_clp :get, public: true

  # Restrict write operations to specific roles
  set_clp :create, public: false, roles: ["Admin", "Editor"]
  set_clp :update, public: false, roles: ["Admin", "Editor"]
  set_clp :delete, public: false, roles: ["Admin"]
end

Requiring authentication

class PrivateData < Parse::Object
  set_clp :find, requires_authentication: true
  set_clp :get, requires_authentication: true
end

Parameters:

  • operation (Symbol)

    the operation (:find, :get, :count, :create, :update, :delete, :addField)

  • public (Boolean, nil) (defaults to: nil)

    whether public access is allowed

  • roles (Array<String>, String) (defaults to: [])

    role names that have access

  • users (Array<String>, String) (defaults to: [])

    user objectIds that have access

  • pointer_fields (Array<String>, String) (defaults to: [])

    pointer field names for userField access

  • requires_authentication (Boolean) (defaults to: false)

    whether authentication is required

See Also:



779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
# File 'lib/parse/model/object.rb', line 779

def set_clp(operation, public: nil, roles: [], users: [], pointer_fields: [], requires_authentication: false)
  # Convert snake_case pointer field names to camelCase
  converted_pointer_fields = Array(pointer_fields).map do |field|
    field_sym = field.to_sym
    field_map[field_sym] || field.to_s.camelize(:lower)
  end

  class_permissions.set_permission(
    operation,
    public_access: public,
    roles: Array(roles),
    users: Array(users),
    pointer_fields: converted_pointer_fields,
    requires_authentication: requires_authentication
  )
end

.set_default_acl(id, read: false, write: false, role: false) ⇒ Object

A method to set default ACLs to be applied for newly created instances of this class. Unless overridden, subclasses inherit the shipped :owner_else_private policy (records are private/master-only until an owner is resolved at save time); use this method (or acl_policy) to grant broader access.

Examples:

class AdminData < Parse::Object

  # Disable public read and write
  set_default_acl :public, read: false, write: false

  # but allow members of the Admin role to read and write
  set_default_acl 'Admin', role: true, read: true, write: true

end

data = AdminData.new
data.acl # => ACL({"role:Admin"=>{"read"=>true, "write"=>true}})

Parameters:

  • id (String|:public)

    The name for ACL entry. This can be an objectId, a role name or :public.

  • read (Boolean) (defaults to: false)

    Whether to allow read permissions (default: false).

  • write (Boolean) (defaults to: false)

    Whether to allow write permissions (default: false).

  • role (Boolean) (defaults to: false)

    Whether the id argument should be applied as a role name.

See Also:

Version:

  • 1.7.0



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/parse/model/object.rb', line 439

def set_default_acl(id, read: false, write: false, role: false)
  unless id.present?
    raise ArgumentError, "Invalid argument applying #{self}.default_acls : must be either objectId, role or :public"
  end
  # Mixing the declarative `acl_policy` DSL with the legacy additive
  # `set_default_acl` API on the same class produces ambiguous behavior
  # (which one wins at save time? which fields get which permissions?).
  # Pick one and stick with it.
  if defined?(@acl_policy_setting) && @acl_policy_setting
    raise ArgumentError,
      "#{self}: cannot combine `set_default_acl` with `acl_policy`. " \
      "This class already declares `acl_policy #{@acl_policy_setting.inspect}`. " \
      "Use the declarative DSL for the entire ACL configuration, or remove " \
      "`acl_policy` and use only `set_default_acl` (the legacy additive API)."
  end
  # Mark the class as using the legacy additive ACL API. The save-time
  # policy resolver respects this and leaves the init-stamped default
  # ACL alone, preserving pre-4.1 behavior for classes that customize
  # via set_default_acl.
  @acl_default_customized_by_set_default_acl = true
  role ? default_acls.apply_role(id, read, write) : default_acls.apply(id, read, write)
end

.set_default_clp(public: nil, roles: [], requires_authentication: false) ⇒ Object

Set default permissions for all CLP operations at once. This is useful for establishing a baseline before customizing specific operations.

Examples:

Public read, authenticated write

class Document < Parse::Object
  # Start with public read access for all operations
  set_default_clp public: true

  # Then restrict write operations
  set_clp :create, requires_authentication: true
  set_clp :update, requires_authentication: true
  set_clp :delete, public: false, roles: ["Admin"]
end

Role-based access for everything

class AdminReport < Parse::Object
  # Only admins can do anything
  set_default_clp public: false, roles: ["Admin"]
end

Authenticated users only

class PrivateData < Parse::Object
  # Require authentication for all operations
  set_default_clp requires_authentication: true
end

Parameters:

  • public (Boolean) (defaults to: nil)

    whether public access is allowed for all operations

  • roles (Array<String>) (defaults to: [])

    role names that have access to all operations

  • requires_authentication (Boolean) (defaults to: false)

    whether authentication is required for all operations



695
696
697
698
699
700
701
702
703
704
705
706
707
708
# File 'lib/parse/model/object.rb', line 695

def set_default_clp(public: nil, roles: [], requires_authentication: false)
  # Set the default permission on the CLP instance
  # This will be used by as_json to fill in missing operations
  class_permissions.set_default_permission(
    public_access: public,
    roles: Array(roles),
    requires_authentication: requires_authentication
  )

  # Also explicitly set all operations to ensure they're included
  Parse::CLP::OPERATIONS.each do |operation|
    set_clp(operation, public: public, roles: roles, requires_authentication: requires_authentication)
  end
end

.set_read_user_fields(*fields) ⇒ Object

Set pointer-permission fields for read access. Users pointed to by these fields can read objects of this class. This is an alternative to ACLs for owner-based access control.

Examples:

class Document < Parse::Object
  belongs_to :owner, as: :user
  belongs_to :editor, as: :user

  # Only owner and editor can read
  set_read_user_fields :owner, :editor
end

Parameters:



723
724
725
726
727
728
729
# File 'lib/parse/model/object.rb', line 723

def set_read_user_fields(*fields)
  converted = fields.flatten.map do |f|
    field_sym = f.to_sym
    field_map[field_sym] || f.to_s.camelize(:lower)
  end
  class_permissions.set_read_user_fields(*converted)
end

.set_write_user_fields(*fields) ⇒ Object

Set pointer-permission fields for write access. Users pointed to by these fields can write to objects of this class.

Examples:

class Document < Parse::Object
  belongs_to :owner, as: :user

  # Only owner can write
  set_write_user_fields :owner
end

Parameters:



742
743
744
745
746
747
748
# File 'lib/parse/model/object.rb', line 742

def set_write_user_fields(*fields)
  converted = fields.flatten.map do |f|
    field_sym = f.to_sym
    field_map[field_sym] || f.to_s.camelize(:lower)
  end
  class_permissions.set_write_user_fields(*converted)
end

.unlistable_class!

This method returns an undefined value.

Restrict find and count to master-key only, leaving the other operations (get, create, update, delete, addField) at their current settings. This is the canonical "Installation-style" pattern: clients can interact with individual records but cannot enumerate or count them.

Examples:

Mirror _Installation semantics

class Invitation < Parse::Object
  unlistable_class!
  # clients can still get/create/update/delete by objectId
end


834
835
836
837
838
# File 'lib/parse/model/object.rb', line 834

def unlistable_class!
  set_clp(:find)
  set_clp(:count)
  nil
end

.update_clp!(client: nil, replace: false) ⇒ Parse::Response Also known as: update_class_permissions!

Update the CLP on the Parse Server for this class. Merges local CLP with any existing server CLP.

Examples:

Push local CLP to server

Song.update_clp!

Replace server CLP entirely

Song.update_clp!(replace: true)

Parameters:

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

    optional client to use

  • replace (Boolean) (defaults to: false)

    if true, replaces server CLP entirely; otherwise merges

Returns:



1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
# File 'lib/parse/model/object.rb', line 1093

def update_clp!(client: nil, replace: false)
  client ||= self.client

  unless client.master_key.present?
    warn "[Parse] CLP changes for #{parse_class} require the master key!"
    return nil
  end

  clp_data = class_permissions.as_json
  return nil if clp_data.empty?

  schema_update = { "classLevelPermissions" => clp_data }
  client.update_schema(parse_class, schema_update)
end

.wait_for(**kwargs, &block) ⇒ Object

Block until the first row matching Core::Querying#where (and an optional predicate block) arrives via LiveQuery. See Console.wait_for.



198
199
200
# File 'lib/parse/console.rb', line 198

def wait_for(**kwargs, &block)
  Parse::Console.wait_for(self, **kwargs, &block)
end

.watch(**kwargs, &block) ⇒ Object

Tail this class as LiveQuery events arrive — blocking, Ctrl-C to stop. See Console.watch.



191
192
193
# File 'lib/parse/console.rb', line 191

def watch(**kwargs, &block)
  Parse::Console.watch(self, **kwargs, &block)
end

.webhook(type, &block) { ... } ⇒ OpenStruct

Register a webhook trigger or function for this subclass.

Examples:

class Post < Parse::Object

 webhook :before_save do
    # ... do something ...
   parse_object
 end

end

Parameters:

  • block (Symbol)

    the name of the method to call, if no block is passed.

  • type (Symbol)

    The type of cloud code webhook to register. This can be any of the supported routes. These are :before_save, :after_save,

Yields:

  • the body of the function to be evaluated in the scope of a Webhooks::Payload instance.

Returns:

  • (OpenStruct)


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/parse/webhooks.rb', line 61

def self.webhook(type, &block)
  if type == :function
    unless block.is_a?(String) || block.is_a?(Symbol)
      raise ArgumentError, "Invalid Cloud Code function name: #{block}"
    end
    Parse::Webhooks.route(:function, block, &block)
    # then block must be a symbol or a string
  else
    if block_given?
      Parse::Webhooks.route(type, self, &block)
    else
      Parse::Webhooks.route(type, self, block)
    end
  end
  #if block

end

.webhook_function(functionName, &block) { ... } ⇒ OpenStruct

Register a webhook function for this subclass.

Examples:

class Post < Parse::Object

 webhook_function :helloWorld do
    # ... do something when this function is called ...
 end
end

Parameters:

  • functionName (String)

    the literal name of the function to be registered with the server.

  • block (Symbol)

    the name of the method to call, if no block is passed.

Yields:

  • the body of the function to be evaluated in the scope of a Webhooks::Payload instance.

Returns:

  • (OpenStruct)


37
38
39
40
41
42
43
44
45
# File 'lib/parse/webhooks.rb', line 37

def self.webhook_function(functionName, &block)
  if block_given?
    Parse::Webhooks.route(:function, functionName, &block)
  else
    block = functionName.to_s.underscore.to_sym if block.blank?
    block = method(block.to_sym) if block.is_a?(Symbol)
    Parse::Webhooks.route(:function, functionName, block)
  end
end

Instance Method Details

#[](key) ⇒ Object

Access the value for a defined property through hash accessor. This method returns nil if the key is not one of the defined properties for this Parse::Object subclass.

Parameters:

Returns:

  • (Object)

    the value for this key.



2049
2050
2051
2052
# File 'lib/parse/model/object.rb', line 2049

def [](key)
  return nil unless self.class.fields[key.to_sym].present?
  send(key)
end

#[]=(key, value) ⇒ Object

Set the value for a specific property through a hash accessor. This method does nothing if key is not one of the defined properties for this Parse::Object subclass.

Parameters:

Returns:

  • (Object)

    the value passed in.



2060
2061
2062
2063
# File 'lib/parse/model/object.rb', line 2060

def []=(key, value)
  return unless self.class.fields[key.to_sym].present?
  send("#{key}=", value)
end

#__typeModel::TYPE_OBJECT

Returns:



194
# File 'lib/parse/model/object.rb', line 194

def __type; Parse::Model::TYPE_OBJECT; end

#_resolve_acl_owner_id(owner) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Resolves an as: value or owner-field pointer to an objectId string. Strictly type-gated to Parse::User-shaped inputs to prevent accidental ACL grants to non-user records (Roles use role: ACL keys, not raw objectIds; pointers to non-User classes would silently grant access to whatever record happens to share that objectId in the User collection). Accepted forms:

  • Parse::User instance
  • Parse::Pointer with parse_class == "_User"
  • Raw objectId String (caller's responsibility to ensure it is a user id) Anything else returns nil and the policy falls through to its else-half.


1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
# File 'lib/parse/model/object.rb', line 1956

def _resolve_acl_owner_id(owner)
  return nil if owner.nil?
  return nil if owner.respond_to?(:empty?) && owner.empty?
  if owner.is_a?(Parse::Pointer)
    return nil unless owner.parse_class == Parse::Model::CLASS_USER
    return owner.id if owner.id.present?
    return nil
  end
  return owner if owner.is_a?(String) && owner.present?
  nil
end

#_resolve_default_aclObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Save-time resolver for the declarative acl_policy default ACL. Runs as a before_save callback. If the caller has not overridden the ACL (no acl= since the init-time default stamp), resolves an owner from @_acl_owner_override (the as: kwarg) or from the class's declared owner field, and applies an owner-only ACL. Falls back to the policy's else-half (:public or :private) when no owner is resolvable.



1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
# File 'lib/parse/model/object.rb', line 1866

def _resolve_default_acl
  return true unless defined?(@_acl_pristine) && @_acl_pristine
  # Legacy classes that customize defaults via set_default_acl opt out
  # of the policy resolver: the init-time stamp already reflects the
  # caller's intent and we must not overwrite it.
  return true if self.class.acl_default_customized_by_set_default_acl?
  # Built-in Parse classes (User, Installation, Session, …) are exempt
  # by default; see the matching guard in #initialize. Parse Server
  # applies its own ACL defaults when the save body omits the `ACL`
  # field, and those defaults (e.g. `_User` → self-write + public read)
  # are the right answer in nearly every case. Applications that need
  # to customize a built-in's ACL policy do so by calling `acl_policy`
  # or `set_default_acl` on the class — that flips
  # `builtin_acl_default_active?` to false and re-enables both the
  # init-time stamp and this resolver for that class.
  return true if self.class.builtin_acl_default_active?
  policy = self.class.acl_policy_setting

  owner = @_acl_owner_override if defined?(@_acl_owner_override)
  if owner.nil? && (field = self.class.acl_owner_field)
    owner = if field == :self
      # Self-referential ownership (Parse::User only — enforced at
      # declaration time). Pre-generate a Parse-compatible objectId
      # client-side so the ACL grant can reference the record's own
      # id in the same POST body that creates it. Skipped when the id
      # is already set (e.g. when re-saving an existing user, or when
      # parse_reference precompute already ran).
      @id = Parse::Core::ParseReference.generate_object_id if @id.blank?
      @id
    elsif respond_to?(field)
      send(field)
    end
  end
  owner_id = _resolve_acl_owner_id(owner)

  target_acl = case policy
               when :public
                 Parse::ACL.everyone(true, true)
               when :public_read
                 Parse::ACL.everyone(true, false)
               when :private
                 Parse::ACL.private
               when :owner_else_public
                 if owner_id
                   acl = Parse::ACL.new
                   acl.apply(owner_id, true, true)
                   acl
                 else
                   Parse::ACL.everyone(true, true)
                 end
               when :owner_else_private
                 if owner_id
                   acl = Parse::ACL.new
                   acl.apply(owner_id, true, true)
                   acl
                 else
                   Parse::ACL.private
                 end
               when :owner_but_public_read
                 acl = Parse::ACL.everyone(true, false)
                 acl.apply(owner_id, true, true) if owner_id
                 acl
               end

  # Only re-stamp if the resolved ACL differs from the init-time stamp;
  # this avoids an unnecessary dirty mark on the acl field for `:public`
  # / `:private` policies where the init stamp already matches.
  if @acl.nil? || @acl.as_json != target_acl.as_json
    self.acl = target_acl.as_json
  end
  # @_acl_pristine is now false via #acl_will_change! (when re-stamped)
  # or it remains true (when nothing needed to change); either way the
  # resolver has done its job and need not run again. Return a non-false
  # value so the save callback chain is not halted by the model's
  # terminator (`result_lambda.call == false`).
  @_acl_pristine = false
  true
end

#acl_changed?Boolean

Override acl_changed? to compare actual ACL content, not just object references. This ensures that setting an ACL to identical values doesn't mark it as changed.

Returns:

  • (Boolean)

    true only if the ACL content has actually changed.



2013
2014
2015
2016
2017
2018
2019
2020
# File 'lib/parse/model/object.rb', line 2013

def acl_changed?
  # First check if ActiveModel thinks it changed
  return false unless super
  # Then verify the content actually changed by comparing JSON representations
  acl_was_json = acl_was.respond_to?(:as_json) ? acl_was.as_json : acl_was
  acl_current_json = @acl&.respond_to?(:as_json) ? @acl.as_json : @acl
  acl_was_json != acl_current_json
end

#acl_wasParse::ACL

Override acl_was to return the captured snapshot instead of the reference stored by ActiveModel's dirty tracking.

Returns:

  • (Parse::ACL)

    the ACL value before any changes were made.



2001
2002
2003
2004
2005
2006
2007
2008
# File 'lib/parse/model/object.rb', line 2001

def acl_was
  # If we have a snapshot, return it; otherwise fall back to ActiveModel's behavior
  if defined?(@_acl_snapshot_before_change) && @_acl_snapshot_before_change
    @_acl_snapshot_before_change
  else
    super
  end
end

#acl_will_change!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Override acl_will_change! to capture a snapshot of the ACL before modification. This is necessary because ACL is a mutable object that can be modified in place (via apply, apply_role, etc.). Without this, acl_was would return a reference to the same object as acl, making them appear identical after in-place changes.

Also clears the ACL-pristine flag so the save-time default-ACL resolver leaves caller-set ACLs alone. The initial default stamp performed in #initialize is excluded by re-asserting @_acl_pristine = true after the stamp, so this hook can safely treat any subsequent change as a caller intent to override.



1981
1982
1983
1984
1985
1986
1987
1988
1989
# File 'lib/parse/model/object.rb', line 1981

def acl_will_change!
  # Only capture snapshot on the first change (before any modifications)
  unless defined?(@_acl_snapshot_before_change) && @_acl_snapshot_before_change
    # Deep copy the ACL by creating a new one from its JSON representation
    @_acl_snapshot_before_change = @acl ? Parse::ACL.new(@acl.as_json) : Parse::ACL.new
  end
  @_acl_pristine = false if defined?(@_acl_pristine)
  super
end

#after_create { ... } ⇒ Object

A callback called after the object has been created.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#after_destroy { ... } ⇒ Object

Note:

This is not related to a Parse afterDelete webhook trigger.

A callback called after the object has been successfully deleted.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#after_save { ... } ⇒ Object

Note:

This is not related to a Parse afterSave webhook trigger.

A callback called after the object has been successfully saved.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#after_update { ... } ⇒ Object

A callback called after the object has been updated.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#after_validation { ... } ⇒ Object

A callback called after validations are run.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#apply_defaults!Array

force apply default values for any properties defined with default values.

Returns:

  • (Array)

    list of default fields



1420
1421
1422
1423
1424
1425
1426
1427
1428
# File 'lib/parse/model/object.rb', line 1420

def apply_defaults!
  self.class.defaults_list.each do |key|
    # Skip applying defaults to unfetched fields on selectively fetched objects.
    # This preserves the ability to autofetch when the field is accessed.
    next if has_selective_keys? && !field_was_fetched?(key)

    send(key) # should call set default proc/values if nil
  end
end

#around_create { ... } ⇒ Object

A callback called around object creation.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#around_destroy { ... } ⇒ Object

A callback called around object destruction.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#around_save { ... } ⇒ Object

A callback called around object save.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#around_update { ... } ⇒ Object

A callback called around object update.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#around_validation { ... } ⇒ Object

A callback called around validations.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#as_json(opts = nil) ⇒ Hash

Returns a json-hash representing this object.

Parameters:

  • opts (Hash) (defaults to: nil)

    options for serialization

Options Hash (opts):

  • :only_fetched (Boolean)

    when true (or when Parse.serialize_only_fetched_fields is true and this option is not explicitly set to false), only serialize fields that were fetched for partially fetched objects. This prevents autofetch during serialization.

  • :only (Array<Symbol,String>)

    limit serialization to these fields. By default, identification fields (objectId, className, __type, id) are always included for proper object identification. Use strict: true to disable this behavior.

  • :except (Array<Symbol,String>)

    exclude these fields from serialization

  • :exclude_keys (Array<Symbol,String>)

    alias for :except

  • :exclude (Array<Symbol,String>)

    alias for :except

  • :strict (Boolean)

    when true with :only, performs strict filtering without automatically including identification fields. Default is false.

Returns:

  • (Hash)

    a json-hash representing this object.



1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
# File 'lib/parse/model/object.rb', line 1224

def as_json(opts = nil)
  opts ||= {}

  # Normalize :exclude_keys and :exclude to :except (alias support)
  if !opts[:except]
    if opts[:exclude_keys]
      opts = opts.merge(except: opts[:exclude_keys])
    elsif opts[:exclude]
      opts = opts.merge(except: opts[:exclude])
    end
  end

  # `:vector` fields are excluded from serialization by default —
  # embeddings are large (often 1024–4096 floats), they leak ML
  # signal to clients, and they round-trip through the dedicated
  # embed/find_similar pipelines rather than the standard REST
  # save/find. Pass `include_vectors: true` to opt back in (e.g.,
  # for tests or internal mongo-direct bulk writes). A class may flip
  # the per-class default with `vector_visibility :public`; an explicit
  # `include_vectors:` in the call always wins over the class default.
  include_vectors =
    if opts.key?(:include_vectors)
      opts[:include_vectors] == true
    else
      self.class.respond_to?(:vectors_public_by_default?) && self.class.vectors_public_by_default?
    end
  unless include_vectors
    vector_fields = self.class.respond_to?(:fields) ? self.class.fields(:vector).keys.map(&:to_s) : []
    if vector_fields.any?
      except = Array(opts[:except]).map(&:to_s) | vector_fields
      opts = opts.merge(except: except)
    end
  end

  # When :only is specified without :strict, automatically include identification fields
  # so the serialized object can be properly identified
  if opts[:only] && !opts[:strict]
    only_keys = Array(opts[:only]).map(&:to_s)
    only_keys |= IDENTIFICATION_FIELDS
    opts = opts.merge(only: only_keys)
  end

  # For selectively fetched objects (partial fetch), serialize only the fetched fields.
  # This takes priority over pointer detection because a partial fetch has actual data
  # even if it lacks timestamps (which would otherwise make it look like a pointer).
  # This behavior is controlled by:
  # 1. Per-call: opts[:only_fetched] (explicit true/false)
  # 2. Global: Parse.serialize_only_fetched_fields (default true)
  if has_selective_keys?
    # Determine if we should serialize only fetched fields
    only_fetched = opts.fetch(:only_fetched) { Parse.serialize_only_fetched_fields }

    if only_fetched && !opts.key?(:only)
      # Build the :only list from fetched keys
      # Use the local field names which match the attribute methods
      only_keys = fetched_keys.map(&:to_s)
      # Always include Parse metadata fields for proper object identification
      only_keys |= IDENTIFICATION_FIELDS
      only_keys |= %w[created_at updated_at]
      opts = opts.merge(only: only_keys)
    end

    changed_fields = changed_attributes
    return super(opts).delete_if { |k, v| v.nil? && !changed_fields.has_key?(k) }
  end

  # When in pointer state (no data fetched, just an objectId), return the serialized
  # pointer hash (with __type, className, objectId) for proper JSON serialization
  return pointer.as_json(opts) if pointer?

  changed_fields = changed_attributes
  super(opts).delete_if { |k, v| v.nil? && !changed_fields.has_key?(k) }
end

#autofetch_disabled?Boolean

Returns whether autofetch is disabled for this instance.

Returns:

  • (Boolean)

    true if autofetch is disabled



1576
1577
1578
# File 'lib/parse/model/object.rb', line 1576

def autofetch_disabled?
  @_autofetch_disabled == true
end

#before_create { ... } ⇒ Object

A callback called before the object has been created.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#before_destroy { ... } ⇒ Object

Note:

This is not related to a Parse beforeDelete webhook trigger.

A callback called before the object is about to be deleted.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#before_save { ... } ⇒ Object

Note:

This is not related to a Parse beforeSave webhook trigger.

A callback called before the object is saved.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#before_update { ... } ⇒ Object

A callback called before the object is updated (not on create).

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#before_validation { ... } ⇒ Object

A callback called before validations are run.

Yields:

  • A block to execute for the callback.

See Also:

  • ActiveModel::Callbacks


# File 'lib/parse/model/object.rb', line 196

#changedArray<String>

Override changed to filter out ACL when its content hasn't actually changed. This ensures dirty? returns false when ACL is rebuilt to identical values. For new objects, ACL is always included since it needs to be sent to the server.

Returns:



2026
2027
2028
2029
2030
2031
2032
2033
2034
# File 'lib/parse/model/object.rb', line 2026

def changed
  result = super.dup
  # If ACL is in the changed list but content is identical, remove it
  # BUT keep it if the object is new (needs to be sent to server)
  if result.include?("acl") && !new? && !acl_changed?
    result.delete("acl")
  end
  result
end

#changed?Boolean

Override changed? to use our filtered changed list. ActiveModel's changed? uses internal tracking that doesn't account for ACL content comparison.

Returns:

  • (Boolean)

    true if any attributes have changed.



2040
2041
2042
# File 'lib/parse/model/object.rb', line 2040

def changed?
  changed.any?
end

#clear_attribute_change!(atts) ⇒ Object

clear all change and dirty tracking information.



1748
1749
1750
# File 'lib/parse/model/object.rb', line 1748

def clear_attribute_change!(atts)
  clear_attribute_changes(atts)
end

#clear_changes!Object

clears all dirty tracking information



1472
1473
1474
1475
1476
# File 'lib/parse/model/object.rb', line 1472

def clear_changes!
  clear_changes_information
  # Clear the ACL snapshot used for proper acl_was tracking
  @_acl_snapshot_before_change = nil
end

#clear_partial_fetch_state!

This method returns an undefined value.

Clears all partial fetch tracking state. Called after successful save since server returns updated object.



1645
1646
1647
1648
# File 'lib/parse/model/object.rb', line 1645

def clear_partial_fetch_state!
  @_fetched_keys = nil
  @_nested_fetched_keys = nil
end

#disable_autofetch!

This method returns an undefined value.

Disables autofetch for this object instance. Useful for preventing automatic network requests.



1564
1565
1566
# File 'lib/parse/model/object.rb', line 1564

def disable_autofetch!
  @_autofetch_disabled = true
end

#enable_autofetch!

This method returns an undefined value.

Enables autofetch for this object instance (default behavior).



1570
1571
1572
# File 'lib/parse/model/object.rb', line 1570

def enable_autofetch!
  @_autofetch_disabled = false
end

#existed?Boolean

Note:

You should not use this method inside a beforeSave webhook.

Existed returns true if the object had existed before its last save operation. This method returns false if the #created_at and #updated_at dates of an object are equal, implyiny this object has been newly created and saved (especially in an afterSave hook).

This is a helper method in a webhook afterSave to know if this object was recently saved in the beforeSave webhook. Checking for #existed? == false in an afterSave hook, is equivalent to using #new? in a beforeSave hook.

Returns:

  • (Boolean)

    true iff the last beforeSave successfully saved this object for the first time.



1515
1516
1517
1518
1519
1520
# File 'lib/parse/model/object.rb', line 1515

def existed?
  if @id.blank? || @created_at.blank? || @updated_at.blank?
    return false
  end
  created_at != updated_at
end

#fetched?Boolean

Returns whether this object has been fetched from the server (fully or partially). Overrides Pointer#fetched? to return true for any object with data.

Returns:

  • (Boolean)

    true if the object has data (not just a pointer).



1549
1550
1551
# File 'lib/parse/model/object.rb', line 1549

def fetched?
  !pointer?
end

#fetched_keysArray<Symbol>

Returns the array of keys that were fetched for this object. Empty array means the object was fully fetched. Returns a frozen duplicate to prevent external mutation.

Returns:



1557
1558
1559
# File 'lib/parse/model/object.rb', line 1557

def fetched_keys
  (@_fetched_keys || []).dup.freeze
end

#fetched_keys=(keys) ⇒ Array

Sets the fetched keys for this object. Used internally when building objects from partial fetch queries.

Parameters:

  • keys (Array)

    the keys that were fetched

Returns:

  • (Array)

    the stored keys



1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
# File 'lib/parse/model/object.rb', line 1584

def fetched_keys=(keys)
  if keys.nil? || keys.empty?
    @_fetched_keys = nil
  else
    # Always include :id and convert to symbols
    @_fetched_keys = keys.map { |k| Parse::Query.format_field(k).to_sym }
    @_fetched_keys << :id unless @_fetched_keys.include?(:id)
    @_fetched_keys << :objectId unless @_fetched_keys.include?(:objectId)
    @_fetched_keys.uniq!
  end
  @_fetched_keys
end

#field_was_fetched?(key) ⇒ Boolean

Returns whether a specific field was fetched for this object. Base keys (id, created_at, updated_at) are always considered fetched.

Parameters:

Returns:

  • (Boolean)

    true if the field was fetched or if object is fully fetched.



1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
# File 'lib/parse/model/object.rb', line 1601

def field_was_fetched?(key)
  # If not partially fetched (i.e., still a pointer), all fields are NOT fetched
  return false if pointer?

  # If no selective keys were specified, this is a fully fetched object
  # All fields are considered fetched
  return true unless has_selective_keys?

  key = key.to_sym
  # Base keys are always considered fetched
  return true if Parse::Properties::BASE_KEYS.include?(key)
  return true if key == :acl || key == :ACL

  # Check both local key and remote field name
  # Convert remote_key to symbol for consistent comparison
  remote_key = self.field_map[key]&.to_sym
  @_fetched_keys.include?(key) || (remote_key && @_fetched_keys.include?(remote_key))
end

#filter_for_user(user, roles: [], authenticated: nil, clp: nil) ⇒ Hash

Filter this object's fields based on Class-Level Permissions for a user. Uses the CLP configured on the model class to determine which fields should be visible to the given user/roles context.

This is useful for filtering webhook responses or API data before sending to clients.

Examples:

Filter object for a specific user

song = Song.first
filtered = song.filter_for_user(current_user, roles: ["Member"])

Filter for unauthenticated access

filtered = song.filter_for_user(nil)

Parameters:

  • user (Parse::User, String, nil)

    the user or user ID

  • roles (Array<String>) (defaults to: [])

    role names the user belongs to

  • authenticated (Boolean) (defaults to: nil)

    whether the user is authenticated

  • clp (Parse::CLP, nil) (defaults to: nil)

    optional CLP to use (defaults to class CLP)

Returns:

  • (Hash)

    filtered data hash with protected fields removed

See Also:



1151
1152
1153
1154
1155
1156
# File 'lib/parse/model/object.rb', line 1151

def filter_for_user(user, roles: [], authenticated: nil, clp: nil)
  clp ||= self.class.class_permissions
  return as_json unless clp.present?

  clp.filter_fields(as_json, user: user, roles: roles, authenticated: authenticated)
end

#fully_fetched?Boolean

Returns whether this object is fully fetched with all fields available. Returns false if the object is a pointer or was fetched with specific keys.

Returns:

  • (Boolean)

    true if the object is fully fetched.



1542
1543
1544
# File 'lib/parse/model/object.rb', line 1542

def fully_fetched?
  !pointer? && !has_selective_keys?
end

#has?(key) ⇒ Boolean

Check if a field has a value (is present and not nil).

Parameters:

Returns:

  • (Boolean)

    true if the field has a non-nil value, false otherwise.



2076
2077
2078
2079
2080
# File 'lib/parse/model/object.rb', line 2076

def has?(key)
  return false unless self.class.fields[key.to_sym].present?
  value = send(key)
  !value.nil?
end

#has_selective_keys?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns whether this object was fetched with specific keys (selective fetch). When selectively fetched, accessing unfetched fields will trigger an autofetch. This is an internal method used for autofetch logic.

Returns:

  • (Boolean)

    true if the object was fetched with specific keys.



1527
1528
1529
# File 'lib/parse/model/object.rb', line 1527

def has_selective_keys?
  @_fetched_keys&.any? || false
end

#hybrid_ranksHash?

Returns per-branch 1-based ranks from Class.hybrid_search ({ lexical:, vector: }).

Returns:

  • (Hash, nil)

    per-branch 1-based ranks from Class.hybrid_search ({ lexical:, vector: }).



191
# File 'lib/parse/model/object.rb', line 191

def hybrid_ranks; @_hybrid_ranks; end

#hybrid_scoreFloat?

Returns fused reciprocal-rank-fusion score from Class.hybrid_search.

Returns:

  • (Float, nil)

    fused reciprocal-rank-fusion score from Class.hybrid_search.



187
# File 'lib/parse/model/object.rb', line 187

def hybrid_score; @_hybrid_score; end

#keysArray<String>

Returns an array of property names (keys) for this Parse::Object. Similar to Hash#keys, this method returns all the defined field names for this object's class.

Returns:

  • (Array<String>)

    an array of property names as strings.



2069
2070
2071
# File 'lib/parse/model/object.rb', line 2069

def keys
  self.class.fields.keys.map(&:to_s)
end

#nested_fetched_keysHash

Returns the nested fetched keys map for building nested objects.

Returns:

  • (Hash)

    map of field names to their fetched keys



1622
1623
1624
# File 'lib/parse/model/object.rb', line 1622

def nested_fetched_keys
  @_nested_fetched_keys || {}
end

#nested_fetched_keys=(keys_map) ⇒ Hash

Sets the nested fetched keys map for building nested objects.

Parameters:

  • keys_map (Hash)

    map of field names to their fetched keys

Returns:

  • (Hash)

    the stored map



1629
1630
1631
# File 'lib/parse/model/object.rb', line 1629

def nested_fetched_keys=(keys_map)
  @_nested_fetched_keys = keys_map.is_a?(Hash) ? keys_map : nil
end

#nested_keys_for(field_name) ⇒ Array?

Gets the fetched keys for a specific nested field.

Parameters:

Returns:

  • (Array, nil)

    the fetched keys for the nested object, or nil if not specified



1636
1637
1638
1639
1640
# File 'lib/parse/model/object.rb', line 1636

def nested_keys_for(field_name)
  return nil unless @_nested_fetched_keys.present?
  field_name = field_name.to_sym
  @_nested_fetched_keys[field_name]
end

#new?Boolean

An object is considered new until it has been successfully persisted to the server. "Persisted" means the server has returned a createdAt timestamp, which only happens after a successful create. Checking path assigns @id client-side in a before_create callback, so an @id-only check would flip mid-callback-chain and confuse user code (validation on: :create / :update, beforeSave handlers, etc.). Treating an object as "new" until createdAt arrives keeps semantics stable from the first before_save through the end of after_create.

Returns:

  • (Boolean)

    true if the object has not yet been persisted.



1488
1489
1490
# File 'lib/parse/model/object.rb', line 1488

def new?
  @id.blank? || @created_at.nil?
end

#parse_classString Also known as: className

Returns the Parse class for this object.

Returns:

  • (String)

    the Parse class for this object.

See Also:



1116
1117
1118
# File 'lib/parse/model/object.rb', line 1116

def parse_class
  self.class.parse_class
end

#partially_fetched?Boolean

Returns whether this object was fetched with specific keys (partial/selective fetch). When partially fetched, only the specified keys are available and accessing other fields will trigger an autofetch. Returns false for pointers and fully fetched objects.

Returns:

  • (Boolean)

    true if the object was fetched with specific keys.



1535
1536
1537
# File 'lib/parse/model/object.rb', line 1535

def partially_fetched?
  !pointer? && has_selective_keys?
end

#persisted?Boolean

Determines if this object has been saved to the Parse database. If an object has pending changes, then it is considered to not yet be persisted.

Returns:

  • (Boolean)

    true if this object has not been saved.



1441
1442
1443
# File 'lib/parse/model/object.rb', line 1441

def persisted?
  changed? == false && !(@id.nil? || @created_at.nil? || @updated_at.nil? || @acl.nil?)
end

#prettyString

Returns a pretty-formatted JSON string.

Returns:

  • (String)

    a pretty-formatted JSON string

See Also:

  • JSON.pretty_generate


1743
1744
1745
# File 'lib/parse/model/object.rb', line 1743

def pretty
  JSON.pretty_generate(as_json)
end

#reload!(**opts) ⇒ Object

Force reload from the database and replace any local fields with data from the persistent store. By default, bypasses cache reads but updates the cache with fresh data (write-only mode) so future cached reads get the latest data.

Examples:

Reload with fresh data (default - updates cache)

song.reload!

Reload with full caching (may return cached data)

song.reload!(cache: true)

Reload completely bypassing cache

song.reload!(cache: false)

Parameters:

  • opts (Hash)

    a set of options to send to fetch!

Options Hash (**opts):

  • :cache (Boolean, Symbol) — default: :write_only

    caching mode:

    • :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)

See Also:

  • Fetching#fetch!


1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
# File 'lib/parse/model/object.rb', line 1460

def reload!(**opts)
  # Default to write-only cache mode - reload always gets fresh data
  # but updates cache for future cached reads. Controlled by feature flag.
  unless opts.key?(:cache)
    opts[:cache] = Parse.cache_write_on_fetch ? :write_only : false
  end
  # get the values from the persistence layer
  fetch!(**opts)
  clear_changes!
end

#rollback!Object

Note:

This does not reload the object from the persistent store, for this use "reload!" instead.

Locally restores the previous state of the object and clears all dirty tracking information.

See Also:



1714
1715
1716
# File 'lib/parse/model/object.rb', line 1714

def rollback!
  restore_attributes
end

#run_after_create_callbacksBoolean

Run after_create callbacks for this object. This method is called by webhook handlers when an object is created.

Returns:

  • (Boolean)

    true if callbacks executed successfully



1653
1654
1655
# File 'lib/parse/model/object.rb', line 1653

def run_after_create_callbacks
  run_callbacks_from_list(self.class._create_callbacks, :after)
end

#run_after_delete_callbacksBoolean

Run after_destroy callbacks for this object. This method is called by webhook handlers when an object is deleted.

Returns:

  • (Boolean)

    true if callbacks executed successfully



1667
1668
1669
# File 'lib/parse/model/object.rb', line 1667

def run_after_delete_callbacks
  run_callbacks_from_list(self.class._destroy_callbacks, :after)
end

#run_after_save_callbacksBoolean

Run after_save callbacks for this object. This method is called by webhook handlers when an object is saved.

Returns:

  • (Boolean)

    true if callbacks executed successfully



1660
1661
1662
# File 'lib/parse/model/object.rb', line 1660

def run_after_save_callbacks
  run_callbacks_from_list(self.class._save_callbacks, :after)
end

#run_before_create_callbacksBoolean

Run before_create callbacks for this object (BEFORE phase only). Parse Server exposes no separate beforeCreate trigger, so the beforeSave webhook runs these for new objects right after before_save -- matching ActiveModel order, where before_save wraps before_create. Honors :if/:unless and the terminator.

Returns:

  • (Boolean)

    false if a before_create callback halted the chain, else true.



1686
1687
1688
# File 'lib/parse/model/object.rb', line 1686

def run_before_create_callbacks
  run_before_phase_callbacks(:create)
end

#run_before_save_callbacksBoolean

Run before_save callbacks for this object (BEFORE phase only). Used by the beforeSave webhook. Honors :if/:unless conditions and the callback terminator: returns false if a callback halts the chain. The after_* callbacks are NOT run here -- they belong to the afterSave webhook.

Returns:

  • (Boolean)

    false if a before_save callback halted the chain, else true.



1676
1677
1678
# File 'lib/parse/model/object.rb', line 1676

def run_before_save_callbacks
  run_before_phase_callbacks(:save)
end

#schemaHash

Returns the schema structure for this Parse collection from the server.

Returns:

  • (Hash)

    the schema structure for this Parse collection from the server.

See Also:



1124
1125
1126
# File 'lib/parse/model/object.rb', line 1124

def schema
  self.class.schema
end

#search_highlightsHash?

Returns Atlas Search highlights blob.

Returns:

  • (Hash, nil)

    Atlas Search highlights blob.



183
# File 'lib/parse/model/object.rb', line 183

def search_highlights; @_search_highlights; end

#search_scoreFloat?

Returns Atlas Search relevance score.

Returns:

  • (Float, nil)

    Atlas Search relevance score.



180
# File 'lib/parse/model/object.rb', line 180

def search_score; @_search_score; end

#twinParse::Object

This method creates a new object of the same instance type with a copy of all the properties of the current instance. This is useful when you want to create a duplicate record.

Returns:

  • (Parse::Object)

    a twin copy of the object without the objectId



1733
1734
1735
1736
1737
1738
1739
# File 'lib/parse/model/object.rb', line 1733

def twin
  h = self.as_json
  h.delete(Parse::Model::OBJECT_ID)
  h.delete(:objectId)
  h.delete(:id)
  self.class.new h
end

#updates(include_all = false) ⇒ Hash

Returns a hash of all the changes that have been made to the object. By default changes to the Parse::Properties::BASE_KEYS are ignored unless you pass true as an argument.

Parameters:

  • include_all (Boolean) (defaults to: false)

    whether to include all keys in result.

Returns:

  • (Hash)

    a hash containing only the change information.

See Also:



1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
# File 'lib/parse/model/object.rb', line 1696

def updates(include_all = false)
  h = {}
  changed.each do |key|
    next if include_all == false && Parse::Properties::BASE_KEYS.include?(key.to_sym)
    # lookup the remote Parse field name incase it is different from the local attribute name
    remote_field = self.field_map[key.to_sym] || key
    h[remote_field] = send key
    # make an exception to Parse::Objects, we should return a pointer to them instead
    h[remote_field] = h[remote_field].parse_pointers if h[remote_field].is_a?(Parse::PointerCollectionProxy)
    h[remote_field] = h[remote_field].pointer if h[remote_field].respond_to?(:pointer)
  end
  h
end

#valid?(context = nil) ⇒ Boolean

Override valid? to run validation callbacks. This wraps the standard ActiveModel validation with our custom :validation callbacks.

Parameters:

  • context (Symbol, nil) (defaults to: nil)

    validation context (same as ActiveModel)

Returns:

  • (Boolean)

    true if the object passes all validations



1496
1497
1498
1499
1500
1501
1502
# File 'lib/parse/model/object.rb', line 1496

def valid?(context = nil)
  result = true
  run_callbacks :validation do
    result = super(context)
  end
  result
end

#validate!self

Overrides ActiveModel::Validations#validate! instance method. It runs all validations for this object. If validation fails, it raises ActiveModel::ValidationError otherwise it returns the object.

Returns:

  • (self)

    self the object if validation passes.

Raises:

  • ActiveModel::ValidationError

See Also:

  • ActiveModel::Validations#validate!


1724
1725
1726
1727
# File 'lib/parse/model/object.rb', line 1724

def validate!
  super
  self
end

#vector_scoreFloat?

Search/vector-search result accessors. Populated by Parse::AtlasSearch.process_search_results and Parse::Core::VectorSearchable.build_vector_hits via instance_variable_set. Defined here once instead of per-result via define_singleton_method so high-k result sets don't inflate a singleton class per row, and so a user-defined override on a subclass can't silently desync from the ivar.

Each returns nil unless the object was returned from the corresponding search path.

Returns:

  • (Float, nil)

    vectorSearch relevance score.



177
# File 'lib/parse/model/object.rb', line 177

def vector_score; @_vector_score; end