Module: Parse::Properties

Included in:
Object
Defined in:
lib/parse/model/core/properties.rb

Overview

This module provides support for handling all the different types of column data types supported in Parse and mapping them between their remote names with their local ruby named attributes.

Defined Under Namespace

Modules: ClassMethods

Constant Summary collapse

TYPES =

These are the base types supported by Parse.

[:string, :relation, :integer, :float, :boolean, :date, :array, :file, :geopoint, :bytes, :object, :acl, :timezone].freeze
BASE =

These are the base mappings of the remote field name types.

{ objectId: :string, createdAt: :date, updatedAt: :date, ACL: :acl }.freeze
BASE_KEYS =

The list of properties that are part of all objects

[:id, :created_at, :updated_at].freeze
BASE_FIELD_MAP =

Default hash map of local attribute name to remote column name

{ id: :objectId, created_at: :createdAt, updated_at: :updatedAt, acl: :ACL }.freeze
CORE_FIELDS =

The delete operation hash.

{ id: :string, created_at: :date, updated_at: :date, acl: :acl }.freeze
DELETE_OP =

The delete operation hash.

{ "__op" => "Delete" }.freeze

Instance Method Summary collapse

Instance Method Details

#apply_attributes!(hash, dirty_track: false) ⇒ Hash

support for setting a hash of attributes on the object with a given dirty tracking value if dirty_track: is set to false (default), attributes are set without dirty tracking. Allos mass assignment of properties with a provided hash.

Parameters:

  • hash (Hash)

    the hash matching the property field names.

  • dirty_track (Boolean) (defaults to: false)

    whether dirty tracking be enabled

Returns:


449
450
451
452
453
454
455
456
457
# File 'lib/parse/model/core/properties.rb', line 449

def apply_attributes!(hash, dirty_track: false)
  return unless hash.is_a?(Hash)

  @id ||= hash[Parse::Model::ID] || hash[Parse::Model::OBJECT_ID] || hash[:objectId]
  hash.each do |key, value|
    method = "#{key}_set_attribute!".freeze
    send(method, value, dirty_track) if respond_to?(method)
  end
end

#attribute_changes?Boolean

Returns true if any of the attributes have changed.

Returns:

  • (Boolean)

    true if any of the attributes have changed.


493
494
495
496
497
# File 'lib/parse/model/core/properties.rb', line 493

def attribute_changes?
  changed.any? do |key|
    fields[key.to_sym].present?
  end
end

#attribute_updates(include_all = false) ⇒ Hash

Returns a hash of attributes for properties that have changed. This will not include any of the base attributes (ex. id, created_at, etc). This method helps generate the change payload that will be sent when saving objects to Parse.

Parameters:

  • include_all (Boolean) (defaults to: false)

    whether to include all BASE_KEYS attributes.

Returns:


474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/parse/model/core/properties.rb', line 474

def attribute_updates(include_all = false)
  # TODO: Replace this algorithm with reduce()
  h = {}
  changed.each do |key|
    key = key.to_sym
    next if include_all == false && Parse::Properties::BASE_KEYS.include?(key)
    next unless fields[key].present?
    remote_field = self.field_map[key] || key
    h[remote_field] = send key
    h[remote_field] = { __op: :Delete } if h[remote_field].nil?
    # in the case that the field is a Parse object, generate a pointer
    # if it is a Parse::PointerCollectionProxy, then make sure we get a list of pointers.
    h[remote_field] = h[remote_field].parse_pointers if h[remote_field].is_a?(Parse::PointerCollectionProxy)
    h[remote_field] = h[remote_field].pointer if h[remote_field].respond_to?(:pointer)
  end
  h
end

#attributesHash

TODO: We can optimize

Returns:

  • (Hash)

    returns the list of property attributes for this class.


439
440
441
# File 'lib/parse/model/core/properties.rb', line 439

def attributes
  { __type: :string, :className => :string }.merge!(self.class.attributes)
end

#attributes=(hash) ⇒ Hash

Supports mass assignment of attributes

Returns:


461
462
463
464
465
466
# File 'lib/parse/model/core/properties.rb', line 461

def attributes=(hash)
  return unless hash.is_a?(Hash)
  # - [:id, :objectId]
  # only overwrite @id if it hasn't been set.
  apply_attributes!(hash, dirty_track: true)
end

#field_mapHash

Returns a hash mapping of all property fields and their types.

Returns:

  • (Hash)

    a hash mapping of all property fields and their types.


428
429
430
# File 'lib/parse/model/core/properties.rb', line 428

def field_map
  self.class.field_map
end

#fields(type = nil) ⇒ Object

Returns the list of fields

Returns:

  • returns the list of fields


433
434
435
# File 'lib/parse/model/core/properties.rb', line 433

def fields(type = nil)
  self.class.fields(type)
end

#format_operation(key, val, data_type) ⇒ Object

Returns a formatted value based on the operation hash and data_type of the property. For some values in Parse, they are specified as operation hashes which could include Add, Remove, Delete, AddUnique and Increment.

Parameters:

  • key (Symbol)

    the name of the property

  • val (Hash)

    the Parse operation hash value.

  • data_type (Symbol)

    The data type of the property.

Returns:


506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# File 'lib/parse/model/core/properties.rb', line 506

def format_operation(key, val, data_type)
  return val unless val.is_a?(Hash) && val["__op"].present?
  op = val["__op"]
  ivar = :"@#{key}"
  #handles delete case otherwise 'null' shows up in column
  if "Delete" == op
    val = nil
  elsif "Add" == op && data_type == :array
    val = (instance_variable_get(ivar) || []).to_a + (val["objects"] || [])
  elsif "Remove" == op && data_type == :array
    val = (instance_variable_get(ivar) || []).to_a - (val["objects"] || [])
  elsif "AddUnique" == op && data_type == :array
    objects = (val["objects"] || []).uniq
    original_items = (instance_variable_get(ivar) || []).to_a
    objects.reject! { |r| original_items.include?(r) }
    val = original_items + objects
  elsif "Increment" == op && data_type == :integer || data_type == :integer
    # for operations that increment by a certain amount, they come as a hash
    val = (instance_variable_get(ivar) || 0) + (val["amount"] || 0).to_i
  end
  val
end

#format_value(key, val, data_type = nil) ⇒ Object

this method takes an input value and transforms it to the proper local format depending on the data type that was set for a particular property key. Return the internal representation of a property value for a given data type.

Parameters:

  • key (Symbol)

    the name of the property

  • val (Object)

    the value to format.

  • data_type (Symbol) (defaults to: nil)

    provide a hint to the data_type of this value.

Returns:


536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'lib/parse/model/core/properties.rb', line 536

def format_value(key, val, data_type = nil)
  # if data_type wasn't passed, then get the data_type from the fields hash
  data_type ||= self.fields[key]

  val = format_operation(key, val, data_type)

  case data_type
  when :object
    val = val.with_indifferent_access if val.is_a?(Hash)
  when :array
    # All "array" types use a collection proxy
    val = val.to_a if val.is_a?(Parse::CollectionProxy) #all objects must be in array form
    val = [val] unless val.is_a?(Array) #all objects must be in array form
    val.compact! #remove any nil
    val = Parse::CollectionProxy.new val, delegate: self, key: key
  when :geopoint
    val = Parse::GeoPoint.new(val) unless val.blank?
  when :file
    val = Parse::File.new(val) unless val.blank?
  when :bytes
    val = Parse::Bytes.new(val) unless val.blank?
  when :integer
    if val.nil? || val.respond_to?(:to_i) == false
      val = nil
    else
      val = val.to_i
    end
  when :boolean
    if val.nil?
      val = nil
    else
      val = val ? true : false
    end
  when :string
    val = val.to_s unless val.blank?
  when :float
    val = val.to_f unless val.blank?
  when :acl
    # ACL types go through a special conversion
    val = ACL.typecast(val, self)
  when :date
    # if it respond to parse_date, then use that as the conversion.
    if val.respond_to?(:parse_date) && val.is_a?(Parse::Date) == false
      val = val.parse_date
      # if the value is a hash, then it may be the Parse hash format for an iso date.
    elsif val.is_a?(Hash) # val.respond_to?(:iso8601)
      val = Parse::Date.parse(val["iso"] || val[:iso])
    elsif val.is_a?(String)
      # if it's a string, try parsing the date
      val = Parse::Date.parse val
      #elsif val.present?
      #  pus "[Parse::Stack] Invalid date value '#{val}' assigned to #{self.class}##{key}, it should be a Parse::Date or DateTime."
      #   raise ValueError, "Invalid date value '#{val}' assigned to #{self.class}##{key}, it should be a Parse::Date or DateTime."
    end
  when :timezone
    val = Parse::TimeZone.new(val) if val.present?
  else
    # You can provide a specific class instead of a symbol format
    if data_type.respond_to?(:typecast)
      val = data_type.typecast(val)
    else
      warn "Property :#{key}: :#{data_type} has no valid data type"
      val = val #default
    end
  end
  val
end