{"id":50172669,"url":"https://github.com/neurosynq/parse-stack-next","last_synced_at":"2026-05-30T05:00:36.783Z","repository":{"id":359858189,"uuid":"1247745168","full_name":"neurosynq/parse-stack-next","owner":"neurosynq","description":"Ruby SDK for Parse Server with extended features: vector \u0026 Atlas Search, agent ACL scopes, GraphQL, MCP, and ORM tooling","archived":false,"fork":false,"pushed_at":"2026-05-28T13:57:30.000Z","size":3050,"stargazers_count":1,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-29T04:24:08.471Z","etag":null,"topics":["activemodel","agent","ai-agent","atlas-search","cloud-code","graphql","live-query","mcp","model-context-protocol","mongodb","orm","parse-platform","parse-server","rails","ruby","ruby-sdk","vector-search","webhooks"],"latest_commit_sha":null,"homepage":"https://neurosynq.github.io/parse-stack-next/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neurosynq.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-23T18:11:37.000Z","updated_at":"2026-05-25T04:09:17.000Z","dependencies_parsed_at":"2026-05-27T02:00:52.098Z","dependency_job_id":null,"html_url":"https://github.com/neurosynq/parse-stack-next","commit_stats":null,"previous_names":["neurosynq/parse-stack-next"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/neurosynq/parse-stack-next","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosynq%2Fparse-stack-next","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosynq%2Fparse-stack-next/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosynq%2Fparse-stack-next/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosynq%2Fparse-stack-next/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neurosynq","download_url":"https://codeload.github.com/neurosynq/parse-stack-next/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosynq%2Fparse-stack-next/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33680527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activemodel","agent","ai-agent","atlas-search","cloud-code","graphql","live-query","mcp","model-context-protocol","mongodb","orm","parse-platform","parse-server","rails","ruby","ruby-sdk","vector-search","webhooks"],"created_at":"2026-05-25T00:10:56.054Z","updated_at":"2026-05-30T05:00:36.658Z","avatar_url":"https://github.com/neurosynq.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"![parse_stack_next - The Ruby stack for Parse Server](https://raw.githubusercontent.com/neurosynq/parse-stack-next/main/assets/parse-stack-next-banner.png)\n\n# Parse Stack Next\n\nA full-featured Ruby client SDK for [Parse Server](http://parseplatform.org/). [parse-stack-next](https://github.com/neurosynq/parse-stack-next) is a Ruby client SDK, REST client, and Active Model ORM for [Parse Server](http://parseplatform.org/), combining a low-level API client, a query engine, an object-relational mapper (ORM), and a Cloud Code Webhooks rack application in a single gem.\n\n### What's new in 5.0\n\n- **RAG foundation** — `:vector` property type, `Parse::Embeddings` provider registry shipping built-in adapters for OpenAI, Cohere (v3 + v4.0 Matryoshka text-mode), Voyage (incl. open-weight `voyage-4-nano` and `voyage-multimodal-3` text-mode), Jina v3/v4/v5/code, Qwen 3 (DashScope), and a generic `LocalHTTP` client for Ollama / LM Studio / vLLM / TEI. `Klass.find_similar(vector:/text:, k:)` over Atlas `$vectorSearch`, and an `embed` class macro that digest-tracks source fields so vectors only recompute when content changes\n- **`Parse::Cache::Redis`** — Moneta-compatible Redis cache wrapper with a built-in `ConnectionPool`, optional `cache_namespace:` for multi-tenant Redis sharing, and graceful degrade on pool saturation\n- **`ActiveSupport::Notifications` instrumentation** — `parse.cache.*`, `parse.mongodb.aggregate`, `parse.mongodb.find`, and `parse.embeddings.embed` events with stable, PII-safe payload schemas; in-core slow-query log via `Parse.slow_query_threshold_ms`\n- **MCP transport hardening** — Streamable HTTP `Mcp-Session-Id` header (renamed from `X-MCP-Session-Id`, **breaking**), `MCP-Protocol-Version` validation, `DELETE /` session termination, structured-content (`outputSchema`) on built-in tools, optional `health_path:` liveness probe\n- **`Parse::GraphQL::TypeGenerator`** — generate `graphql-ruby` types directly from your `Parse::Object` subclasses (no Parse Server round-trip), with `:vector` columns surfaced as `[Float]` and association registries (`has_one_associations`, `has_many_associations`) populated at DSL time\n- **LiveQuery promoted to stable** — the experimental warning is removed; `Parse.live_query_enabled = true` is retained as a network-egress safety toggle, not a stability gate\n- **Server-version deprecation warning** — one-shot warning when connecting to Parse Server below the supported floor (currently 7.0.0); silence with `Parse.suppress_server_version_warning = true`\n- **`mongo_relation_index :field, dedup: true`** — register a compound `{owningId, relatedId}` UNIQUE on relation join collections to prevent duplicate-pair subscriptions without breaking `has_many` semantics\n\nSee [CHANGELOG.md](./CHANGELOG.md) for the full 5.0 entry, including security-hardening notes and Ruby 3.x cleanup.\n\n### Core capabilities\n\n- MongoDB Aggregation Framework support\n- **MongoDB Atlas Search** — full-text search, autocomplete, faceted search with direct MongoDB access\n- **Direct MongoDB Queries** — bypass Parse Server's REST surface for high-performance reads, with SDK-side ACL/CLP/`protectedFields` enforcement for scoped agents\n- **Schema Introspection \u0026 Migration** — compare local models with server schema and generate migrations\n- **Enhanced Role Management** — helper methods for role hierarchies, user membership, and subscription queries\n- **Read Preference Support** — route reads to MongoDB secondary replicas\n- **Class-Level Permissions (CLP)** — define and filter protected fields based on roles and user ownership\n- Advanced ACL query constraints (`readable_by`, `writable_by`)\n- **Owner-aware default ACL policy** (`acl_policy :owner_else_private`) — per-class defaults granting read/write only to the record's owner, with a secure or public fallback for server-context creates\n- Full transaction support with automatic retry\n- Comprehensive integration testing with Docker\n- Enhanced change tracking and webhooks\n- Request idempotency with `Retry-After` header support\n- Timezone support for date operations\n- Partial fetch with smart autofetch and serialization control\n- Multi-Factor Authentication (MFA/2FA) support\n- LiveQuery real-time subscriptions with TLS/SSL, circuit breaker, and health monitoring\n- AI/LLM agent integration (MCP-spec compliant) with security hardening — rate limiting, injection protection, agent ACL scopes\n\nBelow is a [quick start guide](#overview). See also the [Usage Guide](./docs/usage_guide.md) for practical examples covering queries, aggregation, ACLs, and more.\n\n\u003e **Note:** API reference docs are published at [neurosynq.github.io/parse-stack-next](https://neurosynq.github.io/parse-stack-next/index.html). Generated via YARD from the current source; covers the full 5.x surface.\n\n### Credits\n\nThis project (`parse-stack-next`) is a continuation of the [Parse Stack framework](https://github.com/modernistik/parse-stack) originally created by [Modernistik](https://www.modernistik.com). We are grateful for their foundational work and continue to build upon it under the [neurosynq](https://github.com/neurosynq) organization.\n\n### Code Status\n[![Gem Version](https://img.shields.io/gem/v/parse-stack-next.svg)](https://rubygems.org/gems/parse-stack-next)\n[![Downloads](https://img.shields.io/gem/dt/parse-stack-next.svg)](https://rubygems.org/gems/parse-stack-next)\n[![Releases](https://img.shields.io/github/v/release/neurosynq/parse-stack-next)](https://github.com/neurosynq/parse-stack-next/releases)\n\n#### Tutorial Videos\n\nThe following videos were recorded for the original parse-stack gem. The model, query, and association surface they cover is unchanged in parse-stack-next, so they remain a useful introduction; see the [Usage Guide](./docs/usage_guide.md) for v5.x-specific features (vector search, Redis cache, agent tools).\n\n1. Getting Started: https://youtu.be/zoYSGmciDlQ\n2. Custom Classes and Relations: https://youtu.be/tfSesotfU7w\n3. Working with Existing Schemas: https://youtu.be/EJGPT7YWyXA\n\nAny other questions, please post them on StackOverflow with the proper parse-stack / parse-server / ruby tags.\n\n## Installation\n\nAdd this line to your application's `Gemfile`:\n```ruby\ngem 'parse-stack-next'\n```\nAnd then execute:\n```bash\n$ bundle\n```\nOr install it yourself as:\n```bash\n$ gem install parse-stack-next\n```\n\n\u003e **Note:** The Ruby require path and module namespace are unchanged. You still `require 'parse/stack'` and reference classes under `Parse::Object`, `Parse::Query`, etc. Only the gem name on RubyGems has changed.\n### Rack / Sinatra\nParse-Stack API, models and webhooks easily integrate in your existing Rack/Sinatra based applications.\n\n### Rails\nParse-Stack comes with support for Rails by adding additional rake tasks and generators. After adding `parse-stack-next` as a gem dependency in your Gemfile and running `bundle`, you should run the install script:\n```bash\n$ rails g parse_stack:install\n```\n\n### Interactive Command Line Playground\nYou can also used the bundled `parse-console` command line to connect and interact with your Parse Server and its data in an IRB-like console. This is useful for trying concepts and debugging as it will automatically connect to your Parse Server, and if provided the master key, automatically generate all the models entities.\n\n```bash\n$ parse-console -h # see all options\n$ parse-console -v -a myAppId -m myMasterKey http://localhost:2337/parse\nServer : http://localhost:2337/parse\nApp Id : myAppId\nMaster : true\n2.4.0 \u003e Parse::User.first\n```\n\n## Overview\nParse-Stack is a full stack framework that utilizes several ideas behind [DataMapper](http://datamapper.org/docs/find.html) and [ActiveModel](https://github.com/rails/rails/tree/master/activemodel) to manage and maintain larger scale ruby applications and tools that utilize the [Parse Server Platform](http://parseplatform.org/). If you are familiar with these technologies, the framework should feel familiar to you.\n\n```ruby\nrequire 'parse/stack'\n\nParse.setup server_url: 'http://localhost:2337/parse',\n            app_id: APP_ID,\n            api_key: REST_API_KEY,\n            master_key: YOUR_MASTER_KEY # optional\n\n# Automatically build models based on your Parse application schemas.\nParse.auto_generate_models!\n\n# or define custom Subclasses (Highly Recommended)\nclass Song \u003c Parse::Object\n  property :name\n  property :play, :integer\n  property :audio_file, :file\n  property :tags, :array\n  property :released, :date\n  belongs_to :artist\n  # `like` is a Parse Relation to User class\n  has_many :likes, as: :user, through: :relation\n\n  # deny public write to Song records by default\n  set_default_acl :public, read: true, write: false\nend\n\nclass Artist \u003c Parse::Object\n  property :name\n  property :genres, :array\n  has_many :fans, as: :user\n  has_one :manager, as: :user\n\n  scope :recent, -\u003e(x) { query(:created_at.after =\u003e x) }\nend\n\n# updates schemas for your Parse app based on your models (non-destructive)\nParse.auto_upgrade!\n\n# login\nuser = Parse::User.login(username, passwd)\n\nartist = Artist.new(name: \"Frank Sinatra\", genres: [\"swing\", \"jazz\"])\nartist.fans \u003c\u003c user\nartist.save\n\n# Query\nartist = Artist.first(:name.like =\u003e /Sinatra/, :genres.in =\u003e ['swing'])\n\n# more examples\nsong = Song.new name: \"Fly Me to the Moon\"\nsong.artist = artist\n# Parse files - upload a file and attach to object\nsong.audio_file = Parse::File.create(\"http://path_to.mp3\")\n\n# relations - find a User matching username and add it to relation.\nsong.likes.add Parse::User.first(username: \"persaud\")\n\n# saves both attributes and relations\nsong.save\n\n# find songs\nsongs = Song.all(artist: artist, :plays.gt =\u003e 100, :released.on_or_after =\u003e 30.days.ago)\n\nsongs.each { |s| s.tags.add \"awesome\" }\n# batch saves\nsongs.save\n\n# Call Cloud Code functions\nresult = Parse.call_function :myFunctionName, {param: value}\n\n```\n\n## Release History\n\n**Current version: 5.0.1** | **Ruby 3.2+ required**\n\nThe 5.0 highlights (vector search / RAG, pooled Redis cache, AS::N instrumentation, MCP transport hardening, GraphQL type generation) are summarized in the [What's new in 5.0](#whats-new-in-50) section above. Earlier releases are recorded below.\n\nPer-version detail lives in [CHANGELOG.md](./CHANGELOG.md) and on the [Releases page](https://github.com/neurosynq/parse-stack-next/releases). The compact summary below is the major-line view.\n\n### 4.x — MongoDB index management, agent ACL scope, CLP enforced on mongo-direct, and `parse-stack-next` debut\n\n- **`mongo_index` DSL** (`mongo_index`, `mongo_geo_index`, `mongo_relation_index`) with class-load validation (pointer auto-rewrite, parallel-array rejection, `_id` guard, 64-per-collection cap). `parse_reference` fields auto-register a unique-sparse index.\n- **Index migration tooling** — `Parse::MongoDB.configure_writer` (separate write connection, triple-gated), `Parse::Schema::IndexMigrator` (plan / apply with optional orphan drop), and `rake parse:mongo:indexes:plan` / `:apply`.\n- **`Model.describe`** — operator introspection aggregator (local declarations + optional server fetch covering schema, CLP, default ACLs, Atlas Search, MongoDB indexes).\n- **CLP + `protectedFields` enforced on mongo-direct** — `Parse::CLPScope` gates `Parse::MongoDB.aggregate` for scoped agents (`session_token:` / `acl_user:` / `acl_role:`) and strips protected fields from result rows. This is the only first-class enforcement surface for ACL + CLP + protectedFields on scoped reads; Parse Server's REST aggregate enforces neither.\n- **`Parse::Agent.new(acl_user:|acl_role:)`** — declared agent identity without a session token; built-in tools auto-promote to mongo-direct. Sub-agent identity must be a subset of the parent's reach.\n- **Pipeline correctness** — schema-aware `$author` → `$_p_author` rewriter respects pipeline-local aliases; forward-pass field tracking through `$group`/`$addFields`/`$set`/`$lookup.as`; pointer `query_hint:` surfaced in `get_schema`.\n- **`Parse.strict_pointer_shapes`** — opt-in flag that converts unresolvable pointer-shape constraints into a `PointerShapeError` raise (recommended for test/CI and LLM-driven workloads).\n- **`first_or_create!` / `create_lock` accept `Parse::Operation` keys** in `synchronize:`, fixing filter-lock fingerprint collisions on inequality/range constraints.\n- **Security \u0026 modernization** — Ruby 3.2 floor, Rails/ActiveSupport 6.1 floor, CI on Ruby 3.2–3.5. LiveQuery TLS hostname verification. Webhook endpoint fails closed when no key is configured. `Parse::Error.new(code, message)` two-argument constructor. `include`d pointer fields auto-added to `keys` when an allowlist is set.\n- **4.5.0 — first release of this gem.** `parse-stack-next` debuts on RubyGems under the [neurosynq](https://github.com/neurosynq) organization, continuing from the upstream `parse-stack` 4.4.x line. The Ruby require path (`require 'parse/stack'`) and the `Parse::*` namespace are unchanged from upstream — only the gem name on RubyGems is new.\n\n### 3.x — Atlas Search, MongoDB-direct, CLP, AI agent, push, MFA, LiveQuery\n\n- **MongoDB Atlas Search** — full-text search, autocomplete, faceted search.\n- **Direct MongoDB queries** — `results_direct`, `first_direct`, `count_direct` bypassing Parse Server's REST surface.\n- **Schema tools** — `Parse::Schema.diff`, `Parse::Schema.migration`, plus `read_pref(:secondary)` for replica reads.\n- **Role management** — `find_or_create`, `add_users`, `add_child_role`, `all_users` (with hierarchy walks).\n- **Class-Level Permissions (CLP)** declared in models — `set_clp :find, public: true`, `protect_fields \"*\", [:internal_notes]`.\n- **AI/LLM agent** — `Parse::Agent` with natural-language queries over a tool interface.\n- **Push builder API** — `to_channel`, `with_alert`, `silent!`, `send!`; installation channels (`subscribe`, `unsubscribe`).\n- **Session lifecycle** — `expired?`, `time_remaining`, `logout_all!`.\n- **MFA** — TOTP and SMS two-factor authentication.\n- **LiveQuery** — real-time WebSocket subscriptions (promoted to stable in 5.0).\n- **Ruby 3.1 floor** (3.0 reached EOL March 2024).\n\n### 2.x — aggregation, transactions, idempotency, ACL constraints\n\n- **Transactions** — `Parse::Object.transaction` with automatic retry.\n- **MongoDB aggregation** — `group_by`, `count_distinct`, custom pipelines.\n- **ACL query constraints** — `readable_by`, `writable_by`, `publicly_readable`.\n- **Request idempotency** — automatic duplicate prevention, enabled by default.\n- **Change tracking** — works correctly in `after_save` hooks.\n- **Breaking from 1.x** — Ruby 3.0 floor, Faraday 2.x (no `faraday_middleware`), `distinct` returns object IDs by default (pass `return_pointers: true` for pointers), `constaint` → `constraint` typo fix.\n\n### 1.x — initial Parse Server SDK\n\nThe 1.x line is the original [`modernistik/parse-stack`](https://github.com/modernistik/parse-stack) — Active Model ORM, REST client, query DSL, associations, and Cloud Code webhooks for Parse Server. `parse-stack-next` is a continuation of that work; the first release published under the new gem name is **4.5.0** (above), on RubyGems as [`parse-stack-next`](https://rubygems.org/gems/parse-stack-next).\n\n## Table of Contents\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [Architecture](#architecture)\n  - [Parse::Client](#parseclient)\n  - [Parse::Query](#parsequery)\n  - [Parse::Object](#parseobject)\n  - [Parse::Webhooks](#parsewebhooks)\n- [Field Naming Conventions](#field-naming-conventions)\n- [Connection Setup](#connection-setup)\n  - [Connection Options](#connection-options)\n- [Working With Existing Schemas](#working-with-existing-schemas)\n- [Parse Config](#parse-config)\n- [Core Classes](#core-classes)\n  - [Parse::Pointer](#parsepointer)\n  - [Parse::File](#parsefile)\n  - [Parse::Date](#parsedate)\n  - [Parse::GeoPoint](#parsegeopoint)\n    - [Calculating Distances between locations](#calculating-distances-between-locations)\n  - [Parse::Bytes](#parsebytes)\n  - [Parse::TimeZone](#parsetimezone)\n  - [Parse::ACL](#parseacl)\n  - [Parse::CLP (Class-Level Permissions)](#parseclp-class-level-permissions)\n    - [Defining CLPs in Models](#defining-clps-in-models)\n    - [Filtering Data for Webhook Responses](#filtering-data-for-webhook-responses)\n    - [Protected Fields Intersection Logic](#protected-fields-intersection-logic)\n    - [Push CLPs to Parse Server](#push-clps-to-parse-server)\n    - [Fetch and Inspect CLPs](#fetch-and-inspect-clps)\n    - [Owner-Based Access with userField](#owner-based-access-with-userfield)\n  - [Parse::Session](#parsesession)\n  - [Parse::Installation](#parseinstallation)\n  - [Parse::Product](#parseproduct)\n  - [Parse::Role](#parserole)\n  - [Parse::JobStatus](#parsejobstatus)\n  - [Parse::JobSchedule](#parsejobschedule)\n  - [Parse::User](#parseuser)\n    - [Signup](#signup)\n      - [Third-Party Services](#third-party-services)\n    - [Login and Sessions](#login-and-sessions)\n    - [Linking and Unlinking](#linking-and-unlinking)\n    - [Request Password Reset](#request-password-reset)\n- [Modeling and Subclassing](#modeling-and-subclassing)\n  - [Defining Properties](#defining-properties)\n    - [Accessor Aliasing](#accessor-aliasing)\n    - [Property Options](#property-options)\n      - [`:required`](#required)\n      - [`:field`](#field)\n      - [`:default`](#default)\n      - [`:alias`](#alias)\n      - [`:symbolize`](#symbolize)\n      - [`:enum`](#enum)\n      - [`:scope`](#scope)\n  - [Associations](#associations)\n    - [Belongs To](#belongs-to)\n      - [Options](#options)\n        - [`:required`](#required-1)\n        - [`:as`](#as)\n        - [`:field`](#field-1)\n    - [Has One](#has-one)\n    - [Has Many](#has-many)\n      - [Query](#query)\n      - [Array](#array)\n      - [Parse Relation](#parse-relation)\n      - [Options](#options-1)\n        - [`:through`](#through)\n        - [`:scope_only`](#scope_only)\n- [Creating, Saving and Deleting Records](#creating-saving-and-deleting-records)\n  - [Create](#create)\n  - [Upsert Operations](#upsert-operations)\n    - [first_or_create](#first_or_create)\n    - [first_or_create!](#first_or_create_bang)\n    - [create_or_update!](#create_or_update_bang)\n  - [Saving](#saving)\n  - [Saving applying User ACLs](#saving-applying-user-acls)\n    - [Raising an exception when save fails](#raising-an-exception-when-save-fails)\n  - [Enhanced Object Fetching](#enhanced-object-fetching)\n  - [Modifying Associations](#modifying-associations)\n  - [Batch Requests](#batch-requests)\n  - [Atomic Transactions](#atomic-transactions)\n  - [Magic `save_all`](#magic-save_all)\n  - [Deleting](#deleting)\n- [Fetching, Finding and Counting Records](#fetching-finding-and-counting-records)\n  - [Auto-Fetching Associations](#auto-fetching-associations)\n- [Advanced Querying](#advanced-querying)\n  - [Results Caching](#results-caching)\n  - [Counting](#counting)\n  - [Count Distinct](#count-distinct)\n  - [Aggregation Functions](#aggregation-functions)\n  - [Group By Operations](#group-by-operations)\n  - [Distinct Aggregation](#distinct-aggregation)\n  - [Query Expressions](#query-expressions)\n    - [:order](#order)\n    - [:keys](#keys)\n    - [:includes](#includes)\n    - [:limit](#limit)\n    - [:skip](#skip)\n  - [Cursor-Based Pagination](#cursor-based-pagination)\n    - [:cache](#cache)\n    - [:use_master_key](#use_master_key)\n    - [:session](#session)\n    - [:where](#where)\n- [Query Constraints](#query-constraints)\n    - [Equals](#equals)\n    - [Less Than](#less-than)\n    - [Less Than or Equal To](#less-than-or-equal-to)\n    - [Greater Than](#greater-than)\n    - [Greater Than or Equal](#greater-than-or-equal)\n    - [Not Equal To](#not-equal-to)\n    - [Nullability Check](#nullability-check)\n    - [Exists](#exists)\n    - [Contained In](#contained-in)\n    - [Not Contained In](#not-contained-in)\n    - [Contains All](#contains-all)\n    - [Regex Matching](#regex-matching)\n    - [Select](#select)\n    - [Reject](#reject)\n    - [Matches Query](#matches-query)\n    - [Excludes Query](#excludes-query)\n    - [Matches Object Id](#matches-object-id)\n  - [Geo Queries](#geo-queries)\n    - [Max Distance Constraint](#max-distance-constraint)\n    - [Bounding Box Constraint](#bounding-box-constraint)\n    - [Polygon Area Constraint](#polygon-area-constraint)\n    - [Full Text Search Constraint](#full-text-search-constraint)\n  - [Relational Queries](#relational-queries)\n  - [Compound Queries](#compound-queries)\n- [Query Scopes](#query-scopes)\n- [Calling Cloud Code Functions](#calling-cloud-code-functions)\n- [Calling Background Jobs](#calling-background-jobs)\n- [Active Model Callbacks](#active-model-callbacks)\n- [Schema Upgrades and Migrations](#schema-upgrades-and-migrations)\n- [Push Notifications](#push-notifications)\n  - [Builder Pattern API](#builder-pattern-api)\n  - [Silent Push](#silent-push-ios-background-notifications)\n  - [Rich Push](#rich-push-ios-notification-extensions)\n  - [Localization](#localization)\n  - [Badge Management](#badge-management)\n  - [Saved Audiences](#saved-audiences)\n  - [Push Status Tracking](#push-status-tracking)\n  - [Installation Channel Management](#installation-channel-management)\n- [Analytics](#analytics)\n- [Cloud Code Webhooks](#cloud-code-webhooks)\n  - [Cloud Code Functions](#cloud-code-functions)\n  - [Cloud Code Triggers](#cloud-code-triggers)\n  - [Mounting Webhooks Application](#mounting-webhooks-application)\n  - [Register Webhooks](#register-webhooks)\n- [Parse REST API Client](#parse-rest-api-client)\n  - [Request Caching](#request-caching)\n- [Atlas Search](#atlas-search)\n  - [Setup](#setup)\n  - [Full-Text Search](#full-text-search)\n  - [Autocomplete](#autocomplete-search-as-you-type)\n  - [Faceted Search](#faceted-search)\n  - [Search Builder](#search-builder-advanced)\n  - [Query Integration](#query-integration)\n  - [Index Management](#index-management)\n  - [Creating Search Indexes](#creating-search-indexes)\n- [Contributing](#contributing)\n- [Testing](#testing)\n  - [Docker Integration Tests](#docker-integration-tests)\n  - [Unit Tests](#unit-tests)\n  - [Contributing Tests](#contributing-tests)\n- [License](#license)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Architecture\nThe architecture of `Parse::Stack` is broken into four main components.\n\n### [Parse::Client](https://neurosynq.github.io/parse-stack-next/Parse/Client.html)\nThis class is the core and low level API for the Parse Server REST interface that is used by the other components. It can manage multiple sessions, which means you can have multiple client instances pointing to different Parse Server applications at the same time. It handles sending raw requests as well as providing Request/Response objects for all API handlers. The connection engine is Faraday, which means it is open to add any additional middleware for features you'd like to implement.\n\n### [Parse::Query](https://neurosynq.github.io/parse-stack-next/Parse/Query.html)\nThis class implements the [Parse REST Querying](http://docs.parseplatform.org/rest/guide/#queries) interface in the [DataMapper finder syntax style](http://datamapper.org/docs/find.html). It compiles a set of query constraints and utilizes `Parse::Client` to send the request and provide the raw results. This class can be used without the need to define models.\n\n### [Parse::Object](https://neurosynq.github.io/parse-stack-next/Parse/Object.html)\nThis component is main class for all object relational mapping subclasses for your application. It provides features in order to map your remote Parse records to a local ruby object. It implements the Active::Model interface to provide a lot of additional features, CRUD operations, querying, including dirty tracking, JSON serialization, save/destroy callbacks and others. While we are overlooking some functionality, for simplicity, you will mainly be working with Parse::Object as your superclass. While not required, it is highly recommended that you define a model (Parse::Object subclass) for all the Parse classes in your application.\n\n### [Parse::Webhooks](https://neurosynq.github.io/parse-stack-next/Parse/Webhooks.html)\nParse provides a feature called [Cloud Code Webhooks](http://blog.parse.com/announcements/introducing-cloud-code-webhooks/). For most applications, save/delete triggers and cloud functions tend to be implemented by Parse's own hosted Javascript solution called Cloud Code. However, Parse provides the ability to have these hooks utilize your hosted solution instead of their own, since their environment is limited in terms of resources and tools.\n\n## Field Naming Conventions\nBy convention in Ruby (see [Style Guide](https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars)), symbols and variables are expressed in lower_snake_case form. Parse, however, prefers column names in **lower-first camel case** (ex. `objectId`, `createdAt` and `updatedAt`). To keep in line with the style guides between the languages, we do the automatic conversion of the field names when compiling the query. As an additional exception to this rule, the field key of `id` will automatically be converted to the `objectId` field when used. If you do not want this to happen, you can turn off or change the value `Parse::Query.field_formatter` as shown below. Though we recommend leaving the default `:columnize` if possible.\n\n```ruby\n# default uses :columnize\nquery = Parse::User.query :field_one =\u003e 1, :FieldTwo =\u003e 2, :Field_Three =\u003e 3\nquery.compile_where # {\"fieldOne\"=\u003e1, \"fieldTwo\"=\u003e2, \"fieldThree\"=\u003e3}\n\n# turn off\nParse::Query.field_formatter = nil\nquery = Parse::User.query :field_one =\u003e 1, :FieldTwo =\u003e 2, :Field_Three =\u003e 3\nquery.compile_where # {\"field_one\"=\u003e1, \"FieldTwo\"=\u003e2, \"Field_Three\"=\u003e3}\n\n# force everything camel case\nParse::Query.field_formatter = :camelize\nquery = Parse::User.query :field_one =\u003e 1, :FieldTwo =\u003e 2, :Field_Three =\u003e 3\nquery.compile_where # {\"FieldOne\"=\u003e1, \"FieldTwo\"=\u003e2, \"FieldThree\"=\u003e3}\n\n```\n\n\n## Connection Setup\nTo connect to a Parse server, you will need a minimum of an `application_id`, an `api_key` and a `server_url`. To connect to the server endpoint, you use the `Parse.setup()` method below.\n\n```ruby\n  Parse.setup app_id: \"YOUR_APP_ID\",\n              api_key: \"YOUR_API_KEY\",\n              master_key: \"YOUR_MASTER_KEY\", # optional\n              server_url: 'https://localhost:2337/parse' #default\n```\n\nIf you wish to add additional connection middleware to the stack, you may do so by utilizing passing a block to the setup method.\n\n```ruby\n  Parse.setup( ... ) do |conn|\n    # conn is a Faraday connection object\n    conn.use Your::Middleware\n    conn.response :logger\n    # ....\n  end\n```\n\nCalling `setup` will create the default `Parse::Client` session object that will be used for all models and requests in the stack. You may retrive this client by calling the class `client` method. It is possible to create different client connections and have different models point to different Parse applications and endpoints at the same time.\n\n```ruby\n  default_client = Parse.client\n                   # alias Parse::Client.client(:default)\n```\n\n### Connection Options\nThere are additional connection options that you may pass the setup method when creating a `Parse::Client`.\n\n#### `:server_url`\nThe server url of your Parse Server if you are not using the hosted Parse service. By default it will use `PARSE_SERVER_URL` environment variable available or fall back to `https://localhost:2337/parse` if not specified.\n\n#### `:app_id`\nThe Parse application id. By default it will use `PARSE_SERVER_APPLICATION_ID` environment variable if not specified.\n\n#### `:api_key`\nThe Parse REST API Key. By default it will use `PARSE_SERVER_REST_API_KEY` environment variable if not specified.\n\n#### `:master_key` _(optional)_\nThe Parse application master key. If this key is set, it will be sent on every request sent by the client and your models. By default it will use `PARSE_SERVER_MASTER_KEY` environment variable if not specified.\n\n#### `:logging`\nControls request/response logging. Accepts:\n- `true` - Enable logging at `:info` level (logs method, URL, status, timing)\n- `:debug` - Enable verbose logging with headers and body content\n- `:warn` - Only log errors and warnings\n- `false` or `nil` - Disable logging (default)\n\n```ruby\nParse.setup(logging: true, ...)      # info level\nParse.setup(logging: :debug, ...)    # verbose with body content\n```\n\n#### `:logger`\nA custom Logger instance for request/response logging. Defaults to `Logger.new(STDOUT)`.\n\n```ruby\nParse.setup(logging: true, logger: Rails.logger, ...)\n```\n\nYou can also configure logging programmatically after setup:\n\n```ruby\nParse.logging_enabled = true     # Enable/disable\nParse.log_level = :debug         # :info, :debug, or :warn\nParse.logger = Rails.logger      # Custom logger\nParse.log_max_body_length = 1000 # Truncate body after N chars (default: 500)\n```\n\n#### `:adapter`\nThe HTTP connection adapter. By default, Parse Stack uses `:net_http_persistent` for connection pooling, which significantly improves performance by reusing HTTP connections. Set `connection_pooling: false` to use the standard `Net::HTTP` adapter instead.\n\n```ruby\n# Use a custom adapter (overrides connection_pooling setting)\nParse.setup(adapter: :excon, ...)\n```\n\n#### `:connection_pooling`\nControls HTTP connection pooling for improved performance. Enabled by default using the `net_http_persistent` adapter.\n\n**Benefits:**\n- 30-70% latency reduction by eliminating TCP/SSL handshakes per request\n- Reduced server load through connection reuse\n- Better performance for high-throughput applications\n\n```ruby\n# Default: connection pooling enabled\nParse.setup(server_url: \"...\", app_id: \"...\", api_key: \"...\")\n\n# Disable connection pooling\nParse.setup(connection_pooling: false, ...)\n\n# Custom pool configuration\nParse.setup(\n  connection_pooling: {\n    pool_size: 5,      # Connections per thread (default: 1)\n    idle_timeout: 60,  # Seconds before closing idle connections (default: 5)\n    keep_alive: 60     # HTTP Keep-Alive timeout in seconds\n  },\n  ...\n)\n```\n\n**Configuration Options:**\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `pool_size` | 1 | Number of connections per thread. Increase if making parallel requests within a thread. |\n| `idle_timeout` | 5 | Seconds before closing idle connections. Set higher (30-60s) for frequently-used servers. |\n| `keep_alive` | - | HTTP Keep-Alive timeout. Should be less than your Parse Server's `keepAliveTimeout`. |\n\n**Recommended settings for Heroku:**\n```ruby\nParse.setup(\n  connection_pooling: { pool_size: 2, idle_timeout: 60, keep_alive: 60 },\n  ...\n)\n```\n\nIf `faraday-net_http_persistent` is not available, Parse Stack automatically falls back to the standard adapter with a warning.\n\n#### `:cache`\nA caching adapter of type `Moneta::Transformer`. Caching queries and object fetches can help improve the performance of your application, even if it is for a few seconds. Only successful `GET` object fetches and queries (non-empty) will be cached. You may set the default expiration time with the `expires` option. See related: [Moneta](https://github.com/minad/moneta). At any point in time you may clear the cache by calling the `clear_cache!` method on the client connection.\n\n```ruby\n  store = Moneta.new :Redis, url: 'redis://localhost:6379'\n   # use a Redis cache store with an automatic expire of 10 seconds.\n  Parse.setup(cache: store, expires: 10, ...)\n```\n\nAs a shortcut, if you are planning on using REDIS and have configured the use of `redis` in your `Gemfile`, you can just pass the REDIS connection string directly to the cache option.\n\n```ruby\n  Parse.setup(cache: 'redis://localhost:6379', ...)\n```\n\nRedis is the recommended cache backend for multi-process / multi-dyno deployments: an in-memory `Moneta.new(:Memory)` store is local to a single Ruby process, so two Puma workers (or two web dynos) each hold their own cache and a write through one will not invalidate the other. A shared Redis backend gives every process the same view, and the existing PUT/POST/DELETE invalidation in `Parse::Middleware::Caching` runs against the shared store. Cache reads degrade gracefully on `Redis::CannotConnectError` / `Redis::TimeoutError` — the middleware disables caching for the failing request and lets the underlying GET pass through to Parse Server.\n\nThe cache surface is opt-in at two layers. Object fetches (`Model.find(id)`, `obj.reload!` in non-write-only mode) cache by default once a store is configured. Query results do **not** cache by default — pass `cache: true` per call (e.g. `Song.all(limit: 500, cache: true)`) or set `Parse.default_query_cache = true` for opt-out behavior. Both layers honor `cache: false` / `Cache-Control: no-cache` to skip the cache for an individual request.\n\n#### `:expires`\nSets the default cache expiration time (in seconds) for successful non-empty `GET` requests when using the caching middleware. The default value is 3 seconds. If `:expires` is set to 0, caching will be disabled. You can always clear the current state of the cache using the `clear_cache!` method on your `Parse::Client` instance.\n\n#### `:faraday`\nYou may pass a hash of options that will be passed to the `Faraday` constructor.\n\n### Global Settings\n\n#### `Parse.warn_on_query_issues`\nControls whether query validation warnings are displayed. When enabled (default: `true`), Parse-Stack will print helpful warnings about common query mistakes:\n\n- Warning when including non-pointer fields (e.g., including a string field that doesn't need `include`)\n- Warning when including a pointer AND specifying subfield keys (redundant - the full object makes the subfield keys unnecessary)\n\n```ruby\n# Disable query validation warnings globally\nParse.warn_on_query_issues = false\n\n# Example warnings that may be shown when enabled:\n# [Parse::Query] Warning: 'filename' is a string field, not a pointer/relation - it does not need to be included\n# [Parse::Query] Warning: including 'project' returns the full object - keys [\"project.name\"] are unnecessary\n```\n\n#### N+1 Query Detection\n\nParse Stack can detect N+1 query patterns - a common performance issue where accessing associations in a loop triggers separate queries for each item.\n\n**Enable Detection:**\n```ruby\n# Warning mode (logs warnings)\nParse.n_plus_one_mode = :warn\n\n# Or use the legacy API\nParse.warn_on_n_plus_one = true\n```\n\n**Example:**\n```ruby\nParse.n_plus_one_mode = :warn\n\nsongs = Song.all(limit: 100)\nsongs.each do |song|\n  song.artist.name  # Warning: N+1 query detected!\nend\n\n# Output:\n# [Parse::N+1] Warning: N+1 query detected on Song.artist (3 separate fetches for Artist)\n#   Location: app/controllers/songs_controller.rb:42 in `index`\n#   Suggestion: Use `.includes(:artist)` to eager-load this association\n```\n\n**Fix with Includes:**\n```ruby\n# Eager-load associations to avoid N+1\nsongs = Song.all(limit: 100, includes: [:artist])\nsongs.each do |song|\n  song.artist.name  # No warning - already loaded\nend\n```\n\n**Available Modes:**\n\n| Mode | Behavior |\n|------|----------|\n| `:ignore` | Detection disabled (default) |\n| `:warn` | Log warnings when N+1 detected |\n| `:raise` | Raise `Parse::NPlusOneQueryError` - ideal for CI/tests |\n\n**Strict Mode for CI/Tests:**\n```ruby\n# In test_helper.rb or rails_helper.rb\nParse.n_plus_one_mode = :raise\n\n# Now N+1 queries will fail your tests!\n```\n\n**Custom Callbacks:**\n```ruby\n# Track N+1 patterns in your metrics\nParse.on_n_plus_one do |source_class, association, target_class, count, location|\n  StatsD.increment(\"n_plus_one.#{source_class}.#{association}\")\nend\n```\n\n**Configuration:**\n```ruby\nParse.configure_n_plus_one do |config|\n  config.detection_window = 5.0   # Seconds to track related fetches (default: 2.0)\n  config.fetch_threshold = 5      # Fetches to trigger warning (default: 3)\nend\n```\n\n## Working With Existing Schemas\nIf you already have a Parse application with defined schemas and collections, you can have Parse-Stack automatically generate the ruby Parse::Object subclasses instead of writing them on your own. Through this process, the framework will download all the defined schemas of all your collections, and infer the properties and associations defined. While this method is useful for getting started with the framework with an existing app, we highly recommend defining your own models. This would allow you to customize and utilize all the features available in Parse Stack.\n\n```ruby\n  # after you have called Parse.setup\n  # Assume you have a Song and Artist collections defined remotely\n  Parse.auto_generate_models!\n\n  # You can now use them as if you defined them\n  artist = Artist.first\n  Song.all(artist: artist)\n```\n\nYou can always combine both approaches by defining special attributes before you auto generate your models:\n\n```ruby\n  # create a Song class, but only create the artist array pointer association.\n  class Song \u003c Parse::Object\n    has_many :artists, through: :array\n  end\n\n  # Now let Parse Stack generate the rest of the properties and associations\n  # based on your remote schema. Assume there is a `title` field for the `Song`\n  # collection.\n  Parse.auto_generate_models!\n\n  song = Song.first\n  song.artists # created with our definition above\n  song.title # auto-generated property\n\n```\n\n## [Parse Config](https://neurosynq.github.io/parse-stack-next/Parse/API/Config.html)\nGetting your configuration variables once you have a default client setup can be done with `Parse.config`. The first time this method is called, Parse-Stack will get the configuration from Parse Server, and cache it. To force a reload of the config, use `config!`. You\n\n```ruby\n  Parse.setup( ... )\n\n  val = Parse.config[\"myKey\"]\n  val = Parse.config[\"myKey\"] # cached\n\n  # update a config with Parse\n  Parse.set_config \"myKey\", \"someValue\"\n\n  # batch update several\n  Parse.update_config({fieldEnabled: true, searchMiles: 50})\n\n  # Force fetch of config!\n  val = Parse.config![\"myKey\"]\n\n```\n\n## Core Classes\nWhile some native data types are similar to the ones supported by Ruby natively, other ones are more complex and require their dedicated classes.\n\n### [Parse::Pointer](https://neurosynq.github.io/parse-stack-next/Parse/Pointer.html)\nAn important concept is the `Parse::Pointer` class. This is the superclass of `Parse::Object` and represents the pointer type in Parse. A `Parse::Pointer` only contains data about the specific Parse class and the `id` for the object. Therefore, creating an instance of any Parse::Object subclass with only the `:id` field set will be considered in \"pointer\" state even though its specific class is not `Parse::Pointer` type. The only case that you may have a Parse::Pointer is in the case where an object was received for one of your classes and the framework has no registered class handler for it. Using the example above, assume you have the tables `Post`, `Comment` and `Author` defined in your remote Parse application, but have only defined `Post` and `Commentary` locally.\n\n```ruby\n # assume the following\nclass Post \u003c Parse::Object\nend\n\nclass Commentary \u003c Parse::Object\n  parse_class \"Comment\"\n\tbelongs_to :post\n\t#'Author' class not defined locally\n\tbelongs_to :author\nend\n\ncomment = Commentary.first\ncomment.post? # true because it is non-nil\ncomment.artist? # true because it is non-nil\n\n# both are true because they are in a Pointer state\ncomment.post.pointer? # true\ncomment.author.pointer? # true\n\n # we have defined a Post class handler\ncomment.post # \u003cPost @parse_class=\"Post\", @id=\"xdqcCqfngz\"\u003e\n\n # we have not defined an Author class handler\ncomment.author # \u003cParse::Pointer @parse_class=\"Author\", @id=\"hZLbW6ofKC\"\u003e\n\n\ncomment.post.fetch # fetch the relation\ncomment.post.pointer? # false, it is now a full object.\n```\n\n#### Auto-fetch on Property Access\n\nWhen you have a `Parse::Pointer` for a registered model class, you can access properties directly and the object will be automatically fetched:\n\n```ruby\n# Create a pointer (not yet fetched)\npointer = Post.pointer(\"abc123\")\npointer.pointer? # true - no data yet\n\n# Accessing a property auto-fetches and returns the value\npointer.title # Fetches the object, returns \"My Post Title\"\n\n# Subsequent accesses use the cached fetched object (no additional network request)\npointer.content # Returns content without another fetch\npointer.author  # Returns author without another fetch\n\n# The pointer remembers the fetched object\npointer.pointer? # false - now has data\n```\n\nThis auto-fetch behavior respects the `Parse.autofetch_raise_on_missing_keys` setting:\n\n```ruby\nParse.autofetch_raise_on_missing_keys = true\npointer = Post.pointer(\"abc123\")\npointer.title # Raises Parse::AutofetchTriggeredError instead of fetching\n```\n\nThe effect is that for any unknown classes that the framework encounters, it will generate Parse::Pointer instances until you define those classes with valid properties and associations. While this might be ok for some classes you do not use, we still recommend defining all your Parse classes locally in the framework.\n\n### [Parse::File](https://neurosynq.github.io/parse-stack-next/Parse/File.html)\nThis class represents a Parse file pointer. `Parse::File` has helper methods to upload Parse files directly to Parse and manage file associations with your classes. Using our Song class example:\n\n```ruby\n  song = Song.first\n  file = song.audio_file # Parse::File\n  file.url # URL in the Parse file storage system\n\n  file = File.open(\"file_path.jpg\")\n  contents = file.read\n  file = Parse::File.new(\"myimage.jpg\", contents , \"image/jpeg\")\n  file.saved? # false. Hasn't been uploaded to Parse\n  file.save # uploads to Parse.\n\n  file.url # https://files.parsetfss.com/....\n\n  # or create and upload a remote file (auto-detected mime type)\n  file = Parse::File.create(some_url)\n  song.file = file\n  song.save\n\n```\n\nThe default MIME type for all files is `image/jpeg`. This can be default can be changed by setting a value to `Parse::File.default_mime_type`. Other ways of creating a `Parse::File` are provided below. The created Parse::File is not uploaded until you call `save`.\n\n```ruby\n  # urls\n  file = Parse::File.new \"http://example.com/image.jpg\"\n  file.name # image.jpg\n\n  # file objects\n  file = Parse::File.new File.open(\"myimage.jpg\")\n\n  # non-image files work too\n  file = Parse::File.new \"http://www.example.com/something.pdf\"\n  file.mime_type = \"application/octet-stream\" #set the mime-type!\n\n  # or another Parse::File object\n  file = Parse::File.new parse_file\n```\n\nIf you are using displaying these files on a secure site and want to make sure that urls returned by a call to `url` are `https`, you can set `Parse::File.force_ssl` to true.\n\n```ruby\n# Assume file is a Parse::File\n\nfile.url # =\u003e http://www.example.com/file.png\n\nParse::File.force_ssl = true # make all urls be https\n\nfile.url # =\u003e https://www.example.com/file.png\n\n```\n\n### [Parse::Date](https://neurosynq.github.io/parse-stack-next/Parse/Date.html)\nThis class manages dates in the special JSON format it requires for properties of type `:date`. `Parse::Date` subclasses `DateTime`, which allows you to use any features or methods available to `DateTime` with `Parse::Date`. While the conversion between `Time` and `DateTime` objects to a `Parse::Date` object is done implicitly for you, you can use the added special methods, `DateTime#parse_date` and `Time#parse_date`, for special occasions.\n\n```ruby\n  song = Song.first\n  song.released = DateTime.now # converted to Parse::Date\n  song.save # ok\n```\n\n### [Parse::GeoPoint](https://neurosynq.github.io/parse-stack-next/Parse/GeoPoint.html)\nThis class manages the GeoPoint data type that Parse provides to support geo-queries. To define a GeoPoint property, use the `:geopoint` data type. Please note that latitudes should not be between -90.0 and 90.0, and longitudes should be between -180.0 and 180.0.\n\n```ruby\n  class PlaceObject \u003c Parse::Object\n    property :location, :geopoint\n  end\n\n  san_diego = Parse::GeoPoint.new(32.8233, -117.6542)\n  los_angeles = Parse::GeoPoint.new [34.0192341, -118.970792]\n  san_diego == los_angeles # false\n\n  place = PlaceObject.new\n  place.location = san_diego\n  place.save\n```\n\n#### Calculating Distances between locations\nWe include helper methods to calculate distances between GeoPoints: `distance_in_miles` and `distance_in_km`.\n\n```ruby\n\tsan_diego = Parse::GeoPoint.new(32.8233, -117.6542)\n\tlos_angeles = Parse::GeoPoint.new [34.0192341, -118.970792]\n\n\t# Haversine calculations\n\tsan_diego.distance_in_miles(los_angeles)\n\t# ~112.33 miles\n\n\tsan_diego.distance_in_km(los_angeles)\n\t# ~180.793 km\n```\n\n### [Parse::Bytes](https://neurosynq.github.io/parse-stack-next/Parse/Bytes.html)\nThe `Bytes` data type represents the storage format for binary content in a Parse column. The content is needs to be encoded into a base64 string.\n\n```ruby\n  bytes = Parse::Bytes.new( base64_string )\n  # or use helper method\n  bytes = Parse::Bytes.new\n  bytes.encode( content ) # same as Base64.encode64\n\n  decoded = bytes.decoded # same as Base64.decode64\n```\n\n### [Parse::TimeZone](https://neurosynq.github.io/parse-stack-next/Parse/TimeZone.html)\nWhile Parse does not provide a native time zone data type, Parse-Stack provides a class to make it easier to manage time zone attributes, usually stored IANA string identifiers, with your ruby code. This is done by utilizing the features provided by [`ActiveSupport::TimeZone`](http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html). In addition to setting a column as a time zone field, we also add special validations to verify it is of the right IANA identifier.\n\n```ruby\nclass Event \u003c Parse::Object\n  # an event occurs in a time zone.\n  property :time_zone, :timezone, default: 'America/Los_Angeles'\nend\n\nevent = Event.new\nevent.time_zone.name # =\u003e 'America/Los_Angeles'\nevent.time_zone.valid? # =\u003e true\n\nevent.time_zone.zone # =\u003e ActiveSupport::TimeZone\nevent.time_zone.formatted_offset # =\u003e \"-08:00\"\n\nevent.time_zone = 'Europe/Paris'\nevent.time_zone.formatted_offset # =\u003e +01:00\"\n\nevent.time_zone = 'Galaxy/Andromeda'\nevent.time_zone.valid? # =\u003e false\n```\n\n### [Parse::ACL](https://neurosynq.github.io/parse-stack-next/Parse/ACL.html)\nThe `ACL` class represents the access control lists for each record. An ACL is represented by a JSON object with the keys being `Parse::User` object ids or the special key of `*`, which indicates the public access permissions.\nThe value of each key in the hash is a [`Parse::ACL::Permission`](https://neurosynq.github.io/parse-stack-next/Parse/ACL/Permission.html) object which defines the boolean permission state for `read` and `write`.\n\nThe example below illustrates a Parse ACL JSON object where there is a public read permission, but public write is prevented. In addition, the user with id `3KmCvT7Zsb` and the `Admins` role, are allowed to both read and write on this record.\n\n```json\n{\n  \"*\": { \"read\": true },\n  \"3KmCvT7Zsb\": {  \"read\": true, \"write\": true },\n  \"role:Admins\": {  \"read\": true, \"write\": true }\n}\n```\n\nAll `Parse::Object` subclasses have an `acl` property by default. With this property, you can apply and delete permissions for this particular Parse object record.\n\n```ruby\n  user = Parse::User.first\n  artist = Artist.first\n\n  artist.acl # \"*\": { \"read\": true, \"write\": true }\n\n  # apply public read, but no public write\n  artist.acl.everyone true, false\n\n  # allow user to have read and write access\n  artist.acl.apply user.id, true, true\n\n  # remove all permissions for this user id\n  artist.acl.delete user.id\n\n  # allow the 'Admins' role read and write\n  artist.acl.apply_role \"Admins\", true, true\n\n  # remove write from all attached privileges\n  artist.acl.no_write!\n\n  # remove all attached privileges\n  artist.acl.master_key_only!\n\n  artist.save\n```\nYou may also set default ACLs for newly created instances of your subclasses using `set_default_acl`:\n\n```ruby\nclass AdminData \u003c Parse::Object\n\n  # Disable public read and write\n  set_default_acl :public, read: false, write: false\n\n  # but allow members of the Admin role to read and write\n  set_default_acl 'Admin', role: true, read: true, write: true\n\nend\n\ndata = AdminData.new\ndata.acl # =\u003e ACL({\"role:Admin\"=\u003e{\"read\"=\u003etrue, \"write\"=\u003etrue}})\n```\n\n#### Declarative ACL Policy (`acl_policy`)\n\nFor owner-aware defaults — where the record's ACL should grant read/write to a specific user pointer at save time — declare an `acl_policy` instead of (or in addition to) `set_default_acl`. The policy is resolved by a `before_save` callback that walks `as: user` → owner-field pointer → policy fallback, and stamps the resolved ACL onto the record. Any explicit `obj.acl = …` change by the caller is always respected.\n\nThere are four policies:\n\n| Policy | When an owner is resolvable | When no owner is resolvable |\n|---|---|---|\n| `:public` | public read + write | public read + write |\n| `:public_read` | public read, master-key write | public read, master-key write |\n| `:private` | master-key only | master-key only |\n| `:owner_else_public` | owner read + write only | public read + write |\n| `:owner_else_private` | owner read + write only | master-key only |\n| `:owner_but_public_read` | owner read + write *and* public read | public read, master-key write |\n\n`:public_read` (v5.0+) stamps `{\"*\": {\"read\": true}}` — anyone can read the row, but no client can mutate it through ACL (only the master key can write). Useful for catalog / lookup / reference data.\n\n`:owner_but_public_read` (v5.0+) is the \"single-author public post\" case: the resolved owner gets full R/W and the rest of the world gets read-only access in the same ACL — `{\"*\": {\"read\": true}, \"\u003cownerId\u003e\": {\"read\": true, \"write\": true}}`. When no owner resolves at save (no `as:` and no resolvable `owner:` field), it degrades to `:public_read` semantics rather than the all-or-nothing fallback used by the `:owner_else_*` family.\n\n```ruby\nclass Post \u003c Parse::Object\n  property :title, :string\n  belongs_to :author, as: :user\n\n  # Posts grant read/write to their author; server-side creates with no\n  # author resolvable fall back to master-key-only.\n  acl_policy :owner_else_private, owner: :author\nend\n\n# Owner resolved from the belongs_to pointer:\nPost.create!(title: \"draft\", author: current_user)\n# =\u003e ACL { \"\u003ccurrent_user.id\u003e\": { read: true, write: true } }\n\n# Or pass the owner explicitly with `as:`:\nPost.create!(title: \"draft\", as: current_user)\n\n# Server-side, no owner: master-key-only fallback.\nPost.create!(title: \"system note\")\n# =\u003e ACL { } (only the master key can read or write)\n```\n\nResolution order at save (only when the caller has not set the ACL):\n\n1. `obj.acl = …` or in-place mutation of `obj.acl` by the caller — always wins\n2. `as: user` passed at construction\n3. Owner pointer from the property named by `owner:`\n4. The \"else\" half of the policy — public R/W or master-key-only\n\nThe `:as` key may be a `Parse::User`, a `Parse::Pointer` to a user, or a raw `objectId` string. It is popped from the opts hash before attributes are applied, so it never reaches `apply_attributes!` and never appears as a property.\n\nSubclasses inherit the parent's policy and owner field. Classes that already call `set_default_acl` are detected automatically and opt out of the policy resolver, so legacy callers retain pre-4.1 behavior without changes.\n\nOwner resolution is strictly type-gated. The `as:` kwarg and any `owner:` pointer accept a `Parse::User` instance, a `Parse::Pointer` whose `parse_class == \"_User\"`, or a raw `objectId` `String`. Pointers to non-User classes and arbitrary objects responding to `#id` are silently rejected and the policy falls through to its else-half, so a stray pointer to a non-user record cannot accidentally grant ACL access to a user record that happens to share the same `objectId`.\n\nYou may not combine `acl_policy` with `set_default_acl` on the same class — the two APIs have ambiguous interactions at save time. Calling the second one raises `ArgumentError`. Pick one configuration approach per class.\n\n#### Self-Owned Users (`owner: :self`)\n\n`Parse::User` records are special: the record IS the owner. The SDK provides `owner: :self` as a Parse::User-only shorthand for \"this user owns themselves.\" The save-time resolver pre-generates a Parse-compatible `objectId` client-side (via the same helper that backs `parse_reference precompute: true`) when none is set, then stamps the ACL as `{ \u003cgenerated-id\u003e: { read: true, write: true } }`. The signup body then carries both the `objectId` and the `ACL` in a single POST.\n\n```ruby\nclass Parse::User\n  # New users: only the user can read or write their own profile.\n  acl_policy :owner_else_private, owner: :self\nend\n\nnew_user = Parse::User.new(username: \"alice\", password: \"secret\")\nnew_user.save\n# Single roundtrip. After save, new_user.id is a 10-char Parse id and\n# the persisted record's ACL is { \"\u003cthat id\u003e\": { read: true, write: true } }.\n# Other clients (including unauthenticated) cannot see this user.\n```\n\n`owner: :self` is rejected at class-definition time on any non-User class — there's no sensible interpretation when the record's `objectId` is not a user id.\n\nThe signup request body normally has `objectId` and `ACL` stripped (a security mitigation against client-planted permissive ACLs). When `owner: :self` is declared, those two fields are allowed through only when they match the narrow self-only ownership pattern: `objectId` is the 10-char Parse format, and `ACL` has exactly one entry granting `read+write` to that same `objectId`. Any deviation — multiple keys, a `*` (public) entry, a `role:` entry, half-permissions, mismatched id — still triggers the full strip and Parse Server applies its own default.\n\n`acl_policy ..., owner: :self` is orthogonal to `parse_reference precompute: true`. Both reuse `Parse::Core::ParseReference.generate_object_id` for client-side id generation; neither installs the other's side effects. Declare both if you want both the ACL self-ownership AND the canonical reference column.\n\n#### Breaking Change in v4.1: Secure-by-Default ACL Policy\n\nStarting with v4.1, the gem-wide default ACL policy for `Parse::Object` subclasses is `:owner_else_private`. Records created with no resolvable owner (no `as:` kwarg, no `owner:` field) and no class-level `acl_policy` or `set_default_acl` declaration are saved with an empty ACL — readable and writable only with the master key.\n\n**This is a behavioral change.** Pre-4.1, the same class would have produced records with public read + public write. Applications that depend on the historical default for client-side reads of unowned records will see those reads return empty result sets until they update their model declarations.\n\nMigration recipes:\n\n```ruby\n# A class whose records should remain publicly readable + writable:\nclass PublicNotice \u003c Parse::Object\n  property :body, :string\n  acl_policy :public\nend\n\n# A class whose records belong to a user:\nclass JournalEntry \u003c Parse::Object\n  property :text, :string\n  belongs_to :author, as: :user\n  acl_policy :owner_else_private, owner: :author\nend\n\n# A class whose records are written client-side but readable by anyone:\nclass Post \u003c Parse::Object\n  property :title, :string\n  belongs_to :author, as: :user\n  acl_policy :owner_else_public, owner: :author\nend\n```\n\nWhen a class explicitly opts into a permissive policy (`:public` or `:owner_else_public`), a one-time per-class warning is emitted on first instance creation to make the choice visible in logs:\n\n```\n[Parse::Stack security] PublicNotice uses permissive default ACL policy\n`public`. New records can be modified by anyone unless an owner is\nresolved at save. Call `acl_policy :owner_else_private` or `:private`\nin the class to silence this warning.\n```\n\nThe warning fires once per class per process and is automatically suppressed for the SDK's own built-in classes (`Parse::User`, `Parse::Installation`, `Parse::Session`, `Parse::Role`, `Parse::Product`, `Parse::PushStatus`, `Parse::Audience`, `Parse::JobStatus`, `Parse::JobSchedule`). To silence it globally — for example in test suites or in applications that have reviewed and accepted permissive defaults — set either:\n\n```ruby\nParse::Object.suppress_permissive_acl_warning = true\n# or, via the environment:\nENV[\"PARSE_SUPPRESS_PERMISSIVE_ACL_WARNING\"] = \"1\"\n```\n\nFor more information about Parse record ACLs, see the documentation at  [Security](http://docs.parseplatform.org/rest/guide/#security)\n\n### Parse::CLP (Class-Level Permissions)\n\nClass-Level Permissions (CLPs) control access at the schema level, determining who can perform operations on a class and which fields are visible to different users/roles. Unlike ACLs (which are per-object), CLPs apply to the entire class.\n\n#### Defining CLPs in Models\n\nUse the `set_clp` and `protect_fields` DSL methods to define CLPs:\n\n```ruby\nclass Song \u003c Parse::Object\n  property :title, :string\n  property :artist, :string\n  property :internal_notes, :string\n  property :royalty_data, :string\n  belongs_to :owner\n\n  # Set operation-level permissions\n  set_clp :find, public: true\n  set_clp :get, public: true\n  set_clp :create, public: false, roles: [\"Admin\", \"Editor\"]\n  set_clp :update, public: false, roles: [\"Admin\", \"Editor\"]\n  set_clp :delete, public: false, roles: [\"Admin\"]\n\n  # Protect fields from certain users (use camelCase for JSON field names)\n  protect_fields \"*\", [:internalNotes, :royaltyData]  # Hidden from everyone\n  protect_fields \"role:Admin\", []                      # Admins see everything\n  protect_fields \"userField:owner\", []                 # Owners see their own data\nend\n```\n\n**Supported Operations:** `:find`, `:get`, `:count`, `:create`, `:update`, `:delete`, `:addField`\n\n**Supported Patterns:**\n- `\"*\"` - Public (everyone)\n- `\"role:RoleName\"` - Users with specific role\n- `\"userField:fieldName\"` - Users referenced in a pointer field\n- `\"authenticated\"` - Any authenticated user\n- User objectId string - Specific user\n\n#### Filtering Data for Webhook Responses\n\nWhen returning data from webhooks, use `filter_for_user` to apply CLP field protection:\n\n```ruby\n# In a webhook handler\ndef after_find(request)\n  user = request.user\n  roles = Song.roles_for_user(user)\n\n  # Filter each object for the requesting user\n  filtered_results = request.objects.map do |song|\n    song.filter_for_user(user, roles: roles)\n  end\n\n  # Or use the class method for arrays\n  filtered_results = Song.filter_results_for_user(request.objects, user, roles: roles)\n\n  { objects: filtered_results }\nend\n```\n\n#### Protected Fields Intersection Logic\n\nWhen a user matches multiple patterns, the protected fields are the **intersection** of all matching patterns. A field is only hidden if it's protected by ALL patterns that apply to the user:\n\n```ruby\nprotect_fields \"*\", [:owner, :secret, :internal]  # Hide from everyone\nprotect_fields \"role:Admin\", [:owner]             # Admins: only owner hidden\nprotect_fields \"userField:owner\", []              # Owners see everything\n\n# User with Admin role matches \"*\" and \"role:Admin\":\n# - \"*\" protects: [owner, secret, internal]\n# - \"role:Admin\" protects: [owner]\n# - Intersection: [owner] - only this field is hidden\n# - \"secret\" and \"internal\" become visible (cleared by role pattern)\n\n# An empty array [] means \"no fields protected\" (user sees everything)\n# If ANY matching pattern has [], the intersection is empty (nothing hidden)\n```\n\n#### Push CLPs to Parse Server\n\nCLPs are automatically included when upgrading schemas:\n\n```ruby\n# Include CLPs in schema upgrade (default)\nSong.auto_upgrade!\n\n# Skip CLPs during schema upgrade\nSong.auto_upgrade!(include_clp: false)\n\n# Update only CLPs (no schema changes)\nSong.update_clp!\n```\n\n#### Fetch and Inspect CLPs\n\n```ruby\n# Fetch current CLPs from server\nclp = Song.fetch_clp\n\n# Check operation permissions\nclp.find_allowed?(\"*\")           # =\u003e true (public find allowed)\nclp.create_allowed?(\"*\")         # =\u003e false (public create denied)\nclp.role_allowed?(:create, \"Admin\")  # =\u003e true\nclp.requires_authentication?(:update)  # =\u003e false\n\n# Get protected fields for a pattern\nclp.protected_fields_for(\"*\")          # =\u003e [\"internalNotes\", \"royaltyData\"]\nclp.protected_fields_for(\"role:Admin\") # =\u003e []\n\n# Use fetched CLP for filtering\nfiltered = song.filter_for_user(user, roles: roles, clp: clp)\n```\n\n#### Owner-Based Access with userField\n\nThe `userField:fieldName` pattern allows owners (users referenced in a pointer field) to have different visibility:\n\n```ruby\nclass Document \u003c Parse::Object\n  property :content, :string\n  property :secret, :string\n  belongs_to :owner\n\n  # Hide secret and owner from everyone\n  protect_fields \"*\", [:secret, :owner]\n  # But owners of the document can see everything\n  protect_fields \"userField:owner\", []\nend\n\n# When filtering:\ndoc_data = {\n  \"content\" =\u003e \"Public content\",\n  \"secret\" =\u003e \"Private data\",\n  \"owner\" =\u003e { \"objectId\" =\u003e \"user123\", \"__type\" =\u003e \"Pointer\" }\n}\n\nclp = Document.class_permissions\n\n# Owner sees everything\nclp.filter_fields(doc_data, user: \"user123\")\n# =\u003e { \"content\" =\u003e \"...\", \"secret\" =\u003e \"...\", \"owner\" =\u003e {...} }\n\n# Non-owner has protected fields hidden\nclp.filter_fields(doc_data, user: \"other_user\")\n# =\u003e { \"content\" =\u003e \"...\" }\n```\n\nThis also works with arrays of pointers (e.g., `owners: [user1, user2]`).\n\n### [Parse::Session](https://neurosynq.github.io/parse-stack-next/Parse/Session.html)\nThis class represents the data and columns contained in the standard Parse `_Session` collection. You may add additional properties and methods to this class. See [Session API Reference](https://neurosynq.github.io/parse-stack-next/Parse/Session.html). You may call `Parse.use_shortnames!` to use `Session` in addition to `Parse::Session`.\n\nYou can get a specific `Parse::Session` given a session_token by using the `session` method. You can also find the user tied to a specific Parse session or session token with `Parse::User.session`.\n\n```ruby\nsession = Parse::Session.session(token)\n\nsession.user # the Parse user for this session\n\n# or fetch user with a session token\nuser = Parse::User.session(token)\n\n# save an object with the privileges (ACLs) of this user\nsome_object.save( session: user.session_token )\n\n# delete an object with the privileges of this user\nsome_object.destroy( session: user.session_token )\n\n```\n\n### [Parse::Installation](https://neurosynq.github.io/parse-stack-next/Parse/Installation.html)\nThis class represents the data and columns contained in the standard Parse `_Installation` collection. You may add additional properties and methods to this class. See [Installation API Reference](https://neurosynq.github.io/parse-stack-next/Parse/Installation.html). You may call `Parse.use_shortnames!` to use `Installation` in addition to `Parse::Installation`.\n\n### [Parse::Product](https://neurosynq.github.io/parse-stack-next/Parse/Product.html)\nThis class represents the data and columns contained in the standard Parse `_Product` collection. You may add additional properties and methods to this class. See [Product API Reference](https://neurosynq.github.io/parse-stack-next/Parse/Product.html). You may call `Parse.use_shortnames!` to use `Product` in addition to `Parse::Product`.\n\nThe `_Product` collection backs the original Parse iOS SDK's `PFProduct` downloadable-content in-app-purchase flow. That feature was tied to hosted Parse and is not actively used by modern Parse Server deployments — most apps now verify in-app purchase receipts directly against the Apple App Store or Google Play. The class is retained for backwards compatibility with legacy applications that still read or write product metadata. It is also marked `agent_hidden` by default so it does not surface through MCP / agent tooling; applications that genuinely need agent access can call `Parse::Product.agent_unhidden` at boot.\n\n### [Parse::Role](https://neurosynq.github.io/parse-stack-next/Parse/Role.html)\nThis class represents the data and columns contained in the standard Parse `_Role` collection. You may add additional properties and methods to this class. See [Roles API Reference](https://neurosynq.github.io/parse-stack-next/Parse/Role.html). You may call `Parse.use_shortnames!` to use `Role` in addition to `Parse::Role`.\n\n#### Default ACL (master-only)\nParse Server requires every `_Role` row to ship with an ACL — the requirement is hard-coded in `SchemaController.requiredColumns` and cannot be disabled by config. `Parse::Role` declares `acl_policy :private`, so every role saved without an explicit ACL is stamped with `{}` (master-key only). This is intentional: anonymous and authenticated-but-non-master clients cannot enumerate role names, read subscription, or walk the role hierarchy. Parse Server's internal role-subscription expansion (used during ACL evaluation) runs with master context, so the master-only default does not break permission checks on other classes.\n\nTo opt into broader access, pass an explicit ACL:\n\n```ruby\nacl = Parse::ACL.new\nacl.everyone(true, false) # public read, no public write\nadmin = Parse::Role.find_or_create(\"Admin\", acl: acl)\n\n# or on an instance:\nrole = Parse::Role.new(name: \"Editor\")\nrole.acl = acl\nrole.save\n```\n\nThe explicit ACL bypasses the policy resolver — caller-supplied ACLs are never overwritten.\n\n#### Role Management Helpers\n\nParse::Role provides convenient methods for managing users and role hierarchies:\n\n```ruby\n# Find or create roles\nadmin = Parse::Role.find_by_name(\"Admin\")\nmoderator = Parse::Role.find_or_create(\"Moderator\")\n\n# Manage users\nadmin.add_user(user).save\nadmin.add_users(user1, user2, user3).save\nadmin.remove_user(user).save\nadmin.has_user?(user)  # =\u003e true\n\n# Role hierarchy (Admins inherit Moderator permissions)\nadmin.add_child_role(moderator).save\nadmin.has_child_role?(moderator)  # =\u003e true\nadmin.all_child_roles              # =\u003e All child roles recursively\nadmin.all_users                    # =\u003e Users from this role AND child roles\n\n# Counts\nadmin.users_count        # Direct users\nadmin.child_roles_count  # Direct child roles\nadmin.total_users_count  # All users including child roles\n```\n\n### [Parse::JobStatus](https://neurosynq.github.io/parse-stack-next/Parse/JobStatus.html)\n\nThis class represents the data and columns contained in the standard Parse `_JobStatus` collection. Parse Server writes a row here every time a background job — registered server-side via `Parse.Cloud.job(...)` — runs, recording its outcome and any status/message updates emitted via `request.message(...)`.\n\nThis Ruby SDK cannot *define* a job (cloud-code registrations live in server-side JavaScript), but you can read from `_JobStatus` to display the most recent run of a job, count failed runs, or sweep old history rows. `Parse::JobStatus` is marked `agent_hidden` by default — `_JobStatus` carries operational signal (job names, error traces, scheduler parameters) that an LLM-driven agent should not enumerate unsolicited. Applications that need agent visibility can call `Parse::JobStatus.agent_unhidden` at boot.\n\n```ruby\n# Did the nightly cleanup run today? What's the latest state?\nlatest = Parse::JobStatus.latest_for(\"nightlyCleanup\")\nputs \"#{latest.status} at #{latest.created_at}\"\nputs \"Duration: #{latest.duration}s\" if latest.finished?\n\n# Find failed jobs in the last 24h\nyesterday = Time.now - 86_400\nParse::JobStatus.failed.where(:created_at.gt =\u003e yesterday).all\n\n# Status scopes\nParse::JobStatus.running       # =\u003e Parse::Query\nParse::JobStatus.succeeded     # =\u003e Parse::Query\nParse::JobStatus.failed        # =\u003e Parse::Query\nParse::JobStatus.recent(limit: 50)\n\n# Instance predicates\njs.running?    # status == \"running\"\njs.succeeded?  # status == \"succeeded\"\njs.failed?     # status == \"failed\"\njs.finished?   # finished_at present OR status terminal\njs.duration    # finished_at - created_at, or nil while in-flight\n```\n\n#### Cleanup helper\n\nParse Server does not garbage-collect `_JobStatus` rows on its own; long-running deployments accumulate run history indefinitely. `Parse::JobStatus.cleanup_older_than!` mirrors `Parse::Installation.cleanup_stale_tokens!` for this case:\n\n```ruby\n# Default: only destroy rows in a terminal state (succeeded/failed)\n# and older than 30 days. Orphaned `status == \"running\"` rows (from a\n# crashed worker) are PRESERVED so an in-flight job is never reaped\n# mid-execution.\ndeleted_count = Parse::JobStatus.cleanup_older_than!(days: 30)\n\n# Explicit orphan cleanup: drop the status guard.\nParse::JobStatus.cleanup_older_than!(days: 7, terminal_only: false)\n```\n\nThe helper requires master-key access (Parse Server's default `_JobStatus` CLP). Run from a periodic cron or scheduled job to keep `_JobStatus` from growing unboundedly.\n\n### [Parse::JobSchedule](https://neurosynq.github.io/parse-stack-next/Parse/JobSchedule.html)\n\nThis class represents the data and columns contained in the standard Parse `_JobSchedule` collection. Rows here define recurring runs for background jobs registered via `Parse.Cloud.job(...)`. The collection is populated by the Parse Dashboard's \"Schedule a Job\" UI.\n\n**Note:** Parse Server itself does not auto-trigger jobs from `_JobSchedule` rows. The actual dispatch is performed by external scheduling tooling (e.g. `parse-server-scheduler`, dashboard-driven cron wrappers, or a sidecar process) that reads `_JobSchedule` and fires `POST /parse/jobs/\u003cname\u003e` at the appropriate times. Run-status rows then appear in `Parse::JobStatus`.\n\n`Parse::JobSchedule` is marked `agent_hidden` by default because `params` may carry credentials or destination configuration written by external schedulers.\n\n```ruby\nschedule = Parse::JobSchedule.for_job(\"nightlyCleanup\").first\nschedule.time_of_day   # =\u003e \"03:00:00\"\nschedule.days_of_week  # =\u003e [\"mon\",\"tue\",\"wed\",\"thu\",\"fri\"]\nschedule.parsed_params # =\u003e { \"dryRun\" =\u003e false }  — JSON-decoded\n```\n\n`params` is stored on the wire as a JSON-encoded **string** per Parse Server's canonical schema (Object columns reject `$` and `.` in nested keys, which would otherwise break common payload shapes). Use `#parsed_params` to decode; it returns `nil` for blank or invalid JSON instead of raising. `last_run` is a raw `Number` whose unit is scheduler-defined — most external schedulers write `Date.now()` milliseconds, but the canonical schema does not pin a unit.\n\n### [Parse::User](https://neurosynq.github.io/parse-stack-next/Parse/User.html)\nThis class represents the data and columns contained in the standard Parse `_User` collection. You may add additional properties and methods to this class. See [User API Reference](https://neurosynq.github.io/parse-stack-next/Parse/User.html). You may call `Parse.use_shortnames!` to use `User` in addition to `Parse::User`.\n\n#### Signup\nYou can signup new users in two ways. You can either use a class method `Parse::User.signup` to create a new user with the minimum fields of username, password and email, or create a `Parse::User` object can call the `signup!` method. If signup fails, it will raise the corresponding exception.\n\n```ruby\nuser = Parse::User.signup(username, password, email)\n\n#or\nuser = Parse::User.new username: \"user\", password: \"s3cret\"\nuser.signup!\n```\n\n##### Third-Party Services\nYou can signup users using third-party services like Facebook and Twitter as described in: [Signing Up and Logging In](http://docs.parseplatform.org/rest/guide/#signing-up). To do this with Parse-Stack, you can call the `Parse::User.autologin_service` method by passing the service name and the corresponding authentication hash data. For a listing of supported third-party authentication services, see [OAuth](http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication).\n\n```ruby\nfb_auth = {}\nfb_auth[:id] = \"123456789\"\nfb_auth[:access_token] = \"SaMpLeAAiZBLR995wxBvSGNoTrEaL\"\nfb_auth[:expiration_date] = \"2025-02-21T23:49:36.353Z\"\n\n# signup or login a user with this auth data.\nuser = Parse::User.autologin_service(:facebook, fb_auth)\n```\n\nYou may also combine both approaches of signing up a new user with a third-party service and set additional custom fields. For this, use the method `Parse::User.create`.\n\n```ruby\n# or to signup a user with additional data, but linked to Facebook\ndata = {\n  username: \"johnsmith\",\n  name: \"John\",\n  email: \"user@example.com\",\n  authData: { facebook: fb_auth }\n}\nuser = Parse::User.create data\n```\n\n#### Login and Sessions\nWith the `Parse::User` class, you can also perform login and logout functionality. The class special accessors for `session_token` and `session` to manage its authentication state. This will allow you to authenticate users as well as perform Parse queries as a specific user using their session token. To login a user, use the `Parse::User.login` method by supplying the corresponding username and password, or if you already have a user record, use `login!` with the proper password.\n\n```ruby\nuser = Parse::User.login(username,password)\nuser.session_token # session token from a Parse::Session\nuser.session # Parse::Session tied to the token\n\n # You can login user records\nuser = Parse::User.first\nuser.session_token # nil\n\npasswd = 'p_n7!-e8' # corresponding password\nuser.login!(passwd) # true\n\nuser.session_token # 'r:pnktnjyb996sj4p156gjtp4im'\n\n # logout to delete the session\nuser.logout\n```\n\nIf you happen to already have a valid session token, you can use it to retrieve the corresponding Parse::User.\n\n```ruby\n# finds user with session token\nuser = Parse::User.session(session_token)\n\nuser.logout # deletes the corresponding session\n```\n\n#### Linking and Unlinking\nYou can link or unlink user accounts with third-party services like Facebook and Twitter as described in: [Linking and Unlinking Users](http://docs.parseplatform.org/rest/guide/#linking-users). To do this, you must first get the corresponding authentication data for the specific service, and then apply it to the user using the linking and unlinking methods. Each method returns true or false if the action was successful. For a listing of supported third-party authentication services, see [OAuth](http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication).\n\n```ruby\n\nuser = Parse::User.first\n\nfb_auth = { ... } # Facebook auth data\n\n# Link this user's Facebook account with Parse\nuser.link_auth_data! :facebook, fb_auth\n\n# Unlinks this user's Facebook account from Parse\nuser.unlink_auth_data! :facebook\n```\n\n#### Request Password Reset\nYou can reset a user's password using the `Parse::User.request_password_reset` method.\n\n```ruby\nuser = Parse::User.first\n\n# pass a user object\nParse::User.request_password_reset user\n# or email\nParse::User.request_password_reset(\"user@example.com\")\n```\n\n#### Multi-Factor Authentication (MFA)\n\nParse-Stack provides comprehensive MFA support that integrates with Parse Server's built-in MFA adapter. This enables TOTP (Time-based One-Time Password) authentication with apps like Google Authenticator, Authy, or 1Password.\n\n**Prerequisites:**\n- Parse Server must have the MFA adapter enabled\n- Add optional gems to your Gemfile: `gem 'rotp'` and `gem 'rqrcode'`\n\n**Parse Server Configuration:**\n```javascript\n{\n  auth: {\n    mfa: {\n      enabled: true,\n      options: [\"TOTP\"],\n      digits: 6,\n      period: 30,\n      algorithm: \"SHA1\"\n    }\n  }\n}\n```\n\n**Setting Up MFA:**\n```ruby\n# Configure the issuer name shown in authenticator apps\nParse::MFA.configure do |config|\n  config[:issuer] = \"MyApp\"\nend\n\n# Step 1: Generate a TOTP secret\nsecret = Parse::MFA.generate_secret\n\n# Step 2: Display QR code to the user\nqr_svg = user.mfa_qr_code(secret, issuer: \"MyApp\")\n# Render in HTML: \u003c%= raw qr_svg %\u003e\n\n# Step 3: User scans QR and enters code from their authenticator\nrecovery_codes = user.setup_mfa!(secret: secret, token: \"123456\")\n# IMPORTANT: Display recovery codes to user - they can only see them once!\n```\n\n**Logging In with MFA:**\n```ruby\n# Login with username, password, and MFA token\nuser = Parse::User.login_with_mfa(\"username\", \"password\", \"123456\")\n\n# Check if MFA is required before login\nif Parse::User.mfa_required?(\"username\")\n  # Prompt for MFA token\nend\n```\n\n**Managing MFA:**\n```ruby\n# Check MFA status\nuser.mfa_enabled?  # =\u003e true\nuser.mfa_status    # =\u003e :enabled, :disabled, or :unknown\n\n# Disable MFA (requires current token)\nuser.disable_mfa!(current_token: \"123456\")\n\n# Admin reset (requires master key)\nuser.disable_mfa_admin!\n```\n\n**SMS MFA (requires Parse Server SMS callback):**\n```ruby\n# Initiate SMS setup\nuser.setup_sms_mfa!(mobile: \"+1234567890\")\n\n# Confirm with received code\nuser.confirm_sms_mfa!(mobile: \"+1234567890\", token: \"123456\")\n```\n\n**Error Handling:**\n```ruby\nbegin\n  user = Parse::User.login_with_mfa(username, password, token)\nrescue Parse::MFA::RequiredError\n  # MFA token was not provided but is required\nrescue Parse::MFA::VerificationError\n  # Invalid MFA token\nend\n```\n\n\n## Modeling and Subclassing\nFor the general case, your Parse classes should inherit from `Parse::Object`. `Parse::Object` utilizes features from `ActiveModel` to add several features to each instance of your subclass. These include `Dirty`, `Conversion`, `Callbacks`, `Naming` and `Serializers::JSON`.\n\nTo get started use the `property` and `has_many` methods to setup declarations for your fields. Properties define literal values that are columns in your Parse class. These can be any of the base Parse data types. You will not need to define classes for the basic Parse class types - this includes \"\\_User\", \"\\_Installation\", \"\\_Session\" and \"\\_Role\". These are mapped to `Parse::User`, `Parse::Installation`, `Parse::Session` and `Parse::Role` respectively.\n\nTo get started, you define your classes based on `Parse::Object`. By default, the name of the class is used as the name of the remote Parse class. For a class `Post`, we will assume there is a remote camel-cased Parse table called `Post`. If you need to map the local class name to a different remote class, use the `parse_class` method.\n\n```ruby\nclass Post \u003c Parse::Object\n\t# assumes Parse class \"Post\"\nend\n\nclass Commentary \u003c Parse::Object\n\t# set remote class \"Comment\"\n\tparse_class \"Comment\"\nend\n```\n\n### Defining Properties\nProperties are considered a literal-type of association. This means that a defined local property maps directly to a column name for that remote Parse class which contain the value. **All properties are implicitly formatted to map to a lower-first camelcase version in Parse (remote).** Therefore a local property defined as `like_count`, would be mapped to the remote column of `likeCount` automatically. The only special behavior to this rule is the `:id` property which maps to `objectId` in Parse. This implicit conversion mapping is the default behavior, but can be changed on a per-property basis. All Parse data types are supported and all Parse::Object subclasses already provide definitions for `:id` (objectId), `:created_at` (createdAt), `:updated_at` (updatedAt) and `:acl` (ACL) properties.\n\n- **:string** (_default_) - a generic string. Can be used as an enum field, see [Enum](#enum).\n- **:integer** (alias **:int**) - basic number. Will also generate atomic `_increment!` helper method.\n- **:float** - a floating numeric value. Will also generate atomic `_increment!` helper method.\n- **:boolean** (alias **:bool**) - true/false value. This will also generate a class scope helper. See [Query Scopes](#query-scopes).\n- **:date** - a Parse date type. See [Parse::Date](#parsedate).\n- **:timezone** - a time zone object. See [Parse::TimeZone](#parsetimezone).\n- **:array** - a heterogeneous list with dirty tracking. See [Parse::CollectionProxy](https://github.com/modernistik/parse-stack/blob/master/lib/parse/model/associations/collection_proxy.rb).\n- **:file** - a Parse file type. See [Parse::File](#parsefile).\n- **:geopoint** - a GeoPoint type. See [Parse::GeoPoint](#parsegeopoint).\n- **:bytes** - a Parse bytes data type managed as base64. See [Parse::Bytes](#parsebytes).\n- **:object** - an object \"hash\" data type. See [ActiveSupport::HashWithIndifferentAccess](http://apidock.com/rails/ActiveSupport/HashWithIndifferentAccess).\n\nFor completeness, the `:id` and `:acl` data types are also defined in order to handle the Parse `objectId` field and the `ACL` object. Those are special and should not be used in your class (unless you know what you are doing). New data types can be implemented through the internal `typecast` interface. **TODO: discuss `typecast` interface in the future**\n\nWhen declaring a `:boolean` data type, it will also create a special method that uses the `?` convention. As an example, if you have a property named `approved`, the normal getter `obj.approved` can return true, false or nil based on the value in Parse. However with the `obj.approved?` method, it will return true if it set to true, false for any other value.\n\nWhen declaring an `:integer` or `:float` type, it will also create a special method that performs\nan atomic increment of that field through the `_increment!` and `_decrement!` methods. If you have\ndefined a property named `like_count` for one of these numeric types, which would create the normal getter/setter `obj.like_count`; you can now also call `obj.like_count_increment!` or `obj.like_count_decrement!` to perform the atomic operations (done server side) on this field. You may also pass an amount as an argument to these helper methods such as `obj.like_count_increment!(3)`.\n\nUsing the example above, we can add the base properties to our classes.\n\n```ruby\nclass Post \u003c Parse::Object\n  property :title\n  property :content, :string # explicit\n\n  # treat the values of this field as symbols instead of strings.\n  property :category, :string, symbolize: true\n\n  # maybe a count of comments.\n  property :comment_count, :integer, default: 0\n\n  # use lambda to access the instance object.\n  # Set draft_date to the created_at date if empty.\n  property :draft_date, :date, default: lambda { |x| x.created_at }\n  # the published date. Maps to \"publishDate\"\n  property :publish_date, :date, default: lambda { |x| DateTime.now }\n\n  # maybe whether it is currently visible\n  property :visible, :boolean\n\n  # a list using\n  property :tags, :array\n\n  # string column as enumerated type. see :enum\n  property :status, enum: [:active, :archived]\n\n  # Maps to \"featuredImage\" column representing a File.\n  property :featured_image, :file\n\n  property :location, :geopoint\n\n  # Support bytes\n  property :data, :bytes\n\n  # A field that contains time zone information (ex. 'America/Los_Angeles')\n  property :time_zone, :timezone\n\n  # store SEO information. Make sure we map it to the column\n  # \"SEO\", otherwise it would have implicitly used \"seo\"\n  # as the remote column name\n  property :seo, :object, field: \"SEO\"\nend\n```\n\nAfter properties are defined, you can use appropriate getter and setter methods to modify the values. As properties become modified, the model will keep track of the changes using the [dirty tracking feature of ActiveModel](http://api.rubyonrails.org/classes/ActiveModel/Dirty.html). If an attribute is modified in-place then make use of **[attribute_name]_will_change!** to mark that the attribute is changing. Otherwise ActiveModel can't track changes to in-place attributes.\n\nTo support dirty tracking on properties of data type of `:array`, we utilize a proxy class called `Parse::CollectionProxy`. This class has special functionality which allows lazy loading of content as well and keeping track of the changes that are made. While you are able to access the internal array on the collection through the `#collection` method, it is important not to make in-place edits to the object. You should use the preferred methods of `#add` and `#remove` to modify the contents of the collection. When `#save` is called on the object, the changes will be committed to Parse.\n\n```ruby\npost = Post.first\npost.tags.each do |tag|\n  puts tag\nend\npost.tags.empty? # false\npost.tags.count # 3\narray = post.tags.to_a # get array\n\n# Add\npost.tags.add \"music\", \"tech\"\npost.tags.remove \"stuff\"\npost.save # commit changes\n```\n\n#### Accessor Aliasing\nTo enable easy conversion between incoming Parse attributes, which may be different than the locally labeled attribute, we make use of aliasing accessors with their remote field names. As an example, for a `Post` instance and its `publish_date` property, it would have an accessor defined for both `publish_date` and `publishDate` (or whatever value you passed as the `:field` option) that map to the same attribute. We highly discourage turning off this feature, but if you need to, you can pass the value of `false` to the `:alias` option when defining the property.\n\n```ruby\n # These are equivalent\npost.publish_date = DateTime.now\npost.publishDate = DateTime.now\npost.publish_date == post.publishDate\n\npost.seo # ok\npost.SEO # the alias method since 'field: \"SEO\"'\n```\n\n#### Property Options\nThese are the supported options when defining properties. Parse::Objects are backed by `ActiveModel`, which means you can add additional validations and features supported by that library.\n\n##### `:required`\nA boolean property. This option provides information to the property builder that it is a required property. The requirement is not strongly enforced for a save, which means even though the value for the property may not be present, saves and updates can be successfully performed. However, the setting `required` to true, it will set some ActiveModel validations on the property to be used when calling `valid?`. By default it will add a `validates_presence_of` for the property key. If the data type of the property is either `:integer` or `:float`, it will also add a `validates_numericality_of` validation. Default `false`.\n\n##### `:field`\nThis option allows you to set the name of the remote column for the Parse table. Using this will explicitly set the remote property name to the value of this option. The value provided for this option will affect the name of the alias method that is generated when `alias` option is used. **By default, the name of the remote column is the lower-first camelcase version of the property name. As an example, for a property with key `:my_property_name`, the framework will implicitly assume that the remote column is `myPropertyName`.**\n\n##### `:default`\nThis option provides you to set a default value for a specific property when the getter accessor method is used and the internal value of the instance object's property is nil. It can either take a literal value or a Proc/lambda.\n\n```ruby\nclass SomeClass \u003c Parse::Object\n\t# default value\n\tproperty :category, default: \"myValue\"\n\t# default value Proc style\n\tproperty :date, default: lambda { |x| DateTime.now }\nend\n```\n\n##### `:alias`\nA boolean property. It is highly recommended that this is set to true, which is the default. This option allows for the generation of the additional accessors with the value of `:field`. By allowing two accessors methods, aliased to each other, allows for easier importing and automatic object instantiation based on Parse object JSON data into the Parse::Object subclass.\n\n##### `:symbolize`\nA boolean property. This option is only available for fields with data type of `:string`. This allows you to utilize the values for this property as symbols instead of the literal strings, which is Parse's storage format. This feature is useful if a particular property represents a set of enumerable states described in string form. As an example, if you have a `Post` object which has a set of publish states stored in Parse as \"draft\",\"scheduled\", and \"published\" - we can use ruby symbols to make our code easier.\n\n```ruby\nclass Post \u003c Parse::Object\n\tproperty :state, :string, symbolize: true\nend\n\npost = Post.first\n # the value returned is auto-symbolized\nif post.state == :draft\n\t# will be converted to string when updated in Parse\n\tpost.state = :published\n\tpost.save\nend\n```\n\n##### `:enum`\nThe enum option allows you to define an array of possible values that the particular `:string` property should hold. This feature has similarities in the methods and accessors generated for you as described in [ActiveRecord::Enum](http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html). Using the example in that documentation:\n\n```ruby\nclass Conversation \u003c Parse::Object\n  property :status, enum: [ :active, :archived ]\nend\n\nConversation.statuses # =\u003e [ :active, :archived ]\n\n# named scopes\nConversation.active # where status: :active\nConversation.archived(limit: 10) # where status: :archived, limit 10\n\nconversation.active! # sets status to active!\nconversation.active? # =\u003e true\nconversation.status  # =\u003e :active\n\nconversation.archived!\nconversation.archived? # =\u003e true\nconversation.status    # =\u003e :archived\n\n# equivalent\nconversation.status = \"archived\"\nconversation.status = :archived\n\n# allowed by the setter\nconversation.status = :banana\nconversation.status_valid? # =\u003e false\n\n```\n\nSimilar to [ActiveRecord::Enum](http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html), you can use the `:_prefix` or `:_suffix` options when you need to define multiple enums with same values. If the passed value is true, the methods are prefixed/suffixed with the name of the enum. It is also possible to supply a custom value:\n\n```ruby\nclass Conversation \u003c Parse::Object\n  property :status, enum: [:active, :archived], _suffix: true\n  property :comments_status, enum: [:active, :inactive], _prefix: :comments\n  # combined\n  property :discussion, enum: [:casual, :business], _prefix: :talk, _suffix: true\nend\n\nConversation.statuses # =\u003e [:active, :archived]\nConversation.comments # =\u003e [:active, :inactive]\nConversation.talks # =\u003e [:casual, :business]\n\n# affects scopes names\nConversation.archived_status\nConversation.comments_inactive\nConversation.business_talk\n\nconversation.active_status!\nconversation.archived_status? # =\u003e false\n\nconversation.status = :banana\nconversation.valid_status? # =\u003e false\n\nconversation.comments_inactive!\nconversation.comments_active? # =\u003e false\n\nconversation.casual_talk!\nconversation.business_talk? # =\u003e false\n```\n\n##### `:scope`\nA boolean property. For some data types like `:boolean` and enums, some [query scopes](#query-scopes) are generated to more easily query data. To prevent generating these scopes for a particular property, set this value to `false`.\n\n### Associations\nParse 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`.\n\n#### Belongs To\nThis association creates a one-to-one association with another Parse model. This association says that this class contains a foreign pointer column which references a different class. Utilizing the `belongs_to` method in defining a property in a Parse::Object subclass sets up an association between the local table and a foreign table. Specifying the `belongs_to` in the class, tells the framework that the Parse table contains a local column in its schema that has a reference to a record in a foreign table. The argument to `belongs_to` should be the singularized version of the foreign Parse::Object class. you should specify the foreign table as the snake_case singularized version of the foreign table class. It is important to note that the reverse relationship is not generated automatically.\n\n```ruby\nclass Author \u003c Parse::Object\n\tproperty :name\nend\n\nclass Comment \u003c Parse::Object\n\tbelongs_to :user # Parse::User\nend\n\nclass Post \u003c Parse::Object\n\tbelongs_to :author\nend\n\npost = Post.first\n # Follow the author pointer and get name\npost.author.name\n\nother_author = Author.first\n # change author by setting new pointer\npost.author = other_author\npost.save\n```\n\n##### Options\nYou can override some of the default functionality when creating both `belongs_to`, `has_one` and `has_many` associations.\n\n###### `:required`\nA boolean property. Setting the requirement, automatically creates an ActiveModel validation of `validates_presence_of` for the association. This will not prevent the save, but affects the validation check when `valid?` is called on an instance. Default is false.\n\n###### `:as`\nThis option allows you to override the foreign Parse class that this association refers while allowing you to have a different accessor name. As an example, you may have a class `Band` which has a `manager` who is of type `Parse::User` and a set of band members, represented by the class `Artist`. You can override the default casting class as follows:\n\n```ruby\n # represents a member of a band or group\nclass Artist \u003c Parse::Object\nend\n\nclass Band \u003c Parse::Object\n\tbelongs_to :manager, as: :user\n\tbelongs_to :lead_singer, as: :artist\n\tbelongs_to :drummer, as: :artist\nend\n\nband = Band.first\nband.manager # Parse::User object\nband.lead_singer # Artist object\nband.drummer # Artist object\n```\n\n###### `:field`\nThis option allows you to set the name of the remote Parse column for this property. Using this will explicitly set the remote property name to the value of this option. The value provided for this option will affect the name of the alias method that is generated when `alias` option is used. **By default, the name of the remote column is the lower-first camel case version of the property name. As an example, for a property with key `:my_property_name`, the framework will implicitly assume that the remote column is `myPropertyName`.**\n\n#### [Has One](https://neurosynq.github.io/parse-stack-next/Parse/Associations/HasOne.html)\nThe `has_one` creates a one-to-one association with another Parse class. This association says that the other class in the association contains a foreign pointer column which references instances of this class. If your model contains a column that is a Parse pointer to another class, you should use `belongs_to` for that association instead.\n\nDefining a `has_one` property generates a helper query method to fetch a particular record from a foreign class. This is useful for setting up the inverse relationship accessors of a `belongs_to`. In the case of the `has_one` relationship, the `:field` option represents the name of the column of the foreign class where the Parse pointer is stored. By default, the lower-first camel case version of the Parse class name is used.\n\nIn the example below, a `Band` has a local column named `manager` which has a pointer to a `Parse::User` record. This setups up the accessor for `Band` objects to access the band's manager.\n\n```ruby\n# every band has a manager\nclass Band \u003c Parse::Object\n\tbelongs_to :manager, as: :user\nend\n\nband = Band.first id: '12345'\n# the user represented by this manager\nuser = band.manger\n\n```\n\nSince we know there is a column named `manager` in the `Band` class that points to a single `Parse::User`, you can setup the inverse association read accessor in the `Parse::User` class. Note, that to change the association, you need to modify the `manager` property on the band instance since it contains the `belongs_to` property.\n\n```ruby\n# every user manages a band\nclass Parse::User\n  # inverse relationship to `Band.belongs_to :manager`\n  has_one :band, field: :manager\nend\n\nuser = Parse::User.first\n# use the generated has_one accessor `band`.\nuser.band # similar to query: Band.first(:manager =\u003e user)\n\n```\n\nYou may optionally use `has_one` with scopes, in order to fine tune the query result. Using the example above, you can customize the query with a scope that only fetches the association if the band is approved. If the association cannot be fetched, `nil` is returned.\n\n```ruby\n# adding to previous example\nclass Band \u003c Parse::Object\n  property :approved, :boolean\n  property :approved_date, :date\nend\n\n# every user manages a band\nclass Parse::User\n  has_one :recently_approved, -\u003e{ where(order: :approved_date.desc) }, field: :manager, as: :band\n  has_one :band_by_status, -\u003e(status) { where(approved: status) },  field: :manager, as: :band\nend\n\n# gets the band most recently approved\nuser.recently_approved\n# equivalent: Band.first(manager: user, order: :approved_date.desc)\n\n# fetch the managed band that is not approved\nuser.band_by_status(false)\n# equivalent: Band.first(manager: user, approved: false)\n\n```\n\n#### [Has Many](https://neurosynq.github.io/parse-stack-next/Parse/Associations/HasMany.html)\nParse has many ways to implement one-to-many and many-to-many associations: `Array`, `Parse Relation` or through a `Query`. How you decide to implement your associations, will affect how `has_many` works in Parse-Stack. Parse natively supports one-to-many and many-to-many relationships using `Array` and `Relations`, as described in [Relational Data](http://docs.parseplatform.org/js/guide/#relational-data). Both of these methods require you define a specific column type in your Parse table that will be used to store information about the association.\n\nIn addition to `Array` and `Relation`, Parse-Stack also implements the standard `has_many` behavior prevalent in other frameworks through a query where the associated class contains a foreign pointer to the local class, usually the inverse of a `belongs_to`. This requires that the associated class has a defined column\nthat contains a pointer the refers to the defining class.\n\n##### Query\nIn this implementation, a `has_many` association for a Parse class requires that another Parse class will have a foreign pointer that refers to instances of this class. This is the standard way that `has_many` relationships work in most databases systems. This is usually the case when you have a class that has a `belongs_to` relationship to instances of the local class.\n\nIn the example below, many songs belong to a specific artist. We set this association by setting `:belongs_to` relationship from `Song` to `Artist`. Knowing there is a column in `Song` that points to instances of an `Artist`, we can setup a `has_many` association to `Song` instances in the `Artist` class. Doing so will generate a helper query method on the `Artist` instance objects.\n\n```ruby\nclass Song \u003c Parse::Object\n  property :released, :date\n  # this class will have a pointer column to an Artist\n  belongs_to :artist\nend\n\nclass Artist \u003c Parse::Object\n  has_many :songs\nend\n\nartist = Artist.first\n\nartist.songs # =\u003e [all songs belonging to artist]\n# equivalent: Song.all(artist: artist)\n\n# filter also by release date\nartist.songs(:released.after =\u003e 1.year.ago)\n# equivalent: Song.all(artist: artist, :released.after =\u003e 1.year.ago)\n\n```\n\nIn order to modify the associated objects (ex. `songs`), you must modify their corresponding `belongs_to` field (in this case `song.artist`), to another record and save it.\n\nOptions for `has_many` using this approach are `:as` and `:field`. The `:as` option behaves similarly to the `:belongs_to` counterpart. The `:field` option can be used to override the derived column name located in the foreign class. The default value for `:field` is the columnized version of the Parse subclass `parse_class` method.\n\n```ruby\nclass Parse::User\n  # since the foreign column name is :agent\n  has_many :artists, field: :agent\nend\n\nclass Artist \u003c Parse::Object\n  belongs_to :manager, as: :user, field: :agent\nend\n\nartist.manager # =\u003e Parse::User object\n\nuser.artists # =\u003e [artists where :agent colum","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneurosynq%2Fparse-stack-next","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneurosynq%2Fparse-stack-next","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneurosynq%2Fparse-stack-next/lists"}