parse-stack-next 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/.env.sample +112 -0
- data/.env.test +10 -0
- data/.github/workflows/ruby.yml +36 -0
- data/.gitignore +49 -0
- data/.ruby-version +1 -0
- data/.solargraph.yml +22 -0
- data/CHANGELOG.md +5816 -0
- data/Gemfile +30 -0
- data/Gemfile.lock +175 -0
- data/LICENSE.txt +23 -0
- data/Makefile +63 -0
- data/README.md +5655 -0
- data/Rakefile +573 -0
- data/bin/console +38 -0
- data/bin/parse-console +136 -0
- data/bin/server +17 -0
- data/bin/setup +7 -0
- data/config/parse-config.json +12 -0
- data/docs/TEST_SERVER.md +271 -0
- data/docs/_config.yml +1 -0
- data/docs/mcp_guide.md +3484 -0
- data/docs/mongodb_direct_guide.md +1348 -0
- data/docs/mongodb_index_optimization_guide.md +631 -0
- data/examples/transaction_example.rb +219 -0
- data/lib/parse/acl_scope.rb +728 -0
- data/lib/parse/agent/cancellation_token.rb +80 -0
- data/lib/parse/agent/constraint_translator.rb +480 -0
- data/lib/parse/agent/describe.rb +420 -0
- data/lib/parse/agent/errors.rb +133 -0
- data/lib/parse/agent/mcp_client.rb +557 -0
- data/lib/parse/agent/mcp_dispatcher.rb +1023 -0
- data/lib/parse/agent/mcp_rack_app.rb +1143 -0
- data/lib/parse/agent/mcp_server.rb +376 -0
- data/lib/parse/agent/metadata_audit.rb +259 -0
- data/lib/parse/agent/metadata_dsl.rb +733 -0
- data/lib/parse/agent/metadata_registry.rb +794 -0
- data/lib/parse/agent/pipeline_validator.rb +82 -0
- data/lib/parse/agent/prompts.rb +351 -0
- data/lib/parse/agent/rate_limiter.rb +158 -0
- data/lib/parse/agent/relation_graph.rb +162 -0
- data/lib/parse/agent/result_formatter.rb +453 -0
- data/lib/parse/agent/tools.rb +5489 -0
- data/lib/parse/agent.rb +3249 -0
- data/lib/parse/api/aggregate.rb +79 -0
- data/lib/parse/api/all.rb +26 -0
- data/lib/parse/api/analytics.rb +18 -0
- data/lib/parse/api/batch.rb +33 -0
- data/lib/parse/api/cloud_functions.rb +58 -0
- data/lib/parse/api/config.rb +125 -0
- data/lib/parse/api/files.rb +29 -0
- data/lib/parse/api/hooks.rb +117 -0
- data/lib/parse/api/objects.rb +146 -0
- data/lib/parse/api/path_segment.rb +75 -0
- data/lib/parse/api/push.rb +20 -0
- data/lib/parse/api/schema.rb +49 -0
- data/lib/parse/api/server.rb +50 -0
- data/lib/parse/api/sessions.rb +24 -0
- data/lib/parse/api/users.rb +250 -0
- data/lib/parse/atlas_search/index_manager.rb +353 -0
- data/lib/parse/atlas_search/result.rb +204 -0
- data/lib/parse/atlas_search/search_builder.rb +604 -0
- data/lib/parse/atlas_search/session.rb +253 -0
- data/lib/parse/atlas_search.rb +995 -0
- data/lib/parse/client/authentication.rb +97 -0
- data/lib/parse/client/batch.rb +234 -0
- data/lib/parse/client/body_builder.rb +240 -0
- data/lib/parse/client/caching.rb +203 -0
- data/lib/parse/client/logging.rb +293 -0
- data/lib/parse/client/profiling.rb +181 -0
- data/lib/parse/client/protocol.rb +91 -0
- data/lib/parse/client/request.rb +233 -0
- data/lib/parse/client/response.rb +208 -0
- data/lib/parse/client.rb +1104 -0
- data/lib/parse/clp_scope.rb +361 -0
- data/lib/parse/live_query/circuit_breaker.rb +256 -0
- data/lib/parse/live_query/client.rb +1001 -0
- data/lib/parse/live_query/configuration.rb +224 -0
- data/lib/parse/live_query/event.rb +115 -0
- data/lib/parse/live_query/event_queue.rb +272 -0
- data/lib/parse/live_query/health_monitor.rb +214 -0
- data/lib/parse/live_query/logging.rb +149 -0
- data/lib/parse/live_query/subscription.rb +294 -0
- data/lib/parse/live_query.rb +163 -0
- data/lib/parse/lookup_rewriter.rb +445 -0
- data/lib/parse/model/acl.rb +968 -0
- data/lib/parse/model/associations/belongs_to.rb +275 -0
- data/lib/parse/model/associations/collection_proxy.rb +435 -0
- data/lib/parse/model/associations/has_many.rb +597 -0
- data/lib/parse/model/associations/has_one.rb +158 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +134 -0
- data/lib/parse/model/associations/relation_collection_proxy.rb +177 -0
- data/lib/parse/model/bytes.rb +62 -0
- data/lib/parse/model/classes/audience.rb +262 -0
- data/lib/parse/model/classes/installation.rb +363 -0
- data/lib/parse/model/classes/job_schedule.rb +153 -0
- data/lib/parse/model/classes/job_status.rb +264 -0
- data/lib/parse/model/classes/product.rb +75 -0
- data/lib/parse/model/classes/push_status.rb +263 -0
- data/lib/parse/model/classes/role.rb +751 -0
- data/lib/parse/model/classes/session.rb +201 -0
- data/lib/parse/model/classes/user.rb +943 -0
- data/lib/parse/model/clp.rb +544 -0
- data/lib/parse/model/core/actions.rb +1268 -0
- data/lib/parse/model/core/builder.rb +139 -0
- data/lib/parse/model/core/create_lock.rb +386 -0
- data/lib/parse/model/core/describe.rb +382 -0
- data/lib/parse/model/core/enhanced_change_tracking.rb +159 -0
- data/lib/parse/model/core/errors.rb +38 -0
- data/lib/parse/model/core/fetching.rb +566 -0
- data/lib/parse/model/core/field_guards.rb +220 -0
- data/lib/parse/model/core/indexing.rb +382 -0
- data/lib/parse/model/core/parse_reference.rb +407 -0
- data/lib/parse/model/core/properties.rb +809 -0
- data/lib/parse/model/core/querying.rb +491 -0
- data/lib/parse/model/core/schema.rb +202 -0
- data/lib/parse/model/core/search_indexing.rb +174 -0
- data/lib/parse/model/date.rb +88 -0
- data/lib/parse/model/email.rb +213 -0
- data/lib/parse/model/file.rb +527 -0
- data/lib/parse/model/geojson.rb +271 -0
- data/lib/parse/model/geopoint.rb +261 -0
- data/lib/parse/model/model.rb +260 -0
- data/lib/parse/model/object.rb +2068 -0
- data/lib/parse/model/phone.rb +520 -0
- data/lib/parse/model/pointer.rb +443 -0
- data/lib/parse/model/polygon.rb +406 -0
- data/lib/parse/model/push.rb +975 -0
- data/lib/parse/model/shortnames.rb +8 -0
- data/lib/parse/model/time_zone.rb +141 -0
- data/lib/parse/model/validations/uniqueness_validator.rb +97 -0
- data/lib/parse/model/validations.rb +96 -0
- data/lib/parse/mongodb.rb +2300 -0
- data/lib/parse/pipeline_security.rb +554 -0
- data/lib/parse/query/constraint.rb +198 -0
- data/lib/parse/query/constraints.rb +3279 -0
- data/lib/parse/query/cursor.rb +434 -0
- data/lib/parse/query/n_plus_one_detector.rb +445 -0
- data/lib/parse/query/operation.rb +104 -0
- data/lib/parse/query/ordering.rb +66 -0
- data/lib/parse/query.rb +7028 -0
- data/lib/parse/schema/index_migrator.rb +291 -0
- data/lib/parse/schema/search_index_migrator.rb +289 -0
- data/lib/parse/schema.rb +494 -0
- data/lib/parse/stack/generators/rails.rb +40 -0
- data/lib/parse/stack/generators/templates/model.erb +51 -0
- data/lib/parse/stack/generators/templates/model_installation.rb +4 -0
- data/lib/parse/stack/generators/templates/model_role.rb +4 -0
- data/lib/parse/stack/generators/templates/model_session.rb +4 -0
- data/lib/parse/stack/generators/templates/model_user.rb +11 -0
- data/lib/parse/stack/generators/templates/parse.rb +12 -0
- data/lib/parse/stack/generators/templates/webhooks.rb +10 -0
- data/lib/parse/stack/railtie.rb +18 -0
- data/lib/parse/stack/tasks.rb +563 -0
- data/lib/parse/stack/version.rb +11 -0
- data/lib/parse/stack.rb +455 -0
- data/lib/parse/two_factor_auth/user_extension.rb +449 -0
- data/lib/parse/two_factor_auth.rb +310 -0
- data/lib/parse/webhooks/payload.rb +360 -0
- data/lib/parse/webhooks/registration.rb +199 -0
- data/lib/parse/webhooks/replay_protection.rb +189 -0
- data/lib/parse/webhooks.rb +510 -0
- data/lib/parse-stack-next.rb +5 -0
- data/lib/parse-stack.rb +5 -0
- data/parse-stack-next.gemspec +82 -0
- data/parse-stack.png +0 -0
- data/scripts/debug-ips.js +35 -0
- data/scripts/docker/Dockerfile.parse +13 -0
- data/scripts/docker/atlas-init.js +284 -0
- data/scripts/docker/docker-compose.atlas.yml +76 -0
- data/scripts/docker/docker-compose.test.yml +106 -0
- data/scripts/docker/mongo-init.js +21 -0
- data/scripts/eval_mcp_with_lm_studio.rb +274 -0
- data/scripts/start-parse.sh +90 -0
- data/scripts/start_mcp_server.rb +78 -0
- data/scripts/test_server_connection.rb +82 -0
- metadata +377 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
require_relative "object"
|
|
2
|
+
|
|
3
|
+
# Simple include to use short verion of core class names
|
|
4
|
+
::Installation = Parse::Installation unless defined?(::Installation)
|
|
5
|
+
::Role = Parse::Role unless defined?(::Role)
|
|
6
|
+
::Product = Parse::Product unless defined?(::Product)
|
|
7
|
+
::Session = Parse::Session unless defined?(::Session)
|
|
8
|
+
::User = Parse::User unless defined?(::User)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "active_support"
|
|
5
|
+
require "active_support/values/time_zone"
|
|
6
|
+
require_relative "model"
|
|
7
|
+
|
|
8
|
+
module Parse
|
|
9
|
+
# This class a wrapper around ActiveSupport::TimeZone when using Parse columns that
|
|
10
|
+
# store IANA time zone identifiers (ex. Installation collection). Parse does not have a
|
|
11
|
+
# native time zone data type, but this class is provided to manage and perform timezone-like
|
|
12
|
+
# operation on those properties which you have marked as type _:timezone_.
|
|
13
|
+
#
|
|
14
|
+
# When declaring a property of type :timezone, you may also define a default just like
|
|
15
|
+
# any other property. In addition, the framework will automatically add a validation
|
|
16
|
+
# to make sure that your property is either nil or one of the valid IANA time zone identifiers.
|
|
17
|
+
#
|
|
18
|
+
# Each instance of {Parse::TimeZone} has a {Parse::TimeZone#zone} attribute that provides access to
|
|
19
|
+
# the underlying {http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html ActiveSupport::TimeZone}
|
|
20
|
+
# instance, which you can use to perform time zone operations.
|
|
21
|
+
# @example
|
|
22
|
+
# class Event < Parse::Object
|
|
23
|
+
# # an event occurs in a time zone.
|
|
24
|
+
# property :time_zone, :timezone, default: 'America/Los_Angeles'
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# event = Event.new
|
|
28
|
+
# event.time_zone.name # => 'America/Los_Angeles'
|
|
29
|
+
# event.time_zone.valid? # => true
|
|
30
|
+
#
|
|
31
|
+
# event.time_zone.zone # => ActiveSupport::TimeZone
|
|
32
|
+
# event.time_zone.formatted_offset # => "-08:00"
|
|
33
|
+
#
|
|
34
|
+
# event.time_zone = 'Europe/Paris'
|
|
35
|
+
# event.time_zone.formatted_offset # => +01:00"
|
|
36
|
+
#
|
|
37
|
+
# event.time_zone = 'Galaxy/Andromeda'
|
|
38
|
+
# event.time_zone.valid? # => false
|
|
39
|
+
# @version 1.7.1
|
|
40
|
+
class TimeZone
|
|
41
|
+
# The mapping of TimeZones
|
|
42
|
+
MAPPING = ActiveSupport::TimeZone::MAPPING
|
|
43
|
+
|
|
44
|
+
# Create methods based on the allowable public methods on ActiveSupport::TimeZone.
|
|
45
|
+
# Basically sets up sending forwarding calls to the `zone` object for a Parse::TimeZone object.
|
|
46
|
+
(ActiveSupport::TimeZone.public_instance_methods(false) - [:to_s, :name, :as_json]).each do |meth|
|
|
47
|
+
Parse::TimeZone.class_eval do
|
|
48
|
+
define_method meth do |*args|
|
|
49
|
+
zone.send meth, *args
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Creates a new instance given the IANA identifier (ex. America/Los_Angeles)
|
|
55
|
+
# @overload new(iana)
|
|
56
|
+
# @param iana [String] the IANA identifier (ex. America/Los_Angeles)
|
|
57
|
+
# @return [Parse::TimeZone]
|
|
58
|
+
# @overload new(timezone)
|
|
59
|
+
# You can instantiate a new instance with either a {Parse::TimeZone} or an {http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html ActiveSupport::TimeZone}
|
|
60
|
+
# object.
|
|
61
|
+
# @param timezone [Parse::TimeZone|ActiveSupport::TimeZone] an instance of either timezone class.
|
|
62
|
+
# @return [Parse::TimeZone]
|
|
63
|
+
def initialize(iana)
|
|
64
|
+
if iana.is_a?(String)
|
|
65
|
+
@name = iana
|
|
66
|
+
@zone = nil
|
|
67
|
+
elsif iana.is_a?(::Parse::TimeZone)
|
|
68
|
+
@zone = iana.zone
|
|
69
|
+
@name = nil
|
|
70
|
+
elsif iana.is_a?(::ActiveSupport::TimeZone)
|
|
71
|
+
@zone = iana
|
|
72
|
+
@name = nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @!attribute [rw] name
|
|
77
|
+
# @raise ArgumentError if value is not a string type.
|
|
78
|
+
# @return [String] the IANA identifier for this time zone.
|
|
79
|
+
def name
|
|
80
|
+
@zone.present? ? zone.name : @name
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def name=(iana)
|
|
84
|
+
unless iana.nil? || iana.is_a?(String)
|
|
85
|
+
raise ArgumentError, "Parse::TimeZone#name should be an IANA time zone identifier."
|
|
86
|
+
end
|
|
87
|
+
@name = iana
|
|
88
|
+
@zone = nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @!attribute [rw] zone
|
|
92
|
+
# Returns an instance of {http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html ActiveSupport::TimeZone}
|
|
93
|
+
# based on the IANA identifier. The setter may allow usign an IANA string identifier,
|
|
94
|
+
# a {Parse::TimeZone} or an
|
|
95
|
+
# {http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html ActiveSupport::TimeZone}
|
|
96
|
+
# object.
|
|
97
|
+
# @see #name
|
|
98
|
+
# @raise ArgumentError
|
|
99
|
+
# @return [ActiveSupport::TimeZone]
|
|
100
|
+
def zone
|
|
101
|
+
# lazy load the TimeZone object only when the user requests it, otherwise
|
|
102
|
+
# just keep the name of the string around. Makes encoding/decoding faster.
|
|
103
|
+
if @zone.nil? && @name.present?
|
|
104
|
+
@zone = ::ActiveSupport::TimeZone.new(@name)
|
|
105
|
+
@name = nil # clear out the cache
|
|
106
|
+
end
|
|
107
|
+
@zone
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def zone=(timezone)
|
|
111
|
+
if timezone.is_a?(::ActiveSupport::TimeZone)
|
|
112
|
+
@zone = timezone
|
|
113
|
+
@name = nil
|
|
114
|
+
elsif timezone.is_a?(Parse::TimeZone)
|
|
115
|
+
@name = timezone.name
|
|
116
|
+
@zone = nil
|
|
117
|
+
elsif timezone.nil? || timezone.is_a?(String)
|
|
118
|
+
@name = timezone
|
|
119
|
+
@zone = nil
|
|
120
|
+
else
|
|
121
|
+
raise ArgumentError, "Invalid value passed to Parse::TimeZone#zone."
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# (see #to_s)
|
|
126
|
+
def as_json(*args)
|
|
127
|
+
name
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# @return [String] the IANA identifier for this timezone or nil.
|
|
131
|
+
def to_s
|
|
132
|
+
name
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Returns true or false whether the time zone exists in the {http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html ActiveSupport::TimeZone} mapping.
|
|
136
|
+
# @return [Bool] true if it contains a valid time zone
|
|
137
|
+
def valid?
|
|
138
|
+
ActiveSupport::TimeZone[to_s].present?
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "active_model"
|
|
5
|
+
|
|
6
|
+
module Parse
|
|
7
|
+
module Validations
|
|
8
|
+
# A custom validator that checks if a field value is unique in the Parse collection.
|
|
9
|
+
#
|
|
10
|
+
# This validator queries Parse Server to check if another record exists with the
|
|
11
|
+
# same value for the specified field. It properly handles:
|
|
12
|
+
# - New records (no id yet)
|
|
13
|
+
# - Existing records (excludes self from the check)
|
|
14
|
+
# - Case-insensitive matching (optional)
|
|
15
|
+
# - Scoped uniqueness (unique within a subset of records)
|
|
16
|
+
#
|
|
17
|
+
# @example Basic uniqueness
|
|
18
|
+
# class User < Parse::Object
|
|
19
|
+
# property :email, :string
|
|
20
|
+
# validates :email, uniqueness: true
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @example Case-insensitive uniqueness
|
|
24
|
+
# class User < Parse::Object
|
|
25
|
+
# property :username, :string
|
|
26
|
+
# validates :username, uniqueness: { case_sensitive: false }
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @example Scoped uniqueness (unique within an organization)
|
|
30
|
+
# class Employee < Parse::Object
|
|
31
|
+
# property :employee_id, :string
|
|
32
|
+
# belongs_to :organization
|
|
33
|
+
# validates :employee_id, uniqueness: { scope: :organization }
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# @example With custom message
|
|
37
|
+
# class User < Parse::Object
|
|
38
|
+
# property :email, :string
|
|
39
|
+
# validates :email, uniqueness: { message: "is already registered" }
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
class UniquenessValidator < ActiveModel::EachValidator
|
|
43
|
+
# @param record [Parse::Object] the object being validated
|
|
44
|
+
# @param attribute [Symbol] the attribute name being validated
|
|
45
|
+
# @param value [Object] the current value of the attribute
|
|
46
|
+
def validate_each(record, attribute, value)
|
|
47
|
+
return if value.blank? && options[:allow_blank]
|
|
48
|
+
return if value.nil? && options[:allow_nil]
|
|
49
|
+
|
|
50
|
+
# Build the query to check for existing records
|
|
51
|
+
klass = record.class
|
|
52
|
+
|
|
53
|
+
# Get the Parse field name for this attribute (available for debugging)
|
|
54
|
+
_parse_field = klass.field_map[attribute] || attribute.to_s.columnize
|
|
55
|
+
|
|
56
|
+
# Build query conditions
|
|
57
|
+
conditions = {}
|
|
58
|
+
|
|
59
|
+
if options[:case_sensitive] == false && value.is_a?(String)
|
|
60
|
+
# Case-insensitive search using regex
|
|
61
|
+
conditions[attribute.to_sym] = /\A#{Regexp.escape(value)}\z/i
|
|
62
|
+
else
|
|
63
|
+
conditions[attribute.to_sym] = value
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Add scope conditions if specified
|
|
67
|
+
if options[:scope]
|
|
68
|
+
scope_fields = Array(options[:scope])
|
|
69
|
+
scope_fields.each do |scope_field|
|
|
70
|
+
scope_value = record.send(scope_field)
|
|
71
|
+
conditions[scope_field.to_sym] = scope_value
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Build and execute the query
|
|
76
|
+
query = klass.query(conditions)
|
|
77
|
+
|
|
78
|
+
# Exclude the current record if it's not new
|
|
79
|
+
unless record.new?
|
|
80
|
+
query.where(:id.not => record.id)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Check if any matching records exist
|
|
84
|
+
query.limit(1)
|
|
85
|
+
existing = query.first
|
|
86
|
+
|
|
87
|
+
if existing.present?
|
|
88
|
+
error_message = options[:message] || "has already been taken"
|
|
89
|
+
record.errors.add(attribute, error_message)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Register the validator with ActiveModel so it can be used with validates helper
|
|
97
|
+
ActiveModel::Validations::UniquenessValidator = Parse::Validations::UniquenessValidator
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Load all custom validators for Parse Stack
|
|
5
|
+
require_relative "validations/uniqueness_validator"
|
|
6
|
+
|
|
7
|
+
module Parse
|
|
8
|
+
# The Validations module provides custom validators for Parse::Object subclasses.
|
|
9
|
+
#
|
|
10
|
+
# Parse Stack builds on ActiveModel::Validations, which means all standard Rails
|
|
11
|
+
# validations are available:
|
|
12
|
+
#
|
|
13
|
+
# - `validates :field, presence: true`
|
|
14
|
+
# - `validates :field, length: { minimum: 1, maximum: 200 }`
|
|
15
|
+
# - `validates :field, numericality: { greater_than: 0 }`
|
|
16
|
+
# - `validates :field, format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i }`
|
|
17
|
+
# - `validates :field, inclusion: { in: %w[small medium large] }`
|
|
18
|
+
# - `validates :field, exclusion: { in: %w[admin root] }`
|
|
19
|
+
#
|
|
20
|
+
# In addition, Parse Stack provides:
|
|
21
|
+
#
|
|
22
|
+
# - `validates :field, uniqueness: true` - Queries Parse to ensure uniqueness
|
|
23
|
+
#
|
|
24
|
+
# @example Full validation example
|
|
25
|
+
# class User < Parse::Object
|
|
26
|
+
# property :email, :string
|
|
27
|
+
# property :username, :string
|
|
28
|
+
# property :age, :integer
|
|
29
|
+
# property :status, :string
|
|
30
|
+
#
|
|
31
|
+
# # Standard ActiveModel validations
|
|
32
|
+
# validates :email, presence: true,
|
|
33
|
+
# format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
34
|
+
# validates :username, presence: true,
|
|
35
|
+
# length: { minimum: 3, maximum: 30 }
|
|
36
|
+
# validates :age, numericality: { greater_than_or_equal_to: 0 },
|
|
37
|
+
# allow_nil: true
|
|
38
|
+
# validates :status, inclusion: { in: %w[active inactive pending] }
|
|
39
|
+
#
|
|
40
|
+
# # Parse-specific uniqueness validation
|
|
41
|
+
# validates :email, uniqueness: true
|
|
42
|
+
# validates :username, uniqueness: { case_sensitive: false }
|
|
43
|
+
#
|
|
44
|
+
# # Custom validation method
|
|
45
|
+
# validate :email_domain_allowed
|
|
46
|
+
#
|
|
47
|
+
# private
|
|
48
|
+
#
|
|
49
|
+
# def email_domain_allowed
|
|
50
|
+
# return if email.blank?
|
|
51
|
+
# domain = email.split('@').last
|
|
52
|
+
# unless %w[company.com partner.org].include?(domain)
|
|
53
|
+
# errors.add(:email, "must be from an allowed domain")
|
|
54
|
+
# end
|
|
55
|
+
# end
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# @example Validation callbacks
|
|
59
|
+
# class Song < Parse::Object
|
|
60
|
+
# property :title, :string
|
|
61
|
+
#
|
|
62
|
+
# validates :title, presence: true
|
|
63
|
+
#
|
|
64
|
+
# before_validation :normalize_title
|
|
65
|
+
# after_validation :log_validation_result
|
|
66
|
+
#
|
|
67
|
+
# private
|
|
68
|
+
#
|
|
69
|
+
# def normalize_title
|
|
70
|
+
# self.title = title.strip.titleize if title.present?
|
|
71
|
+
# end
|
|
72
|
+
#
|
|
73
|
+
# def log_validation_result
|
|
74
|
+
# if errors.any?
|
|
75
|
+
# puts "Validation failed: #{errors.full_messages.join(', ')}"
|
|
76
|
+
# end
|
|
77
|
+
# end
|
|
78
|
+
# end
|
|
79
|
+
#
|
|
80
|
+
# @example Conditional validations
|
|
81
|
+
# class Order < Parse::Object
|
|
82
|
+
# property :status, :string
|
|
83
|
+
# property :shipping_address, :string
|
|
84
|
+
# property :tracking_number, :string
|
|
85
|
+
#
|
|
86
|
+
# validates :shipping_address, presence: true, if: :requires_shipping?
|
|
87
|
+
# validates :tracking_number, presence: true, if: -> { status == "shipped" }
|
|
88
|
+
#
|
|
89
|
+
# def requires_shipping?
|
|
90
|
+
# status.in?(%w[processing shipped delivered])
|
|
91
|
+
# end
|
|
92
|
+
# end
|
|
93
|
+
#
|
|
94
|
+
module Validations
|
|
95
|
+
end
|
|
96
|
+
end
|