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,443 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "active_model"
|
|
5
|
+
require "active_support"
|
|
6
|
+
require "active_support/inflector"
|
|
7
|
+
require "active_support/core_ext"
|
|
8
|
+
require "active_model/serializers/json"
|
|
9
|
+
require_relative "model"
|
|
10
|
+
|
|
11
|
+
module Parse
|
|
12
|
+
|
|
13
|
+
# The Pointer class represents the pointer type in Parse and is the superclass
|
|
14
|
+
# of Parse::Object types. A pointer can be considered a type of Parse::Object
|
|
15
|
+
# in which only the class name and id is known. In most cases, you may not
|
|
16
|
+
# deal with Parse::Pointer objects directly if you have defined all your
|
|
17
|
+
# Parse::Object subclasses.
|
|
18
|
+
#
|
|
19
|
+
# A `Parse::Pointer` only contains data about the specific Parse class and
|
|
20
|
+
# the `id` for the object. Therefore, creating an instance of any
|
|
21
|
+
# Parse::Object subclass with only the `:id` field set will be
|
|
22
|
+
# considered in "pointer" state even though its specific class is not
|
|
23
|
+
# `Parse::Pointer` type. The only case that you may have a Parse::Pointer
|
|
24
|
+
# is in the case where an object was received for one of your classes and
|
|
25
|
+
# the framework has no registered class handler for it.
|
|
26
|
+
# Assume you have the tables `Post`, `Comment` and `Author` defined in your
|
|
27
|
+
# remote Parse database, but have only defined `Post` and `Commentary`
|
|
28
|
+
# locally.
|
|
29
|
+
# @example
|
|
30
|
+
# class Post < Parse::Object
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# class Commentary < Parse::Object
|
|
34
|
+
# belongs_to :post
|
|
35
|
+
# belongs_to :author
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# comment = Commentary.first
|
|
39
|
+
# comment.post? # true because it is non-nil
|
|
40
|
+
# comment.artist? # true because it is non-nil
|
|
41
|
+
#
|
|
42
|
+
# # both are true because they are in a Pointer state
|
|
43
|
+
# comment.post.pointer? # true
|
|
44
|
+
# comment.author.pointer? # true
|
|
45
|
+
#
|
|
46
|
+
# # we have defined a Post class handler
|
|
47
|
+
# comment.post # <Post @parse_class="Post", @id="xdqcCqfngz">
|
|
48
|
+
#
|
|
49
|
+
# # we have not defined an Author class handler
|
|
50
|
+
# comment.author # <Parse::Pointer @parse_class="Author", @id="hZLbW6ofKC">
|
|
51
|
+
#
|
|
52
|
+
#
|
|
53
|
+
# comment.post.fetch # fetch the relation
|
|
54
|
+
# comment.post.pointer? # false, it is now a full object.
|
|
55
|
+
#
|
|
56
|
+
# The effect is that for any unknown classes that the framework encounters,
|
|
57
|
+
# it will generate Parse::Pointer instances until you define those classes
|
|
58
|
+
# with valid properties and associations. While this might be ok for some
|
|
59
|
+
# classes you do not use, we still recommend defining all your Parse classes
|
|
60
|
+
# locally in the framework.
|
|
61
|
+
#
|
|
62
|
+
# Once you have a subclass, you may also create a Parse::Pointer object using
|
|
63
|
+
# the _pointer_ method.
|
|
64
|
+
# @example
|
|
65
|
+
# Parse::User.pointer("123456") # => Parse::Pointer for "_User" class
|
|
66
|
+
#
|
|
67
|
+
# @see Parse::Object
|
|
68
|
+
class Pointer < Model
|
|
69
|
+
# The default attributes in a Parse Pointer hash.
|
|
70
|
+
ATTRIBUTES = { __type: :string, className: :string, objectId: :string }.freeze
|
|
71
|
+
# Permitted character set + length for a Parse objectId. Parse Server
|
|
72
|
+
# itself generates 10-char `[A-Za-z0-9]` ids; with
|
|
73
|
+
# `allowCustomObjectId: true` apps can pass arbitrary identifiers, so
|
|
74
|
+
# we accept the wider URL-safe set `[A-Za-z0-9_.-]` and cap length
|
|
75
|
+
# at 64. Anything OUTSIDE this set (`/`, `\`, CR/LF, `?`, `&`, `#`,
|
|
76
|
+
# `%`, quotes, angle brackets, semicolons, whitespace) is rejected
|
|
77
|
+
# — those are the bytes that turn a `Pointer.id=` write into a
|
|
78
|
+
# path-traversal, header-injection, or batch-op-path-poisoning
|
|
79
|
+
# vector when interpolated into REST URLs or batch op `path` fields.
|
|
80
|
+
OBJECT_ID_FORMAT = /\A[A-Za-z0-9_.\-]{1,64}\z/.freeze
|
|
81
|
+
|
|
82
|
+
# @return [String] the name of the Parse class for this pointer.
|
|
83
|
+
attr_accessor :parse_class
|
|
84
|
+
# @return [String] the objectId field
|
|
85
|
+
attr_reader :id
|
|
86
|
+
|
|
87
|
+
# Assign the Parse objectId. Empty / nil values are permitted (Pointer
|
|
88
|
+
# in unbound state); non-empty values must match {OBJECT_ID_FORMAT}.
|
|
89
|
+
# @raise [ArgumentError] when +value+ is a non-empty string that does
|
|
90
|
+
# not match the format.
|
|
91
|
+
def id=(value)
|
|
92
|
+
if value.nil?
|
|
93
|
+
@id = nil
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
str = value.to_s
|
|
97
|
+
if str.empty?
|
|
98
|
+
@id = str
|
|
99
|
+
return
|
|
100
|
+
end
|
|
101
|
+
unless OBJECT_ID_FORMAT.match?(str)
|
|
102
|
+
raise ArgumentError,
|
|
103
|
+
"Invalid Parse objectId #{str.inspect}: must match #{OBJECT_ID_FORMAT.source}. " \
|
|
104
|
+
"Refusing to assign a value that could be a path-traversal or injection payload."
|
|
105
|
+
end
|
|
106
|
+
@id = str
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @return [Model::TYPE_POINTER]
|
|
110
|
+
def __type; Parse::Model::TYPE_POINTER; end
|
|
111
|
+
|
|
112
|
+
alias_method :className, :parse_class
|
|
113
|
+
# A Parse object as a className field and objectId. In ruby, we will use the
|
|
114
|
+
# id attribute method, but for usability, we will also alias it to objectId
|
|
115
|
+
alias_method :objectId, :id
|
|
116
|
+
|
|
117
|
+
# A Parse pointer only requires the name of the remote Parse collection name,
|
|
118
|
+
# and the `objectId` of the record.
|
|
119
|
+
# @param table [String] The Parse class name in the Parse database.
|
|
120
|
+
# @param oid [String] The objectId
|
|
121
|
+
def initialize(table, oid)
|
|
122
|
+
@parse_class = table.to_s
|
|
123
|
+
self.id = oid
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @return [String] the name of the collection for this Pointer.
|
|
127
|
+
def parse_class
|
|
128
|
+
@parse_class
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @return [String] a string representing the class and id of this instance.
|
|
132
|
+
def sig
|
|
133
|
+
"#{@parse_class}##{id || "new"}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @return [Hash]
|
|
137
|
+
def attributes
|
|
138
|
+
ATTRIBUTES
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @return [Hash] serialized JSON structure
|
|
142
|
+
def json_hash
|
|
143
|
+
JSON.parse to_json
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Create a new pointer with the current class name and id. While this may not make sense
|
|
147
|
+
# for a pointer instance, Parse::Object subclasses use this inherited method to turn themselves into
|
|
148
|
+
# pointer objects.
|
|
149
|
+
# @example
|
|
150
|
+
# user = Parse::User.first
|
|
151
|
+
# user.pointer # => Parse::Pointer("_User", user.id)
|
|
152
|
+
#
|
|
153
|
+
# @return [Pointer] a new Pointer for this object.
|
|
154
|
+
# @see Parse::Object
|
|
155
|
+
def pointer
|
|
156
|
+
Pointer.new parse_class, @id
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Whether this instance is in pointer state. A pointer is determined
|
|
160
|
+
# if we have a parse class and an id, but no created_at or updated_at fields.
|
|
161
|
+
# @return [Boolean] true if instance is in pointer state.
|
|
162
|
+
def pointer?
|
|
163
|
+
present? && @created_at.blank? && @updated_at.blank?
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns true if the data for this instance has been fetched. Because of some autofetching
|
|
167
|
+
# mechanisms, this is useful to know whether the object already has data without actually causing
|
|
168
|
+
# a fetch of the data.
|
|
169
|
+
# @return [Boolean] true if not in pointer state.
|
|
170
|
+
def fetched?
|
|
171
|
+
present? && pointer? == false
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# This method is a general implementation that gets overriden by Parse::Object subclass.
|
|
175
|
+
# Given the class name and the id, we will go to Parse and fetch the actual record, returning the
|
|
176
|
+
# Parse::Object by default.
|
|
177
|
+
# @overload fetch
|
|
178
|
+
# Full fetch - fetches all fields
|
|
179
|
+
# @return [Parse::Object] the fetched Parse::Object, nil otherwise.
|
|
180
|
+
# @overload fetch(return_object)
|
|
181
|
+
# Legacy signature for backward compatibility.
|
|
182
|
+
# @param return_object [Boolean] if true returns object, if false returns JSON
|
|
183
|
+
# @return [Parse::Object, Hash] the object or raw JSON data
|
|
184
|
+
# @overload fetch(keys:, includes:, cache:)
|
|
185
|
+
# Partial fetch - fetches only specified fields
|
|
186
|
+
# @param keys [Array<Symbol, String>, nil] optional list of fields to fetch (partial fetch).
|
|
187
|
+
# @param includes [Array<String>, nil] optional list of pointer fields to expand.
|
|
188
|
+
# @param cache [Boolean, Symbol, Integer] caching mode:
|
|
189
|
+
# - true - read from and write to cache
|
|
190
|
+
# - false - completely bypass cache
|
|
191
|
+
# - :write_only - skip cache read, but update cache with fresh data
|
|
192
|
+
# - Integer - cache for specific number of seconds
|
|
193
|
+
# @return [Parse::Object] a partially fetched Parse::Object, nil otherwise.
|
|
194
|
+
def fetch(return_object = nil, keys: nil, includes: nil, cache: nil)
|
|
195
|
+
# Handle legacy signature: fetch(false) returns JSON
|
|
196
|
+
if return_object == false
|
|
197
|
+
return fetch_json(keys: keys, includes: includes)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Build query parameters for partial fetch
|
|
201
|
+
query = {}
|
|
202
|
+
if keys.present?
|
|
203
|
+
keys_array = Array(keys).map { |k| Parse::Query.format_field(k) }
|
|
204
|
+
query[:keys] = keys_array.join(",")
|
|
205
|
+
end
|
|
206
|
+
if includes.present?
|
|
207
|
+
includes_array = Array(includes).map(&:to_s)
|
|
208
|
+
query[:include] = includes_array.join(",")
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Build opts for caching
|
|
212
|
+
opts = {}
|
|
213
|
+
opts[:cache] = cache unless cache.nil?
|
|
214
|
+
|
|
215
|
+
response = client.fetch_object(parse_class, id, query: query.presence, **opts)
|
|
216
|
+
return nil if response.error?
|
|
217
|
+
|
|
218
|
+
# Check if the result is empty - this indicates object not found
|
|
219
|
+
result = response.result
|
|
220
|
+
if result.nil? || (result.is_a?(Array) && result.empty?)
|
|
221
|
+
return nil
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Convert the JSON result to a proper Parse::Object
|
|
225
|
+
return nil unless result.is_a?(Hash)
|
|
226
|
+
|
|
227
|
+
# Try to find the appropriate Parse class, fallback to Parse::Object
|
|
228
|
+
klass = Parse::Model.find_class(parse_class) || Parse::Object
|
|
229
|
+
|
|
230
|
+
# For partial fetch, build with fetched_keys tracking
|
|
231
|
+
if keys.present?
|
|
232
|
+
# Parse keys to get top-level field names and nested keys
|
|
233
|
+
top_level_keys = Array(keys).map { |k| Parse::Query.format_field(k).split(".").first.to_sym }
|
|
234
|
+
top_level_keys << :id unless top_level_keys.include?(:id)
|
|
235
|
+
top_level_keys << :objectId unless top_level_keys.include?(:objectId)
|
|
236
|
+
top_level_keys.uniq!
|
|
237
|
+
|
|
238
|
+
# Parse dot notation into nested fetched keys
|
|
239
|
+
nested_keys = Parse::Query.parse_keys_to_nested_keys(Array(keys))
|
|
240
|
+
|
|
241
|
+
obj = klass.build(result, parse_class, fetched_keys: top_level_keys, nested_fetched_keys: nested_keys.presence)
|
|
242
|
+
else
|
|
243
|
+
# Full fetch - create without partial fetch tracking. Trusted
|
|
244
|
+
# hydration: +result+ is the server response body, which
|
|
245
|
+
# legitimately carries +createdAt+/+updatedAt+/+sessionToken+
|
|
246
|
+
# and other PROTECTED_MASS_ASSIGNMENT_KEYS. The +@_trusted_init+
|
|
247
|
+
# ivar tells {Parse::Object#initialize} to skip the protected-key
|
|
248
|
+
# filter — see that method for why we don't use a kwarg.
|
|
249
|
+
obj = klass.allocate
|
|
250
|
+
obj.instance_variable_set(:@_trusted_init, true)
|
|
251
|
+
obj.send(:initialize, result)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
obj.clear_changes! if obj.respond_to?(:clear_changes!)
|
|
255
|
+
obj
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Returns raw JSON data from the server without creating an object.
|
|
259
|
+
# @param keys [Array<Symbol, String>, nil] optional list of fields to fetch.
|
|
260
|
+
# @param includes [Array<String>, nil] optional list of pointer fields to expand.
|
|
261
|
+
# @return [Hash, nil] the raw JSON data or nil if error.
|
|
262
|
+
def fetch_json(keys: nil, includes: nil)
|
|
263
|
+
query = {}
|
|
264
|
+
if keys.present?
|
|
265
|
+
keys_array = Array(keys).map { |k| Parse::Query.format_field(k) }
|
|
266
|
+
query[:keys] = keys_array.join(",")
|
|
267
|
+
end
|
|
268
|
+
if includes.present?
|
|
269
|
+
includes_array = Array(includes).map(&:to_s)
|
|
270
|
+
query[:include] = includes_array.join(",")
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
response = client.fetch_object(parse_class, id, query: query.presence)
|
|
274
|
+
return nil if response.error?
|
|
275
|
+
response.result
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Fetches the Parse object from the data store and returns a Parse::Object instance.
|
|
279
|
+
# This is a convenience method that calls fetch.
|
|
280
|
+
# @return [Parse::Object] the fetched Parse::Object, nil otherwise.
|
|
281
|
+
def fetch_object
|
|
282
|
+
fetch
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Fetches the pointer with explicit caching enabled and returns a Parse::Object.
|
|
286
|
+
# This is a convenience method that calls fetch with cache: true.
|
|
287
|
+
# Use this when you want to leverage cached responses for better performance.
|
|
288
|
+
# @param keys [Array<Symbol, String>, nil] optional list of fields to fetch (partial fetch).
|
|
289
|
+
# @param includes [Array<String>, nil] optional list of pointer fields to expand.
|
|
290
|
+
# @return [Parse::Object] the fetched Parse::Object, nil otherwise.
|
|
291
|
+
# @example Fetch pointer with caching
|
|
292
|
+
# capture = capture_pointer.fetch_cache!
|
|
293
|
+
# @example Partial fetch with caching
|
|
294
|
+
# capture = capture_pointer.fetch_cache!(keys: [:title, :status])
|
|
295
|
+
# @see #fetch
|
|
296
|
+
def fetch_cache!(keys: nil, includes: nil)
|
|
297
|
+
fetch(keys: keys, includes: includes, cache: true)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Two Parse::Pointers (or Parse::Objects) are equal if both of them have
|
|
301
|
+
# the same Parse class and the same id.
|
|
302
|
+
# @return [Boolean]
|
|
303
|
+
def ==(o)
|
|
304
|
+
return false unless o.is_a?(Pointer)
|
|
305
|
+
#only equal if the Parse class and object ID are the same.
|
|
306
|
+
self.parse_class == o.parse_class && id == o.id
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
alias_method :eql?, :==
|
|
310
|
+
|
|
311
|
+
# Compute a hash-code for this object based on identity (class and id).
|
|
312
|
+
# This is consistent with the == method which compares by parse_class and id.
|
|
313
|
+
#
|
|
314
|
+
# Two objects with the same class and id will have the same hash code
|
|
315
|
+
# regardless of their dirty state or other attributes. This is important for:
|
|
316
|
+
# - Array operations (uniq, &, |) to work correctly based on identity
|
|
317
|
+
# - Hash key lookups to find objects by identity
|
|
318
|
+
# - Set operations
|
|
319
|
+
#
|
|
320
|
+
# @return [Integer] hash code based on class name and object id
|
|
321
|
+
def hash
|
|
322
|
+
[parse_class, id].hash
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# @return [Boolean] true if instance has a Parse class and an id.
|
|
326
|
+
def present?
|
|
327
|
+
parse_class.present? && @id.present?
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Access the pointer properties through hash accessor. This is done for
|
|
331
|
+
# compatibility with the hash access of a Parse::Object. This method
|
|
332
|
+
# returns nil if the key is not one of: :id, :objectId, or :className.
|
|
333
|
+
# @param key [String] the name of the property.
|
|
334
|
+
# @return [Object] the value for this key.
|
|
335
|
+
def [](key)
|
|
336
|
+
return nil unless [:id, :objectId, :className].include?(key.to_sym)
|
|
337
|
+
send(key)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Handles method calls for properties that exist on the target model class.
|
|
341
|
+
# When a property is accessed on a Pointer, this will auto-fetch the object
|
|
342
|
+
# and delegate the method call to the fetched object.
|
|
343
|
+
#
|
|
344
|
+
# If Parse.autofetch_raise_on_missing_keys is enabled, this will raise
|
|
345
|
+
# Parse::AutofetchTriggeredError instead of fetching.
|
|
346
|
+
#
|
|
347
|
+
# @example
|
|
348
|
+
# pointer = Post.pointer("abc123")
|
|
349
|
+
# pointer.title # auto-fetches and returns title
|
|
350
|
+
#
|
|
351
|
+
# @param method_name [Symbol] the method being called
|
|
352
|
+
# @param args [Array] arguments to the method
|
|
353
|
+
# @param block [Proc] optional block
|
|
354
|
+
# @return [Object] the result of calling the method on the fetched object
|
|
355
|
+
# @raise [Parse::AutofetchTriggeredError] if autofetch_raise_on_missing_keys is enabled
|
|
356
|
+
def method_missing(method_name, *args, &block)
|
|
357
|
+
# Try to find the model class for this pointer
|
|
358
|
+
klass = Parse::Model.find_class(parse_class)
|
|
359
|
+
|
|
360
|
+
# If no class is registered or the class doesn't have this field, use default behavior
|
|
361
|
+
unless klass && klass.respond_to?(:fields) && klass.fields[method_name.to_s.chomp("=").to_sym]
|
|
362
|
+
return super
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# We have a registered class with this field - handle autofetch
|
|
366
|
+
field_name = method_name.to_s.chomp("=").to_sym
|
|
367
|
+
|
|
368
|
+
# If autofetch_raise_on_missing_keys is enabled, raise an error
|
|
369
|
+
if Parse.autofetch_raise_on_missing_keys
|
|
370
|
+
raise Parse::AutofetchTriggeredError.new(klass, id, field_name, is_pointer: true)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Log info about autofetch being triggered
|
|
374
|
+
if Parse.warn_on_query_issues
|
|
375
|
+
puts "[Parse::Autofetch] Fetching #{parse_class}##{id} - pointer accessed field :#{field_name} (silence with Parse.warn_on_query_issues = false)"
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Fetch the object and delegate the method call
|
|
379
|
+
@_fetched_object ||= fetch
|
|
380
|
+
return nil unless @_fetched_object
|
|
381
|
+
|
|
382
|
+
@_fetched_object.send(method_name, *args, &block)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Indicates whether this object responds to methods that would trigger autofetch.
|
|
386
|
+
# Returns true for properties defined on the target model class.
|
|
387
|
+
#
|
|
388
|
+
# @param method_name [Symbol] the method name to check
|
|
389
|
+
# @param include_private [Boolean] whether to include private methods
|
|
390
|
+
# @return [Boolean] true if the method can be handled
|
|
391
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
392
|
+
klass = Parse::Model.find_class(parse_class)
|
|
393
|
+
if klass && klass.respond_to?(:fields)
|
|
394
|
+
field_name = method_name.to_s.chomp("=").to_sym
|
|
395
|
+
return true if klass.fields[field_name]
|
|
396
|
+
end
|
|
397
|
+
super
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Set the pointer properties through hash accessor. This is done for
|
|
401
|
+
# compatibility with the hash access of a Parse::Object. This method
|
|
402
|
+
# does nothing if the key is not one of: :id, :objectId, or :className.
|
|
403
|
+
# @param key (see #[])
|
|
404
|
+
# @return [Object]
|
|
405
|
+
def []=(key, value)
|
|
406
|
+
return unless [:id, :objectId, :className].include?(key.to_sym)
|
|
407
|
+
send("#{key}=", value)
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# extensions
|
|
413
|
+
class Array
|
|
414
|
+
# This method maps all the ids (String) of all Parse::Objects in the array.
|
|
415
|
+
# @return [Array<String>] an array of strings of ids.
|
|
416
|
+
def objectIds
|
|
417
|
+
map { |m| m.is_a?(Parse::Pointer) ? m.id : nil }.compact
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Filter all objects in the array that do not inherit from Parse::Pointer or
|
|
421
|
+
# Parse::Object.
|
|
422
|
+
# @return [Array<Parse::Object,Parse::Pointer>] an array of Parse::Objects.
|
|
423
|
+
def valid_parse_objects
|
|
424
|
+
select { |s| s.is_a?(Parse::Pointer) }
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Convert all potential objects in the array to a list of Parse::Pointer instances.
|
|
428
|
+
# The array can contain a mixture of objects types including JSON Parse-like hashes.
|
|
429
|
+
# @return [Array<Parse::Pointer>] an array of Parse::Pointer objects.
|
|
430
|
+
def parse_pointers(table = nil)
|
|
431
|
+
self.map do |m|
|
|
432
|
+
#if its an exact Parse::Pointer
|
|
433
|
+
if m.is_a?(Parse::Pointer) || m.respond_to?(:pointer)
|
|
434
|
+
next m.pointer
|
|
435
|
+
elsif m.is_a?(Hash) && m[Parse::Model::KEY_CLASS_NAME] && m[Parse::Model::OBJECT_ID]
|
|
436
|
+
next Parse::Pointer.new m[Parse::Model::KEY_CLASS_NAME], m[Parse::Model::OBJECT_ID]
|
|
437
|
+
elsif m.is_a?(Hash) && m[:className] && m[:objectId]
|
|
438
|
+
next Parse::Pointer.new m[:className], m[:objectId]
|
|
439
|
+
end
|
|
440
|
+
nil
|
|
441
|
+
end.compact
|
|
442
|
+
end
|
|
443
|
+
end
|