Skip to content

Releases: commandpostsoft/parse-stack

v3.3.4 - Cloud Code Error Handling

21 Apr 17:11

Choose a tag to compare

Cloud Code Error Handling

This release adds raising variants of Parse.call_function and Parse.trigger_job that surface cloud-code errors instead of silently returning nil, introduces Parse::Error::CloudCodeError, and hardens result extraction against unusual response bodies.

Changes

  • NEW: Parse.call_function!, Parse.call_function_with_session!, Parse.trigger_job!, and Parse.trigger_job_with_session! raise Parse::Error::CloudCodeError when the cloud function or job returns an error response, instead of silently returning nil. The error carries function_name, code, http_status, and the underlying Parse::Response for debugging. Use these variants when you want failures to propagate rather than be coerced to nil.
  • IMPROVED: Parse.call_function and Parse.trigger_job now emit a [Parse:CloudCodeError] warning to stderr when the response indicates an error. Previously both methods coerced any cloud-code error response to a nil return value with no log line, making misconfigured calls (missing session token, failed error!() in cloud code) invisible to callers and tests. The nil return is preserved for backwards compatibility; the warning surfaces the failure. Matches the existing warn-then-raise pattern used by other HTTP error paths in Parse::Client#request.
  • FIXED: Parse.call_function, Parse.trigger_job, and their ! variants no longer raise TypeError on unusual successful response bodies. Result extraction now guards against non-Hash response payloads (e.g., a bare string body) by returning the raw result rather than indexing into a non-Hash.

Code Examples

# Raise on error instead of silently returning nil
result = Parse.call_function!(:addUserToTeams, { userId: id }, session_token: token)

# Handle the raise with structured error info
begin
  Parse.call_function!(:addUserToTeams, params, session_token: token)
rescue Parse::Error::CloudCodeError => e
  Rails.logger.error "Cloud function #{e.function_name} failed: [#{e.code}] HTTP #{e.http_status}"
  raise
end

# Non-bang form now warns on error (still returns nil)
Parse.call_function(:broken, {})
# stderr: [Parse:CloudCodeError] broken [141] Missing session token (HTTP 400)
# returns: nil

Commit: 7ff4741
Author: Adrian Curtin
Date: April 2026

v3.3.0 - Ruby 3.1 Minimum Version

09 Jan 02:18

Choose a tag to compare

Ruby 3.1 Minimum Version

This release updates the minimum Ruby version requirement.

Breaking Changes

  • BREAKING: Minimum Ruby version is now 3.1 (previously 3.0). Ruby 3.0 reached end-of-life in March 2024.

Changes

  • IMPROVED: CI now tests against Ruby 3.1, 3.2, 3.3, and 3.4.

Commit: f4bd663
Author: Adrian Curtin
Date: January 2026

v3.2.2 - Query Methods & JSON Serialization

09 Jan 02:18

Choose a tag to compare

Query Method Improvements & JSON Serialization

This release improves query methods and adds JSON serialization options.

Improvements

  • IMPROVED: latest and last_updated methods now support a limit: option when passing constraints.
Song.latest(:user.eq => user, limit: 5)
Song.last_updated(status: "active", limit: 10)
query.where(genre: "rock").last_updated(limit: 3)
  • IMPROVED: PointerCollectionProxy#as_json now supports the pointers_only option. Set pointers_only: false to serialize objects with their fetched fields instead of just pointers.
# Default - pointers for storage
capture.assets.as_json
# => [{"__type"=>"Pointer", "className"=>"Asset", "objectId"=>"abc"}, ...]

# Serialize with fetched fields
capture.assets.as_json(pointers_only: false)
# => [{"objectId"=>"abc", "file"=>{...}, "caption"=>"My photo", ...}, ...]
  • IMPROVED: Parse::Object#as_json with :only option now automatically includes identification fields (objectId, className, __type, id). Use strict: true to disable this.
# Default: identification fields included
song.as_json(only: [:title, :artist])
# => {"objectId"=>"abc", "className"=>"Song", "__type"=>"Object", "title"=>"...", "artist"=>"..."}

# Strict mode: only specified fields
song.as_json(only: [:title, :artist], strict: true)
# => {"title"=>"...", "artist"=>"..."}
  • NEW: Added :exclude as an alias for :except in as_json.
song.as_json(exclude: [:acl, :created_at])

Author: Adrian Curtin
Date: December 2025

v3.2.1 - CLP Default Permissions & User Fields

09 Jan 02:18

Choose a tag to compare

CLP Default Permissions & User Fields

This release adds new CLP configuration methods and improves field name handling.

New Features

  • NEW: Added set_default_clp method to set a default permission for all CLP operations at once.
class Document < Parse::Object
  # Set all operations to public by default
  set_default_clp public: true

  # Or require authentication for all operations
  set_default_clp requires_authentication: true

  # Or restrict all operations to specific roles
  set_default_clp roles: ["Admin", "Editor"]

  # Then override specific operations as needed
  set_clp :delete, public: false, roles: ["Admin"]
end
  • NEW: Added set_read_user_fields and set_write_user_fields for pointer-based permissions.
class Document < Parse::Object
  belongs_to :owner, as: :user
  belongs_to :editor, as: :user

  # Owner can read, editor can write
  set_read_user_fields [:owner]
  set_write_user_fields [:editor]
end
  • NEW: Added reset_clp! method to reset CLPs to public defaults.

Improvements

  • IMPROVED: CLP methods now automatically convert snake_case Ruby property names to camelCase Parse Server field names.
  • IMPROVED: Added include_defaults parameter to CLP#as_json for pushing complete CLPs to server.

Bug Fixes

  • FIXED: auto_upgrade! now resets CLPs before applying new ones, preventing old restrictive permissions from persisting.
  • FIXED: as_json(include_defaults: true) now properly includes all operations even when no explicit set_default_clp is called.

Author: Adrian Curtin
Date: December 2025

v3.2.0 - Class-Level Permissions & Consolidated Updates

09 Jan 02:17

Choose a tag to compare

Class-Level Permissions (CLP) Support

This release adds comprehensive Class-Level Permissions support for protecting fields and controlling access at the schema level.

Note: This release consolidates changes from versions 3.1.1 through 3.1.12 (GitHub releases were skipped for those versions). See CHANGELOG.md for detailed version-by-version changes.

New Features: Class-Level Permissions

DSL for Defining CLPs:

class Song < Parse::Object
  property :title, :string
  property :internal_notes, :string

  # Set operation-level permissions
  set_clp :find, public: true
  set_clp :create, public: false, roles: ["Admin", "Editor"]
  set_clp :delete, public: false, roles: ["Admin"]

  # Protect fields from certain users
  protect_fields "*", [:internal_notes]  # Hidden from everyone
  protect_fields "role:Admin", []         # Admins see everything
end

Filter Data for Webhook Responses:

filtered = song.filter_for_user(current_user, roles: ["Member"])
filtered_results = Song.filter_results_for_user(songs, current_user, roles: user_roles)

Push CLPs to Parse Server:

Song.auto_upgrade!   # Includes CLPs in schema upgrades
Song.update_clp!     # Update only CLPs
Song.fetch_clp       # Fetch current CLPs from server

Consolidated Changes from 3.1.1 - 3.1.12

Query & Constraint Improvements (3.1.12, 3.1.4, 3.1.3)

  • NEW: ends_with query constraint for string suffix matching
  • NEW: ACL query convenience methods: publicly_readable, publicly_writable, privately_readable, privately_writable, private_acl, not_publicly_readable, not_publicly_writable
  • NEW: not_readable_by and not_writeable_by constraints
  • NEW: ACL queries support readable_by: user and writable_by: role as hash keys
  • NEW: Role hierarchy expansion - queries automatically include child roles
  • NEW: default_acl_private class setting and private_acl! convenience method
  • NEW: Parse::ACL.private class method for empty ACLs

Caching Improvements (3.1.9, 3.1.7, 3.1.5)

  • NEW: fetch_cache! method on Parse::Pointer with caching support
  • NEW: cache: parameter for Parse::Pointer#fetch
  • NEW: "Write-only" cache mode (:write_only) - skip cache read, update cache with fresh data
  • NEW: fetch_cache! and find_cached convenience methods
  • CHANGED: Query caching is now opt-in by default (was opt-out)
  • NEW: Parse.default_query_cache and Parse.cache_write_on_fetch configuration options

Performance Improvements (3.1.10)

  • IMPROVED: Aggregation pipeline optimization automatically merges consecutive $match stages

Validation & Callback Improvements (3.1.2)

  • NEW: save() passes validation context (:create or :update) to validations and callbacks
  • NEW: before_validation, after_validation, around_validation support on: :create/:update option
  • NEW: create! class method for Active Model consistency

ACL Dirty Tracking (3.1.3)

  • FIXED: acl_was correctly captures ACL state before in-place modifications
  • NEW: acl_changed? compares actual ACL content, not just object references

Serialization (3.1.1)

  • NEW: :exclude_keys option as alias for :except in as_json
  • NEW: Parse::MongoDB.to_mongodb_date helper for date conversion

Bug Fixes (Various)

  • FIXED: auto_upgrade! skips read-only system classes (_PushStatus, _SCHEMA)
  • FIXED: Date property parsing handles empty strings gracefully
  • FIXED: Query methods first, latest, last_updated properly accept keyword options
  • FIXED: Connection pooling pool_size option works correctly

Code Quality (3.1.6)

  • FIXED: Resolved circular require warnings
  • FIXED: Resolved method redefinition warnings
  • UPDATED: Parse::Installation device_type enum updated for current Parse Server types
  • NEW: Push notification validation for installation targeting

Author: Adrian Curtin
Date: November 2025

v3.1.0 - Role Management, Schema Tools & Atlas Search

09 Jan 02:16

Choose a tag to compare

Role Management, Schema Tools & Atlas Search

This release adds enhanced role management, schema introspection tools, MongoDB Atlas Search integration, and direct MongoDB query support.

Note: This release also includes changes from v3.0.2 (GitHub release was skipped for that version).

Enhanced Role Management

New helper methods for managing Parse roles and role hierarchies:

# Find or create a role
admin = Parse::Role.find_or_create("Admin")

# Add/remove users
admin.add_users(user1, user2).save
admin.remove_user(user).save

# Check membership
admin.has_user?(user)  # => true

# Role hierarchy
admin.add_child_role(moderator).save
admin.all_child_roles   # => [moderator, ...]
admin.all_users         # => Users from this role AND child roles

HTTP 429 Retry-After Header Support

The client now respects the Retry-After HTTP header when handling rate limit responses. Supports both integer seconds and HTTP-date formats.

MongoDB Read Preference Support

Direct read queries to secondary replicas for load balancing:

songs = Song.query.read_pref(:secondary).where(genre: "Rock").results

Schema Introspection and Migration Tools

New Parse::Schema module for inspecting and migrating schemas:

# Fetch and inspect schema
schema = Parse::Schema.fetch("Song")
schema.field_names      # => ["objectId", "title", "duration", ...]
schema.field_type(:title)  # => :string

# Compare local model with server
diff = Parse::Schema.diff(Song)
diff.summary            # => Human-readable diff

# Generate and apply migration
migration = Parse::Schema.migration(Song)
migration.apply!(dry_run: true)  # Preview only

MongoDB Atlas Search Integration

Full-text search, autocomplete, and faceted search via MongoDB Atlas Search:

# Configure
Parse::MongoDB.configure(uri: "mongodb+srv://...", enabled: true)
Parse::AtlasSearch.configure(enabled: true, default_index: "default")

# Full-text search
result = Parse::AtlasSearch.search("Song", "love ballad")

# Autocomplete
result = Parse::AtlasSearch.autocomplete("Song", "Lov", field: :title)

# Faceted search
result = Parse::AtlasSearch.faceted_search("Song", "rock", facets)

Direct MongoDB Query Methods

New methods for executing queries directly against MongoDB:

songs = Song.query(:plays.gt => 1000).results_direct
song = Song.query.order(:plays.desc).first_direct
count = Song.query(:plays.gt => 1000).count_direct

Also Includes Changes from v3.0.2 (GitHub release skipped)

Push Notification User/Installation Targeting

Parse::Push.to_user(current_user).with_alert("Hello!").send!
Parse::Push.to_users(user1, user2, user3).with_alert("Group message!").send!
Parse::Push.to_installation(device).with_alert("Hello!").send!

Bug Fixes from v3.0.2

  • FIXED: Array constraint field name formatting for MongoDB aggregation queries
  • FIXED: All 13 array constraints now use proper field name conversion (empty_or_nil, not_empty, set_equals, etc.)
  • FIXED: build_aggregation_pipeline now merges all $match stages correctly

Author: Adrian Curtin
Date: December 2025

v3.0.1 - Agent Enhancements & LiveQuery

09 Jan 02:15

Choose a tag to compare

Agent Enhancements & LiveQuery Improvements

This release adds comprehensive agent features and LiveQuery improvements.

Agent Enhancements

Environment Variable Gating for MCP:

# Requires PARSE_MCP_ENABLED=true AND Parse.mcp_server_enabled = true
Parse.mcp_server_enabled = true
Parse::Agent.enable_mcp!(port: 3001)

Conversation Support (Multi-turn):

agent = Parse::Agent.new
agent.ask("How many users are there?")
agent.ask_followup("What about admins?")
agent.clear_conversation!

Token Usage Tracking:

agent.token_usage  # => { prompt_tokens: 450, completion_tokens: 120, total_tokens: 570 }
agent.estimated_cost  # => 0.0234

Callback/Hooks System:

agent.on_tool_call { |tool, args| puts "Calling: #{tool}" }
agent.on_tool_result { |tool, args, result| log_result(tool, result) }
agent.on_error { |error, context| notify_slack(error) }

Streaming Support:

agent.ask_streaming("Analyze user growth trends") { |chunk| print chunk }

Export/Import Conversation:

state = agent.export_conversation
new_agent.import_conversation(state)

LiveQuery Enhancements

  • NEW: Configurable frame read timeout to prevent indefinite socket blocking
  • NEW: Automatic cleanup of expired cache entries in Parse::Audience

Bug Fixes

  • FIXED: Arrays containing Parse objects now stored in proper pointer format for .in/.nin queries
  • NEW: pointers_only option for CollectionProxy#as_json
  • FIXED: add!, add_unique!, remove! methods correctly convert Parse objects to pointer format

Author: Adrian Curtin
Date: November 2025

v3.0.0 - Push, Sessions, Agent & LiveQuery

09 Jan 02:14

Choose a tag to compare

Major Release - Push, Sessions, Agent, LiveQuery & Validation

This major release adds comprehensive push notification features, session management, AI agent integration, LiveQuery client, and model validation utilities.

Push Builder Pattern API

New fluent API for building push notifications:

Parse::Push.new
  .to_channel("news")
  .with_title("Breaking News")
  .with_body("Major event happening now!")
  .with_badge(1)
  .with_sound("alert.caf")
  .with_data(article_id: "12345")
  .schedule(Time.now + 3600)
  .send!

# Class method shortcuts
Parse::Push.to_channel("news").with_alert("Hello!").send!

Silent Push Support (iOS)

Parse::Push.new
  .to_channel("sync")
  .silent!
  .with_data(action: "refresh")
  .send!

Rich Push Support (iOS)

Parse::Push.new
  .to_channel("media")
  .with_title("New Photo")
  .with_image("https://example.com/photo.jpg")
  .with_category("PHOTO_ACTIONS")
  .send!

Installation Channel Management

installation.subscribe("news", "weather")
installation.unsubscribe("sports")
installation.subscribed_to?("news")

Parse::Installation.all_channels
Parse::Installation.subscribers_count("news")

Push Localization & Badge Increment

# Localized alerts
Parse::Push.new
  .with_localized_alerts(en: "Hello!", fr: "Bonjour!", es: "Hola!")
  .send!

# Badge increment
Parse::Push.new.increment_badge.send!
Parse::Push.new.clear_badge.send!

Session Management

session.expired?
session.time_remaining
session.revoke!

Parse::Session.active.all
Parse::Session.for_user(user).all
user.logout_all!
user.active_session_count

AI Agent Integration

New Parse::Agent module for AI-powered database interactions:

agent = Parse::Agent.new
result = agent.ask("How many users signed up this week?")
result = agent.ask("Find all songs with more than 1000 plays")

# Available tools for LLM
Parse::Agent.available_tools

MCP Server Support:

Parse.mcp_server_enabled = true
Parse::Agent.enable_mcp!(port: 3001)

LiveQuery Client

Real-time subscriptions for Parse objects:

# Configure
Parse::LiveQuery.configure(
  server_url: "wss://your-server.com/parse",
  application_id: "your-app-id"
)

# Subscribe to changes
subscription = Song.live_query.where(:plays.gt => 1000).subscribe
subscription.on(:create) { |song| puts "New hit: #{song.title}" }
subscription.on(:update) { |song| puts "Updated: #{song.title}" }
subscription.on(:delete) { |song| puts "Removed: #{song.title}" }

# Unsubscribe
subscription.unsubscribe

Features:

  • Circuit breaker for connection resilience
  • Health monitoring with automatic reconnection
  • Event queue for ordered processing

Email Validation Model

New Parse::Email class for email validation:

email = Parse::Email.new("user@example.com")
email.valid?        # => true
email.domain        # => "example.com"
email.local_part    # => "user"
email.normalized    # => "user@example.com"

Phone Validation Model

New Parse::Phone class for phone number validation:

phone = Parse::Phone.new("+1 (555) 123-4567")
phone.valid?        # => true
phone.e164          # => "+15551234567"
phone.national      # => "(555) 123-4567"
phone.country_code  # => "US"

Saved Audiences & Push Status

# Target saved audiences
Parse::Push.new.to_audience("VIP Users").with_alert("Exclusive!").send!
Parse::Audience.find_by_name("VIP Users")

# Track push status
status = Parse::PushStatus.find(push_id)
status.succeeded?
status.num_sent
status.success_rate

Author: Adrian Curtin
Date: November 2025

v2.3.0 - Connection Pooling, Cursor Pagination & N+1 Detection

09 Jan 02:14

Choose a tag to compare

Connection Pooling, Cursor Pagination & N+1 Detection

This release adds HTTP connection pooling, cursor-based pagination, N+1 query detection, and thread safety improvements.

HTTP Connection Pooling (Default)

Parse Stack now uses HTTP persistent connections by default for improved performance.

# Default: connection pooling enabled
Parse.setup(
  server_url: "https://your-parse-server.com/parse",
  application_id: "your-app-id",
  api_key: "your-api-key"
)

# Custom configuration
Parse.setup(
  ...,
  connection_pooling: {
    pool_size: 5,
    idle_timeout: 60,
    keep_alive: 60
  }
)

# Disable if needed
Parse.setup(..., connection_pooling: false)

Cursor-Based Pagination

New Parse::Cursor for efficiently traversing large datasets:

# Iterate over pages
cursor = Song.cursor(limit: 100, order: :created_at.desc)
cursor.each_page { |page| process(page) }

# Iterate over items
Song.cursor(limit: 50).each { |song| puts song.title }

# Manual control
cursor = User.cursor(limit: 100)
first_page = cursor.next_page
second_page = cursor.next_page
cursor.reset!

# Resumable cursors
state = cursor.serialize
# Later...
cursor = Parse::Cursor.deserialize(state)

N+1 Query Detection

New Parse::NPlusOneDetector to detect N+1 query patterns:

# Enable warnings
Parse.warn_on_n_plus_one = true
# Or use mode API
Parse.n_plus_one_mode = :warn

# Strict mode for CI
Parse.n_plus_one_mode = :raise

# Fix with includes
songs = Song.all(limit: 100, includes: [:artist])

Available modes: :ignore (default), :warn, :raise

Thread Safety & Marshaling Improvements

  • IMPROVED: Thread safety improvements in Parse::Object for concurrent access
  • IMPROVED: Better marshaling support for Parse objects

Bug Fixes

  • IMPROVED: Aggregation pipeline correctly handles __aggregation_pipeline stages
  • IMPROVED: Better whitespace formatting in SortableGroupBy pipeline generation

Author: Adrian Curtin
Date: November 2025

v2.2.0 - Validations DSL, Profiling & Consolidated Updates

09 Jan 02:13

Choose a tag to compare

Validations DSL, Performance Profiling & Partial Fetch System

This release adds Rails-style validations, performance profiling middleware, and comprehensive partial fetch tracking.

Note: This release consolidates changes from versions 2.1.1 through 2.1.10 (GitHub releases were skipped). See CHANGELOG.md for detailed version-by-version changes.

New Features: Validations DSL

Rails-style validations with custom uniqueness validator:

class User < Parse::Object
  property :email, :string
  property :username, :string

  validates :email, uniqueness: true
  validates :username, uniqueness: { case_sensitive: false }
  validates :employee_id, uniqueness: { scope: :organization }
end

Validation Callbacks:

  • before_validation, after_validation, around_validation

Update Callbacks:

  • before_update, after_update, around_update

Performance Profiling Middleware

Parse.profiling_enabled = true

Parse.recent_profiles.each do |profile|
  puts "#{profile[:method]} #{profile[:url]}: #{profile[:duration_ms]}ms"
end

stats = Parse.profiling_statistics
# => { count: N, avg_ms: X, min_ms: Y, max_ms: Z, by_method: {...} }

Query Explain

plan = Song.query(:plays.gt => 1000).explain

Consolidated Changes from 2.1.1 - 2.1.10

Partial Fetch Tracking System (2.1.0-2.1.6)

  • NEW: partially_fetched?, fetched_keys, field_was_fetched?(key) methods
  • NEW: Autofetch triggers automatically when accessing unfetched fields
  • NEW: disable_autofetch!, enable_autofetch! methods
  • NEW: Parse::UnfetchedFieldAccessError when accessing unfetched fields with autofetch disabled
  • NEW: Parse.serialize_only_fetched_fields configuration option
  • NEW: fully_fetched?, fetched? methods for checking fetch state

Partial Fetch on Existing Objects (2.1.5)

post.fetch(keys: [:view_count])
post.fetch!(keys: [:title], includes: [:author])
pointer.fetch(keys: [:title, :content])

Advanced Array Query Constraints (2.1.9-2.1.10)

  • NEW: :field.size => n - Match arrays with exact size
  • NEW: :field.arr_empty => true/false - Match empty arrays
  • NEW: :field.eq_array => [values] - Exact match (order matters)
  • NEW: :field.set_equals => [values] - Set equality (any order)
  • NEW: :field.elem_match => { criteria } - Match array elements
  • NEW: :field.any => [values], :field.none => [values]
  • NEW: :field.first => value, :field.last => value

Request/Response Logging Middleware (2.1.10)

Parse.setup(..., logging: true)
Parse.log_level = :debug

Bug Fixes (Various)

  • FIXED: fetch! handles array responses gracefully
  • FIXED: Transaction objects receive IDs after successful create
  • FIXED: Setting fields on pointer objects correctly marks them as dirty
  • FIXED: hash method consistent with == for Parse objects
  • FIXED: Autofetch no longer wipes out nested embedded data
  • FIXED: as_json with :only option works correctly
  • FIXED: Assignment to unfetched fields no longer triggers autofetch
  • FIXED: Partial fetch handles fields with default values correctly

Other Improvements

  • REMOVED: active_model_serializers gem dependency
  • FIXED: ActiveSupport deprecation warnings from Rails 8.2
  • NEW: Parse.autofetch_raise_on_missing_keys for debugging
  • NEW: Query validation warnings for common mistakes

Author: Adrian Curtin
Date: November 2025