Class: Quickbooks::Base

Inherits:
Model show all
Defined in:
lib/quickbooks/base.rb

Overview

Base is just base for ListItem and Transaction. It inherits from Model, just as Ref does.

Some Qbxml specifications require certain finder-options to be placed inside a containing entity, such as:

...
<DeletedDateRangeFilter>
  <FromDeletedDate>#{(Time.now - 5*60*60).xmlschema}</FromDeletedDate>
  <ToDeletedDate>#{(Time.now - 3*60*60).xmlschema}</ToDeletedDate>
</DeletedDateRangeFilter>
...

The Quickbooks Models define aliases to these “inside” options. The equivalent to the above [partial] request:

Quickbooks::Deleted.all(:deleted_after => (Time.now - 5*60*60).xmlschema, :deleted_before => (Time.now - 3*60*60).xmlschema)

(Type-casting hasn’t made it in yet.)

Qbxml makes a special allowance for Deleted items. Much of the time, we’d rather access a model’s deleted items through that model class instead of through the deleted class. So Qbxml allows any class to ask for its deleted items through a call like this:

Quickbooks::Customer.deleted(:deleted_after => Time.now - 3*60*60)

Direct Known Subclasses

Deleted, ListItem, Transaction

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Model

#===, #attributes, #attributes=, camelized_valid_filters, #dirty?, #dirty_attributes, filter_aliases, filter_aliases=, inherited, #original_values, properties, read_only, read_write, #to_dirty_hash, #to_hash, valid_filters, valid_filters=

Constructor Details

#initialize(*args) ⇒ Base

Generates a new object that can be saved into Quickbooks once the required attributes are set.



213
214
215
216
# File 'lib/quickbooks/base.rb', line 213

def initialize(*args)
  super # from Quickbooks::Model - sets the *args into attributes
  @new_record = true
end

Instance Attribute Details

#response_logObject

:nodoc:



100
101
102
# File 'lib/quickbooks/base.rb', line 100

def response_log
  @response_log
end

Class Method Details

.all(filters = {}) ⇒ Object

Queries Quickbooks for all of the objects of the current class’s type. For example, Quickbooks::Customer.all will return an array of Quickbooks::Customer objects representing all customers.



189
190
191
192
# File 'lib/quickbooks/base.rb', line 189

def all(filters={})
  filters.reverse_merge!(:active_status => 'All')
  [query(self, :query, filters)].flatten
end

.connectionObject

Returns the current Connection



127
128
129
# File 'lib/quickbooks/base.rb', line 127

def connection
  @connection || (@@connection ||= self.establish_connection())
end

.connection=(conn) ⇒ Object

Sets the current Connection.

This is normally not needed, but in the case that you may want to connect a separate connection to Quickbooks, you can use this method to explicitly set the connection in a class that inherits from Quickbooks::Base.

Quickbooks::Models::Base.connection = Quickbooks::Connection.new('My Test App', 'C:\\Some File.QBW', 'user', 'pass')

Raises:

  • (ArgumentError)


136
137
138
139
# File 'lib/quickbooks/base.rb', line 136

def connection=(conn)
  raise ArgumentError, "Cannot set connection to anything but a (*)Adapter::Connection object" unless conn.class.name =~ /Adapter::Connection$/
  @connection = conn
end

.create(*args) ⇒ Object

Creates a new object of the current class’s type. For example, Quickbooks::Customer.create(:name => ‘Tommy’) will create a customer object with a Name of Tommy.



207
208
209
# File 'lib/quickbooks/base.rb', line 207

def create(*args)
  new(*args).save
end

.deleted(filters = {}) ⇒ Object

Still in testing… these should be equivalent:

Quickbooks::Customer.deleted(:deleted_before => Time.now) == Quickbooks::Deleted.all(:type => 'Customer', :deleted_before => Time.now)


202
203
204
# File 'lib/quickbooks/base.rb', line 202

def deleted(filters={})
  query(self, :deleted, filters)
end

.establish_connection(*args) ⇒ Object

Establishes a connection to the Quickbooks RDS Server for all Model Classes



121
122
123
124
# File 'lib/quickbooks/base.rb', line 121

def establish_connection(*args)
  @@connection_adapter ||= use_adapter(:ole)
  @@connection = @@connection_adapter.new(*args)
end

.first(filters = {}) ⇒ Object

Queries Quickbooks for the first object of the current class’s type. For example, Quickbooks::Customer.first will return a Quickbooks::Customer object representing the first customer.



195
196
197
198
# File 'lib/quickbooks/base.rb', line 195

def first(filters={})
  (filters.merge!(:max_returned => 1) unless filters.keys.include?(:list_id) || filters.keys.include?(:txn_id) || filters.keys.include?(:full_name)) if filters.is_a?(Hash)
  query(self, :query, filters)
end

.instantiate(obj_or_attrs = {}, attrs = {}) ⇒ Object

Instantiate a new object with just attributes, or an existing object replacing the attributes



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/quickbooks/base.rb', line 172

def instantiate(obj_or_attrs={},attrs={})
  if obj_or_attrs.is_a?(Quickbooks::Base)
    obj = obj_or_attrs
  else
    obj = allocate
    attrs = obj_or_attrs
  end
  attrs.each do |key,value|
    if obj.respond_to?(key.to_s.underscore+'=')
      obj.send(key.to_s.underscore+'=', value)
      obj.original_values[key.to_s.underscore] = obj.instance_variable_get('@' + key.to_s.underscore).dup
    end
  end if attrs
  obj # Will be either a nice object, or a Qbxml::Error object.
end

.query(obj_or_args, *args) ⇒ Object

Generates a request by sending *args to Qbxml::Request.new, sends the request over the current connection, and interprets the response using Qbxml::ResponseSet. The response is then instantiated into an object or an array of objects.

This method is used mostly internally, but it is the yoke of this library - use it to perform custom requests.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/quickbooks/base.rb', line 146

def query(obj_or_args,*args)
  # If an object is sent, we need to reinstantiate the response into that object
  reinstantiate = if obj_or_args.is_a?(Quickbooks::Base)
    obj_or_args
  elsif obj_or_args.is_a?(Class)
    nil
  else
    args.unshift(obj_or_args)
    nil
  end
  objects = [] # This will hold and return the instantiated objects from the quickbooks response
# The following is subject to bugginess, IF the response contains more than one object: it will instantiate only the last one.
  self.request(reinstantiate || self, *args).each { |response| objects << response.instantiate(reinstantiate) } # Does not instantiate if it's an error, but simply records response into response_log
  objects.length == 1 ? objects[0] : objects
end

.request(*args) ⇒ Object

Generates a request using Qbxml::Request, sends it, and returns a Qbxml::ResponseSet object containing the response(s).



163
164
165
166
167
168
169
# File 'lib/quickbooks/base.rb', line 163

def request(*args)
  Qbxml::ResponseSet.new(
    self.connection.send_xml(
      Qbxml::Request.new(*args).to_xml
    )
  )
end

.use_adapter(adapter) ⇒ Object



114
115
116
117
118
# File 'lib/quickbooks/base.rb', line 114

def use_adapter(adapter)
  # Should complain if the adapter doesn't exist.
  require "#{File.dirname(__FILE__)}/adapters/#{adapter.to_s}_adapter"
  @@connection_adapter = Object.module_eval("::Quickbooks::#{adapter.to_s.camelize}Adapter::Connection", __FILE__, __LINE__)
end

Instance Method Details

#==(other) ⇒ Object

Usual comparison (super), but add in false if either is a new record.



271
272
273
274
275
# File 'lib/quickbooks/base.rb', line 271

def ==(other)
  return false unless other.is_a?(self.class)
  return false if self.new_record? || other.new_record?
  super
end

#destroyObject

Destroys a record in Quickbooks. Note that even though Quickbooks will destroy the record, the record’s ListID and DeletedTime can be accessed by doing a query for deleted objects of the appropriate type.



266
267
268
# File 'lib/quickbooks/base.rb', line 266

def destroy
  self.class.query(self, :delete).nil?
end

#inspectObject

:nodoc:



109
110
111
# File 'lib/quickbooks/base.rb', line 109

def inspect #:nodoc:
  "#<#{self.class.name}:#{self.object_id} #{instance_variables.reject {|i| i.is_one_of?('@response_log', '@original_values')}.map {|i| "#{i}=#{instance_variable_get(i).inspect}"}.join(' ')}>"
end

#new_record?Boolean

Returns true if the object is a new object (that doesn’t represent an existing object in Quickbooks).

Returns:

  • (Boolean)


219
220
221
# File 'lib/quickbooks/base.rb', line 219

def new_record?
  @new_record
end

#reloadObject

Reloads the record from Quickbooks, discarding any changes that have been made to it.



260
261
262
# File 'lib/quickbooks/base.rb', line 260

def reload
  self.class.query(self, :query)
end

#saveObject

Saves the attributes that have changed.

If the EditSequence is out of date but none of the changes conflict, the object will be saved to Quickbooks. But if there are conflicts, the updated values from Quickbooks will be written to the original_values, and false is returned. This way you can deal with the differences (conflicts), if you so desire, and simply call save again to commit your changes.



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/quickbooks/base.rb', line 228

def save
  return false unless dirty?
  self.errors.clear # Clear out any errors: start with a clean slate!
  if new_record?
    self.class.query(self, :add)
    ret = !dirty?
    @new_record = false if ret
  else
    # Smart system that respects EditSequences and other people's changes.
    # 1) Try to save
    # 2) When we get a status of 3200, that means our EditSequence is not up to date
    # 3) Replace self's original_attributes with those just retrieved, and update the automatic attributes, like EditSequence and TimeModified
    # 4) Return false, return the object dirty but ready to save
    old_originals = original_values.dup # Save the old_originals so we can detect any attributes that changed since we last loaded the object
    ret = self.class.query(self, :mod).error? ? false : true # Saves if possible, if EditSequence is out of date, it will read the up-to-date object into original_values
    # If save failed (dirty?) because of old record (status 3200), but none of the fields conflict (attributes I've modified and are still different aren't the same attributes as any of the attributes someone else updated), then re-save!
    if dirty? && self.response_log.last.status == 3200 && (dirty_attributes.only(dirty_attributes(old_originals).keys).keys - self.class.read_only.stringify_values).length == (dirty_attributes(old_originals).keys - old_originals.diff(original_values).keys - self.class.read_only.stringify_values).length
      # 'Revert' fields I didn't modify to equal the values of the more up-to-date record just loaded.
      # Fields I didn't modify: dirty_attributes - dirty_attributes(old_originals).keys
      (dirty_attributes - dirty_attributes(old_originals).keys).each_key do |at|
        self.send(at + '=', original_values[at]) if respond_to?(at + '=')
      end
      ret = self.save
    end
  end
  # ret should be a false value if self has errors. Either way, ret gets the errors too.
  ret.errors << self.errors if self.error?
  # Should be true or false with an error attached.
  ret
end

#success?Boolean

Returns success (true/false) status of the last quickbooks communication called from this object.

Returns:

  • (Boolean)


106
107
108
# File 'lib/quickbooks/base.rb', line 106

def success?
  @response_log.last.success?
end