lib/QuickBaseClient.rb in quickbase_client-1.0.2 vs lib/QuickBaseClient.rb in quickbase_client-1.0.3

- old
+ new

@@ -11,14 +11,20 @@ # Intuit Partner Platform. #++##################################################################### require 'rexml/document' require 'net/https' -require 'socket' require 'json' require 'QuickBaseMisc' +begin + require 'httpclient' + USING_HTTPCLIENT = true +rescue LoadError + USING_HTTPCLIENT = false +end + module QuickBase # QuickBase client: Version 1.0.0: Ruby wrapper class for QuickBase HTTP API. # The class's method and member variable names correspond closely to the QuickBase HTTP API reference. # This class was written using ruby 1.8.6. @@ -142,29 +148,40 @@ options["proxy_options"]) end # Initializes the connection to QuickBase. def setHTTPConnection( useSSL, org = "www", domain = "quickbase", proxy_options = nil ) + @useSSL = useSSL @org = org @domain = domain - if proxy_options - @httpProxy = Net::HTTP::Proxy(proxy_options["proxy_server"], proxy_options["proxy_port"], proxy_options["proxy_user"], proxy_options["proxy_password"]) - @httpConnection = @httpProxy.new( "#{@org}.#{@domain}.com", useSSL ? 443 : 80) - else - @httpConnection = Net::HTTP.new( "#{@org}.#{@domain}.com", useSSL ? 443 : 80 ) - end - @httpConnection.use_ssl = useSSL - @httpConnection.verify_mode = OpenSSL::SSL::VERIFY_NONE + if USING_HTTPCLIENT + if proxy_options + @httpConnection = HTTPClient.new( "#{proxy_options["proxy_server"]}:#{proxy_options["proxy_port"] || useSSL ? "443" : "80"}" ) + @httpConnection.set_auth(proxy_options["proxy_server"], proxy_options["proxy_user"], proxy_options["proxy_password"]) + else + @httpConnection = HTTPClient.new + end + else + if proxy_options + @httpProxy = Net::HTTP::Proxy(proxy_options["proxy_server"], proxy_options["proxy_port"], proxy_options["proxy_user"], proxy_options["proxy_password"]) + @httpConnection = @httpProxy.new( "#{@org}.#{@domain}.com", useSSL ? 443 : 80) + else + @httpConnection = Net::HTTP.new( "#{@org}.#{@domain}.com", useSSL ? 443 : 80 ) + end + @httpConnection.use_ssl = useSSL + @httpConnection.verify_mode = OpenSSL::SSL::VERIFY_NONE + end end # Causes useful information to be printed to the screen for every HTTP request. def debugHTTPConnection() - @httpConnection.set_debug_output $stdout if @httpConnection + @httpConnection.set_debug_output $stdout if @httpConnection and USING_HTTPCLIENT == false end # Sets the QuickBase URL and port to use for requests. def setqbhost( useSSL, org = "www", domain = "quickbase" ) + @useSSL = useSSL @org = org @domain = domain @qbhost = useSSL ? "https://#{@org}.#{@domain}.com:443" : "http://#{@org}.#{@domain}.com" @qbhost end @@ -214,12 +231,18 @@ @logger.logRequest( @dbidForRequestURL, api_Request, @requestXML ) if @logger begin # send the request - @responseCode, @responseXML = @httpConnection.post( @requestURL, @requestXML, @requestHeaders ) - + if USING_HTTPCLIENT + response = @httpConnection.post( @requestURL, @requestXML, @requestHeaders ) + @responseCode = response.status + @responseXML = response.content + else + @responseCode, @responseXML = @httpConnection.post( @requestURL, @requestXML, @requestHeaders ) + end + printResponse( @responseCode, @responseXML ) if @printRequestsAndResponses if not isHTMLRequest processResponse( @responseXML ) end @@ -350,11 +373,11 @@ def processResponse( responseXML ) fire( "onProcessResponse" ) parseResponseXML( responseXML ) - @ticket = getResponseValue( :ticket ) if @ticket.nil? + @ticket ||= getResponseValue( :ticket ) @udata = getResponseValue( :udata ) getErrorInfoFromResponse end # Extracts error info from XML responses returned by QuickBase. @@ -386,10 +409,11 @@ end # Gets the value for a specific field at the top level # of the XML returned from QuickBase. def getResponseValue( field ) + @fieldValue = nil if field and @responseXMLdoc @fieldValue = @responseXMLdoc.root.elements[ field.to_s ] @fieldValue = fieldValue.text if fieldValue and fieldValue.has_text? end @fieldValue @@ -488,17 +512,18 @@ findElementsByAttributeValue( @fields, "field_type", type ) end # Returns the value of a field property, or nil. def lookupFieldPropertyByName( fieldName, property ) + theproperty = nil if isValidFieldProperty?(property) fid = lookupFieldIDByName( fieldName ) field = lookupField( fid ) if fid - theproperty = nil theproperty = field.elements[ property ] if field theproperty = theproperty.text if theproperty and theproperty.has_text? end + theproperty end # Returns whether a field will show a Total on reports. def isTotalField?(fieldName) does_total = lookupFieldPropertyByName(fieldName,"does_total") @@ -689,11 +714,11 @@ element end # Returns an array of XML sub-elements with the specified attribute value. def findElementsByAttributeValue( elements, attribute_name, attribute_value ) - elementArray = Array.new + elementArray = [] if elements if elements.is_a?( REXML::Element ) elements.each_element_with_attribute( attribute_name, attribute_value ) { |e| elementArray << e } elsif elements.is_a?( Array ) elements.each{ |e| @@ -706,11 +731,11 @@ elementArray end # Returns an array of XML sub-elements with the specified attribute name. def findElementsByAttributeName( elements, attribute_name ) - elementArray = Array.new + elementArray = [] if elements elements.each_element_with_attribute( attribute_name ) { |e| elementArray << e } end elementArray end @@ -953,11 +978,11 @@ numTables = 0 dbid ||= @dbid if getSchema(dbid) if @chdbids chdbidArray = findElementsByAttributeName( @chdbids, "name" ) - chdbidArray.each{|chdbid| numTables += 1 } + numTables = chdbidArray.length end end numTables end @@ -988,15 +1013,13 @@ ret end # Returns whether a given string represents a valid QuickBase field type. def isValidFieldType?( type ) - if @validFieldTypes.nil? - @validFieldTypes = %w{ checkbox dblink date duration email file fkey float formula currency + @validFieldTypes ||= %w{ checkbox dblink date duration email file fkey float formula currency lookup phone percent rating recordid text timeofday timestamp url userid icalendarbutton } - end - ret = @validFieldTypes.include?( type ) + @validFieldTypes.include?( type ) end # Returns a field type string given the more human-friendly label for a field type. def fieldTypeForLabel( fieldTypeLabel ) @fieldTypeLabelMap ||= Hash["Text","text","Numeric","float","Date / Time","timestamp","Date","date","Checkbox","checkbox","Database Link","dblink","Duration","duration","Email","email","File Attachment","file","Numeric-Currency","currency","Numeric-Rating","rating","Numeric-Percent","percent","Phone Number","phone","Relationship","fkey","Time Of Day","timeofday","URL","url","User","user","Record ID#","recordid","Report Link","dblink","iCalendar","icalendarbutton"] @@ -1107,11 +1130,11 @@ # * name: label of the field value to be set. # * fid: id of the field to be set. # * filename: if the field is a file attachment field, the name of the file that should be displayed in QuickBase. # * value: the value to set in this field. If the field is a file attachment field, the name of the file that should be uploaded into QuickBase. def addFieldValuePair( name, fid, filename, value ) - @fvlist = Array.new if @fvlist.nil? + @fvlist ||= [] @fvlist << FieldValuePairXML.new( self, name, fid, filename, value ).to_s @fvlist end # Replaces a field value in the list of fields to be set by the next addRecord() or editRecord() call to QuickBase. @@ -1145,11 +1168,11 @@ if fids.is_a?( Array ) and fids.length > 0 fids.each { |id| fid = lookupField( id ) if fid fname = lookupFieldNameFromID( id ) - @fnames = Array.new if @fnames.nil? + @fnames ||= [] @fnames << fname else raise "verifyFieldList: '#{id}' is not a valid field ID" end } @@ -1160,11 +1183,11 @@ elsif fnames if fnames.is_a?( Array ) and fnames.length > 0 fnames.each { |name| fid = lookupFieldIDByName( name ) if fid - @fids = Array.new if @fids.nil? + @fids ||= [] @fids << fid else raise "verifyFieldList: '#{name}' is not a valid field name" end } @@ -1228,12 +1251,12 @@ # Returns a valid query operator. def verifyQueryOperator( operator, fieldType ) queryOperator = "" if @queryOperators.nil? - @queryOperators = Hash.new - @queryOperatorFieldType = Hash.new + @queryOperators = {} + @queryOperatorFieldType = {} @queryOperators[ "CT" ] = [ "contains", "[]" ] @queryOperators[ "XCT" ] = [ "does not contain", "![]" ] @queryOperators[ "EX" ] = [ "is", "==", "eq" ] @@ -1286,20 +1309,24 @@ queryOperator end # Get a field's base type using its name. def lookupBaseFieldTypeByName( fieldName ) + type = "" fid = lookupFieldIDByName( fieldName ) field = lookupField( fid ) if fid type = field.attributes[ "base_type" ] if field + type end # Get a field's type using its name. def lookupFieldTypeByName( fieldName ) + type = "" fid = lookupFieldIDByName( fieldName ) field = lookupField( fid ) if fid type = field.attributes[ "field_type" ] if field + type end # Returns the string required for emebedding CSV data in a request. def formatImportCSV( csv ) "<![CDATA[#{csv}]]>" @@ -1436,11 +1463,11 @@ end # Converts a string into an array, given a field separator. # '"' followed by the field separator are treated the same way as just the field separator. def splitString( string, fieldSeparator = "," ) - ra = Array.new + ra = [] string.chomp! if string.include?( "\"" ) a=string.split( "\"#{fieldSeparator}" ) a.each{ |b| c=b.split( "#{fieldSeparator}\"" ) c.each{ |d| @@ -1454,11 +1481,11 @@ end # Returns the URL-encoded version of a non-printing character. def escapeXML( char ) if @xmlEscapes.nil? - @xmlEscapes = Hash.new + @xmlEscapes = {} (0..255).each{ |i| @xmlEscapes[ i.chr ] = sprintf( "&#%03d;", i ) } end return @xmlEscapes[ char ] if @xmlEscapes[ char ] char end @@ -1554,14 +1581,14 @@ onSetActiveField } if @events.nil? if @events.include?( event ) if handler and handler.is_a?( EventHandler ) if @eventSubscribers.nil? - @eventSubscribers = Hash.new + @eventSubscribers = {} end if not @eventSubscribers.include?( event ) - @eventSubscribers[ event ] = Array.new + @eventSubscribers[ event ] = [] end if not @eventSubscribers[ event ].include?( handler ) @eventSubscribers[ event ] << handler end else @@ -1739,11 +1766,11 @@ end # API_DeleteAppZip def deleteAppZip( dbid ) @dbid = dbid - sendRequest( :deleteAppZip, xmlRequestData ) + sendRequest( :deleteAppZip ) return self if @chainAPIcalls @responseCode end # API_DumpAppZip @@ -1862,11 +1889,11 @@ end # API_ChangeRecordOwner def _changeRecordOwner( rid, newowner ) changeRecordOwner( @dbid, rid, newowner ) end - # API_ChangeUserRole, using the active table id. + # API_ChangeUserRole. def changeUserRole( dbid, userid, roleid, newroleid ) @dbid, @userid, @roleid, @newroleid = dbid, userid, roleid, newroleid xmlRequestData = toXML( :userid, @userid ) xmlRequestData << toXML( :roleid, @roleid ) @@ -2025,12 +2052,16 @@ @variables = getResponseElements( "qdbapi/variable" ) end return self if @chainAPIcalls - if @records and block_given? - @records.each { |element| yield element } + if block_given? + if @records + @records.each { |element| yield element } + else + yield nil + end else @records end end @@ -2069,22 +2100,22 @@ return self if @chainAPIcalls @numMatches end - # API_DoQuery, using the active table id. + # API_DoQueryCount, using the active table id. def _doQueryCount( query ) doQueryCount( @dbid, query ) end # Download a file's contents from a file attachment field in QuickBase. # You must write the contents to disk before a local file exists. def downLoadFile( dbid, rid, fid, vid = "0" ) @dbid, @rid, @fid, @vid = dbid, rid, fid, vid @downLoadFileURL = "http://#{@org}.#{@domain}.com/up/#{dbid}/a/r#{rid}/e#{fid}/v#{vid}" - if @httpConnection.use_ssl? + if @useSSL @downLoadFileURL.gsub!( "http:", "https:" ) end @requestHeaders = { "Cookie" => "ticket=#{@ticket}" } @@ -2095,12 +2126,18 @@ p @requestHeaders end begin - @responseCode, @fileContents = @httpConnection.get( @downLoadFileURL, @requestHeaders ) - + if USING_HTTPCLIENT + response = @httpConnection.get( @downLoadFileURL, nil, @requestHeaders ) + @responseCode = response.status + @fileContents = response.body.content if response.body + else + @responseCode, @fileContents = @httpConnection.get( @downLoadFileURL, @requestHeaders ) + end + rescue Net::HTTPBadResponse => @lastError rescue Net::HTTPHeaderSyntaxError => @lastError rescue StandardError => @lastError end @@ -2360,10 +2397,11 @@ # API_GetDBPage def getDBPage( dbid, pageid, pagename = nil ) @dbid, @pageid, @pagename = dbid, pageid, pagename + xmlRequestData = nil if @pageid xmlRequestData = toXML( :pageid, @pageid ) elsif @pagename xmlRequestData = toXML( :pagename, @pagename ) else @@ -2681,11 +2719,11 @@ # API_ImportFromCSV def importFromCSV( dbid, records_csv, clist, skipfirst = nil, msInUTC = nil ) @dbid, @records_csv, @clist, @skipfirst, @msInUTC = dbid, records_csv, clist, skipfirst, msInUTC - @clist = @clist ? @clist : "0" + @clist ||= "0" xmlRequestData = toXML( :records_csv, @records_csv ) xmlRequestData << toXML( :clist, @clist ) xmlRequestData << toXML( :skipfirst, "1" ) if @skipfirst xmlRequestData << toXML( :msInUTC, "1" ) if @msInUTC @@ -2724,12 +2762,16 @@ sendRequest( :listDBPages ) @pages = getResponseValue( :pages ) return self if @chainAPIcalls - if @pages and block_given? - @pages.each{ |element| yield element } + if block_given? + if @pages + @pages.each{ |element| yield element } + else + yield nil + end else @pages end end @@ -2839,11 +2881,11 @@ xmlRequestData = toXML( :appdata , @appdata ) sendRequest( :setAppData ) return self if @chainAPIcalls - return @appdata + @appdata end # API_SetAppData, using the active table id. def _setAppData( appdata ) setAppData( @dbid, appdata ) end @@ -3194,35 +3236,35 @@ def getAllValuesForFields( dbid, fieldNames = nil, query = nil, qid = nil, qname = nil, clist = nil, slist = nil, fmt = "structured", options = nil ) if dbid getSchema(dbid) - values = Hash.new - fieldIDs = Hash.new + values = {} + fieldIDs = {} if fieldNames and fieldNames.is_a?( String ) - values[ fieldNames ] = Array.new + values[ fieldNames ] = [] fieldID = lookupFieldIDByName( fieldNames ) if fieldID fieldIDs[ fieldNames ] = fieldID elsif fieldNames.match(/[0-9]+/) # assume fieldNames is a field ID fieldIDs[ fieldNames ] = fieldNames end elsif fieldNames and fieldNames.is_a?( Array ) fieldNames.each{ |name| if name - values[ name ] = Array.new + values[ name ] = [] fieldID = lookupFieldIDByName( name ) if fieldID fieldIDs[ fieldID ] = name elsif name.match(/[0-9]+/) # assume name is a field ID fieldIDs[ name ] = name end end } elsif fieldNames.nil? getFieldNames(dbid).each{|name| - values[ name ] = Array.new + values[ name ] = [] fieldID = lookupFieldIDByName( name ) fieldIDs[ fieldID ] = name } end @@ -3339,11 +3381,11 @@ queryResults = getAllValuesForFields(dbid,fieldNames,query,qid,qname,clist,slist,fmt,options) if queryResults numRecords = 0 numRecords = queryResults[fieldNames[0]].length if queryResults[fieldNames[0]] (0..(numRecords-1)).each{|recNum| - recordHash = Hash.new + recordHash = {} fieldNames.each{|fieldName| recordHash[fieldName]=queryResults[fieldName][recNum] } yield recordHash } @@ -3354,12 +3396,12 @@ end # Same as iterateRecords but with fields optionally filtered by Ruby regular expressions. # e.g. iterateFilteredRecords( "dhnju5y7", [{"Name" => "[A-E].+}","Address"] ) { |values| puts values["Name"], values["Address"] } def iterateFilteredRecords( dbid, fieldNames, query = nil, qid = nil, qname = nil, clist = nil, slist = nil, fmt = "structured", options = nil ) - fields = Array.new - regexp = Hash.new + fields = [] + regexp = {} fieldNames.each{|field| if field.is_a?(Hash) fields << field.keys[0] regexp[field.keys[0]] = field.values[0] elsif field.is_a?(String) @@ -3401,14 +3443,14 @@ raise "'iterateJoinRecords' must be called with a block." if not block_given? if tablesAndFields and tablesAndFields.is_a?(Array) # get all the records from QuickBase that we might need - fewer API calls is faster than processing extra data - tables = Array.new - numRecords = Hash.new - tableRecords = Hash.new - joinfield = Hash.new + tables = [] + numRecords = {} + tableRecords = {} + joinfield = {} tablesAndFields.each{|tableAndFields| if tableAndFields and tableAndFields.is_a?(Hash) if tableAndFields["dbid"] and tableAndFields["fields"] and tableAndFields["joinfield"] if tableAndFields["fields"].is_a?(Array) @@ -3443,14 +3485,14 @@ # get the value of the join field in each record of the first table joinfieldValue = tableRecords[tables[0]][joinfield[tables[0]]][i] # save the other tables' record indices of records containing the joinfield value - tableIndices = Array.new + tableIndices = [] (1..(numTables-1)).each{|tableNum| - tableIndices[tableNum] = Array.new + tableIndices[tableNum] = [] (0..(numRecords[tables[tableNum]]-1)).each{|j| if joinfieldValue == tableRecords[tables[tableNum]][joinfield[tables[tableNum]]][j] tableIndices[tableNum] << j end } @@ -3462,18 +3504,18 @@ buildJoinRecord = false if not tableIndices[tableNum].length > 0 } if buildJoinRecord - joinRecord = Hash.new + joinRecord = {} tableRecords[tables[0]].each_key{|field| joinRecord[field] = tableRecords[tables[0]][field][i] } # nested loops for however many tables we have - currentIndex = Array.new + currentIndex = [] numTables.times{ currentIndex << 0 } loop { (1..(numTables-1)).each{|tableNum| index = tableIndices[tableNum][currentIndex[tableNum]] tableRecords[tables[tableNum]].each_key{|field| @@ -3519,11 +3561,11 @@ raise "'iterateUnionRecords' must be called with a block." if not block_given? if tables and tables.is_a?(Array) if fieldNames and fieldNames.is_a?(Array) - tableRecords = Array.new + tableRecords = [] tables.each{|table| if table and table.is_a?(Hash) and table["dbid"] tableRecords << getAllValuesForFields( table["dbid"], fieldNames, table["query"], @@ -3541,17 +3583,17 @@ raise "'fieldNames' must be an Array of field names valid in all the tables." end else raise "'tables' must be an Array of Hashes." end - usedRecords = Hash.new + usedRecords = {} tableRecords.each{|queryResults| if queryResults numRecords = 0 numRecords = queryResults[fieldNames[0]].length if queryResults[fieldNames[0]] (0..(numRecords-1)).each{|recNum| - recordHash = Hash.new + recordHash = {} fieldNames.each{|fieldName| recordHash[fieldName]=queryResults[fieldName][recNum] } if not usedRecords[recordHash.values.join] usedRecords[recordHash.values.join]=true @@ -3585,15 +3627,15 @@ def iterateSummaryRecords( dbid, fieldNames,query = nil, qid = nil, qname = nil, clist = nil, slist = nil, fmt = "structured", options = nil ) getSchema(dbid) slist = "" - summaryRecord = Hash.new - doesTotal = Hash.new - doesAverage = Hash.new - summaryField = Hash.new - fieldType = Hash.new + summaryRecord = {} + doesTotal = {} + doesAverage = {} + summaryField = {} + fieldType = {} fieldNames.each{ |fieldName| fieldType[fieldName] = lookupFieldTypeByName(fieldName) isSummaryField = true doesTotal[fieldName] = isTotalField?(fieldName) @@ -3640,11 +3682,11 @@ } yield summaryRecord count=0 - summaryRecord = Hash.new + summaryRecord = {} fieldNames.each{|fieldName| if doesTotal[fieldName] summaryRecord["#{fieldName}:Total"] = 0 end if doesAverage[fieldName] @@ -3811,11 +3853,11 @@ end # Find the lowest value for one or more fields in the records returned by a query. # e.g. minimumsHash = min("dfdfafff",["Date Sent","Quantity","Part Name"]) def min( dbid, fieldNames, query = nil, qid = nil, qname = nil, clist = nil, slist = nil, fmt = "structured", options = nil ) - min = Hash.new + min = {} hasValues = false iterateRecords(dbid,fieldNames,query,qid,qname,clist,slist,fmt,options){|record| fieldNames.each{|field| value = record[field] if value @@ -3838,11 +3880,11 @@ end # Find the highest value for one or more fields in the records returned by a query. # e.g. maximumsHash = max("dfdfafff",["Date Sent","Quantity","Part Name"]) def max( dbid, fieldNames, query = nil, qid = nil, qname = nil, clist = nil, slist = nil, fmt = "structured", options = nil ) - max = Hash.new + max = {} hasValues = false iterateRecords(dbid,fieldNames,query,qid,qname,clist,slist,fmt,options){|record| fieldNames.each{|field| value = record[field] if value @@ -3865,11 +3907,11 @@ end # Returns the number non-null values for one or more fields in the records returned by a query. # e.g. countsHash = count("dfdfafff",["Date Sent","Quantity","Part Name"]) def count( dbid, fieldNames, query = nil, qid = nil, qname = nil, clist = nil, slist = nil, fmt = "structured", options = nil ) - count = Hash.new + count = {} fieldNames.each{|field| count[field]=0 } hasValues = false iterateRecords(dbid,fieldNames,query,qid,qname,clist,slist,fmt,options){|record| fieldNames.each{|field| if record[field] and record[field].length > 0 @@ -3883,11 +3925,11 @@ end # Returns the sum of the values for one or more fields in the records returned by a query. # e.g. sumsHash = sum("dfdfafff",["Date Sent","Quantity","Part Name"]) def sum( dbid, fieldNames, query = nil, qid = nil, qname = nil, clist = nil, slist = nil, fmt = "structured", options = nil ) - sum = Hash.new + sum = {} hasValues = false iterateRecords(dbid,fieldNames,query,qid,qname,clist,slist,fmt,options){|record| fieldNames.each{|field| value = record[field] if value @@ -3912,13 +3954,13 @@ end # Returns the average of the values for one or more fields in the records returned by a query. # e.g. averagesHash = average("dfdfafff",["Date Sent","Quantity","Part Name"]) def average( dbid, fieldNames, query = nil, qid = nil, qname = nil, clist = nil, slist = nil, fmt = "structured", options = nil ) - count = Hash.new + count = {} fieldNames.each{|field| count[field]=0 } - sum = Hash.new + sum = {} iterateRecords(dbid,fieldNames,query,qid,qname,clist,slist,fmt,options){|record| fieldNames.each{|field| value = record[field] if value baseFieldType = lookupBaseFieldTypeByName(field) @@ -3935,11 +3977,11 @@ end count[field] += 1 end } } - average = Hash.new + average = {} hasValues = false fieldNames.each{|field| if sum[field] and count[field] > 0 average[field] = (sum[field]/count[field]) hasValues = true @@ -4007,11 +4049,11 @@ elsif not fid raise "'fieldName' or 'fid' must be specified" end field = lookupField( fid ) if field - choices = Array.new + choices = [] choicesProc = proc { |element| if element.is_a?(REXML::Element) if element.name == "choice" and element.has_text? choices << element.text end @@ -4026,11 +4068,11 @@ end # Get an array of all the record IDs for a specified table. # e.g. IDs = getAllRecordIDs( "dhnju5y7" ){ |id| puts "Record #{id}" } def getAllRecordIDs( dbid ) - rids = Array.new + rids = [] if dbid getSchema( dbid ) next_record_id = getResponseElement( "table/original/next_record_id" ) if next_record_id and next_record_id.has_text? next_record_id = next_record_id.text @@ -4053,34 +4095,34 @@ # Finds records with the same values in a specified list of fields. # The field list may be a list of field IDs or a list of field names. # Returns a hash with the structure { "duplicated values" => [ rid, rid, ... ] } def findDuplicateRecordIDs( fnames, fids, dbid = @dbid, ignoreCase = true ) verifyFieldList( fnames, fids, dbid ) - duplicates = Hash.new + duplicates = {} if @fids cslist = @fids.join( "." ) ridFields = lookupFieldsByType( "recordid" ) if ridFields and ridFields.last cslist << "." recordidFid = ridFields.last.attributes["id"] cslist << recordidFid - valuesUsed = Hash.new + valuesUsed = {} doQuery( @dbid, nil, nil, nil, cslist ) { |record| next unless record.is_a?( REXML::Element) and record.name == "record" recordID = "" - recordValues = Array.new + recordValues = [] record.each { |f| if f.is_a?( REXML::Element) and f.name == "f" and f.has_text? if recordidFid == f.attributes["id"] recordID = f.text else recordValues << f.text end end } if not valuesUsed[ recordValues ] - valuesUsed[ recordValues ] = Array.new + valuesUsed[ recordValues ] = [] end valuesUsed[ recordValues ] << recordID } valuesUsed.each{ |valueArray, recordArray| @@ -4106,11 +4148,11 @@ def deleteDuplicateRecords( fnames, fids = nil, options = nil, dbid = @dbid ) num_deleted = 0 if options and not options.is_a?( Hash ) raise "deleteDuplicateRecords: 'options' parameter must be a Hash" else - options = Hash.new + options = {} options[ "keeplastrecord" ] = true options[ "ignoreCase" ] = true end findDuplicateRecordIDs( fnames, fids, dbid, options[ "ignoreCase" ] ) { |dupeValues, recordIDs| if options[ "keeplastrecord" ] @@ -4130,11 +4172,11 @@ if field.elements[ "fid" ].text.to_i > 5 #skip built-in fields addFieldValuePair( field.elements[ "name" ].text, nil, nil, field.elements[ "value" ].text ) end end } - newRecordIDs = Array.new + newRecordIDs = [] if @fvlist and @fvlist.length > 0 numCopies.times { addRecord( dbid, @fvlist ) newRecordIDs << @rid if @rid and @update_id } @@ -4184,11 +4226,11 @@ workbook.Close excel.Quit csvData = "" - targetFieldIDs = Array.new + targetFieldIDs = [] if fieldNames and fieldNames.length > 0 fieldNames.each{ |fieldNameRow| fieldNameRow.each{ |fieldName| if fieldName @@ -4259,22 +4301,22 @@ num_recs_imported = 0 if FileTest.readable?( filename ) if dbid getSchema( dbid ) - targetFieldIDs = Array.new + targetFieldIDs = [] if targetFieldNames and targetFieldNames.is_a?( Array ) targetFieldNames.each{ |fieldName| targetFieldIDs << lookupFieldIDByName( fieldName ) } return 0 if targetFieldIDs.length != targetFieldNames.length end useAddRecord = false - invalidLines = Array.new - validLines = Array.new + invalidLines = [] + validLines = [] linenum = 1 IO.foreach( filename ){ |line| if fieldSeparator != "," and line.index( "," ) @@ -4435,10 +4477,22 @@ # Update the file attachment in an existing record in the active record in the active table. # e.g. _updateFile( "contacts.txt", "Contacts File" ) def _updateFile( filename, fileAttachmentFieldName ) updateFile( @dbid, @rid, filename, fileAttachmentFieldName ) end + + # Remove the file from a File Attachment field in an existing record. + # e.g. removeFileAttachment( "bdxxxibz4", "6", "Document" ) + def removeFileAttachment( dbid, rid , fileAttachmentFieldName ) + updateFile( dbid, rid, "delete", fileAttachmentFieldName ) + end + + # Remove the file from a File Attachment field in an existing record in the active table + # e.g. _removeFileAttachment( "6", "Document" ) + def _removeFileAttachment( rid , fileAttachmentFieldName ) + updateFile( @dbid, rid, "delete", fileAttachmentFieldName ) + end # Translate a simple SQL SELECT statement to a QuickBase query and run it. # # If any supplied field names are numeric, they will be treated as QuickBase field IDs if # they aren't valid field names. @@ -4455,18 +4509,18 @@ dbid = nil clist = nil slist = nil state = nil dbname = "" - columns = Array.new - sortFields = Array.new + columns = [] + sortFields = [] limit = nil offset = nil options = nil getRecordCount = false - queryFields = Array.new + queryFields = [] query = "{'[" queryState = "getFieldName" queryField = "" sql.split(' ').each{ |token| @@ -4772,11 +4826,11 @@ end recordid end - # Iterate @records XML and yield only <record> elements. + # Iterate @records XML and yield only 'record' elements. def eachRecord( records = @records ) if records and block_given? records.each { |record| if record.is_a?( REXML::Element) and record.name == "record" @record = record @@ -4785,10 +4839,10 @@ } end nil end - # Iterate record XML and yield only <f> elements. + # Iterate record XML and yield only 'f' elements. def eachField( record = @record ) if record and block_given? record.each{ |field| if field.is_a?( REXML::Element) and field.name == "f" and field.attributes["id"] @field = field