Module: Parse::Core::Actions

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

Overview

Defines some of the save, update and destroy operations for Parse objects.

Defined Under Namespace

Modules: ClassMethods

Instance Method Summary collapse

Instance Method Details

#change_requests(force = false) ⇒ Array<Parse::Request>

Creates an array of all possible operations that need to be performed on this object. This includes all property and relational operation changes.

Parameters:

  • force (Boolean) (defaults to: false)

    whether this object should be saved even if does not have pending changes.

Returns:



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/parse/model/core/actions.rb', line 384

def change_requests(force = false)
  requests = []
  # get the URI path for this object.
  uri = self.uri_path

  # generate the request to update the object (PUT)
  if attribute_changes? || force
    # if it's new, then we should call :post for creating the object.
    method = new? ? :post : :put
    r = Request.new(method, uri, body: attribute_updates)
    r.tag = object_id
    requests << r
  end

  # if the object is not new, then we can also add all the relational changes
  # we need to perform.
  if @id.present? && relation_changes?
    relation_change_operations.each do |ops|
      next if ops.empty?
      r = Request.new(:put, uri, body: ops)
      r.tag = object_id
      requests << r
    end
  end
  requests
end

#changes_applied!Object

Clears changes information on all collections (array and relations) and all local attributes.



656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
# File 'lib/parse/model/core/actions.rb', line 656

def changes_applied!
  # find all fields that are of type :array
  fields(:array) do |key, v|
    proxy = send(key)
    # clear changes
    proxy.changes_applied! if proxy.respond_to?(:changes_applied!)
  end

  # for all relational fields,
  relations.each do |key, v|
    proxy = send(key)
    # clear changes if they support the method.
    proxy.changes_applied! if proxy.respond_to?(:changes_applied!)
  end
  changes_applied
end

#changes_payloadHash Also known as: update_payload

Returns a hash of the list of changes made to this instance.

Returns:

  • (Hash)

    a hash of the list of changes made to this instance.



564
565
566
567
568
569
570
571
572
# File 'lib/parse/model/core/actions.rb', line 564

def changes_payload
  h = attribute_updates
  if relation_changes?
    r = relation_change_operations.select { |s| s.present? }.first
    h.merge!(r) if r.present?
  end
  #h.merge!(className: parse_class) unless h.empty?
  h.as_json
end

#createBoolean

Save the object as a new record, running all callbacks.

Returns:

  • (Boolean)

    true/false whether it was successful.



445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/parse/model/core/actions.rb', line 445

def create
  run_callbacks :create do
    res = client.create_object(parse_class, attribute_updates, session_token: _session_token)
    unless res.error?
      result = res.result
      @id = result[Parse::Model::OBJECT_ID] || @id
      @created_at = result["createdAt"] || @created_at
      #if the object is created, updatedAt == createdAt
      @updated_at = result["updatedAt"] || result["createdAt"] || @updated_at
      # Because beforeSave hooks can change the fields we are saving, any items that were
      # changed, are returned to us and we should apply those locally to be in sync.
      set_attributes!(result)
    end
    puts "Error creating #{self.parse_class}: #{res.error}" if res.error?
    res.success?
  end
end

#destroy(session: nil) ⇒ Boolean

Delete this record from the Parse collection. Only valid if this object has an `id`. This will run all the `destroy` callbacks.

Parameters:

  • session (String) (defaults to: nil)

    a session token if you want to apply ACLs for a user in this operation.

Returns:

  • (Boolean)

    whether the operation was successful.

Raises:

  • ArgumentError if a non-nil value is passed to `session` that doesn't provide a session token string.



539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
# File 'lib/parse/model/core/actions.rb', line 539

def destroy(session: nil)
  @_session_token = _validate_session_token! session, :destroy
  return false if new?
  success = false
  run_callbacks :destroy do
    res = client.delete_object parse_class, id, session_token: _session_token
    success = res.success?
    if success
      @id = nil
      changes_applied!
    elsif self.class.raise_on_save_failure
      raise Parse::RecordNotSaved.new(self), "Failed to create or save attributes. #{self.parse_class} was not saved."
    end
    # Your create action methods here
  end
  @_session_token = nil
  success
end

#destroy_requestParse::Request

Returns a destroy_request for the current object.

Returns:



366
367
368
369
370
371
372
# File 'lib/parse/model/core/actions.rb', line 366

def destroy_request
  return nil unless @id.present?
  uri = self.uri_path
  r = Request.new(:delete, uri)
  r.tag = object_id
  r
end

#op_add!(field, objects) ⇒ Boolean

Perform an atomic add operation to the array field.

Parameters:

  • field (String)

    the name of the field in the Parse collection.

  • objects (Array)

    the set of items to add to this field.

Returns:

  • (Boolean)

    whether it was successful

See Also:



299
300
301
# File 'lib/parse/model/core/actions.rb', line 299

def op_add!(field, objects)
  operate_field! field, { __op: :Add, objects: objects }
end

#op_add_relation!(field, objects = []) ⇒ Boolean

Perform an atomic add operation on this relational field.

Parameters:

  • field (String)

    the name of the field in the Parse collection.

  • objects (Array<Parse::Object>) (defaults to: [])

    the set of objects to add to this relational field.

Returns:

  • (Boolean)

    whether it was successful

See Also:



335
336
337
338
339
340
# File 'lib/parse/model/core/actions.rb', line 335

def op_add_relation!(field, objects = [])
  objects = [objects] unless objects.is_a?(Array)
  return false if objects.empty?
  relation_action = Parse::RelationAction.new(field, polarity: true, objects: objects)
  operate_field! field, relation_action
end

#op_add_unique!(field, objects) ⇒ Boolean

Perform an atomic add unique operation to the array field. The objects will only be added if they don't already exists in the array for that particular field.

Parameters:

  • field (String)

    the name of the field in the Parse collection.

  • objects (Array)

    the set of items to add uniquely to this field.

Returns:

  • (Boolean)

    whether it was successful

See Also:



309
310
311
# File 'lib/parse/model/core/actions.rb', line 309

def op_add_unique!(field, objects)
  operate_field! field, { __op: :AddUnique, objects: objects }
end

#op_destroy!(field) ⇒ Boolean

Perform an atomic delete operation on this field.

Parameters:

  • field (String)

    the name of the field in the Parse collection.

Returns:

  • (Boolean)

    whether it was successful

See Also:



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

def op_destroy!(field)
  operate_field! field, { __op: :Delete }.freeze
end

#op_increment!(field, amount = 1) ⇒ Object

Atomically increment or decrement a specific field.

Parameters:

  • field (String)

    the name of the field in the Parse collection.

  • amount (Integer) (defaults to: 1)

    the amoun to increment. Use negative values to decrement.

See Also:



358
359
360
361
362
363
# File 'lib/parse/model/core/actions.rb', line 358

def op_increment!(field, amount = 1)
  unless amount.is_a?(Numeric)
    raise ArgumentError, "Amount should be numeric"
  end
  operate_field! field, { __op: :Increment, amount: amount.to_i }.freeze
end

#op_remove!(field, objects) ⇒ Boolean

Perform an atomic remove operation to the array field.

Parameters:

  • field (String)

    the name of the field in the Parse collection.

  • objects (Array)

    the set of items to remove to this field.

Returns:

  • (Boolean)

    whether it was successful

See Also:



318
319
320
# File 'lib/parse/model/core/actions.rb', line 318

def op_remove!(field, objects)
  operate_field! field, { __op: :Remove, objects: objects }
end

#op_remove_relation!(field, objects = []) ⇒ Boolean

Perform an atomic remove operation on this relational field.

Parameters:

  • field (String)

    the name of the field in the Parse collection.

  • objects (Array<Parse::Object>) (defaults to: [])

    the set of objects to remove to this relational field.

Returns:

  • (Boolean)

    whether it was successful

See Also:



347
348
349
350
351
352
# File 'lib/parse/model/core/actions.rb', line 347

def op_remove_relation!(field, objects = [])
  objects = [objects] unless objects.is_a?(Array)
  return false if objects.empty?
  relation_action = Parse::RelationAction.new(field, polarity: false, objects: objects)
  operate_field! field, relation_action
end

#operate_field!(field, op_hash) ⇒ Boolean

Perform an atomic operation on this field. This operation is done on the Parse server which guarantees the atomicity of the operation. This is the low-level API on performing atomic operations on properties for classes. These methods do not update the current instance with any changes the server may have made to satisfy this operation.

Parameters:

  • field (String)

    the name of the field in the Parse collection.

  • op_hash (Hash)

    The operation hash. It may also be of type RelationAction.

Returns:

  • (Boolean)

    whether the operation was successful.



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/parse/model/core/actions.rb', line 278

def operate_field!(field, op_hash)
  field = field.to_sym
  field = self.field_map[field] || field
  if op_hash.is_a?(Parse::RelationAction)
    op_hash = op_hash.as_json
  else
    op_hash = { field => op_hash }.as_json
  end

  response = client.update_object(parse_class, id, op_hash, session_token: _session_token)
  if response.error?
    puts "[#{parse_class}:#{field} Operation] #{response.error}"
  end
  response.success?
end

#prepare_save!Object

Runs all the registered `before_save` related callbacks.



559
560
561
# File 'lib/parse/model/core/actions.rb', line 559

def prepare_save!
  run_callbacks(:save) { false }
end

#relation_change_operationsArray

Generates an array with two entries for addition and removal operations. The first entry of the array will contain a hash of all the change operations regarding adding new relational objects. The second entry in the array is a hash of all the change operations regarding removing relation objects from this field.

Returns:

  • (Array)

    an array with two hashes; the first is a hash of all the addition operations and the second hash, all the remove operations.



582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
# File 'lib/parse/model/core/actions.rb', line 582

def relation_change_operations
  return [{}, {}] unless relation_changes?

  additions = []
  removals = []
  # go through all the additions of a collection and generate an action to add.
  relation_updates.each do |field, collection|
    if collection.additions.count > 0
      additions.push Parse::RelationAction.new(field, objects: collection.additions, polarity: true)
    end
    # go through all the additions of a collection and generate an action to remove.
    if collection.removals.count > 0
      removals.push Parse::RelationAction.new(field, objects: collection.removals, polarity: false)
    end
  end
  # merge all additions and removals into one large hash
  additions = additions.reduce({}) { |m, v| m.merge! v.as_json }
  removals = removals.reduce({}) { |m, v| m.merge! v.as_json }
  [additions, removals]
end

#save(session: nil, autoraise: false) ⇒ Boolean

saves the object. If the object has not changed, it is a noop. If it is new, we will create the object. If the object has an id, we will update the record.

You may pass a session token to the `session` argument to perform this actions with the privileges of a certain user.

You can define before and after :save callbacks autoraise: set to true will automatically raise an exception if the save fails

Parameters:

  • session (String) (defaults to: nil)

    a session token in order to apply ACLs to this operation.

  • autoraise (Boolean) (defaults to: false)

    whether to raise an exception if the save fails.

Returns:

  • (Boolean)

    whether the save was successful.

Raises:

  • (Parse::RecordNotSaved)

    if the save fails

  • ArgumentError if a non-nil value is passed to `session` that doesn't provide a session token string.



492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
# File 'lib/parse/model/core/actions.rb', line 492

def save(session: nil, autoraise: false)
  @_session_token = _validate_session_token! session, :save
  return true unless changed?
  success = false
  run_callbacks :save do
    #first process the create/update action if any
    #then perform any relation changes that need to be performed
    success = new? ? create : update

    # if the save was successful and we have relational changes
    # let's update send those next.
    if success
      if relation_changes?
        # get the list of changed keys
        changed_attribute_keys = changed - relations.keys.map(&:to_s)
        clear_attribute_changes(changed_attribute_keys)
        success = update_relations
        if success
          changes_applied!
        elsif self.class.raise_on_save_failure || autoraise.present?
          raise Parse::RecordNotSaved.new(self), "Failed updating relations. #{self.parse_class} partially saved."
        end
      else
        changes_applied!
      end
    elsif self.class.raise_on_save_failure || autoraise.present?
      raise Parse::RecordNotSaved.new(self), "Failed to create or save attributes. #{self.parse_class} was not saved."
    end
  end #callbacks
  @_session_token = nil
  success
end

#save!(session: nil) ⇒ Boolean

Save this object and raise an exception if it fails.

Parameters:

  • session (String) (defaults to: nil)

    a session token in order to apply ACLs to this operation.

Returns:

  • (Boolean)

    whether the save was successful.

Raises:

  • (Parse::RecordNotSaved)

    if the save fails

  • ArgumentError if a non-nil value is passed to `session` that doesn't provide a session token string.



530
531
532
# File 'lib/parse/model/core/actions.rb', line 530

def save!(session: nil)
  save(autoraise: true, session: session)
end

#set_attributes!(hash, dirty_track = false) ⇒ Hash

Performs mass assignment using a hash with the ability to modify dirty tracking. This is an internal method used to set properties on the object while controlling whether they are dirty tracked. Each defined property has a method defined with the suffix `_set_attribute!` that can will be called if it is contained in the hash.

Examples:

object.set_attributes!( {"myField" => value}, false)

# equivalent to calling the specific method.
object.myField_set_attribute!(value, false)

Parameters:

  • hash (Hash)

    the hash containing all the attribute names and values.

  • dirty_track (Boolean) (defaults to: false)

    whether the assignment should be tracked in the change tracking system.

Returns:



645
646
647
648
649
650
651
652
# File 'lib/parse/model/core/actions.rb', line 645

def set_attributes!(hash, dirty_track = false)
  return unless hash.is_a?(Hash)
  hash.each do |k, v|
    next if k == Parse::Model::OBJECT_ID || k == Parse::Model::ID
    method = "#{k}_set_attribute!"
    send(method, v, dirty_track) if respond_to?(method)
  end
end

#updateBoolean

Save all the changes related to this object.

Returns:

  • (Boolean)

    true/false whether it was successful.



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

def update
  return true unless attribute_changes?
  update!
end

#update!(raw: false) ⇒ Boolean

This methods sends an update request for this object with the any change information based on its local attributes. The bang implies that it will send the request even though it is possible no changes were performed. This is useful in kicking-off an beforeSave / afterSave hooks Save the object regardless of whether there are changes. This would call any beforeSave and afterSave cloud code hooks you have registered for this class.

Returns:

  • (Boolean)

    true/false whether it was successful.



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/parse/model/core/actions.rb', line 418

def update!(raw: false)
  if valid? == false
    errors.full_messages.each do |msg|
      warn "[#{parse_class}] warning: #{msg}"
    end
  end
  response = client.update_object(parse_class, id, attribute_updates, session_token: _session_token)
  if response.success?
    result = response.result
    # Because beforeSave hooks can change the fields we are saving, any items that were
    # changed, are returned to us and we should apply those locally to be in sync.
    set_attributes!(result)
  end
  puts "Error updating #{self.parse_class}: #{response.error}" if response.error?
  return response if raw
  response.success?
end

#update_relationsBoolean

Saves and updates all the relational changes for made to this object.

Returns:

  • (Boolean)

    whether all the save or update requests were successful.



605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
# File 'lib/parse/model/core/actions.rb', line 605

def update_relations
  # relational saves require an id
  return false unless @id.present?
  # verify we have relational changes before we do work.
  return true unless relation_changes?
  raise "Unable to update relations for a new object." if new?
  # get all the relational changes (both additions and removals)
  additions, removals = relation_change_operations

  responses = []
  # Send parallel Parse requests for each of the items to update.
  # since we will have multiple responses, we will track it in array
  [removals, additions].threaded_each do |ops|
    next if ops.empty? #if no operations to be performed, then we are done
    responses << client.update_object(parse_class, @id, ops, session_token: _session_token)
  end
  # check if any of them ended up in error
  has_error = responses.any? { |response| response.error? }
  # if everything was ok, find the last response to be returned and update
  #their fields in case beforeSave made any changes.
  unless has_error || responses.empty?
    result = responses.last.result #last result to come back
    set_attributes!(result)
  end #unless
  has_error == false
end

#uri_pathString

Returns the API uri path for this class.

Returns:

  • (String)

    the API uri path for this class.



375
376
377
# File 'lib/parse/model/core/actions.rb', line 375

def uri_path
  self.client.url_prefix.path + Client.uri_path(self)
end