module QBFC
# OLEWrapper is more or less the centerpiece of RubyQBFC. (Nearly) every
# WIN32OLE object accessed within the library is wrapped in this class, which
# is responsible for allowing Ruby-esque methods in place of the OLE methods.
#
# customer.full_name # => Customer.FullName.GetValue
# customer.full_name=(val) # => Customer.FullName.SetValue(val)
#
# It also creates referenced objects when accessed.
#
# check.payee # => Entity.find_by_list_id(check.PayeeEntityRef.ListID.GetValue)
# check.account # => Account.find_by_list_id(check.AccountRef.ListID.GetValue)
#
# When an OLE method called via OLEWrapper returns a WIN32OLE object, a new
# OLEWrapper object is created with the WIN32OLE object and returned.
#
# Now, the fun (and +really+ hackish) part. In many cases within the QBFC
# library, the wrapper is actually wrapping two WIN32OLE objects, the additional
# being a 'setter' object. This object is used when creating a ModRequest. In
# such cases, a method ending in '=' is always sent to both the primary and the
# setter objects. To facilitate this, traversing child ole_objects also
# traverses the child setter objects.
class OLEWrapper
attr_reader :ole_object
attr_accessor :setter
# Set up wrapped object, by passing a WIN32OLE object
# (or a String with the name of a WIN32OLE server)
# Optionally, pass a +setter+ WIN32OLE object.
def initialize(ole_object, setter = nil)
ole_object = WIN32OLE.new(ole_object) if ole_object.kind_of?(String)
@ole_object = ole_object
@setter = setter
end
# Return Array of ole_methods for request WIN32OLE object.
def ole_methods
@ole_object.ole_methods
end
# Does this OLE object respond to the given ole method?
def respond_to_ole?(symbol)
detect_ole_method?(@ole_object, symbol)
end
# Use [idx] syntax for objects that respond to GetAt(idx)
def [](idx)
if idx.kind_of? Integer
self.class.new(@ole_object.GetAt(idx))
else
@ole_object[idx]
end
end
# Called by #method_missing of other classes. Initiates the OLEWrapper#method_missing
# method which is responsible for the various method conversions.
# +sess+ argument is a QBFC::Session.
def qbfc_method_missing(sess, symbol, *params)
@sess = sess
method_missing(symbol, *params)
end
# If the method name is capitalized, send directly to ole_object; if
# a WIN32OLE is returned, wrap it.
# If the method name starts with a lower-case letter, send to +lower_method_missing+
# for conversion.
def method_missing(symbol, *params) #:nodoc:
if (('a'..'z') === symbol.to_s[0].chr)
lower_case_method_missing(symbol, *params)
else
resp = @ole_object.send(symbol, *params)
return( resp.kind_of?(WIN32OLE) ?
self.class.new(resp) :
resp )
end
end
private
# Decide which conversion method needs to handle this method and send.
def lower_case_method_missing(symbol, *params)
if '=' == symbol.to_s[-1].chr
set_value(symbol.to_s[0..-2], *params)
elsif symbol.to_s =~ /\A(\w+)_(full_name|id)\Z/ && ref_name($1)
get_ref_name_or_id(ref_name($1), $2)
elsif detect_ole_method?(@ole_object, ole_sym(symbol))
get_value(symbol, *params)
elsif detect_ole_method?(@ole_object, (s = symbol.to_s.singularize.camelize + "RetList"))
setup_array(s)
elsif detect_ole_method?(@ole_object, (s = "OR" + symbol.to_s.singularize.camelize + "RetList"))
setup_array(s, true)
elsif detect_ole_method?(@ole_object, (s = symbol.to_s.singularize.camelize + "List"))
setup_array(s)
elsif detect_ole_method?(@ole_object, (s = "OR" + symbol.to_s.singularize.camelize + "List"))
setup_array(s, true)
elsif ref_name(symbol)
create_ref(ref_name(symbol), *params)
elsif detect_ole_method?(@ole_object, symbol)
# Occassionally, QBFC uses a lower case method
@ole_object.send(symbol, *params)
else
raise NoMethodError, symbol.to_s
end
end
# Sets a value by calling OLEMethodName.SetValue(*params)
def set_value(ole_method_name, *params)
ole_method_name = ole_sym(ole_method_name)
obj = @ole_object.send(ole_method_name)
if detect_ole_method?(obj, "SetValue")
params[0] = params[0].strftime("%Y-%m-%d") if params[0].kind_of?(Date)
obj.SetValue(*params)
if @setter && detect_ole_method?(@setter, ole_method_name)
@setter.send(ole_method_name).SetValue(*params)
end
else
raise SetValueMissing, "SetValue is expected, but missing, for #{ole_method_name}"
end
end
# Gets a value by calling OLEMethodName.GetValue
def get_value(ole_method_name, *params)
ole_method_name = ole_sym(ole_method_name)
obj = @ole_object.send(ole_method_name, *params)
if detect_ole_method?(obj, "GetValue")
if ole_method_name =~ /date/i || ole_method_name.to_s =~ /time/i
Time.parse(obj.GetValue)
else
obj.GetValue
end
else
if obj.kind_of?(WIN32OLE)
if @setter && detect_ole_method?(@setter, ole_method_name)
self.class.new(obj, @setter.send(ole_method_name, *params))
else
self.class.new(obj)
end
else
obj
end
end
end
# Sets up an array to return if the return of OLEMethodName appears
# to be a list structure.
# is_OR_list indicates the list is an OR*RetList which
# is structured differently.
def setup_array(ole_method_name, is_OR_list = false)
list = @ole_object.send(ole_method_name)
ary = []
0.upto(list.Count - 1) do |i|
if is_OR_list
ary << self.class.new(list.GetAt(i)).send(ole_method_name.match(/\AOR(.*)List\Z/)[1])
else
ary << self.class.new(list.GetAt(i))
end
end
return ary
end
def ref_name(symbol)
if detect_ole_method?(@ole_object, symbol.to_s.camelize + "Ref")
symbol.to_s.camelize + "Ref"
elsif detect_ole_method?(@ole_object, symbol.to_s.camelize + "EntityRef")
symbol.to_s.camelize + "EntityRef"
else
nil
end
end
# Creates a QBFC::Base inherited object if the return of
# OLEMethodName appears to be a reference to such an object.
def create_ref(ref_ole_name, *options)
ref_ole_object = @ole_object.send(ref_ole_name)
if ref_ole_object
ref_ole_name =~ /EntityRef/ ?
QBFC::Entity.find_by_id(@sess, ref_ole_object.ListID.GetValue(), *options) :
QBFC::const_get(ref_ole_name.gsub(/Ref/,"")).find_by_id(@sess, ref_ole_object.ListID.GetValue(), *options)
else
return nil
end
end
def get_ref_name_or_id(symbol, field)
field = (field == "id" ? "ListID" : "FullName")
@ole_object.send(symbol).send(field).GetValue()
end
# Check if the obj has an ole_method matching the symbol.
def detect_ole_method?(obj, symbol)
obj && obj.respond_to?(:ole_methods) && obj.ole_methods.detect{|m| m.to_s == symbol.to_s}
end
# Helper method to convert 'Ruby-ish' method name to WIN32OLE method name
def ole_sym(symbol)
symbol.to_s.camelize.gsub(/Id/, 'ID')
end
end
end