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)

    whether dirty tracking be enabled

Returns:


463
464
465
466
467
468
469
470
471
# File 'lib/parse/model/core/properties.rb', line 463

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.


507
508
509
510
511
# File 'lib/parse/model/core/properties.rb', line 507

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:


488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/parse/model/core/properties.rb', line 488

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.


453
454
455
# File 'lib/parse/model/core/properties.rb', line 453

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

#attributes=(hash) ⇒ Hash

Supports mass assignment of attributes

Returns:


475
476
477
478
479
480
# File 'lib/parse/model/core/properties.rb', line 475

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.


442
443
444
# File 'lib/parse/model/core/properties.rb', line 442

def field_map
  self.class.field_map
end

#fields(type = nil) ⇒ Object

Returns the list of fields

Returns:

  • returns the list of fields


447
448
449
# File 'lib/parse/model/core/properties.rb', line 447

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:


519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
# File 'lib/parse/model/core/properties.rb', line 519

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:


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
603
604
605
606
607
608
609
610
611
612
613
614
615
# File 'lib/parse/model/core/properties.rb', line 549

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