Class: Quickbooks::Qbxml::Request

Inherits:
Object
  • Object
show all
Defined in:
lib/quickbooks/qbxml/request.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(object, type, options_or_regexp = {}) ⇒ Request

  1. List queries (:query)

Request.new(object, :query) # <= will return the record matching the object supplied (automatically searches by list_id or txn_id)
Request.new(Customer, :query, :limit => 1) # <= will return the first customer
Request.new(Customer, :query, /some match/) # <= will return all customers matching the regexp
  1. Object-specific transaction query (:transaction)

Request.new(Transaction, :query) # <= will return the transaction matching the object supplied
  1. Mod requests (:mod)

Request.new(object, :mod) # <= will update the object
  1. Delete requests (:delete)

Request.new(object, :delete) # <= will delete the object

We want the attributes of the object when we are updating, but no other time.

Raises:

  • (ArgumentError)


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/quickbooks/qbxml/request.rb', line 62

def initialize(object, type, options_or_regexp={})
  options = options_or_regexp.is_a?(Regexp) ? {:matches => options_or_regexp} : options_or_regexp
  @type = type
  raise ArgumentError, "Quickbooks::Qbxml::Requests can only be of one of the following types: :query, :transaction, :any_transaction, :mod, :add, :delete, or :report" unless @type.is_one_of?(:query, :transaction, :any_transaction, :mod, :report, :add, :delete)
  @klass = object.is_a?(Class) ? object : object.class
  @object = object
  @options = options

  # Return only specific properties: Request.new(Customer, :query, :only => [:list_id, :full_name]); Quickbooks::Customer.first(:only => :list_id)
  @ret_elements = @options.delete(:only).to_a.only(@klass.properties).order!(@klass.properties).stringify_values.camelize_values!(Quickbooks::CAMELIZE_EXCEPTIONS) if @options.has_key?(:only)

  # Includes only valid filters + aliases for valid filters, then transforms aliased filters to real filters, then camelizes keys to prepare for writing to XML, lastly orders the keys to a valid filter order.
# You may optionally have ListID OR FullName OR ( MaxReturned AND ActiveStatus AND FromModifiedDate AND ToModifiedDate AND ( NameFilter OR NameRangeFilter ) )
  @filters = options.stringify_keys.only(@klass.valid_filters + @klass.filter_aliases.keys).transform_keys!(@klass.filter_aliases).camelize_keys!(Quickbooks::CAMELIZE_EXCEPTIONS).order!(@klass.camelized_valid_filters)

  # Complain if:
  #   1) type is :mod or :delete, and object supplied is not a valid model
  raise ArgumentError, "A Quickbooks record object must be supplied to perform an add, mod or del action" if @type.is_one_of?(:add, :mod, :delete) && !@object.is_a?(Quickbooks::Base)
end

Class Method Details

.next_request_idObject



82
83
84
85
# File 'lib/quickbooks/qbxml/request.rb', line 82

def self.next_request_id
  @request_id ||= 0
  @request_id += 1
end

Instance Method Details

#to_xml(as_set = true) ⇒ Object

This is where the magic happens to convert a request object into xml worthy of quickbooks.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/quickbooks/qbxml/request.rb', line 88

def to_xml(as_set=true)
  return (RequestSet.new(self)).to_xml if as_set # Simple call yields xml as a single request in a request set. However, if the xml for the lone request is required, pass false.
  req = Builder::XmlMarkup.new(:indent => 2)
  request_root, container = case
  when @type.is_one_of?(:query)
    ["#{@klass.class_leaf_name}QueryRq", nil]
  when @type == :add
    ["#{@klass.class_leaf_name}AddRq", "#{@klass.class_leaf_name}Add"]
  when @type == :mod
    ["#{@klass.class_leaf_name}ModRq", "#{@klass.class_leaf_name}Mod"]
  when @type == :delete
    ["#{@klass.ListOrTxn}DelRq", nil]
  else
    raise RuntimeError, "Could not convert this request to qbxml!\n#{self.inspect}"
  end
  inner_stuff = lambda {
    deep_tag = lambda {|k,v|
      if v.is_a?(Hash)
        if k == ''
          v.each { |k,v|
            deep_tag.call(k,v)
          }
        else
          req.tag!(k.camelcase) { v.each { |k,v| deep_tag.call(k,v) } }
        end
      else
        req.tag!(k.camelcase,uncast(v))
      end
    }

    # Add the specific elements for the respective request type
    if @type.is_one_of?(:add, :mod)
      if @type == :mod
        # First the ObjectId:
        req.tag!(@klass.ListOrTxn + 'ID', @object.send("#{@klass.ListOrTxn}Id".underscore))
        # Second the EditSequence
        req.tag!('EditSequence', @object.send(:edit_sequence))
      end
      # Then, all the dirty_attributes
      deep_tag.call('',@object.to_dirty_hash) # (this is an hash statically ordered to the model's qbxml attribute order)
    elsif @type == :query && @object.class == @klass
      # Sent an instance object for a query - we should include the ListId/TxnId (then other filters?)
      req.tag!(@klass.ListOrTxn + 'ID', @object.send("#{@klass.ListOrTxn}Id".underscore))
      deep_tag.call('', @filters)
    elsif @type == :delete
      req.tag!(@klass.ListOrTxn + 'DelType', @klass.class_leaf_name)
      req.tag!(@klass.ListOrTxn + 'ID', @object.send("#{@klass.ListOrTxn}Id".underscore))
    else
      # just filters
      deep_tag.call('', @filters)
    end
    # Lastly, specify the fields to return, if desired
    @ret_elements.each { |r| req.tag!('IncludeRetElement', r) } if !@ret_elements.blank?
  }
  req.tag!(request_root, :requestID => self.class.next_request_id) {
    if container
      req.tag!(container) {
        inner_stuff.call
      }
    else
      inner_stuff.call
    end
  }
  # puts req.target!
  req.target!
end