When integrating with QuickBooks Online (QBO) there is one thing you better be prepared for: errors and lots of 'em.
qbo_rails gem is a Rails engine that is all about handling and responding to QBO errors. It does some other common things as well such as automatically determining and submitting create or update requests.
qbo_rails gem depends on both the quickbooks-ruby and the quickbooks-ruby-base. In essence, it is a thin wrapper around the CRUD actions of
lib/qbo_rails.rb source is only ~100 lines of code.
- Rails 4
- Ruby 2
Quick Start Guide
rails generate qbo_rails:install
- this creates:
- db migration for the
- this creates:
development: &default ... qbo_app_consumer_key: <%= ENV['YOUR_QBO_APP_DEV_CONSUMER_KEY'] %> qbo_app_consumer_secret: <%= ENV['YOUR_QBO_APP_DEV_CONSUMER_SECRET'] %> ...
- Need to fill in the proper attribute names that are used for persisting the Intuit OAuth credentials.
The gem assumes that you store QBO ids in a column/attribute called
qbo_id. You can change that as so:
QboRails.foreign_key = 'qbo_external_id'
quickbooks-ruby-base gem is composed into a new instance of
QboRails and can accessed as so:
qbo_rails = QboRails.new(account, :customer) qbo_rails.base
You can access the
quickbooks-ruby instance through
.base. See the docs.
You must keep entities in sync between your app and QBO. Using the
qbo_rails will send a
update based on if say
customer.qbo_id is nil.
account = Account.find(1) # this is the where the QBO OAuth access_token, secret, and company_id are found customer = Customer.find(1) qbo_rails = QboRails.new(account, :customer) qb_customer = qbo_rails.base.qr_model(:customer) qb_customer.display_name = customer.display_name address = qbo_rails.base.qr_model(:physical_address) address.line1 = customer.address_1 address.city = customer.city address.country_sub_division_code = customer.state address.postal_code = customer.zip qb_customer.billing_address = address qbo_rails.create_or_update(customer, qb_customer)
nil then a create request is sent to QBO. If successful the QBO Id for the new Customer record will be automatically recorded in
customer.qbo_id. Therefore, the next time
qbo_rails.create_or_update(customer, qbo_customer) for this customer is called an
update request will be sent. Sending an update first involves querying QBO to get the latest sync token so it is nice to DRY up that procedure.
If you don't need to do an update and don't need to record the QBO Id in a model but still want the error handling goodness then the
create() method is available
qbo_rails = QboRails.new(account, :customer) qb_customer = qbo_rails.base.qr_model(:customer) qb_customer.display_name = 'Hockey Mom' qbo_rails.create(qbo_customer)
delete method takes either the ActiveRecord instance or a QBO Id.
Example: This deletes the QBO Customer with Id = 1
qbo_rails = QboRails.new(account, :customer) qbo_rails.delete(1)
Example: Pass in ActiveRecord instance
customer = account.customers.find(2) qbo_rails = QboRails.new(account, :customer) qbo_rails.delete(customer)
Usage: Returned results
qbo_rails.create_or_update(customer, qbo_customer) puts qbo_rails.result.id
Usage: Error handling
QBO API Errors are recorded in the
QboError model. The column names are:
=> QboError(id: integer, message: string, body: text, resource_type: string, resource_id: integer, request_xml: text, created_at: datetime, updated_at: datetime )
Adding custom QboError columns
For example, let's say I want to record an association to a
qbo_account model. Use the
before_create callback to populate the column. e.g.:
class QboError < ActiveRecord::Base belongs_to :resource, polymorphic: true belongs_to :qbo_account before_create :set_qbo_account private def set_qbo_account self.qbo_account = self.resource.try(:import_file).try(:qbo_account) end end
resource_id are for recording the ActiveRecord model and are ready-to-go for polymorphic associations.
Usage: Responding to an error
Let's say that you have a customer in Rails, Jane Riley, and you send her in as a create request to QBO. There is already a Jane Riley on QBO. Guess what?
Duplicate Name Exists Error. But
qbo_rails can handle this by simple adding the below method with the prefix
For example in your Rails app you could add a file in
app/services/qbo_rails/error_handler.rb that responds to QBO API's
Duplicate Name Exists Error.
class QboRails module ErrorHandler def handle_error_name_entity_already_exists(exception) if exception.message =~ /Duplicate Name Exists Error.*Another (customer|vendor|employee)/m display_name = Nokogiri::XML(exception.request_xml).at('DisplayName').content result = @base.find_by_display_name(display_name) if result.entries.size == 1 @record.update_column(foreign_key, result.entries.first.id) @only_run_once = true @record.reload create_or_update(@record, @qb_record) true else false end else false end end end
It catches the error, queries QBO for Jane Riley, sets the customer record with the Id, and then calls
create_or_update, which will properly send an update request. You have access to all
QboRails instance variables such as:
@record: This is the ActiveRecord instance (only available if passed) @qb_record: This is the `quickbooks-ruby` instance. @base: This is the `quickbooks-ruby-base` instance.
See the source to get all the instance vars and methods available to you when crafting custom error handling.
Usage: Responding to errors ground rules
- Always return
trueafter you have run a response action.
- Always set
@only_run_once = truewhen returning
- If not 1 & 2 always return
These rules are for preventing infinite looping. See the
spec/dummy code for more examples.
- Fork it ( https://github.com/minimul/qbo_rails/fork )
- Create your feature branch (
git checkout -b my-new-feature)
- Commit your changes (
git commit -am 'Add some feature')
- Push to the branch (
git push origin my-new-feature)
- Create a new Pull Request
Note: If you are going to adding new specs or modify existing ones that involve a transaction with the QBO API then they must be recorded using VCR. To do that set your test QBO app and sandbox credentials in
spec/dummy/.env file (don't commit this in your PR). The
.env file should be in this format:
export QBO_RAILS_CONSUMER_KEY= export QBO_RAILS_CONSUMER_SECRET= export QBO_RAILS_ACCESS_TOKEN= export QBO_RAILS_ACCESS_TOKEN_SECRET= export QBO_RAILS_COMPANY_ID=
To make it easy to get your OAuth information you can:
- Go through the OAuth process and the
QBO_RAILS_COMPANY_IDwill be displayed.