Class: Parse::Pointer

Inherits:
Model
  • Object
show all
Defined in:
lib/parse/model/pointer.rb

Overview

The Pointer class represents the pointer type in Parse and is the superclass of Parse::Object types. A pointer can be considered a type of Parse::Object in which only the class name and id is known. In most cases, you may not deal with Parse::Pointer objects directly if you have defined all your Parse::Object subclasses.

A ‘Parse::Pointer` only contains data about the specific Parse class and the `id` for the object. Therefore, creating an instance of any Parse::Object subclass with only the `:id` field set will be considered in “pointer” state even though its specific class is not `Parse::Pointer` type. The only case that you may have a Parse::Pointer is in the case where an object was received for one of your classes and the framework has no registered class handler for it. Assume you have the tables `Post`, `Comment` and `Author` defined in your remote Parse database, but have only defined `Post` and `Commentary` locally. The effect is that for any unknown classes that the framework encounters, it will generate Parse::Pointer instances until you define those classes with valid properties and associations. While this might be ok for some classes you do not use, we still recommend defining all your Parse classes locally in the framework.

Once you have a subclass, you may also create a Parse::Pointer object using the pointer method.

Examples:

class Post < Parse::Object
end

class Commentary < Parse::Object
 belongs_to :post
 belongs_to :author
end

comment = Commentary.first
comment.post? # true because it is non-nil
comment.artist? # true because it is non-nil

# both are true because they are in a Pointer state
comment.post.pointer? # true
comment.author.pointer? # true

# we have defined a Post class handler
comment.post # <Post @parse_class="Post", @id="xdqcCqfngz">

# we have not defined an Author class handler
comment.author # <Parse::Pointer @parse_class="Author", @id="hZLbW6ofKC">

comment.post.fetch # fetch the relation
comment.post.pointer? # false, it is now a full object.
Parse::User.pointer("123456") # => Parse::Pointer for "_User" class

See Also:

Direct Known Subclasses

Object

Constant Summary collapse

ATTRIBUTES =

The default attributes in a Parse Pointer hash.

{ __type: :string, className: :string, objectId: :string }.freeze
OBJECT_ID_FORMAT =

Permitted character set + length for a Parse objectId. Parse Server itself generates 10-char ‘[A-Za-z0-9]` ids; with `allowCustomObjectId: true` apps can pass arbitrary identifiers, so we accept the wider URL-safe set `[A-Za-z0-9_.-]` and cap length at 64. Anything OUTSIDE this set (`/`, ``, CR/LF, `?`, `&`, `#`, `%`, quotes, angle brackets, semicolons, whitespace) is rejected — those are the bytes that turn a `Pointer.id=` write into a path-traversal, header-injection, or batch-op-path-poisoning vector when interpolated into REST URLs or batch op `path` fields.

/\A[A-Za-z0-9_.\-]{1,64}\z/.freeze

Constants inherited from Model

Model::CLASS_AUDIENCE, Model::CLASS_INSTALLATION, Model::CLASS_JOB_SCHEDULE, Model::CLASS_JOB_STATUS, Model::CLASS_PRODUCT, Model::CLASS_PUSH_STATUS, Model::CLASS_ROLE, Model::CLASS_SCHEMA, Model::CLASS_SESSION, Model::CLASS_USER, Model::ID, Model::KEY_CLASS_NAME, Model::KEY_CREATED_AT, Model::KEY_OBJECT_ID, Model::KEY_UPDATED_AT, Model::OBJECT_ID, Model::TYPE_ACL, Model::TYPE_BYTES, Model::TYPE_DATE, Model::TYPE_FIELD, Model::TYPE_FILE, Model::TYPE_GEOPOINT, Model::TYPE_NUMBER, Model::TYPE_OBJECT, Model::TYPE_POINTER, Model::TYPE_POLYGON, Model::TYPE_RELATION

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Model

#dirty?, find_class

Methods included from Client::Connectable

#client

Constructor Details

#initialize(table, oid) ⇒ Pointer

A Parse pointer only requires the name of the remote Parse collection name, and the ‘objectId` of the record.

Parameters:

  • table (String)

    The Parse class name in the Parse database.

  • oid (String)

    The objectId



121
122
123
124
# File 'lib/parse/model/pointer.rb', line 121

def initialize(table, oid)
  @parse_class = table.to_s
  self.id = oid
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object

Handles method calls for properties that exist on the target model class. When a property is accessed on a Pointer, this will auto-fetch the object and delegate the method call to the fetched object.

If Parse.autofetch_raise_on_missing_keys is enabled, this will raise Parse::AutofetchTriggeredError instead of fetching.

Examples:

pointer = Post.pointer("abc123")
pointer.title  # auto-fetches and returns title

Parameters:

  • method_name (Symbol)

    the method being called

  • args (Array)

    arguments to the method

  • block (Proc)

    optional block

Returns:

  • (Object)

    the result of calling the method on the fetched object

Raises:



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/parse/model/pointer.rb', line 356

def method_missing(method_name, *args, &block)
  # Try to find the model class for this pointer
  klass = Parse::Model.find_class(parse_class)

  # If no class is registered or the class doesn't have this field, use default behavior
  unless klass && klass.respond_to?(:fields) && klass.fields[method_name.to_s.chomp("=").to_sym]
    return super
  end

  # We have a registered class with this field - handle autofetch
  field_name = method_name.to_s.chomp("=").to_sym

  # If autofetch_raise_on_missing_keys is enabled, raise an error
  if Parse.autofetch_raise_on_missing_keys
    raise Parse::AutofetchTriggeredError.new(klass, id, field_name, is_pointer: true)
  end

  # Log info about autofetch being triggered
  if Parse.warn_on_query_issues
    puts "[Parse::Autofetch] Fetching #{parse_class}##{id} - pointer accessed field :#{field_name} (silence with Parse.warn_on_query_issues = false)"
  end

  # Fetch the object and delegate the method call
  @_fetched_object ||= fetch
  return nil unless @_fetched_object

  @_fetched_object.send(method_name, *args, &block)
end

Instance Attribute Details

#idString Also known as: objectId

Returns the objectId field.

Returns:

  • (String)

    the objectId field



85
86
87
# File 'lib/parse/model/pointer.rb', line 85

def id
  @id
end

#parse_classString

Returns the name of the collection for this Pointer.

Returns:

  • (String)

    the name of the collection for this Pointer.



83
84
85
# File 'lib/parse/model/pointer.rb', line 83

def parse_class
  @parse_class
end

Instance Method Details

#==(o) ⇒ Boolean Also known as: eql?

Two Parse::Pointers (or Parse::Objects) are equal if both of them have the same Parse class and the same id.

Returns:

  • (Boolean)


303
304
305
306
307
# File 'lib/parse/model/pointer.rb', line 303

def ==(o)
  return false unless o.is_a?(Pointer)
  #only equal if the Parse class and object ID are the same.
  self.parse_class == o.parse_class && id == o.id
end

#[](key) ⇒ Object

Access the pointer properties through hash accessor. This is done for compatibility with the hash access of a Parse::Object. This method returns nil if the key is not one of: :id, :objectId, or :className.

Parameters:

  • key (String)

    the name of the property.

Returns:

  • (Object)

    the value for this key.



335
336
337
338
# File 'lib/parse/model/pointer.rb', line 335

def [](key)
  return nil unless [:id, :objectId, :className].include?(key.to_sym)
  send(key)
end

#[]=(key, value) ⇒ Object

Set the pointer properties through hash accessor. This is done for compatibility with the hash access of a Parse::Object. This method does nothing if the key is not one of: :id, :objectId, or :className.

Parameters:

  • key (String)

    the name of the property.

Returns:



405
406
407
408
# File 'lib/parse/model/pointer.rb', line 405

def []=(key, value)
  return unless [:id, :objectId, :className].include?(key.to_sym)
  send("#{key}=", value)
end

#__typeModel::TYPE_POINTER

Returns:



110
# File 'lib/parse/model/pointer.rb', line 110

def __type; Parse::Model::TYPE_POINTER; end

#attributesHash

Returns:



137
138
139
# File 'lib/parse/model/pointer.rb', line 137

def attributes
  ATTRIBUTES
end

#classNameString

Returns the name of the Parse class for this pointer.

Returns:

  • (String)

    the name of the Parse class for this pointer.



112
113
114
# File 'lib/parse/model/pointer.rb', line 112

def parse_class
  @parse_class
end

#fetchParse::Object #fetch(return_object) ⇒ Parse::Object, Hash #fetch(keys:, includes:, cache:) ⇒ Parse::Object

This method is a general implementation that gets overriden by Parse::Object subclass. Given the class name and the id, we will go to Parse and fetch the actual record, returning the Parse::Object by default.

Overloads:

  • #fetchParse::Object

    Full fetch - fetches all fields

    Returns:

  • #fetch(return_object) ⇒ Parse::Object, Hash

    Legacy signature for backward compatibility.

    Parameters:

    • return_object (Boolean)

      if true returns object, if false returns JSON

    Returns:

  • #fetch(keys:, includes:, cache:) ⇒ Parse::Object

    Partial fetch - fetches only specified fields

    Parameters:

    • keys (Array<Symbol, String>, nil)

      optional list of fields to fetch (partial fetch).

    • includes (Array<String>, nil)

      optional list of pointer fields to expand.

    • cache (Boolean, Symbol, Integer)

      caching mode:

      • true - read from and write to cache

      • false - completely bypass cache

      • :write_only - skip cache read, but update cache with fresh data

      • Integer - cache for specific number of seconds

    Returns:

    • (Parse::Object)

      a partially fetched Parse::Object, nil otherwise.



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/parse/model/pointer.rb', line 194

def fetch(return_object = nil, keys: nil, includes: nil, cache: nil)
  # Handle legacy signature: fetch(false) returns JSON
  if return_object == false
    return fetch_json(keys: keys, includes: includes)
  end

  # Build query parameters for partial fetch
  query = {}
  if keys.present?
    keys_array = Array(keys).map { |k| Parse::Query.format_field(k) }
    query[:keys] = keys_array.join(",")
  end
  if includes.present?
    includes_array = Array(includes).map(&:to_s)
    query[:include] = includes_array.join(",")
  end

  # Build opts for caching
  opts = {}
  opts[:cache] = cache unless cache.nil?

  response = client.fetch_object(parse_class, id, query: query.presence, **opts)
  return nil if response.error?

  # Check if the result is empty - this indicates object not found
  result = response.result
  if result.nil? || (result.is_a?(Array) && result.empty?)
    return nil
  end

  # Convert the JSON result to a proper Parse::Object
  return nil unless result.is_a?(Hash)

  # Try to find the appropriate Parse class, fallback to Parse::Object
  klass = Parse::Model.find_class(parse_class) || Parse::Object

  # For partial fetch, build with fetched_keys tracking
  if keys.present?
    # Parse keys to get top-level field names and nested keys
    top_level_keys = Array(keys).map { |k| Parse::Query.format_field(k).split(".").first.to_sym }
    top_level_keys << :id unless top_level_keys.include?(:id)
    top_level_keys << :objectId unless top_level_keys.include?(:objectId)
    top_level_keys.uniq!

    # Parse dot notation into nested fetched keys
    nested_keys = Parse::Query.parse_keys_to_nested_keys(Array(keys))

    obj = klass.build(result, parse_class, fetched_keys: top_level_keys, nested_fetched_keys: nested_keys.presence)
  else
    # Full fetch - create without partial fetch tracking. Trusted
    # hydration: +result+ is the server response body, which
    # legitimately carries +createdAt+/+updatedAt+/+sessionToken+
    # and other PROTECTED_MASS_ASSIGNMENT_KEYS. The +@_trusted_init+
    # ivar tells {Parse::Object#initialize} to skip the protected-key
    # filter — see that method for why we don't use a kwarg.
    obj = klass.allocate
    obj.instance_variable_set(:@_trusted_init, true)
    obj.send(:initialize, result)
  end

  obj.clear_changes! if obj.respond_to?(:clear_changes!)
  obj
end

#fetch_cache!(keys: nil, includes: nil) ⇒ Parse::Object

Fetches the pointer with explicit caching enabled and returns a Parse::Object. This is a convenience method that calls fetch with cache: true. Use this when you want to leverage cached responses for better performance.

Examples:

Fetch pointer with caching

capture = capture_pointer.fetch_cache!

Partial fetch with caching

capture = capture_pointer.fetch_cache!(keys: [:title, :status])

Parameters:

  • keys (Array<Symbol, String>, nil) (defaults to: nil)

    optional list of fields to fetch (partial fetch).

  • includes (Array<String>, nil) (defaults to: nil)

    optional list of pointer fields to expand.

Returns:

See Also:



296
297
298
# File 'lib/parse/model/pointer.rb', line 296

def fetch_cache!(keys: nil, includes: nil)
  fetch(keys: keys, includes: includes, cache: true)
end

#fetch_json(keys: nil, includes: nil) ⇒ Hash?

Returns raw JSON data from the server without creating an object.

Parameters:

  • keys (Array<Symbol, String>, nil) (defaults to: nil)

    optional list of fields to fetch.

  • includes (Array<String>, nil) (defaults to: nil)

    optional list of pointer fields to expand.

Returns:

  • (Hash, nil)

    the raw JSON data or nil if error.



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/parse/model/pointer.rb', line 262

def fetch_json(keys: nil, includes: nil)
  query = {}
  if keys.present?
    keys_array = Array(keys).map { |k| Parse::Query.format_field(k) }
    query[:keys] = keys_array.join(",")
  end
  if includes.present?
    includes_array = Array(includes).map(&:to_s)
    query[:include] = includes_array.join(",")
  end

  response = client.fetch_object(parse_class, id, query: query.presence)
  return nil if response.error?
  response.result
end

#fetch_objectParse::Object

Fetches the Parse object from the data store and returns a Parse::Object instance. This is a convenience method that calls fetch.

Returns:



281
282
283
# File 'lib/parse/model/pointer.rb', line 281

def fetch_object
  fetch
end

#fetched?Boolean

Returns true if the data for this instance has been fetched. Because of some autofetching mechanisms, this is useful to know whether the object already has data without actually causing a fetch of the data.

Returns:

  • (Boolean)

    true if not in pointer state.



170
171
172
# File 'lib/parse/model/pointer.rb', line 170

def fetched?
  present? && pointer? == false
end

#hashInteger

Compute a hash-code for this object based on identity (class and id). This is consistent with the == method which compares by parse_class and id.

Two objects with the same class and id will have the same hash code regardless of their dirty state or other attributes. This is important for:

  • Array operations (uniq, &, |) to work correctly based on identity

  • Hash key lookups to find objects by identity

  • Set operations

Returns:

  • (Integer)

    hash code based on class name and object id



321
322
323
# File 'lib/parse/model/pointer.rb', line 321

def hash
  [parse_class, id].hash
end

#json_hashHash

Returns serialized JSON structure.

Returns:

  • (Hash)

    serialized JSON structure



142
143
144
# File 'lib/parse/model/pointer.rb', line 142

def json_hash
  JSON.parse to_json
end

#pointerPointer

Create a new pointer with the current class name and id. While this may not make sense for a pointer instance, Parse::Object subclasses use this inherited method to turn themselves into pointer objects.

Examples:

user = Parse::User.first
user.pointer # => Parse::Pointer("_User", user.id)

Returns:

  • (Pointer)

    a new Pointer for this object.

See Also:



155
156
157
# File 'lib/parse/model/pointer.rb', line 155

def pointer
  Pointer.new parse_class, @id
end

#pointer?Boolean

Whether this instance is in pointer state. A pointer is determined if we have a parse class and an id, but no created_at or updated_at fields.

Returns:

  • (Boolean)

    true if instance is in pointer state.



162
163
164
# File 'lib/parse/model/pointer.rb', line 162

def pointer?
  present? && @created_at.blank? && @updated_at.blank?
end

#present?Boolean

Returns true if instance has a Parse class and an id.

Returns:

  • (Boolean)

    true if instance has a Parse class and an id.



326
327
328
# File 'lib/parse/model/pointer.rb', line 326

def present?
  parse_class.present? && @id.present?
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean

Indicates whether this object responds to methods that would trigger autofetch. Returns true for properties defined on the target model class.

Parameters:

  • method_name (Symbol)

    the method name to check

  • include_private (Boolean) (defaults to: false)

    whether to include private methods

Returns:

  • (Boolean)

    true if the method can be handled



391
392
393
394
395
396
397
398
# File 'lib/parse/model/pointer.rb', line 391

def respond_to_missing?(method_name, include_private = false)
  klass = Parse::Model.find_class(parse_class)
  if klass && klass.respond_to?(:fields)
    field_name = method_name.to_s.chomp("=").to_sym
    return true if klass.fields[field_name]
  end
  super
end

#sigString

Returns a string representing the class and id of this instance.

Returns:

  • (String)

    a string representing the class and id of this instance.



132
133
134
# File 'lib/parse/model/pointer.rb', line 132

def sig
  "#{@parse_class}##{id || "new"}"
end