Half-Penny For Your Thoughts

rounded down to the nearest cent



Categories


Recent Articles




Computing

QBFC and Ruby

I discovered how to use QBFC with Ruby. QBFC is part of the Quickbooks SDK and is a fairly basic wrapper around QBXML. To me, it’s not the simplest interface, but it’s a nice not to bother with the XML, as I’ve been doing. Whereas when using QBXML, you would call WIN32OLE.new(“QBXMLRP2.RequestProcessor”), use WIN32OLE.new(“QBFC6.QBSessionManager”) instead.

Here’s quick example:

require 'win32ole'

qb = WIN32OLE.new("QBFC6.QBSessionManager")

qb.OpenConnection2 "", "Ruby Test", 1 # 1 is ctLocalQBD
qb.BeginSession("", 2) # 2 is omDontCare

request_set = qb.CreateMsgSetRequest("US", 6, 0)
customer_query = request_set.AppendCustomerQueryRq

response = qb.DoRequests(request_set)
customer_set = response.ResponseList.GetAt(0)
first_customer = customer_set.Detail.GetAt(0)
puts first_customer.FullName.GetValue

qb.EndSession
qb.CloseConnection
qb = nil 

Now, I like to wrap all that opening and closing connection stuff in a block, so here’s a version I like a bit better:

require 'win32ole'

OPEN_MODES = {
  :single => 0,
  :multi  => 1,
  :either => 2
}

def session(app_name, options = {})
  com_obj = WIN32OLE.new("QBFC6.QBSessionManager")
  com_obj.OpenConnection2(options[:app_id].to_s, app_name, 1) # 1 is ctLocalQBD

      open_mode = OPEN_MODES[options[:open_mode].to_s] || 2 # :either is default
  com_obj.BeginSession(options[:filename].to_s, open_mode)
  
  begin
    yield(com_obj)
  ensure
    com_obj.EndSession
    com_obj.CloseConnection
    com_obj = nil
  end
end

session("Test Application") do | qb |
  request_set = qb.CreateMsgSetRequest("US", 6, 0)
  customer_query = request_set.AppendCustomerQueryRq

  response = qb.DoRequests(request_set)
  customer_set = response.ResponseList.GetAt(0)
  first_customer = customer_set.Detail.GetAt(0)
  puts first_customer.FullName.GetValue
end

And, finally, a bit of playing with WIN32OLE (risky, most likely):

require 'win32ole'

unless Object.const_defined?(:ActiveSupport)
  gem 'activesupport'
  require 'active_support'
end

class WIN32OLE

  alias_method :old_array_selector, :[]
  
  def [](idx)
    if idx.kind_of? Integer
      self.GetAt(idx)
    else
      old_array_selector(idx)
    end
  end
  
  alias_method :old_method_missing, :method_missing
  
  def method_missing(symbol, *params)
    if (('a'..'z') === symbol.to_s[0].chr)
      obj = old_method_missing(symbol.to_s.camelize.to_sym, *params)

      if obj.ole_methods.detect{|m| m.to_s == 'GetValue'}
        obj.GetValue
      end
    else
      old_method_missing(symbol, *params)
    end
  end
end

The new definition of [] checks if an integer was passed. If so, and if self responds to “GetAt”, go ahead and send the integer value to self#GetAt . This allows me to use response.ResponseList[0] instead response.ResponseList.GetAt(0).

The method_missing stuff allows does a similar shortcut for GetValue . Instead of first_customer.FullName.GetValue, I can call first_customer.full_name.

This is not at all tested, so I can’t say what these changes might mess up.


Computing

Simple Ruby DNS Server

Update 2009-09-10: If you’re interested in a DNS server written in Ruby, check out RubyDNS. Thanks to Samuel for his comment below announcing its release.

A few weeks ago, I started playing with Ruby’s Resolv library, which abstracts the DNS protocol. I’m playing with creating a DNS server in Ruby. It turns out to be pretty easy to get a very basic DNS server going in Ruby, And, for the curious, here it is.


require 'socket'
require 'resolv'

hosts = [
  {:name => "example.com", :type => "A", :data => "192.168.0.1"}
]

# Bind to UDP port 53 to receive requests
$port = 53
server = UDPSocket.open
server.bind(nil, $port)

while true
  # Receive and parse query
  data = server.recvfrom(10000)
  query = Resolv::DNS::Message::decode(data[0])
  
  # Setup answer
  answer = Resolv::DNS::Message::new(query.id)
  answer.qr = 1                 # 0 = Query, 1 = Response
  answer.opcode = query.opcode  # Type of Query; copy from query
  answer.aa = 1                 # Is this an authoritative response: 0 = No, 1 = Yes
  answer.rd = query.rd          # Is Recursion Desired, copied from query
  answer.ra = 0                 # Does name server support recursion: 0 = No, 1 = Yes
  answer.rcode = 0              # Response code: 0 = No errors

  query.each_question do |question, typeclass|    # There may be multiple questions per query
    name = question.to_s                          # The domain name looked for in the query.
    record_type = typeclass.name.split("::").last # For example "A", "MX"
    ttl = 16000
    record = hosts.find{|host| host[:name] == name && host[:type] == record_type }
    unless record.nil?
      # Setup answer to this question
      answer.add_answer(name + ".",ttl,typeclass.new(record[:data]))
      answer.encode
    end
  end
  
  # Send the response
  server.send(answer.encode, 0, data[1][2], data[1][1])
end

To try this out, run the above script (may require sudo, since it binds to port 53; I haven’t figured out a way to test this at a different port) and then, in irb:

require 'resolv'
d = Resolv::DNS::new(:nameserver => "localhost")
puts d.getaddress("example.com")

This would only respond to A record requests for example.com, and the array of hashes is not particularly scalable. One could, for example, use ActiveRecord to access the records from a database, which is something I’m currently working on.