Class: Parse::Object
- Extended by:
- Core::Describe, Core::Indexing, Core::Querying, Core::Schema, Core::SearchIndexing
- Includes:
- Agent::MetadataDSL, Associations::BelongsTo, Associations::HasMany, Associations::HasOne, Core::Actions, Core::EnhancedChangeTracking, Core::Fetching, Core::FieldGuards, Core::ParseReference, ValidationCallbackOnSupport, Properties
- Defined in:
- lib/parse/model/object.rb,
lib/parse/webhooks.rb
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`.
Direct Known Subclasses
Audience, Installation, JobSchedule, JobStatus, Product, PushStatus, Role, Session, User
Defined Under Namespace
Modules: ValidationCallbackOnSupport
Constant Summary collapse
- VALID_ACL_POLICIES =
Valid ACL policies that can be passed to acl_policy.
[:public, :private, :owner_else_public, :owner_else_private].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 `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::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::Fetching
Core::Fetching::NON_SERIALIZABLE_IVARS
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::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
-
.default_acl_private ⇒ Boolean
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.
-
.parse_class(remoteName = nil) ⇒ String
The class method to override the implicitly assumed Parse collection name in your Parse database.
-
.suppress_permissive_acl_warning ⇒ Boolean
When set on ‘Parse::Object` itself, suppresses the one-time per-class warning emitted when a class’s effective Object.acl_policy_setting is ‘:public` or `:owner_else_public`.
Instance Attribute Summary collapse
-
#acl ⇒ ACL
The access control list (permissions) object for this record.
-
#created_at ⇒ Date
readonly
The created_at date of the record in UTC Zulu iso 8601 with 3 millisecond format.
-
#id ⇒ String
The value of Parse “objectId” field.
-
#updated_at ⇒ Date
readonly
The updated_at date of the record in UTC Zulu iso 8601 with 3 millisecond format.
Class-Level Permissions (CLP) collapse
-
.class_permissions ⇒ Parse::CLP
(also: clp)
The Class-Level Permissions for this model.
-
.describe_access ⇒ Hash
Introspect the locally-configured access surface for this class.
-
.fetch_clp(client: nil) ⇒ Parse::CLP
(also: fetch_class_permissions)
Fetch the current CLP from the Parse Server for this class.
-
.master_only_class! ⇒ void
Lock every CLP operation to master-key access only.
-
.protect_fields(pattern, fields) ⇒ Object
(also: set_protected_fields)
Define protected fields that should be hidden from certain users/roles.
-
.set_class_access(**ops_to_access) ⇒ void
Set CLP for multiple operations in one call, choosing a coarse access mode per operation.
-
.set_clp(operation, public: nil, roles: [], users: [], pointer_fields: [], requires_authentication: false) ⇒ Object
(also: set_class_permission)
Set a class-level permission for a specific operation.
-
.set_default_clp(public: nil, roles: [], requires_authentication: false) ⇒ Object
Set default permissions for all CLP operations at once.
-
.set_read_user_fields(*fields) ⇒ Object
Set pointer-permission fields for read access.
-
.set_write_user_fields(*fields) ⇒ Object
Set pointer-permission fields for write access.
-
.unlistable_class! ⇒ void
Restrict ‘find` and `count` to master-key only, leaving the other operations (`get`, `create`, `update`, `delete`, `addField`) at their current settings.
-
.update_clp!(client: nil, replace: false) ⇒ Parse::Response
(also: update_class_permissions!)
Update the CLP on the Parse Server for this class.
Field Filtering (CLP) collapse
-
.filter_results_for_user(objects, user, roles: [], authenticated: nil, clp: nil) ⇒ Array<Hash>
Filter an array of Parse objects or hashes for a user.
-
.roles_for_user(user) ⇒ Array<String>
Fetch a user’s roles for use with field filtering.
-
#filter_for_user(user, roles: [], authenticated: nil, clp: nil) ⇒ Hash
Filter this object’s fields based on Class-Level Permissions for a user.
Class Method Summary collapse
-
.acl_owner_field ⇒ Symbol?
The name of the property/belongs_to designating the owner user for ‘:owner_else_*` ACL policies.
-
.acl_policy(policy, owner: nil) ⇒ Object
Declarative ACL policy applied to newly-created instances of this class.
-
.acl_policy_setting ⇒ Symbol
The effective ACL policy for this class.
-
.build(json, table = nil, fetched_keys: nil, nested_fetched_keys: nil) ⇒ Parse::Object
Method used for decoding JSON objects into their corresponding Object subclasses.
-
.default_acls ⇒ Parse::ACL
The set of default ACLs to be applied on newly created instances of this class.
-
.pointer(id) ⇒ Parse::Pointer
Helper method to create a Parse::Pointer object for a given id.
-
.private_acl! ⇒ Object
Convenience method to set default ACL to private (no public access).
-
.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.
-
.webhook(type, &block) { ... } ⇒ OpenStruct
Register a webhook trigger or function for this subclass.
-
.webhook_function(functionName, &block) { ... } ⇒ OpenStruct
Register a webhook function for this subclass.
Instance Method Summary collapse
-
#[](key) ⇒ Object
Access the value for a defined property through hash accessor.
-
#[]=(key, value) ⇒ Object
Set the value for a specific property through a hash accessor.
- #__type ⇒ Model::TYPE_OBJECT
-
#_resolve_acl_owner_id(owner) ⇒ Object
private
Resolves an ‘as:` value or owner-field pointer to an objectId string.
-
#_resolve_default_acl ⇒ Object
private
Save-time resolver for the declarative Object.acl_policy default ACL.
-
#acl_changed? ⇒ Boolean
Override acl_changed? to compare actual ACL content, not just object references.
-
#acl_was ⇒ Parse::ACL
Override acl_was to return the captured snapshot instead of the reference stored by ActiveModel’s dirty tracking.
-
#acl_will_change! ⇒ Object
private
Override acl_will_change! to capture a snapshot of the ACL before modification.
-
#after_create { ... } ⇒ Object
A callback called after the object has been created.
-
#after_destroy { ... } ⇒ Object
A callback called after the object has been successfully deleted.
-
#after_save { ... } ⇒ Object
A callback called after the object has been successfully saved.
-
#after_update { ... } ⇒ Object
A callback called after the object has been updated.
-
#after_validation { ... } ⇒ Object
A callback called after validations are run.
-
#apply_defaults! ⇒ Array
force apply default values for any properties defined with default values.
-
#around_create { ... } ⇒ Object
A callback called around object creation.
-
#around_destroy { ... } ⇒ Object
A callback called around object destruction.
-
#around_save { ... } ⇒ Object
A callback called around object save.
-
#around_update { ... } ⇒ Object
A callback called around object update.
-
#around_validation { ... } ⇒ Object
A callback called around validations.
-
#as_json(opts = nil) ⇒ Hash
A json-hash representing this object.
-
#autofetch_disabled? ⇒ Boolean
Returns whether autofetch is disabled for this instance.
-
#before_create { ... } ⇒ Object
A callback called before the object has been created.
-
#before_destroy { ... } ⇒ Object
A callback called before the object is about to be deleted.
-
#before_save { ... } ⇒ Object
A callback called before the object is saved.
-
#before_update { ... } ⇒ Object
A callback called before the object is updated (not on create).
-
#before_validation { ... } ⇒ Object
A callback called before validations are run.
-
#changed ⇒ Array<String>
Override changed to filter out ACL when its content hasn’t actually changed.
-
#changed? ⇒ Boolean
Override changed? to use our filtered changed list.
-
#clear_attribute_change!(atts) ⇒ Object
clear all change and dirty tracking information.
-
#clear_changes! ⇒ Object
clears all dirty tracking information.
-
#clear_partial_fetch_state! ⇒ void
Clears all partial fetch tracking state.
-
#disable_autofetch! ⇒ void
Disables autofetch for this object instance.
-
#enable_autofetch! ⇒ void
Enables autofetch for this object instance (default behavior).
-
#existed? ⇒ Boolean
Existed returns true if the object had existed before *its last save operation*.
-
#fetched? ⇒ Boolean
Returns whether this object has been fetched from the server (fully or partially).
-
#fetched_keys ⇒ Array<Symbol>
Returns the array of keys that were fetched for this object.
-
#fetched_keys=(keys) ⇒ Array
Sets the fetched keys for this object.
-
#field_was_fetched?(key) ⇒ Boolean
Returns whether a specific field was fetched for this object.
-
#fully_fetched? ⇒ Boolean
Returns whether this object is fully fetched with all fields available.
-
#has?(key) ⇒ Boolean
Check if a field has a value (is present and not nil).
-
#has_selective_keys? ⇒ Boolean
private
Returns whether this object was fetched with specific keys (selective fetch).
-
#initialize(opts = {}) ⇒ Parse::Object
constructor
The main constructor for subclasses.
-
#keys ⇒ Array<String>
Returns an array of property names (keys) for this Parse::Object.
-
#nested_fetched_keys ⇒ Hash
Returns the nested fetched keys map for building nested objects.
-
#nested_fetched_keys=(keys_map) ⇒ Hash
Sets the nested fetched keys map for building nested objects.
-
#nested_keys_for(field_name) ⇒ Array?
Gets the fetched keys for a specific nested field.
-
#new? ⇒ Boolean
An object is considered new until it has been successfully persisted to the server.
-
#parse_class ⇒ String
(also: #className)
The Parse class for this object.
-
#partially_fetched? ⇒ Boolean
Returns whether this object was fetched with specific keys (partial/selective fetch).
-
#persisted? ⇒ Boolean
Determines if this object has been saved to the Parse database.
-
#pretty ⇒ String
A pretty-formatted JSON string.
-
#reload!(**opts) ⇒ Object
Force reload from the database and replace any local fields with data from the persistent store.
-
#rollback! ⇒ Object
Locally restores the previous state of the object and clears all dirty tracking information.
-
#run_after_create_callbacks ⇒ Boolean
Run after_create callbacks for this object.
-
#run_after_delete_callbacks ⇒ Boolean
Run after_destroy callbacks for this object.
-
#run_after_save_callbacks ⇒ Boolean
Run after_save callbacks for this object.
-
#schema ⇒ Hash
The schema structure for this Parse collection from the server.
-
#twin ⇒ Parse::Object
This method creates a new object of the same instance type with a copy of all the properties of the current instance.
-
#updates(include_all = false) ⇒ Hash
Returns a hash of all the changes that have been made to the object.
-
#valid?(context = nil) ⇒ Boolean
Override valid? to run validation callbacks.
-
#validate! ⇒ self
Overrides ActiveModel::Validations#validate! instance method.
Methods included from Core::Querying
all, count, count_distinct, cursor, distinct, each, find, find_cached, first, last_updated, latest, literal_where, newest, oldest, 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
Methods included from Core::Indexing
apply_indexes!, indexes_plan, mongo_geo_index, mongo_index, mongo_index_declarations, mongo_relation_index
Methods included from Core::SearchIndexing
apply_search_indexes!, mongo_search_index, mongo_search_index_declarations, search_indexes_plan
Methods included from Agent::MetadataDSL
#agent_description, #agent_methods, included, #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 Associations::HasMany
has_many, #relation_changes?, #relation_updates, #relations
Methods included from Associations::BelongsTo
Methods included from Associations::HasOne
Methods included from Core::ParseReference
format, generate_object_id, parse
Methods included from Core::FieldGuards
Methods included from Core::EnhancedChangeTracking
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
Methods included from Client::Connectable
Constructor Details
#new(id) ⇒ Parse::Object #new(hash = {}) ⇒ Parse::Object
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:
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 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 |
# File 'lib/parse/model/object.rb', line 1265 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_private ⇒ Boolean
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.
324 325 326 |
# File 'lib/parse/model/object.rb', line 324 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.
354 355 356 357 358 |
# File 'lib/parse/model/object.rb', line 354 def parse_class(remoteName = nil) @parse_class ||= model_name.name @parse_class = remoteName.to_s unless remoteName.nil? @parse_class end |
.suppress_permissive_acl_warning ⇒ Boolean
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` is set to a truthy value (`1`, `true`, `yes`).
304 |
# File 'lib/parse/model/object.rb', line 304 attr_writer :suppress_permissive_acl_warning |
Instance Attribute Details
#acl ⇒ ACL
Returns the access control list (permissions) object for this record.
1762 |
# File 'lib/parse/model/object.rb', line 1762 property :acl, :acl, field: :ACL |
#created_at ⇒ Date (readonly)
Returns the created_at date of the record in UTC Zulu iso 8601 with 3 millisecond format.
1754 |
# File 'lib/parse/model/object.rb', line 1754 property :created_at, :date |
#id ⇒ String
Returns the value of Parse “objectId” field.
1750 |
# File 'lib/parse/model/object.rb', line 1750 property :id, field: :objectId |
#updated_at ⇒ Date (readonly)
Returns the updated_at date of the record in UTC Zulu iso 8601 with 3 millisecond format.
1758 |
# File 'lib/parse/model/object.rb', line 1758 property :updated_at, :date |
Class Method Details
.acl_owner_field ⇒ Symbol?
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.
534 535 536 537 538 539 540 541 |
# File 'lib/parse/model/object.rb', line 534 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):
-
Explicit ‘as: user` passed at construction → owner R/W only
-
Owner pointer resolved from the declared ‘owner:` field → owner R/W only
-
The else-half of the policy: ‘:public` → public R/W, `:private` → master-key only
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 |
# File 'lib/parse/model/object.rb', line 465 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 and :owner_else_private use it." end if owner.nil? && policy.to_s.start_with?("owner_") fallback = (policy == :owner_else_public) ? "public R/W" : "master-key-only" 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_setting ⇒ Symbol
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.
517 518 519 520 521 522 523 524 525 526 527 |
# File 'lib/parse/model/object.rb', line 517 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
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.
1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 |
# File 'lib/parse/model/object.rb', line 1679 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 && 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_permissions ⇒ Parse::CLP Also known as: clp
The Class-Level Permissions for this model. CLPs control access to the class at the schema level.
607 608 609 |
# File 'lib/parse/model/object.rb', line 607 def @class_permissions ||= Parse::CLP.new end |
.default_acls ⇒ Parse::ACL
The set of default ACLs to be applied on newly created instances of this class. By default, public read and write are enabled unless default_acl_private is true.
365 366 367 368 369 370 371 |
# File 'lib/parse/model/object.rb', line 365 def default_acls @default_acls ||= case acl_policy_setting when :public, :owner_else_public then Parse::ACL.everyone when :private, :owner_else_private then Parse::ACL.private else Parse::ACL.everyone end end |
.describe_access ⇒ Hash
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.
968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 |
# File 'lib/parse/model/object.rb', line 968 def describe_access perms = 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..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.
1017 1018 1019 1020 1021 1022 1023 1024 |
# File 'lib/parse/model/object.rb', line 1017 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.
1120 1121 1122 1123 1124 1125 1126 1127 1128 |
# File 'lib/parse/model/object.rb', line 1120 def self.filter_results_for_user(objects, user, roles: [], authenticated: nil, clp: nil) clp ||= 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! ⇒ void
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.
763 764 765 766 |
# File 'lib/parse/model/object.rb', line 763 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.
1358 1359 1360 1361 |
# File 'lib/parse/model/object.rb', line 1358 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`.
333 334 335 |
# File 'lib/parse/model/object.rb', line 333 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.
906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 |
# File 'lib/parse/model/object.rb', line 906 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 .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.
1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 |
# File 'lib/parse/model/object.rb', line 1139 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.}" [] end |
.set_class_access(**ops_to_access) ⇒ void
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.
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 |
# File 'lib/parse/model/object.rb', line 824 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.
726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 |
# File 'lib/parse/model/object.rb', line 726 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 .( 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. All subclasses have public read and write enabled by default.
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/parse/model/object.rb', line 397 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.
642 643 644 645 646 647 648 649 650 651 652 653 654 655 |
# File 'lib/parse/model/object.rb', line 642 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 .( 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.
670 671 672 673 674 675 676 |
# File 'lib/parse/model/object.rb', line 670 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 .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.
689 690 691 692 693 694 695 |
# File 'lib/parse/model/object.rb', line 689 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 .set_write_user_fields(*converted) end |
.unlistable_class! ⇒ void
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.
781 782 783 784 785 |
# File 'lib/parse/model/object.rb', line 781 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.
1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 |
# File 'lib/parse/model/object.rb', line 1040 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 = .as_json return nil if clp_data.empty? schema_update = { "classLevelPermissions" => clp_data } client.update_schema(parse_class, schema_update) end |
.webhook(type, &block) { ... } ⇒ OpenStruct
Register a webhook trigger or function for this subclass.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/parse/webhooks.rb', line 60 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.
36 37 38 39 40 41 42 43 44 |
# File 'lib/parse/webhooks.rb', line 36 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.
1949 1950 1951 1952 |
# File 'lib/parse/model/object.rb', line 1949 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.
1960 1961 1962 1963 |
# File 'lib/parse/model/object.rb', line 1960 def []=(key, value) return unless self.class.fields[key.to_sym].present? send("#{key}=", value) end |
#__type ⇒ Model::TYPE_OBJECT
160 |
# File 'lib/parse/model/object.rb', line 160 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.
1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 |
# File 'lib/parse/model/object.rb', line 1856 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_acl ⇒ 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.
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.
1772 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 1841 1842 1843 |
# File 'lib/parse/model/object.rb', line 1772 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 :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 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.
1913 1914 1915 1916 1917 1918 1919 1920 |
# File 'lib/parse/model/object.rb', line 1913 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_was ⇒ Parse::ACL
Override acl_was to return the captured snapshot instead of the reference stored by ActiveModel’s dirty tracking.
1901 1902 1903 1904 1905 1906 1907 1908 |
# File 'lib/parse/model/object.rb', line 1901 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.
1881 1882 1883 1884 1885 1886 1887 1888 1889 |
# File 'lib/parse/model/object.rb', line 1881 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.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#after_destroy { ... } ⇒ Object
This is not related to a Parse afterDelete webhook trigger.
A callback called after the object has been successfully deleted.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#after_save { ... } ⇒ Object
This is not related to a Parse afterSave webhook trigger.
A callback called after the object has been successfully saved.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#after_update { ... } ⇒ Object
A callback called after the object has been updated.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#after_validation { ... } ⇒ Object
A callback called after validations are run.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#apply_defaults! ⇒ Array
force apply default values for any properties defined with default values.
1345 1346 1347 1348 1349 1350 1351 1352 1353 |
# File 'lib/parse/model/object.rb', line 1345 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.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#around_destroy { ... } ⇒ Object
A callback called around object destruction.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#around_save { ... } ⇒ Object
A callback called around object save.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#around_update { ... } ⇒ Object
A callback called around object update.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#around_validation { ... } ⇒ Object
A callback called around validations.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#as_json(opts = nil) ⇒ Hash
Returns a json-hash representing this object.
1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 |
# File 'lib/parse/model/object.rb', line 1171 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 # 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.
1501 1502 1503 |
# File 'lib/parse/model/object.rb', line 1501 def autofetch_disabled? @_autofetch_disabled == true end |
#before_create { ... } ⇒ Object
A callback called before the object has been created.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#before_destroy { ... } ⇒ Object
This is not related to a Parse beforeDelete webhook trigger.
A callback called before the object is about to be deleted.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#before_save { ... } ⇒ Object
This is not related to a Parse beforeSave webhook trigger.
A callback called before the object is saved.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#before_update { ... } ⇒ Object
A callback called before the object is updated (not on create).
|
|
# File 'lib/parse/model/object.rb', line 162
|
#before_validation { ... } ⇒ Object
A callback called before validations are run.
|
|
# File 'lib/parse/model/object.rb', line 162
|
#changed ⇒ Array<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.
1926 1927 1928 1929 1930 1931 1932 1933 1934 |
# File 'lib/parse/model/object.rb', line 1926 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.
1940 1941 1942 |
# File 'lib/parse/model/object.rb', line 1940 def changed? changed.any? end |
#clear_attribute_change!(atts) ⇒ Object
clear all change and dirty tracking information.
1654 1655 1656 |
# File 'lib/parse/model/object.rb', line 1654 def clear_attribute_change!(atts) clear_attribute_changes(atts) end |
#clear_changes! ⇒ Object
clears all dirty tracking information
1397 1398 1399 1400 1401 |
# File 'lib/parse/model/object.rb', line 1397 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! ⇒ void
This method returns an undefined value.
Clears all partial fetch tracking state. Called after successful save since server returns updated object.
1570 1571 1572 1573 |
# File 'lib/parse/model/object.rb', line 1570 def clear_partial_fetch_state! @_fetched_keys = nil @_nested_fetched_keys = nil end |
#disable_autofetch! ⇒ void
This method returns an undefined value.
Disables autofetch for this object instance. Useful for preventing automatic network requests.
1489 1490 1491 |
# File 'lib/parse/model/object.rb', line 1489 def disable_autofetch! @_autofetch_disabled = true end |
#enable_autofetch! ⇒ void
This method returns an undefined value.
Enables autofetch for this object instance (default behavior).
1495 1496 1497 |
# File 'lib/parse/model/object.rb', line 1495 def enable_autofetch! @_autofetch_disabled = false end |
#existed? ⇒ Boolean
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.
1440 1441 1442 1443 1444 1445 |
# File 'lib/parse/model/object.rb', line 1440 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.
1474 1475 1476 |
# File 'lib/parse/model/object.rb', line 1474 def fetched? !pointer? end |
#fetched_keys ⇒ Array<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.
1482 1483 1484 |
# File 'lib/parse/model/object.rb', line 1482 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.
1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 |
# File 'lib/parse/model/object.rb', line 1509 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.
1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 |
# File 'lib/parse/model/object.rb', line 1526 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.
1098 1099 1100 1101 1102 1103 |
# File 'lib/parse/model/object.rb', line 1098 def filter_for_user(user, roles: [], authenticated: nil, clp: nil) clp ||= self.class. 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.
1467 1468 1469 |
# File 'lib/parse/model/object.rb', line 1467 def fully_fetched? !pointer? && !has_selective_keys? end |
#has?(key) ⇒ Boolean
Check if a field has a value (is present and not nil).
1976 1977 1978 1979 1980 |
# File 'lib/parse/model/object.rb', line 1976 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.
1452 1453 1454 |
# File 'lib/parse/model/object.rb', line 1452 def has_selective_keys? @_fetched_keys&.any? || false end |
#keys ⇒ Array<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.
1969 1970 1971 |
# File 'lib/parse/model/object.rb', line 1969 def keys self.class.fields.keys.map(&:to_s) end |
#nested_fetched_keys ⇒ Hash
Returns the nested fetched keys map for building nested objects.
1547 1548 1549 |
# File 'lib/parse/model/object.rb', line 1547 def nested_fetched_keys @_nested_fetched_keys || {} end |
#nested_fetched_keys=(keys_map) ⇒ Hash
Sets the nested fetched keys map for building nested objects.
1554 1555 1556 |
# File 'lib/parse/model/object.rb', line 1554 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.
1561 1562 1563 1564 1565 |
# File 'lib/parse/model/object.rb', line 1561 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`.
1413 1414 1415 |
# File 'lib/parse/model/object.rb', line 1413 def new? @id.blank? || @created_at.nil? end |
#parse_class ⇒ String Also known as: className
Returns the Parse class for this object.
1063 1064 1065 |
# File 'lib/parse/model/object.rb', line 1063 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.
1460 1461 1462 |
# File 'lib/parse/model/object.rb', line 1460 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.
1366 1367 1368 |
# File 'lib/parse/model/object.rb', line 1366 def persisted? changed? == false && !(@id.nil? || @created_at.nil? || @updated_at.nil? || @acl.nil?) end |
#pretty ⇒ String
Returns a pretty-formatted JSON string.
1649 1650 1651 |
# File 'lib/parse/model/object.rb', line 1649 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.
1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 |
# File 'lib/parse/model/object.rb', line 1385 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
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.
1620 1621 1622 |
# File 'lib/parse/model/object.rb', line 1620 def rollback! restore_attributes end |
#run_after_create_callbacks ⇒ Boolean
Run after_create callbacks for this object. This method is called by webhook handlers when an object is created.
1578 1579 1580 |
# File 'lib/parse/model/object.rb', line 1578 def run_after_create_callbacks run_callbacks_from_list(self.class._create_callbacks, :after) end |
#run_after_delete_callbacks ⇒ Boolean
Run after_destroy callbacks for this object. This method is called by webhook handlers when an object is deleted.
1592 1593 1594 |
# File 'lib/parse/model/object.rb', line 1592 def run_after_delete_callbacks run_callbacks_from_list(self.class._destroy_callbacks, :after) end |
#run_after_save_callbacks ⇒ Boolean
Run after_save callbacks for this object. This method is called by webhook handlers when an object is saved.
1585 1586 1587 |
# File 'lib/parse/model/object.rb', line 1585 def run_after_save_callbacks run_callbacks_from_list(self.class._save_callbacks, :after) end |
#schema ⇒ Hash
Returns the schema structure for this Parse collection from the server.
1071 1072 1073 |
# File 'lib/parse/model/object.rb', line 1071 def schema self.class.schema end |
#twin ⇒ Parse::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.
1639 1640 1641 1642 1643 1644 1645 |
# File 'lib/parse/model/object.rb', line 1639 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.
1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 |
# File 'lib/parse/model/object.rb', line 1602 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.
1421 1422 1423 1424 1425 1426 1427 |
# File 'lib/parse/model/object.rb', line 1421 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.
1630 1631 1632 1633 |
# File 'lib/parse/model/object.rb', line 1630 def validate! super self end |