parse-stack-next 5.6.0 → 5.7.1

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.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +489 -0
  3. data/README.md +378 -6
  4. data/docs/caching.md +748 -0
  5. data/examples/basic_client.rb +3 -3
  6. data/examples/basic_server.rb +3 -3
  7. data/examples/live_query_listener.rb +12 -12
  8. data/examples/rag_chatbot.rb +14 -14
  9. data/examples/transaction_example.rb +44 -45
  10. data/examples/webhook_server.rb +3 -3
  11. data/lib/parse/access.rb +493 -0
  12. data/lib/parse/acl_scope.rb +125 -52
  13. data/lib/parse/agent/approval_gate.rb +0 -0
  14. data/lib/parse/agent/cancellation_token.rb +3 -3
  15. data/lib/parse/agent/constraint_translator.rb +4 -4
  16. data/lib/parse/agent/describe.rb +34 -34
  17. data/lib/parse/agent/errors.rb +9 -9
  18. data/lib/parse/agent/mcp_client.rb +61 -59
  19. data/lib/parse/agent/mcp_dispatcher.rb +89 -101
  20. data/lib/parse/agent/mcp_rack_app.rb +92 -93
  21. data/lib/parse/agent/mcp_server.rb +1 -1
  22. data/lib/parse/agent/mcp_subscriptions.rb +22 -22
  23. data/lib/parse/agent/metadata_audit.rb +1 -2
  24. data/lib/parse/agent/metadata_dsl.rb +7 -7
  25. data/lib/parse/agent/metadata_registry.rb +12 -12
  26. data/lib/parse/agent/prompt_hardening.rb +4 -4
  27. data/lib/parse/agent/prompts.rb +10 -10
  28. data/lib/parse/agent/result_formatter.rb +4 -4
  29. data/lib/parse/agent/tools.rb +493 -400
  30. data/lib/parse/agent.rb +157 -116
  31. data/lib/parse/api/hooks.rb +1 -1
  32. data/lib/parse/api/server.rb +2 -2
  33. data/lib/parse/api/users.rb +2 -2
  34. data/lib/parse/atlas_search/index_manager.rb +1 -1
  35. data/lib/parse/atlas_search/session.rb +40 -218
  36. data/lib/parse/atlas_search.rb +181 -60
  37. data/lib/parse/authorization.rb +466 -0
  38. data/lib/parse/cache/invalidation.rb +219 -0
  39. data/lib/parse/cache/keyspace.rb +306 -0
  40. data/lib/parse/cache/moneta_surface.rb +126 -0
  41. data/lib/parse/cache/pool.rb +48 -5
  42. data/lib/parse/cache/redis.rb +425 -10
  43. data/lib/parse/cache/scoped_view.rb +443 -0
  44. data/lib/parse/cache/sub_cache.rb +264 -0
  45. data/lib/parse/cache/upstream_roles.rb +230 -0
  46. data/lib/parse/client/authentication.rb +1 -1
  47. data/lib/parse/client/body_builder.rb +1 -2
  48. data/lib/parse/client/caching.rb +84 -5
  49. data/lib/parse/client.rb +327 -189
  50. data/lib/parse/clp_scope.rb +225 -28
  51. data/lib/parse/console.rb +3 -3
  52. data/lib/parse/embeddings/batch_embedder.rb +3 -3
  53. data/lib/parse/embeddings/cache.rb +17 -17
  54. data/lib/parse/embeddings/cohere.rb +21 -22
  55. data/lib/parse/embeddings/image_fetch.rb +22 -21
  56. data/lib/parse/embeddings/jina.rb +21 -21
  57. data/lib/parse/embeddings/local_http.rb +6 -7
  58. data/lib/parse/embeddings/media_file.rb +5 -4
  59. data/lib/parse/embeddings/openai.rb +5 -5
  60. data/lib/parse/embeddings/provider.rb +1 -1
  61. data/lib/parse/embeddings/qwen.rb +10 -10
  62. data/lib/parse/embeddings/spend_cap.rb +2 -3
  63. data/lib/parse/embeddings/streaming_body.rb +11 -11
  64. data/lib/parse/embeddings/video_source.rb +7 -6
  65. data/lib/parse/embeddings/voyage.rb +59 -61
  66. data/lib/parse/embeddings.rb +28 -27
  67. data/lib/parse/graphql/type_generator.rb +1 -1
  68. data/lib/parse/graphql.rb +5 -5
  69. data/lib/parse/live_query/client.rb +1 -1
  70. data/lib/parse/live_query.rb +1 -1
  71. data/lib/parse/lock.rb +15 -18
  72. data/lib/parse/lock_backend.rb +1 -1
  73. data/lib/parse/lookup_rewriter.rb +6 -6
  74. data/lib/parse/model/associations/belongs_to.rb +4 -0
  75. data/lib/parse/model/associations/collection_proxy.rb +3 -0
  76. data/lib/parse/model/associations/has_many.rb +4 -0
  77. data/lib/parse/model/classes/role.rb +227 -28
  78. data/lib/parse/model/classes/user.rb +60 -1
  79. data/lib/parse/model/clp.rb +4 -4
  80. data/lib/parse/model/core/actions.rb +366 -113
  81. data/lib/parse/model/core/create_lock.rb +0 -2
  82. data/lib/parse/model/core/describe.rb +57 -57
  83. data/lib/parse/model/core/embed_managed.rb +21 -23
  84. data/lib/parse/model/core/fetching.rb +6 -0
  85. data/lib/parse/model/core/indexing.rb +14 -14
  86. data/lib/parse/model/core/parse_reference.rb +1 -1
  87. data/lib/parse/model/core/properties.rb +6 -2
  88. data/lib/parse/model/core/querying.rb +1 -1
  89. data/lib/parse/model/core/schema.rb +2 -2
  90. data/lib/parse/model/core/search_indexing.rb +2 -2
  91. data/lib/parse/model/core/vector_searchable.rb +4 -5
  92. data/lib/parse/model/file.rb +21 -15
  93. data/lib/parse/model/geojson.rb +2 -2
  94. data/lib/parse/model/geopoint.rb +1 -0
  95. data/lib/parse/model/object.rb +136 -66
  96. data/lib/parse/model/pointer.rb +2 -0
  97. data/lib/parse/model/polygon.rb +3 -6
  98. data/lib/parse/model/push.rb +2 -2
  99. data/lib/parse/model/vector.rb +3 -1
  100. data/lib/parse/mongodb.rb +512 -282
  101. data/lib/parse/pipeline_security.rb +3 -0
  102. data/lib/parse/query/constraints.rb +46 -46
  103. data/lib/parse/query.rb +146 -59
  104. data/lib/parse/retrieval/agent_tool.rb +34 -20
  105. data/lib/parse/retrieval/chunk.rb +1 -0
  106. data/lib/parse/retrieval/reranker/cohere.rb +11 -11
  107. data/lib/parse/retrieval/reranker.rb +3 -4
  108. data/lib/parse/retrieval/retriever.rb +4 -6
  109. data/lib/parse/schema/index_migrator.rb +26 -26
  110. data/lib/parse/schema/search_index_migrator.rb +19 -19
  111. data/lib/parse/stack/tasks.rb +6 -6
  112. data/lib/parse/stack/version.rb +1 -1
  113. data/lib/parse/stack.rb +10 -10
  114. data/lib/parse/vector_search/hybrid.rb +66 -41
  115. data/lib/parse/vector_search.rb +14 -11
  116. data/lib/parse/webhooks/payload.rb +1 -0
  117. data/lib/parse/webhooks/registration.rb +4 -4
  118. data/lib/parse/webhooks/trigger_audit.rb +50 -49
  119. data/lib/parse/webhooks.rb +120 -5
  120. data/parse-stack-next.gemspec +6 -6
  121. metadata +10 -1
@@ -43,7 +43,6 @@ module Parse
43
43
  # in logs or a CDN access trail.
44
44
  class SignedUrlError < Parse::Error; end
45
45
 
46
-
47
46
  # Regular expression that matches the old legacy Parse hosted file name
48
47
  LEGACY_FILE_RX = /^tfss-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-/
49
48
  # The default attributes in a Parse File hash. Matches the Parse
@@ -86,7 +85,7 @@ module Parse
86
85
  # Alibaba Cloud metadata service (public-IP-space but well-known
87
86
  # cloud-metadata endpoint that must not be reachable from SDK fetches).
88
87
  "100.100.100.200/32",
89
- "::/128", "::1/128", "fc00::/7", "fe80::/10", "ff00::/8", "::ffff:0:0/96"
88
+ "::/128", "::1/128", "fc00::/7", "fe80::/10", "ff00::/8", "::ffff:0:0/96",
90
89
  ].map { |c| IPAddr.new(c) }.freeze
91
90
  # Restrictive port allowlist for Parse::File URL fetches. By default
92
91
  # only the standard HTTP/HTTPS ports are permitted. Operators may
@@ -166,12 +165,14 @@ module Parse
166
165
  # @return [Integer] Maximum byte size for a remote URL fetch via
167
166
  # `Parse::File.create` / `Parse::File.new(url)`.
168
167
  attr_writer :max_remote_size
168
+
169
169
  def max_remote_size
170
170
  @max_remote_size ||= DEFAULT_MAX_REMOTE_SIZE
171
171
  end
172
172
 
173
173
  # @return [Integer] Read/open timeout (seconds) for remote URL fetches.
174
174
  attr_writer :remote_timeout
175
+
175
176
  def remote_timeout
176
177
  @remote_timeout ||= DEFAULT_REMOTE_TIMEOUT
177
178
  end
@@ -182,6 +183,7 @@ module Parse
182
183
  # ".example.com" matches "files.example.com"). Default: empty (any
183
184
  # public host is allowed; private hosts are always denied).
184
185
  attr_writer :allowed_remote_hosts
186
+
185
187
  def allowed_remote_hosts
186
188
  @allowed_remote_hosts ||= []
187
189
  end
@@ -202,6 +204,7 @@ module Parse
202
204
  # entries via leading "." (e.g. `".cdn.example.com"`) match any
203
205
  # subdomain.
204
206
  attr_writer :trusted_url_hosts
207
+
205
208
  def trusted_url_hosts
206
209
  @trusted_url_hosts ||= ["files.parsetfss.com"]
207
210
  end
@@ -226,6 +229,7 @@ module Parse
226
229
  # hydration `attributes=` — asymmetric writer behavior was an
227
230
  # explicit anti-goal of the design.
228
231
  attr_writer :signed_url_policy
232
+
229
233
  def signed_url_policy
230
234
  @signed_url_policy ||= :strip
231
235
  end
@@ -244,12 +248,14 @@ module Parse
244
248
  # The default is intentionally non-breaking; integrators ready to
245
249
  # enforce flip the policy explicitly.
246
250
  attr_writer :untrusted_url_policy
251
+
247
252
  def untrusted_url_policy
248
253
  @untrusted_url_policy ||= :warn
249
254
  end
250
255
 
251
256
  # @return [Array<Integer>] Allowed remote ports for URL fetches.
252
257
  attr_writer :allowed_remote_ports
258
+
253
259
  def allowed_remote_ports
254
260
  @allowed_remote_ports ||= DEFAULT_ALLOWED_REMOTE_PORTS.dup
255
261
  end
@@ -543,10 +549,10 @@ module Parse
543
549
  # "exceeds". Fail fast with a clear message before any DNS/host work.
544
550
  max_bytes = coerce_positive_max_bytes(max_bytes)
545
551
  uri = begin
546
- URI.parse(url_string)
547
- rescue URI::InvalidURIError => e
548
- raise ArgumentError, "Invalid URL: #{e.message}"
549
- end
552
+ URI.parse(url_string)
553
+ rescue URI::InvalidURIError => e
554
+ raise ArgumentError, "Invalid URL: #{e.message}"
555
+ end
550
556
  unless %w[http https].include?(uri.scheme)
551
557
  raise ArgumentError, "Parse::File only supports http(s) URLs (got #{uri.scheme.inspect})"
552
558
  end
@@ -603,8 +609,7 @@ module Parse
603
609
  # @return [Integer, nil]
604
610
  def coerce_positive_max_bytes(max_bytes)
605
611
  return nil if max_bytes.nil?
606
- requested =
607
- begin
612
+ requested = begin
608
613
  Integer(max_bytes)
609
614
  rescue ArgumentError, TypeError
610
615
  raise ArgumentError, "max_bytes must be a positive integer (got #{max_bytes.inspect})"
@@ -931,6 +936,7 @@ module Parse
931
936
  @url = Parse::File.sanitize_hydrated_url(value, fallback: @url, name: @name)
932
937
  end
933
938
  end
939
+
934
940
  private :normalize_and_store_url
935
941
 
936
942
  # @!visibility private
@@ -953,10 +959,10 @@ module Parse
953
959
  return raw unless raw.start_with?("http://") || raw.start_with?("https://")
954
960
 
955
961
  uri = begin
956
- URI.parse(raw)
957
- rescue URI::InvalidURIError
958
- return raw # malformed URL — leave it alone; downstream code already handles
959
- end
962
+ URI.parse(raw)
963
+ rescue URI::InvalidURIError
964
+ return raw # malformed URL — leave it alone; downstream code already handles
965
+ end
960
966
  host = uri.host.to_s.downcase
961
967
  return raw if host.empty?
962
968
 
@@ -975,7 +981,7 @@ module Parse
975
981
  when :strip
976
982
  warn_untrusted_url_host_once(host, action: "stripped")
977
983
  fallback
978
- else # :warn (default)
984
+ else # :warn (default)
979
985
  warn_untrusted_url_host_once(host, action: "accepted")
980
986
  raw
981
987
  end
@@ -1034,8 +1040,8 @@ module Parse
1034
1040
  def save(session_token: nil, use_master_key: nil)
1035
1041
  unless saved? || @contents.nil? || @name.nil?
1036
1042
  opts = {}
1037
- opts[:session_token] = session_token unless session_token.nil?
1038
- opts[:use_master_key] = use_master_key unless use_master_key.nil?
1043
+ opts[:session_token] = session_token unless session_token.nil?
1044
+ opts[:use_master_key] = use_master_key unless use_master_key.nil?
1039
1045
  response = client.create_file(@name, @contents, @mime_type, **opts)
1040
1046
  unless response.error?
1041
1047
  result = response.result
@@ -49,8 +49,7 @@ module Parse
49
49
  end
50
50
 
51
51
  def coordinates=(value)
52
- coords =
53
- case value
52
+ coords = case value
54
53
  when self.class
55
54
  deep_copy_array(value.coordinates)
56
55
  when Hash
@@ -76,6 +75,7 @@ module Parse
76
75
  def to_geojson
77
76
  { "type" => geojson_type, "coordinates" => deep_copy_array(@coordinates) }
78
77
  end
78
+
79
79
  alias_method :as_json, :to_geojson
80
80
 
81
81
  # @return [String] the JSON form, suitable for direct shipment to
@@ -109,6 +109,7 @@ module Parse
109
109
  km = 0 if km.nil?
110
110
  [@latitude, @longitude, km, :km]
111
111
  end
112
+
112
113
  alias_method :max_km, :max_kilometers
113
114
 
114
115
  # Helper method for performing geo-queries with a radial radians
@@ -403,11 +403,11 @@ module Parse
403
403
  # @return [Parse::ACL] the current default ACLs for this class.
404
404
  def default_acls
405
405
  @default_acls ||= case acl_policy_setting
406
- when :public, :owner_else_public then Parse::ACL.everyone
407
- when :public_read, :owner_but_public_read then Parse::ACL.everyone(true, false)
408
- when :private, :owner_else_private then Parse::ACL.private
409
- else Parse::ACL.everyone
410
- end
406
+ when :public, :owner_else_public then Parse::ACL.everyone
407
+ when :public_read, :owner_but_public_read then Parse::ACL.everyone(true, false)
408
+ when :private, :owner_else_private then Parse::ACL.private
409
+ else Parse::ACL.everyone
410
+ end
411
411
  end
412
412
 
413
413
  # A method to set default ACLs to be applied for newly created
@@ -541,10 +541,10 @@ module Parse
541
541
  end
542
542
  if owner.nil? && policy.to_s.start_with?("owner_")
543
543
  fallback = case policy
544
- when :owner_else_public then "public R/W"
545
- when :owner_but_public_read then "public read only"
546
- else "master-key-only"
547
- end
544
+ when :owner_else_public then "public R/W"
545
+ when :owner_but_public_read then "public read only"
546
+ else "master-key-only"
547
+ end
548
548
  warn "[#{self}] acl_policy #{policy.inspect} declared without `owner:` field; ACL resolution will always use the fallback (#{fallback}). Pass `as:` at construction to override."
549
549
  end
550
550
  @acl_policy_setting = policy
@@ -698,7 +698,7 @@ module Parse
698
698
  class_permissions.set_default_permission(
699
699
  public_access: public,
700
700
  roles: Array(roles),
701
- requires_authentication: requires_authentication
701
+ requires_authentication: requires_authentication,
702
702
  )
703
703
 
704
704
  # Also explicitly set all operations to ensure they're included
@@ -789,7 +789,7 @@ module Parse
789
789
  roles: Array(roles),
790
790
  users: Array(users),
791
791
  pointer_fields: converted_pointer_fields,
792
- requires_authentication: requires_authentication
792
+ requires_authentication: requires_authentication,
793
793
  )
794
794
  end
795
795
 
@@ -1043,8 +1043,8 @@ module Parse
1043
1043
 
1044
1044
  per_field[local_sym] = {
1045
1045
  write: guards_map[local_sym] || :open,
1046
- read: hidden_from.empty? ? :open : { hidden_from: hidden_from },
1047
- type: data_type,
1046
+ read: hidden_from.empty? ? :open : { hidden_from: hidden_from },
1047
+ type: data_type,
1048
1048
  }
1049
1049
  end
1050
1050
 
@@ -1057,10 +1057,10 @@ module Parse
1057
1057
  end
1058
1058
 
1059
1059
  {
1060
- operations: operations,
1061
- read_user_fields: perms.respond_to?(:read_user_fields) ? perms.read_user_fields : [],
1060
+ operations: operations,
1061
+ read_user_fields: perms.respond_to?(:read_user_fields) ? perms.read_user_fields : [],
1062
1062
  write_user_fields: perms.respond_to?(:write_user_fields) ? perms.write_user_fields : [],
1063
- fields: per_field,
1063
+ fields: per_field,
1064
1064
  }
1065
1065
  end
1066
1066
 
@@ -1241,8 +1241,7 @@ module Parse
1241
1241
  # for tests or internal mongo-direct bulk writes). A class may flip
1242
1242
  # the per-class default with `vector_visibility :public`; an explicit
1243
1243
  # `include_vectors:` in the call always wins over the class default.
1244
- include_vectors =
1245
- if opts.key?(:include_vectors)
1244
+ include_vectors = if opts.key?(:include_vectors)
1246
1245
  opts[:include_vectors] == true
1247
1246
  else
1248
1247
  self.class.respond_to?(:vectors_public_by_default?) && self.class.vectors_public_by_default?
@@ -1338,6 +1337,8 @@ module Parse
1338
1337
  # for trusted hydration from server JSON; it bypasses the filter.
1339
1338
  # @return [Parse::Object] a the corresponding Parse::Object or subclass.
1340
1339
  def initialize(opts = {})
1340
+ Parse::Core::Actions.mark_transaction_object_created(self)
1341
+
1341
1342
  # Trusted hydration is signalled by the `@_trusted_init` instance
1342
1343
  # variable rather than by a `trusted:` keyword argument. Using a
1343
1344
  # keyword would break subclasses that override `initialize(*args)`
@@ -1351,6 +1352,12 @@ module Parse
1351
1352
  # the trust signal here.
1352
1353
  trusted = @_trusted_init == true
1353
1354
  @_trusted_init = nil
1355
+ input_hash = opts.is_a?(Hash) ? opts : nil
1356
+ input_had_acl = input_hash && %w[ACL acl].any? do |key|
1357
+ input_hash.key?(key) || input_hash.key?(key.to_sym)
1358
+ end
1359
+ input_had_id = input_hash && (input_hash.key?(Parse::Model::OBJECT_ID) || input_hash.key?(:objectId) ||
1360
+ input_hash.key?(:id) || input_hash.key?(Parse::Model::ID))
1354
1361
  acl_owner_override = nil
1355
1362
  if opts.is_a?(String) #then it's the objectId
1356
1363
  @id = opts.to_s
@@ -1398,6 +1405,31 @@ module Parse
1398
1405
  @_acl_pristine = !acl_was_user_supplied
1399
1406
  @_acl_owner_override = acl_owner_override
1400
1407
 
1408
+ # Record where our ACL knowledge came from. `acl.nil?` alone is not
1409
+ # enough: it can mean a genuinely ACL-less (therefore public) server
1410
+ # row, but it also describes an id-only pointer or a selective response
1411
+ # that never fetched ACL. Conversely, a custom class may stamp its local
1412
+ # default ACL while hydrating a full server row whose ACL was absent.
1413
+ # Access inspection uses this evidence to distinguish those cases.
1414
+ @_authorization_acl_state = if trusted
1415
+ if has_selective_keys? && !input_had_acl
1416
+ :unknown
1417
+ elsif input_had_acl
1418
+ self.acl.nil? ? :absent : :present
1419
+ else
1420
+ :absent
1421
+ end
1422
+ elsif opts.is_a?(String) || input_had_id
1423
+ :unknown
1424
+ elsif input_had_acl
1425
+ self.acl.nil? ? :unknown : :local
1426
+ elsif self.class.builtin_acl_default_active?
1427
+ :unknown
1428
+ else
1429
+ :local
1430
+ end
1431
+ @_authorization_acl_tracking_ready = true
1432
+
1401
1433
  # One-time per-class permissive-default warning. Fires only when the
1402
1434
  # effective policy is :public or :owner_else_public.
1403
1435
  self.class._warn_permissive_acl_default_once
@@ -1617,6 +1649,39 @@ module Parse
1617
1649
  @_fetched_keys.include?(key) || (remote_key && @_fetched_keys.include?(remote_key))
1618
1650
  end
1619
1651
 
1652
+ # Describes whether the in-memory ACL is authoritative enough for local
1653
+ # access inspection. `:absent` means a full trusted server response omitted
1654
+ # ACL (Parse Server's public default); `:present` and `:local` have a usable
1655
+ # ACL value; `:unknown` covers pointers and incomplete hydration.
1656
+ #
1657
+ # @return [Symbol] `:present`, `:absent`, `:local`, or `:unknown`.
1658
+ # @api private
1659
+ def authorization_acl_state
1660
+ @_authorization_acl_state || :unknown
1661
+ end
1662
+
1663
+ # Update ACL evidence after `fetch!` applies a server response to an
1664
+ # existing instance. Save/update responses are deliberately excluded: they
1665
+ # are deltas and omission there says nothing about the stored ACL.
1666
+ # @param attributes [Hash] trusted server response.
1667
+ # @param partial [Boolean] whether a selective key fetch produced it.
1668
+ # @api private
1669
+ def record_authorization_hydration!(attributes, partial: false)
1670
+ return unless attributes.is_a?(Hash)
1671
+ has_acl = %w[ACL acl].any? do |key|
1672
+ attributes.key?(key) || attributes.key?(key.to_sym)
1673
+ end
1674
+ @_authorization_acl_state = if has_acl
1675
+ self.acl.nil? ? :absent : :present
1676
+ elsif partial
1677
+ :unknown
1678
+ else
1679
+ :absent
1680
+ end
1681
+ @_authorization_acl_tracking_ready = true
1682
+ @_authorization_acl_state
1683
+ end
1684
+
1620
1685
  # Returns the nested fetched keys map for building nested objects.
1621
1686
  # @return [Hash] map of field names to their fetched keys
1622
1687
  def nested_fetched_keys
@@ -1884,48 +1949,48 @@ module Parse
1884
1949
  owner = @_acl_owner_override if defined?(@_acl_owner_override)
1885
1950
  if owner.nil? && (field = self.class.acl_owner_field)
1886
1951
  owner = if field == :self
1887
- # Self-referential ownership (Parse::User only — enforced at
1888
- # declaration time). Pre-generate a Parse-compatible objectId
1889
- # client-side so the ACL grant can reference the record's own
1890
- # id in the same POST body that creates it. Skipped when the id
1891
- # is already set (e.g. when re-saving an existing user, or when
1892
- # parse_reference precompute already ran).
1893
- @id = Parse::Core::ParseReference.generate_object_id if @id.blank?
1894
- @id
1895
- elsif respond_to?(field)
1896
- send(field)
1897
- end
1952
+ # Self-referential ownership (Parse::User only — enforced at
1953
+ # declaration time). Pre-generate a Parse-compatible objectId
1954
+ # client-side so the ACL grant can reference the record's own
1955
+ # id in the same POST body that creates it. Skipped when the id
1956
+ # is already set (e.g. when re-saving an existing user, or when
1957
+ # parse_reference precompute already ran).
1958
+ @id = Parse::Core::ParseReference.generate_object_id if @id.blank?
1959
+ @id
1960
+ elsif respond_to?(field)
1961
+ send(field)
1962
+ end
1898
1963
  end
1899
1964
  owner_id = _resolve_acl_owner_id(owner)
1900
1965
 
1901
1966
  target_acl = case policy
1902
- when :public
1903
- Parse::ACL.everyone(true, true)
1904
- when :public_read
1905
- Parse::ACL.everyone(true, false)
1906
- when :private
1907
- Parse::ACL.private
1908
- when :owner_else_public
1909
- if owner_id
1910
- acl = Parse::ACL.new
1911
- acl.apply(owner_id, true, true)
1912
- acl
1913
- else
1914
- Parse::ACL.everyone(true, true)
1915
- end
1916
- when :owner_else_private
1917
- if owner_id
1918
- acl = Parse::ACL.new
1919
- acl.apply(owner_id, true, true)
1920
- acl
1921
- else
1922
- Parse::ACL.private
1923
- end
1924
- when :owner_but_public_read
1925
- acl = Parse::ACL.everyone(true, false)
1926
- acl.apply(owner_id, true, true) if owner_id
1927
- acl
1928
- end
1967
+ when :public
1968
+ Parse::ACL.everyone(true, true)
1969
+ when :public_read
1970
+ Parse::ACL.everyone(true, false)
1971
+ when :private
1972
+ Parse::ACL.private
1973
+ when :owner_else_public
1974
+ if owner_id
1975
+ acl = Parse::ACL.new
1976
+ acl.apply(owner_id, true, true)
1977
+ acl
1978
+ else
1979
+ Parse::ACL.everyone(true, true)
1980
+ end
1981
+ when :owner_else_private
1982
+ if owner_id
1983
+ acl = Parse::ACL.new
1984
+ acl.apply(owner_id, true, true)
1985
+ acl
1986
+ else
1987
+ Parse::ACL.private
1988
+ end
1989
+ when :owner_but_public_read
1990
+ acl = Parse::ACL.everyone(true, false)
1991
+ acl.apply(owner_id, true, true) if owner_id
1992
+ acl
1993
+ end
1929
1994
 
1930
1995
  # Only re-stamp if the resolved ACL differs from the init-time stamp;
1931
1996
  # this avoids an unnecessary dirty mark on the acl field for `:public`
@@ -1979,12 +2044,17 @@ module Parse
1979
2044
  # caller intent to override.
1980
2045
  # @api private
1981
2046
  def acl_will_change!
2047
+ _capture_transaction_state!
2048
+
1982
2049
  # Only capture snapshot on the first change (before any modifications)
1983
2050
  unless defined?(@_acl_snapshot_before_change) && @_acl_snapshot_before_change
1984
2051
  # Deep copy the ACL by creating a new one from its JSON representation
1985
2052
  @_acl_snapshot_before_change = @acl ? Parse::ACL.new(@acl.as_json) : Parse::ACL.new
1986
2053
  end
1987
2054
  @_acl_pristine = false if defined?(@_acl_pristine)
2055
+ if defined?(@_authorization_acl_tracking_ready) && @_authorization_acl_tracking_ready
2056
+ @_authorization_acl_state = :local
2057
+ end
1988
2058
  super
1989
2059
  end
1990
2060
 
@@ -2156,16 +2226,16 @@ class Array
2156
2226
  next m if m.is_a?(Parse::Pointer)
2157
2227
  if m.is_a?(Hash)
2158
2228
  resolved = if className
2159
- # Caller knows the type; warn on mismatch but always
2160
- # use the declared className.
2161
- incoming = m[f] || m[:className]
2162
- if incoming && !Parse::Model.same_parse_class?(incoming, className)
2163
- warn "[Parse::Array#parse_objects] expected className=#{className.inspect}, ignoring incoming className=#{incoming.inspect}"
2164
- end
2165
- className
2166
- else
2167
- m[f] || m[:className]
2168
- end
2229
+ # Caller knows the type; warn on mismatch but always
2230
+ # use the declared className.
2231
+ incoming = m[f] || m[:className]
2232
+ if incoming && !Parse::Model.same_parse_class?(incoming, className)
2233
+ warn "[Parse::Array#parse_objects] expected className=#{className.inspect}, ignoring incoming className=#{incoming.inspect}"
2234
+ end
2235
+ className
2236
+ else
2237
+ m[f] || m[:className]
2238
+ end
2169
2239
  next Parse::Object.build(m, resolved) if resolved
2170
2240
  end
2171
2241
  nil
@@ -118,8 +118,10 @@ module Parse
118
118
  # nil unless the instance came from the corresponding search path.
119
119
  # @return [Float, nil]
120
120
  def vector_score; @_vector_score; end
121
+
121
122
  # @return [Float, nil]
122
123
  def search_score; @_search_score; end
124
+
123
125
  # @return [Hash, nil]
124
126
  def search_highlights; @_search_highlights; end
125
127
 
@@ -99,8 +99,7 @@ module Parse
99
99
  end
100
100
  outer = rings.first
101
101
  pairs = outer.map do |(lng, lat)|
102
- raise ArgumentError, "[Parse::Polygon] GeoJSON ring entries must be [lng, lat] numeric pairs." \
103
- unless lng.is_a?(Numeric) && lat.is_a?(Numeric)
102
+ raise ArgumentError, "[Parse::Polygon] GeoJSON ring entries must be [lng, lat] numeric pairs." unless lng.is_a?(Numeric) && lat.is_a?(Numeric)
104
103
  [lat.to_f, lng.to_f]
105
104
  end
106
105
  new(pairs)
@@ -120,8 +119,7 @@ module Parse
120
119
  # - a Hash with a `coordinates` key (the Parse REST wire shape)
121
120
  # - another {Parse::Polygon}
122
121
  def coordinates=(value)
123
- coords =
124
- case value
122
+ coords = case value
125
123
  when Parse::Polygon
126
124
  # Duplicate so external mutation of the source doesn't leak in.
127
125
  value.coordinates.map { |pair| pair.dup }
@@ -270,8 +268,7 @@ module Parse
270
268
  # @param point [Parse::GeoPoint, Array<Numeric>] the point to test.
271
269
  # @return [Boolean]
272
270
  def contains_point?(point)
273
- lat, lng =
274
- case point
271
+ lat, lng = case point
275
272
  when Parse::GeoPoint then [point.latitude, point.longitude]
276
273
  when Array then [point[0].to_f, point[1].to_f]
277
274
  else
@@ -963,11 +963,11 @@ module Parse
963
963
  if device_type.present? && !SUPPORTED_PUSH_DEVICE_TYPES.include?(device_type)
964
964
  if UNSUPPORTED_PUSH_DEVICE_TYPES.include?(device_type)
965
965
  warn "[Parse::Push] Warning: device_type '#{device_type}' may not be supported for push notifications. " \
966
- "Supported types: #{SUPPORTED_PUSH_DEVICE_TYPES.join(', ')}"
966
+ "Supported types: #{SUPPORTED_PUSH_DEVICE_TYPES.join(", ")}"
967
967
  else
968
968
  warn "[Parse::Push] Warning: unknown device_type '#{device_type}' for installation #{installation_id}. " \
969
969
  "This device type may not receive push notifications. " \
970
- "Supported types: #{SUPPORTED_PUSH_DEVICE_TYPES.join(', ')}"
970
+ "Supported types: #{SUPPORTED_PUSH_DEVICE_TYPES.join(", ")}"
971
971
  end
972
972
  end
973
973
  end
@@ -57,6 +57,7 @@ module Parse
57
57
  def dimensions
58
58
  @values.length
59
59
  end
60
+
60
61
  alias_method :length, :dimensions
61
62
  alias_method :size, :dimensions
62
63
 
@@ -84,10 +85,11 @@ module Parse
84
85
  def ==(other)
85
86
  case other
86
87
  when Parse::Vector then @values == other.values
87
- when Array then @values == other
88
+ when Array then @values == other
88
89
  else false
89
90
  end
90
91
  end
92
+
91
93
  alias_method :eql?, :==
92
94
 
93
95
  def hash