tstmedia/active_zuora

View on GitHub
lib/active_zuora/persistence.rb

Summary

Maintainability
A
3 hrs
Test Coverage
module ActiveZuora
  module Persistence

    extend ActiveSupport::Concern

    MAX_BATCH_SIZE = 50

    def new_record?
      id.blank?
    end

    def save
      new_record? ? create : update
    end

    def save!
      raise "Could Not Save Zuora Object: #{errors.full_messages.join ', '}" unless save
    end

    def update_attributes(attributes)
      self.attributes = attributes
      save
    end

    def update_attributes!(attributes)
      self.attributes = attributes
      save!
    end

    def delete
      self.class.delete(id) > 0
    end

    def reload
      raise ArgumentError.new("You can't reload a new record") if new_record?
      self.untracked_attributes = self.class.find(id).attributes
      self
    end

    def xml_field_names
      # If we're rendering an existing record, always include the id.
      new_record? ? super : ([:id] + super).uniq
    end

    private

    def create
      return false unless new_record? && valid?
      result = self.class.connection.request(:create) do |soap|
        soap.body do |xml|
          build_xml(xml, soap, 
            :namespace => soap.namespace, 
            :element_name => :zObjects,
            :force_type => true)
        end
      end[:create_response][:result]
      if result[:success]
        self.id = result[:id]
        clear_changed_attributes
        true
      else
        errors.add(:base, result[:errors][:message]) if result[:errors]
        false
      end
    end

    def update
      self.class.update(self)
      self.errors.blank?
    end

    module ClassMethods

      def create(attributes={})
        new(attributes).tap(&:save)
      end

      def create!(attributes={})
        new(attributes).tap(&:save!)
      end

      # Takes an array of zobjects and batch saves new and updated records separately
      def save(*zobjects)
        new_records = 0
        updated_records = 0

        # Get all new objects
        new_objects = zobjects.flatten.select do |zobject|
          zobject.new_record? && zobject.changed.present? && zobject.valid?
        end

        # Get all updated objects
        updated_objects = zobjects.flatten.select do |zobject|
          !zobject.new_record? && zobject.changed.present? && zobject.valid?
        end

        # Make calls in batches of 50
        new_objects.each_slice(MAX_BATCH_SIZE) do |batch|
          new_records += process_save(batch, :create)
        end

        updated_objects.each_slice(MAX_BATCH_SIZE) do |batch|
          updated_records += process_save(batch, :update)
        end

        new_records + updated_records
      end

      # For backwards compatability
      def update(*zobjects)
        save(zobjects)
      end

      def process_save(zobjects, action)
        unless [:create, :update].include? action
          raise "Invalid action type for saving. Must be create or update." 
        end

        return 0 if zobjects.empty?
        results = connection.request(action) do |soap|
          soap.body do |xml|
            zobjects.map do |zobject|
              zobject.build_xml(xml, soap,
                :namespace => soap.namespace,
                :element_name => :zObjects,
                :force_type => true,
                :nil_strategy => :fields_to_nil)
            end.last
          end
        end["#{action.to_s}_response".to_sym][:result]
        results = [results] unless results.is_a?(Array)
        zobjects.each_with_index do |zobject, i|
          # If it's an update, grab by id, otherwise by index
          if action == :update
            result = results.find { |r| r[:id] == zobject.id } || 
              { :errors => { :message => "No result returned." } }
          else
            result = results[i] || { :errors => { :message => "No result returned." } }
          end
          if result[:success]
            zobject.clear_changed_attributes
          else
            zobject.add_zuora_errors result[:errors]
          end
        end
        # Return the count of updates that succeeded.
        results.select{ |result| result[:success] }.size
      end

      def delete(*ids)
        ids.flatten!
        deleted_records = 0
        ids.each_slice(MAX_BATCH_SIZE) do |batch|
          deleted_records += process_delete(batch)
        end
        deleted_records
      end

      def process_delete(*ids)
        ids.flatten!
        results = connection.request(:delete) do |soap|
          qualifier = soap.namespace_by_uri(soap.namespace)
          soap.body do |xml|
            xml.tag!(qualifier, :type, zuora_object_name)
            ids.map { |id| xml.tag!(qualifier, :ids, id) }.last
          end
        end[:delete_response][:result]
        results = [results] unless results.is_a?(Array)
        # Return the count of deletes that succeeded.
        results.select{ |result| result[:success] }.size
      end

    end

  end
end