ipp_quickbase_devkit 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +87 -0
- data/README.rdoc +112 -0
- data/doc/QuickBaseClient.rb.htm +1896 -0
- data/doc/ReleaseNotes.txt +43 -0
- data/doc/qbc.makeCSVFile.qbc +4 -0
- data/doc/qbc.makeCSVFile.rb +4 -0
- data/doc/quickbase_adapter.rb.htm +399 -0
- data/examples/cookbookfiles/QuickBaseAPICookbook.html +2590 -0
- data/examples/cookbookfiles/addChangeRemoveUserRole.rb +21 -0
- data/examples/cookbookfiles/addOrEditRecord.rb +10 -0
- data/examples/cookbookfiles/application_object.rb +55 -0
- data/examples/cookbookfiles/applyRubyFormulas.rb +10 -0
- data/examples/cookbookfiles/average.rb +27 -0
- data/examples/cookbookfiles/backupApplication.rb +8 -0
- data/examples/cookbookfiles/cacheSchemas.rb +53 -0
- data/examples/cookbookfiles/calculateRunningTotals.rb +11 -0
- data/examples/cookbookfiles/copyrecords.rb +73 -0
- data/examples/cookbookfiles/count.rb +26 -0
- data/examples/cookbookfiles/createRecordNavigatorHTML.rb +42 -0
- data/examples/cookbookfiles/createReportDashboard.rb +35 -0
- data/examples/cookbookfiles/createTable.rb +12 -0
- data/examples/cookbookfiles/deviation.rb +25 -0
- data/examples/cookbookfiles/downloadCookbook.rb +97 -0
- data/examples/cookbookfiles/downloadFile.rb +10 -0
- data/examples/cookbookfiles/downloadFilesToFolder.rb +81 -0
- data/examples/cookbookfiles/downloadToTextFile.rb +64 -0
- data/examples/cookbookfiles/dumpSchema.rb +11 -0
- data/examples/cookbookfiles/duplicateRecord.rb +8 -0
- data/examples/cookbookfiles/dynamicMethods.rb +33 -0
- data/examples/cookbookfiles/editRecords.rb +15 -0
- data/examples/cookbookfiles/findJohnsLast10Records.rb +17 -0
- data/examples/cookbookfiles/findRubyRecords.rb +17 -0
- data/examples/cookbookfiles/formatCurrency.rb +24 -0
- data/examples/cookbookfiles/formatDate.rb +10 -0
- data/examples/cookbookfiles/formatDuration.rb +27 -0
- data/examples/cookbookfiles/formatPercent.rb +24 -0
- data/examples/cookbookfiles/getAllValuesForFields.rb +18 -0
- data/examples/cookbookfiles/getAppDTMInfo.rb +29 -0
- data/examples/cookbookfiles/getApplicationVariable.rb +5 -0
- data/examples/cookbookfiles/getChildTableDBID.rb +11 -0
- data/examples/cookbookfiles/getColumnListForReport.rb +6 -0
- data/examples/cookbookfiles/getFieldChoices.rb +13 -0
- data/examples/cookbookfiles/getFieldIDs.rb +6 -0
- data/examples/cookbookfiles/getFieldNames.rb +6 -0
- data/examples/cookbookfiles/getLastModTime.rb +8 -0
- data/examples/cookbookfiles/getLastRecModTime.rb +8 -0
- data/examples/cookbookfiles/getNumRecords.rb +8 -0
- data/examples/cookbookfiles/getNumTables.rb +4 -0
- data/examples/cookbookfiles/getRecord.rb +5 -0
- data/examples/cookbookfiles/getRecordDisplayURL.rb +13 -0
- data/examples/cookbookfiles/getRecordsAddedToday.rb +20 -0
- data/examples/cookbookfiles/getRecordsAsJSON.rb +6 -0
- data/examples/cookbookfiles/getReportNames.rb +25 -0
- data/examples/cookbookfiles/getRoleInfo.rb +48 -0
- data/examples/cookbookfiles/getServerStatus.rb +11 -0
- data/examples/cookbookfiles/getSortListForReport.rb +6 -0
- data/examples/cookbookfiles/getTableIDs.rb +6 -0
- data/examples/cookbookfiles/getTableName.rb +8 -0
- data/examples/cookbookfiles/getTableNames.rb +25 -0
- data/examples/cookbookfiles/getTimeCreated.rb +8 -0
- data/examples/cookbookfiles/getTimeInMilliseconds.rb +5 -0
- data/examples/cookbookfiles/getUserInfo.rb +26 -0
- data/examples/cookbookfiles/getUserRole.rb +15 -0
- data/examples/cookbookfiles/intranet.rb +101 -0
- data/examples/cookbookfiles/isAverageField.rb +17 -0
- data/examples/cookbookfiles/isDbidString.rb +8 -0
- data/examples/cookbookfiles/isTotalField.rb +16 -0
- data/examples/cookbookfiles/iterateDBPages.rb +8 -0
- data/examples/cookbookfiles/iterateFilteredRecords.rb +12 -0
- data/examples/cookbookfiles/iterateJoinRecords.rb +68 -0
- data/examples/cookbookfiles/iterateRecordInfos.rb +8 -0
- data/examples/cookbookfiles/iterateRecords.rb +23 -0
- data/examples/cookbookfiles/iterateSummaryRecords.rb +13 -0
- data/examples/cookbookfiles/iterateUnionRecords.rb +38 -0
- data/examples/cookbookfiles/listAccessibleApplications.rb +6 -0
- data/examples/cookbookfiles/logRequestAndResponseXML.rb +8 -0
- data/examples/cookbookfiles/lookupFieldPropertyByName.rb +62 -0
- data/examples/cookbookfiles/lookupFieldTypeByName.rb +10 -0
- data/examples/cookbookfiles/makeCSVFile.rb +4 -0
- data/examples/cookbookfiles/makeSlideShow.rb +42 -0
- data/examples/cookbookfiles/makerecs.rb +64 -0
- data/examples/cookbookfiles/max.rb +26 -0
- data/examples/cookbookfiles/min.rb +26 -0
- data/examples/cookbookfiles/percent.rb +29 -0
- data/examples/cookbookfiles/printChildElements.rb +54 -0
- data/examples/cookbookfiles/printNewRecords.rb +12 -0
- data/examples/cookbookfiles/processRESTRequest.rb +21 -0
- data/examples/cookbookfiles/provisionAndInviteNewUser.rb +13 -0
- data/examples/cookbookfiles/purgeRecords.rb +15 -0
- data/examples/cookbookfiles/quickbase_adapter.rb.htm +397 -0
- data/examples/cookbookfiles/quickbase_record_finder.zip +0 -0
- data/examples/cookbookfiles/recordAndFieldIterator.rb +24 -0
- data/examples/cookbookfiles/runImport.rb +9 -0
- data/examples/cookbookfiles/runQuickBaseTwitterConnector.rb +41 -0
- data/examples/cookbookfiles/sendToQuickBase.rb +33 -0
- data/examples/cookbookfiles/setDBvar.rb +6 -0
- data/examples/cookbookfiles/showRequestAndResponseXML.rb +8 -0
- data/examples/cookbookfiles/sqlQuery.rb +11 -0
- data/examples/cookbookfiles/stopOnError.rb +10 -0
- data/examples/cookbookfiles/sum.rb +26 -0
- data/examples/cookbookfiles/twitterFromQuickBase.rb +42 -0
- data/examples/cookbookfiles/twitterWithQuickBase.rb +36 -0
- data/examples/cookbookfiles/uploadCSVData.rb +20 -0
- data/examples/cookbookfiles/uploadExcelData.rb +22 -0
- data/examples/cookbookfiles/uploadFileEveryHour.rb +18 -0
- data/examples/cookbookfiles/uploadFileIntoNewRecord.rb +8 -0
- data/examples/cookbookfiles/uploadFilesFromFolder.exe +0 -0
- data/examples/cookbookfiles/uploadFilesFromFolder.rb +69 -0
- data/examples/cookbookfiles/useCompanyURL.rb +12 -0
- data/examples/cookbookfiles/userRoles.rb +49 -0
- data/examples/cookbookfiles/watchCommunityForum.rb +5 -0
- data/examples/cookbookfiles/wikifyTable.rb +29 -0
- data/examples/cookbookfiles/xmlShortcuts.rb +33 -0
- data/examples/pmp/app/controllers/application.rb +7 -0
- data/examples/pmp/app/controllers/contacts_controller.rb +8 -0
- data/examples/pmp/app/controllers/document_library_controller.rb +2 -0
- data/examples/pmp/app/controllers/issues_controller.rb +5 -0
- data/examples/pmp/app/controllers/projects_controller.rb +22 -0
- data/examples/pmp/app/controllers/resources_controller.rb +2 -0
- data/examples/pmp/app/controllers/tasks_controller.rb +13 -0
- data/examples/pmp/app/controllers/time_cards_controller.rb +5 -0
- data/examples/pmp/app/helpers/application_helper.rb +3 -0
- data/examples/pmp/app/helpers/contacts_helper.rb +2 -0
- data/examples/pmp/app/helpers/document_library_helper.rb +2 -0
- data/examples/pmp/app/helpers/issues_helper.rb +2 -0
- data/examples/pmp/app/helpers/projects_helper.rb +2 -0
- data/examples/pmp/app/helpers/resources_helper.rb +2 -0
- data/examples/pmp/app/helpers/tasks_helper.rb +2 -0
- data/examples/pmp/app/helpers/time_cards_helper.rb +2 -0
- data/examples/pmp/app/models/contacts.rb +26 -0
- data/examples/pmp/app/models/document_library.rb +2 -0
- data/examples/pmp/app/models/issues.rb +6 -0
- data/examples/pmp/app/models/projects.rb +26 -0
- data/examples/pmp/app/models/resources.rb +2 -0
- data/examples/pmp/app/models/tasks.rb +12 -0
- data/examples/pmp/app/models/time_cards.rb +7 -0
- data/examples/pmp/app/schemas/contacts.xml +1 -0
- data/examples/pmp/app/schemas/document_library.xml +1 -0
- data/examples/pmp/app/schemas/issues.xml +1 -0
- data/examples/pmp/app/schemas/pmp.xml +1 -0
- data/examples/pmp/app/schemas/projects.xml +1 -0
- data/examples/pmp/app/schemas/readme.txt +8 -0
- data/examples/pmp/app/schemas/resources.xml +1 -0
- data/examples/pmp/app/schemas/tasks.xml +1 -0
- data/examples/pmp/app/schemas/time_cards.xml +1 -0
- data/examples/pmp/app/views/contacts/companies.rhtml +31 -0
- data/examples/pmp/app/views/contacts/project_contacts.rhtml +31 -0
- data/examples/pmp/app/views/issues/filter_issues.rhtml +26 -0
- data/examples/pmp/app/views/layouts/application.rhtml +56 -0
- data/examples/pmp/app/views/projects/all_projects.rhtml +33 -0
- data/examples/pmp/app/views/projects/home.rhtml +11 -0
- data/examples/pmp/app/views/projects/my_open_projects.rhtml +27 -0
- data/examples/pmp/app/views/projects/open_projects.rhtml +44 -0
- data/examples/pmp/app/views/projects/project_sorted_by_company.rhtml +40 -0
- data/examples/pmp/app/views/projects/projects_sorted_by_priority.rhtml +30 -0
- data/examples/pmp/app/views/projects/updated_projects.rhtml +0 -0
- data/examples/pmp/app/views/tasks/all_tasks.rhtml +27 -0
- data/examples/pmp/app/views/tasks/search.rhtml +23 -0
- data/examples/pmp/app/views/tasks/search2.rhtml +23 -0
- data/examples/pmp/app/views/tasks/search3.rhtml +23 -0
- data/examples/pmp/app/views/time_cards/summary.rhtml +38 -0
- data/examples/pmp/config/boot.rb +45 -0
- data/examples/pmp/config/database.yml +30 -0
- data/examples/pmp/config/environment.rb +60 -0
- data/examples/pmp/config/environments/development.rb +21 -0
- data/examples/pmp/config/environments/production.rb +18 -0
- data/examples/pmp/config/environments/test.rb +19 -0
- data/examples/pmp/config/routes.rb +23 -0
- data/examples/pmp/db/migrate/001_create_projects.rb +10 -0
- data/examples/pmp/db/migrate/002_create_tasks.rb +10 -0
- data/examples/pmp/db/migrate/003_create_issues.rb +10 -0
- data/examples/pmp/db/migrate/004_create_document_libraries.rb +10 -0
- data/examples/pmp/db/migrate/005_create_resources.rb +10 -0
- data/examples/pmp/db/migrate/006_create_time_cards.rb +10 -0
- data/examples/pmp/db/migrate/007_create_contacts.rb +10 -0
- data/examples/pmp/public/404.html +30 -0
- data/examples/pmp/public/500.html +30 -0
- data/examples/pmp/public/app.index.html +277 -0
- data/examples/pmp/public/dispatch.cgi +10 -0
- data/examples/pmp/public/dispatch.fcgi +24 -0
- data/examples/pmp/public/dispatch.rb +10 -0
- data/examples/pmp/public/favicon.ico +0 -0
- data/examples/pmp/public/images/rails.png +0 -0
- data/examples/pmp/public/javascripts/application.js +2 -0
- data/examples/pmp/public/javascripts/controls.js +833 -0
- data/examples/pmp/public/javascripts/dragdrop.js +942 -0
- data/examples/pmp/public/javascripts/effects.js +1088 -0
- data/examples/pmp/public/javascripts/prototype.js +2515 -0
- data/examples/pmp/public/robots.txt +1 -0
- data/examples/pmp/test/fixtures/contacts.yml +5 -0
- data/examples/pmp/test/fixtures/document_libraries.yml +5 -0
- data/examples/pmp/test/fixtures/issues.yml +5 -0
- data/examples/pmp/test/fixtures/projects.yml +5 -0
- data/examples/pmp/test/fixtures/resources.yml +5 -0
- data/examples/pmp/test/fixtures/tasks.yml +5 -0
- data/examples/pmp/test/fixtures/time_cards.yml +5 -0
- data/examples/pmp/test/functional/contacts_controller_test.rb +18 -0
- data/examples/pmp/test/functional/document_library_controller_test.rb +18 -0
- data/examples/pmp/test/functional/issues_controller_test.rb +18 -0
- data/examples/pmp/test/functional/projects_controller_test.rb +18 -0
- data/examples/pmp/test/functional/resources_controller_test.rb +18 -0
- data/examples/pmp/test/functional/tasks_controller_test.rb +18 -0
- data/examples/pmp/test/functional/time_cards_controller_test.rb +18 -0
- data/examples/pmp/test/test_helper.rb +28 -0
- data/examples/pmp/test/unit/contacts_test.rb +10 -0
- data/examples/pmp/test/unit/document_library_test.rb +10 -0
- data/examples/pmp/test/unit/issues_test.rb +10 -0
- data/examples/pmp/test/unit/projects_test.rb +10 -0
- data/examples/pmp/test/unit/resources_test.rb +10 -0
- data/examples/pmp/test/unit/tasks_test.rb +10 -0
- data/examples/pmp/test/unit/time_cards_test.rb +10 -0
- data/lib/QuickBaseClient.rb +5054 -0
- data/lib/QuickBaseCommandLineClient.rb +401 -0
- data/lib/QuickBaseContactsAppBuilder.rb +419 -0
- data/lib/QuickBaseEmailer.rb +334 -0
- data/lib/QuickBaseEventNotifier.rb +592 -0
- data/lib/QuickBaseMisc.rb +96 -0
- data/lib/QuickBaseObjects.rb +566 -0
- data/lib/QuickBaseRSSGenerator.rb +286 -0
- data/lib/QuickBaseTextData.rb +545 -0
- data/lib/QuickBaseTwitterConnector.rb +300 -0
- data/lib/QuickBaseWebClient.rb +126 -0
- data/lib/WorkPlaceClient.rb +45 -0
- data/lib/qbc.makeCSVFile.qbc +4 -0
- data/lib/qbc.makeCSVFile.rb +17 -0
- data/lib/quickbase_adapter.rb +320 -0
- data/lib/runFieldEntryDialog.rb +151 -0
- data/lib/runOfflineFieldEntryDialog.rb +203 -0
- data/rakefile +100 -0
- data/test/run_tests.bat +7 -0
- data/test/spec_all_tests.rb +13 -0
- data/test/spec_smoke_tests.rb +58 -0
- data/test/spec_workplace_addrecord_test.rb +46 -0
- data/test/spec_workplace_base_test.rb +57 -0
- data/test/spec_workplace_editrecord_test.rb +38 -0
- data/test/spec_workplace_json_test.rb +38 -0
- data/test/spec_workplace_objects_test.rb +39 -0
- data/test/spec_workplace_smoke_tests.rb +45 -0
- metadata +353 -0
@@ -0,0 +1,286 @@
|
|
1
|
+
#--#####################################################################
|
2
|
+
# Copyright (c) 2009 Gareth Lewis and Intuit, Inc.
|
3
|
+
#
|
4
|
+
# All rights reserved. This program and the accompanying materials
|
5
|
+
# are made available under the terms of the Eclipse Public License v1.0
|
6
|
+
# which accompanies this distribution, and is available at
|
7
|
+
# http://www.opensource.org/licenses/eclipse-1.0.php
|
8
|
+
#
|
9
|
+
# Contributors:
|
10
|
+
# Gareth Lewis - Initial contribution.
|
11
|
+
# Intuit Partner Platform.
|
12
|
+
#++#####################################################################
|
13
|
+
|
14
|
+
require 'QuickBaseClient'
|
15
|
+
|
16
|
+
module QuickBase
|
17
|
+
|
18
|
+
# This class generates RSS data from specific fields in one or more
|
19
|
+
# QuickBase tables. It is intended to make standard RSS text that can
|
20
|
+
# be displayed by RSS readers or processed by other utilities.
|
21
|
+
#
|
22
|
+
# See the test() method at the bottom of this file for an example
|
23
|
+
# of using this class.
|
24
|
+
#
|
25
|
+
# Note that by default this class sorts the RSS items by <pubDate>
|
26
|
+
# descending order (i.e. most recent first); this means
|
27
|
+
# that items generated from records from different tables can be
|
28
|
+
# interspersed which each other. Some RSS readers do not appear
|
29
|
+
# to interpret <pubDate> correctly and therefore don't sort items
|
30
|
+
# correctly by themselves. Use 'unSorted' to prevent this class
|
31
|
+
# from sorting <items>. Also note that the sort order of records
|
32
|
+
# retrieved from tables can be set separately for each table, which
|
33
|
+
# is useful if the output is not going to be processed by an RSS readser.
|
34
|
+
class RSSGenerator
|
35
|
+
|
36
|
+
# Nested class to encapsulate RSS retrieval options for a QuickBase table.
|
37
|
+
class Table
|
38
|
+
|
39
|
+
attr_reader :dbid, :tableName, :fields, :clist, :slist, :query, :qid, :qname, :numRecords, :options
|
40
|
+
attr_reader :optionalFields, :title, :link, :description
|
41
|
+
|
42
|
+
# 'tableName' is the table name as it should appear in the output.
|
43
|
+
# Gets all records by default, sorted by Date Modified ("2") in descending order.
|
44
|
+
# Only retrieves fields in 'fields' parameter.
|
45
|
+
# <pubDate> is set to Date Modified ("2") by default
|
46
|
+
# <link> is set to Record ID# ("3") by default
|
47
|
+
# any additional quickbase field can be included in 'fields'
|
48
|
+
#-----------------------------------------------------------------------------
|
49
|
+
def initialize( dbid, tableName, fields, query = nil, qid = nil, qname = nil, numRecords = 0, slist = "2", options = "sortorder-D" )
|
50
|
+
|
51
|
+
raise "fields parameter must be a Hash" if !fields.is_a?( Hash )
|
52
|
+
|
53
|
+
@dbid, @tableName, @fields = dbid, tableName, fields
|
54
|
+
@query, @qid, @qname, @numRecords, @slist, @options = query, qid, qname, numRecords, slist, options
|
55
|
+
|
56
|
+
@fields["pubDate"] = "2" if @fields["pubDate"].nil?
|
57
|
+
@fields["link"] = "3" if @fields["link"].nil?
|
58
|
+
|
59
|
+
@optionalFields = Hash.new
|
60
|
+
|
61
|
+
@clist = ""
|
62
|
+
if @fields["description"] and @fields["title"]
|
63
|
+
@fields.each{ |k,v|
|
64
|
+
@clist << "#{v}."
|
65
|
+
if k != "pubDate" and k != "link" and k != "description" and k != "title"
|
66
|
+
@optionalFields[v] = k
|
67
|
+
end
|
68
|
+
}
|
69
|
+
@clist[-1] = ""
|
70
|
+
else
|
71
|
+
raise "fields parameter must include a 'description' and 'title'"
|
72
|
+
end
|
73
|
+
|
74
|
+
@title = "Default RSS Item Title"
|
75
|
+
@link = "http://www.quickbase.com/db/main"
|
76
|
+
@description = "Default RSS item description"
|
77
|
+
|
78
|
+
@options << ".num-#{numRecords}" if numRecords > 0
|
79
|
+
end
|
80
|
+
|
81
|
+
end # class Table
|
82
|
+
|
83
|
+
|
84
|
+
def initialize( qbc )
|
85
|
+
raise "Bad qbc parameter. qbc must be an instance of QuickBase::Client." if !qbc.is_a?( Client )
|
86
|
+
@qbc = qbc
|
87
|
+
raise "Please sign into QuickBase before using the RSSGenerator class" if @qbc.ticket.nil?
|
88
|
+
@items = Hash.new
|
89
|
+
@sortableItems = Hash.new
|
90
|
+
@sorted = true
|
91
|
+
@tables = Array.new
|
92
|
+
@timeNow = @namespace = ""
|
93
|
+
end
|
94
|
+
|
95
|
+
# Generates RSS <item>s from a filtered list of records and fields from a table.
|
96
|
+
def generateRSSItems( table )
|
97
|
+
|
98
|
+
@qbc.doQuery( table.dbid, table.query, table.qid, table.qname, table.clist, table.slist, "structured", table.options )
|
99
|
+
|
100
|
+
@qbc.eachRecord { |r|
|
101
|
+
|
102
|
+
pubDate = ""
|
103
|
+
title = ""
|
104
|
+
description = ""
|
105
|
+
link = ""
|
106
|
+
otherFieldValues = ""
|
107
|
+
itemText = ""
|
108
|
+
sortValue = 0
|
109
|
+
|
110
|
+
@qbc.eachField(r){ |f|
|
111
|
+
case f.attributes[ "id" ]
|
112
|
+
when table.fields[ "title" ]
|
113
|
+
title = f.text if f.has_text?
|
114
|
+
next
|
115
|
+
when table.fields[ "description" ]
|
116
|
+
description = f.text if f.has_text?
|
117
|
+
next
|
118
|
+
when table.fields[ "pubDate" ]
|
119
|
+
pubDate = f.text if f.has_text?
|
120
|
+
next
|
121
|
+
when table.fields[ "link" ]
|
122
|
+
link = f.text if f.has_text?
|
123
|
+
next
|
124
|
+
when table.slist
|
125
|
+
sortValue = f.text if f.has_text? and @sorted
|
126
|
+
next
|
127
|
+
end
|
128
|
+
table.optionalFields.each{ |fid,fieldName|
|
129
|
+
if f.attributes["id"] == fid and f.has_text?
|
130
|
+
field, value = onSetItemField( "#{@namespace}#{fieldName}", f.text )
|
131
|
+
otherFieldValues << " <#{field}>#{@qbc.encodeXML(value)}</#{field}>\n"
|
132
|
+
end
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
titleHash = "#{table.tableName}(#{link}): #{description[0..20]}..."
|
137
|
+
link = "https://www.quickbase.com/db/#{table.dbid}?a=dr&rid=#{link}"
|
138
|
+
|
139
|
+
itemText << " <item>\n"
|
140
|
+
|
141
|
+
title << " (#{table.tableName})"
|
142
|
+
field, value = onSetItemField( "#{@namespace}title", title )
|
143
|
+
itemText << " <#{field}>#{@qbc.encodeXML(value)}</#{field}>\n"
|
144
|
+
|
145
|
+
field, value = onSetItemField( "#{@namespace}pubDate", @qbc.formatDate(pubDate, "%a, %d %b %Y %H:%M PST" ) )
|
146
|
+
itemText << " <#{field}>#{value}</#{field}>\n"
|
147
|
+
|
148
|
+
guid = link.dup
|
149
|
+
field, value = onSetItemField( "#{@namespace}link", link )
|
150
|
+
itemText << " <#{field}>#{@qbc.encodeXML(value)}</#{field}>\n"
|
151
|
+
|
152
|
+
field, value = onSetItemField( "#{@namespace}guid", guid )
|
153
|
+
itemText << " <#{field}>#{@qbc.encodeXML(value)}</#{field}>\n"
|
154
|
+
|
155
|
+
field, value = onSetItemField( "#{@namespace}description", description )
|
156
|
+
itemText << " <#{field}>#{@qbc.encodeXML(value)}</#{field}>\n"
|
157
|
+
|
158
|
+
itemText << otherFieldValues
|
159
|
+
itemText << " </item>\n"
|
160
|
+
|
161
|
+
@items[titleHash] = itemText
|
162
|
+
@sortableItems[titleHash] = sortValue
|
163
|
+
|
164
|
+
}
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
# To modify item field names or values before they are inserted into the RSS output,
|
169
|
+
# derive from this class, override this method, and modify the return values.
|
170
|
+
def onSetItemField( field, value )
|
171
|
+
return field, value
|
172
|
+
end
|
173
|
+
|
174
|
+
# Prepend 'quickbase:' namespace to all data from QuickBase.
|
175
|
+
# intended for use by RSS processors other than RSS Readers.
|
176
|
+
def useNamespace
|
177
|
+
@namespace = "quickbase:"
|
178
|
+
end
|
179
|
+
|
180
|
+
# Items are sorted by <pubDate> unless this method is called.
|
181
|
+
def unSorted
|
182
|
+
@sorted = false
|
183
|
+
end
|
184
|
+
|
185
|
+
# Set the title for the RSS feed generated by this class.
|
186
|
+
def setTitle( title )
|
187
|
+
@title = " <#{@namespace}title>#{@qbc.encodeXML(title)}</#{@namespace}title>\n"
|
188
|
+
end
|
189
|
+
|
190
|
+
# Set the link for the RSS feed generated by this class.
|
191
|
+
# Set isDBID = false if this is not a QuickBase dbid.
|
192
|
+
def setLink( link, isDBID = true )
|
193
|
+
if isDBID
|
194
|
+
@link = " <#{@namespace}link>https://www.quickbase.com/db/#{link}</#{@namespace}link>\n"
|
195
|
+
else
|
196
|
+
@link = " <#{@namespace}link>#{@qbc.encodeXML(link)}</#{@namespace}link>\n"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Insert the current time at the end of the descripton for the RSS
|
201
|
+
# feed generated by this class.
|
202
|
+
def appendTimeToDescription
|
203
|
+
@timeNow = " (#{Time.now})"
|
204
|
+
end
|
205
|
+
|
206
|
+
# Set the description for the RSS feed generated by this class.
|
207
|
+
def setDescription( description, appendTime = true )
|
208
|
+
appendTimeToDescription if appendTime
|
209
|
+
@description = " <#{@namespace}description>#{@qbc.encodeXML(description)}#{@timeNow}</#{@namespace}description>\n"
|
210
|
+
end
|
211
|
+
|
212
|
+
def header
|
213
|
+
text = "<?xml version=\"1.0\" ?>\n"
|
214
|
+
text << " <rss version=\"2.0\">\n"
|
215
|
+
text << " <channel>\n"
|
216
|
+
end
|
217
|
+
|
218
|
+
def footer
|
219
|
+
text = " </channel>\n"
|
220
|
+
text << " </rss>\n"
|
221
|
+
end
|
222
|
+
|
223
|
+
# Add a QuickBase table to the list of tables from which to generate RSS
|
224
|
+
#
|
225
|
+
# * dbid = QuickBase table dbid
|
226
|
+
# * tableName = the name of the QuickBase table as it should appear in the generated RSS
|
227
|
+
# * fields = Hash of RSS fields and the IDs of QuickBase fields , e.g. { "description" => "10", "myRSSfield" => "13" }
|
228
|
+
# * query, qid, qname = query parameters passed directly to QuickBase::Client,doQuery()
|
229
|
+
# * numItems = the number of records to retrieve from QuickBase
|
230
|
+
def addTable( dbid, tableName, fields, query = nil, qid = nil, qname = nil, numRecords = 0 )
|
231
|
+
t = Table.new( dbid, tableName, fields, query, qid, qname, numRecords )
|
232
|
+
@tables << t
|
233
|
+
end
|
234
|
+
|
235
|
+
# Generate all the RSS text for all the tables.
|
236
|
+
def generateRSStext
|
237
|
+
rssText = ""
|
238
|
+
if @tables.length > 0
|
239
|
+
rssText = header
|
240
|
+
rssText << @title if @title
|
241
|
+
rssText << @link if @link
|
242
|
+
rssText << @description if @description
|
243
|
+
@tables.each{ |table| generateRSSItems( table ) }
|
244
|
+
if @sorted
|
245
|
+
sortedItems = @sortableItems.sort{|a,b| b[1]<=>a[1] if a[1] and b[1] }
|
246
|
+
sortedItems.each{ |item| rssText << @items[item[0]] }
|
247
|
+
else
|
248
|
+
@items.each{ |item| rssText << @items[item[0]] }
|
249
|
+
end
|
250
|
+
rssText << footer
|
251
|
+
else
|
252
|
+
raise "Call addTable() one or more times before calling generateRSS"
|
253
|
+
end
|
254
|
+
rssText
|
255
|
+
end
|
256
|
+
|
257
|
+
end # class RSSGenerator
|
258
|
+
|
259
|
+
end #module QuickBase
|
260
|
+
|
261
|
+
# The following test code generates RSS text from recently modified
|
262
|
+
# records in the QuickBase Community Forum and KnowledgeBase.
|
263
|
+
def test( username, password )
|
264
|
+
|
265
|
+
qbc = QuickBase::Client.new( username, password )
|
266
|
+
qbRSSgen = QuickBase::RSSGenerator.new( qbc )
|
267
|
+
|
268
|
+
qbRSSgen.unSorted
|
269
|
+
|
270
|
+
qbRSSgen.setTitle( "QuickBase Forum/KnowledgeBase RSS" )
|
271
|
+
qbRSSgen.setLink( "main" )
|
272
|
+
qbRSSgen.setDescription( "RSS view of QuickBase Community Forum and KnowledgeBase" )
|
273
|
+
|
274
|
+
qbRSSgen.addTable("8emtadvk", "Community Forum", { "title" => "6", "description" => "10" }, nil, nil, nil, 75 )
|
275
|
+
qbRSSgen.addTable( "6mztyxu8", "KnowledgeBase", { "title" => "5", "description" => "6" }, nil, nil, nil, 50 )
|
276
|
+
|
277
|
+
rssText = qbRSSgen.generateRSStext
|
278
|
+
|
279
|
+
File.open( "QuickBaseInfoRSS.xml", "w" ) { |file| file.write( rssText ) }
|
280
|
+
print "\nPlease view QuickBaseInfoRSS.xml in an RSS reader, a browser or an XML editor.\n"
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
# uncomment the following line to test this class using 'ruby QuickBaseRSSGenerator.rb username password'
|
285
|
+
#test( ARGV[0], ARGV[1] ) if ARGV[1]
|
286
|
+
|
@@ -0,0 +1,545 @@
|
|
1
|
+
#--#####################################################################
|
2
|
+
# Copyright (c) 2009 Gareth Lewis and Intuit, Inc.
|
3
|
+
#
|
4
|
+
# All rights reserved. This program and the accompanying materials
|
5
|
+
# are made available under the terms of the Eclipse Public License v1.0
|
6
|
+
# which accompanies this distribution, and is available at
|
7
|
+
# http://www.opensource.org/licenses/eclipse-1.0.php
|
8
|
+
#
|
9
|
+
# Contributors:
|
10
|
+
# Gareth Lewis - Initial contribution.
|
11
|
+
# Intuit Partner Platform.
|
12
|
+
#++#####################################################################
|
13
|
+
require 'QuickBaseClient'
|
14
|
+
|
15
|
+
module QuickBase
|
16
|
+
|
17
|
+
=begin rdoc
|
18
|
+
|
19
|
+
The data file format processed by this class is:-
|
20
|
+
|
21
|
+
application: <application name>
|
22
|
+
table: <table name>
|
23
|
+
dbid: <dbid>
|
24
|
+
record: [record id#]
|
25
|
+
<field name>: value
|
26
|
+
<field name>: value
|
27
|
+
...
|
28
|
+
record:
|
29
|
+
<field name>: value
|
30
|
+
<field name>: value
|
31
|
+
|
32
|
+
1) application:, table:, dbid:, record: <field name>: must appear at the beginning of a line.
|
33
|
+
2) Except for text fields, whitespace will be trimmed from field values.
|
34
|
+
3) Lines starting with whitespace are processed as data for a multi-line text field.
|
35
|
+
The first whitespace character will be ignored.
|
36
|
+
4) If <application name> is followed by 'record:' , the table is assumed to have the
|
37
|
+
same name as the application, as in a single-table application.
|
38
|
+
5) <dbid> will change the target/source table for any subsequent 'record:'.
|
39
|
+
When possible, use <dbid> instead of <application name> and <table name>.
|
40
|
+
6) If <application name> refers to a non-existent application, the <application name> will be created
|
41
|
+
as a new single-table application and <table name> will be ignored.
|
42
|
+
7) If <field name>: is not an existing field and is followed by a valid field type name, such as 'date',
|
43
|
+
the <field name> field wil be added to the QuickBase table. Field properties can appear after the
|
44
|
+
field type.
|
45
|
+
8) If <field name> is a number and is not an existing field, the field will be treated as a QuickBase field id.
|
46
|
+
9) If record: is followed by a number, the number is assumed to be the id of an existing record, and data
|
47
|
+
in subsequent <field name>: will overwite existing fields when sent to QuickBase.
|
48
|
+
|
49
|
+
|
50
|
+
Example:
|
51
|
+
|
52
|
+
application:Email Inbox
|
53
|
+
table:Messages
|
54
|
+
record:
|
55
|
+
From: my@email.com
|
56
|
+
Date Sent: 12/31/2007
|
57
|
+
Subject:Testing TextData class
|
58
|
+
Body: This is a test of the TextData class.
|
59
|
+
This is a multi-line text field.
|
60
|
+
|
61
|
+
=end
|
62
|
+
|
63
|
+
# Class to read and write human-editable text files of data that can be sent to QuickBase.
|
64
|
+
# The file format can also be used as a simple intermediate format for getting data into
|
65
|
+
# QuickBase programmatically from other sources. This format is better than CSV for
|
66
|
+
# human-readability and allows fields to be skipped and to appear in any sequence.
|
67
|
+
# The format is like yaml, probably isn't a subset of it, but is simpler.
|
68
|
+
class TextData
|
69
|
+
|
70
|
+
attr_reader :dbid
|
71
|
+
|
72
|
+
def initialize(username,password)
|
73
|
+
@username,@password=username,password
|
74
|
+
end
|
75
|
+
|
76
|
+
# Read a file in the format described above and send the data to QuickBase.
|
77
|
+
# Records are added or edited when tables, fields and values have been validated
|
78
|
+
# and all the data for a record has been accumulated.
|
79
|
+
# Any error in the input file will stop further processing of the file.
|
80
|
+
def sendDataToQuickBase(inputFilename, errorFilename)
|
81
|
+
@errorFilename = errorFilename
|
82
|
+
if FileTest.exist?(inputFilename)
|
83
|
+
if login
|
84
|
+
lineNumber = 1
|
85
|
+
begin
|
86
|
+
IO.foreach(inputFilename){|line|
|
87
|
+
if line.index( "application:") == 0
|
88
|
+
line.sub!("application:","")
|
89
|
+
line.strip!
|
90
|
+
setApplication(line)
|
91
|
+
elsif line.index( "table:") == 0
|
92
|
+
line.sub!("table:","")
|
93
|
+
line.strip!
|
94
|
+
setTable(line)
|
95
|
+
elsif line.index( "dbid:") == 0
|
96
|
+
line.sub!("dbid:","")
|
97
|
+
line.strip!
|
98
|
+
setDBID(line)
|
99
|
+
elsif line.index( "record:") == 0
|
100
|
+
line.sub!("record:","")
|
101
|
+
line.strip!
|
102
|
+
if line.match(/[0-9]+/)
|
103
|
+
setRecord(line)
|
104
|
+
else
|
105
|
+
setRecord(nil)
|
106
|
+
end
|
107
|
+
elsif line.match( /^([^:]+):(.*)$/)
|
108
|
+
setFieldValue($1, $2)
|
109
|
+
elsif line.match( /^([\s\t])(.*)$/)
|
110
|
+
appendFieldValue($2)
|
111
|
+
end
|
112
|
+
lineNumber = lineNumber + 1
|
113
|
+
}
|
114
|
+
addOrEditRecord
|
115
|
+
rescue StandardError => error
|
116
|
+
writeError("File #{inputFilename}, line #{lineNumber}: #{error}.")
|
117
|
+
end
|
118
|
+
else
|
119
|
+
writeError("Error connecting to QuickBase.")
|
120
|
+
end
|
121
|
+
logout
|
122
|
+
else
|
123
|
+
writeError("Input file #{inputFilename} does not exist.")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def writeError(error)
|
128
|
+
@errorFile = File.new(@errorFilename,"w") if @errorFilename and @errorFile.nil?
|
129
|
+
@errorFile.puts(error)
|
130
|
+
end
|
131
|
+
|
132
|
+
def login
|
133
|
+
if @username.nil? or @password.nil?
|
134
|
+
writeError("Must set username and password")
|
135
|
+
elsif @qbc.nil?
|
136
|
+
@qbc = QuickBase::Client.new(@username,@password)
|
137
|
+
end
|
138
|
+
ret = @qbc and @qbc.requestSucceeded
|
139
|
+
end
|
140
|
+
|
141
|
+
def logout
|
142
|
+
@qbc.signOut if @qbc
|
143
|
+
@qbc = nil
|
144
|
+
end
|
145
|
+
|
146
|
+
def resetTableVars
|
147
|
+
@activeRecordNumber = nil
|
148
|
+
@activeFieldID = nil
|
149
|
+
@activeField = nil
|
150
|
+
@fieldIDValues = nil
|
151
|
+
@fieldValues = nil
|
152
|
+
@fieldType = nil
|
153
|
+
@fieldProperties = nil
|
154
|
+
@fieldAllowsNewChoices = false
|
155
|
+
@fieldIsMultiLineText = false
|
156
|
+
@fieldIsValidFileAttachment = false
|
157
|
+
end
|
158
|
+
|
159
|
+
def setApplication(appName)
|
160
|
+
addOrEditRecord
|
161
|
+
resetTableVars
|
162
|
+
@singleTableApplication = false
|
163
|
+
@qbc.findDBByname(appName)
|
164
|
+
if @qbc.dbid.nil?
|
165
|
+
@qbc.createDatabase(appName,appName)
|
166
|
+
raise "Error creating application '#{name}'" if @qbc.dbid.nil?
|
167
|
+
raise "Unable to find table '#{@appName}'" if !@qbc.lookupChdbid(@appName)
|
168
|
+
@singleTableApplication = true
|
169
|
+
end
|
170
|
+
raise "Error finding or creating application '#{name}'" if @qbc.dbid.nil?
|
171
|
+
@appName = appName
|
172
|
+
@qbc._getSchema
|
173
|
+
@dbid = @qbc.dbid.dup
|
174
|
+
end
|
175
|
+
|
176
|
+
def setTable(name)
|
177
|
+
addOrEditRecord
|
178
|
+
resetTableVars
|
179
|
+
if !@singleTableApplication
|
180
|
+
raise "Unable to find table '#{name}'" if !@qbc.lookupChdbid(name)
|
181
|
+
@qbc._getSchema
|
182
|
+
end
|
183
|
+
@dbid = @qbc.dbid.dup
|
184
|
+
end
|
185
|
+
|
186
|
+
def setDBID(id)
|
187
|
+
addOrEditRecord
|
188
|
+
resetTableVars
|
189
|
+
@singleTableApplication = false
|
190
|
+
@qbc.getSchema(id)
|
191
|
+
raise "Unable to find table with the '#{id}' id" if @qbc.dbid.nil?
|
192
|
+
@dbid = @qbc.dbid.dup
|
193
|
+
end
|
194
|
+
|
195
|
+
def setRecord(number)
|
196
|
+
addOrEditRecord
|
197
|
+
@activeRecordNumber = number
|
198
|
+
@activeRecordNumber = nil if @activeRecordNumber and @activeRecordNumber.length == 0
|
199
|
+
if @activeRecordNumber
|
200
|
+
@qbc._setActiveRecord(@activeRecordNumber)
|
201
|
+
else
|
202
|
+
@qbc.resetrid
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def checkFieldNameAndValue(fieldName,fieldValue)
|
207
|
+
|
208
|
+
@fieldNameIsFieldID = false
|
209
|
+
@fieldAllowsNewChoices = false
|
210
|
+
@fieldIsMultiLineText = false
|
211
|
+
@fieldIsValidFileAttachment = false
|
212
|
+
@fieldType = nil
|
213
|
+
@fieldProperties = nil
|
214
|
+
|
215
|
+
fieldElement = @qbc.lookupField( @qbc.lookupFieldIDByName(fieldName) )
|
216
|
+
if !fieldElement and fieldName.match(/[0-9]+/) # if it's a number, see if it's a valid field id
|
217
|
+
fieldElement = @qbc.lookupField(fieldName,fieldValue)
|
218
|
+
@fieldNameIsFieldID = true if fieldElement
|
219
|
+
end
|
220
|
+
|
221
|
+
if !fieldElement # field doesn't exist. the field can be created if the value is actually a valid field type
|
222
|
+
if fieldValue and fieldValue.length > 0
|
223
|
+
fieldValue.strip!
|
224
|
+
if fieldValue.length > 0
|
225
|
+
fieldTypeAndProperties = fieldValue.split(/ /)
|
226
|
+
type = fieldTypeAndProperties[0]
|
227
|
+
if @qbc.isValidFieldType?(type)
|
228
|
+
@fieldType = Hash.new if @fieldType.nil?
|
229
|
+
@fieldType[fieldName] = type.dup
|
230
|
+
fieldTypeAndProperties.shift # anything after the field type must be valid field properties in the form property="value"
|
231
|
+
fieldTypeAndProperties.each { |property|
|
232
|
+
propertyType = property[0..(property.index("=")-1)]
|
233
|
+
raise "Invalid field property type #{propertyType}" if !@qbc.isValidFieldProperty?(propertyType)
|
234
|
+
propertyValue = property[(property.index("=")+1)..(property.rindex("\"")-1)]
|
235
|
+
raise "Missing value for field property #{propertyType}" if propertyValue.nil? or propertyValue.length == 0
|
236
|
+
@fieldProperties = Hash.new if @fieldProperties.nil?
|
237
|
+
propertyAndValuePair = Hash.new
|
238
|
+
propertyAndValuePair[propertyType]=propertyValue
|
239
|
+
@fieldProperties[fieldName] << propertyAndValuePair
|
240
|
+
}
|
241
|
+
else
|
242
|
+
raise "Invalid field type #{type}."
|
243
|
+
end
|
244
|
+
else
|
245
|
+
raise "Invalid field name #{fieldName}."
|
246
|
+
end
|
247
|
+
end
|
248
|
+
else
|
249
|
+
# do basic data type check
|
250
|
+
type = fieldElement.attributes["field_type"].dup
|
251
|
+
raise "Unable to determine data type for #{fieldName} field" if type.nil?
|
252
|
+
if fieldValue.length > 0 #any field can be blanked out
|
253
|
+
case type
|
254
|
+
when "checkbox"
|
255
|
+
if !(fieldValue == "1" or fieldValue == "0")
|
256
|
+
raise "Invalid data '#{fieldValue}' for checkbox field #{fieldName}"
|
257
|
+
end
|
258
|
+
when "date"
|
259
|
+
fieldValue.gsub!("/","-")
|
260
|
+
if !fieldValue.match(/[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]/)
|
261
|
+
raise "Invalid data '#{fieldValue}' for date field #{fieldName}"
|
262
|
+
end
|
263
|
+
when "duration", "float", "currency", "rating"
|
264
|
+
fieldValue.gsub!(",","")
|
265
|
+
if !fieldValue.match(/[0-9]*\.?[0-9]*/)
|
266
|
+
raise "Invalid data '#{fieldValue}' for field #{fieldName}"
|
267
|
+
end
|
268
|
+
when "phone"
|
269
|
+
if !fieldValue.match(/[0-9|\.|x]+/)
|
270
|
+
raise "Invalid data '#{fieldValue}' for phone field #{fieldName}"
|
271
|
+
end
|
272
|
+
when "file"
|
273
|
+
if FileTest.exist?(fieldValue)
|
274
|
+
@fieldIsValidFileAttachment = true
|
275
|
+
else
|
276
|
+
raise "Invalid file name '#{fieldValue}' for file attachment field #{fieldName}"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
# check whether field allows user to add to a choicelist.
|
281
|
+
@fieldAllowsNewChoices = fieldAllowsNewChoices?(fieldElement)
|
282
|
+
# check whether field allows mutliple lines.
|
283
|
+
@fieldIsMultiLineText = fieldIsMultiLineText?(fieldElement)
|
284
|
+
end
|
285
|
+
fieldElement
|
286
|
+
end
|
287
|
+
|
288
|
+
def fieldAllowsNewChoices?(fieldElement)
|
289
|
+
allowsUserChoices = false
|
290
|
+
findPropertyBlock = proc { |element|
|
291
|
+
if element.is_a?(REXML::Element) and element.name == "allow_new_choices" and element.has_text?
|
292
|
+
allowsUserChoices = (element.text == "1")
|
293
|
+
end
|
294
|
+
}
|
295
|
+
@qbc.processChildElements(fieldElement, true, findPropertyBlock)
|
296
|
+
allowsUserChoices
|
297
|
+
end
|
298
|
+
|
299
|
+
def fieldIsMultiLineText?(fieldElement)
|
300
|
+
isMultiLineText = false
|
301
|
+
findPropertyBlock = proc { |element|
|
302
|
+
if element.is_a?(REXML::Element) and element.name == "num_lines" and element.has_text?
|
303
|
+
isMultiLineText = (element.text != "1")
|
304
|
+
end
|
305
|
+
}
|
306
|
+
@qbc.processChildElements(fieldElement, true, findPropertyBlock)
|
307
|
+
isMultiLineText
|
308
|
+
end
|
309
|
+
|
310
|
+
def addField(field,type,properties)
|
311
|
+
newFieldID, newfieldLabel = @qbc._addField(field, type)
|
312
|
+
if newFieldID
|
313
|
+
if properties
|
314
|
+
fieldPropertiesToSet = Hash.new
|
315
|
+
properties.each{|property|
|
316
|
+
property.each{|propertyName,propertyValue| fieldPropertiesToSet[propertyName] = propertyValue }
|
317
|
+
}
|
318
|
+
@qbc._setFieldProperties(fieldPropertiesToSet,newFieldID)
|
319
|
+
@qbc._getSchema
|
320
|
+
end
|
321
|
+
else
|
322
|
+
raise "Error creating new field #{field}"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def setFieldValue(fieldName, value)
|
327
|
+
if checkFieldNameAndValue(fieldName,value)
|
328
|
+
if @fieldNameIsFieldID
|
329
|
+
@activeFieldID = fieldName
|
330
|
+
@activeField = nil
|
331
|
+
@fieldIDValues = Hash.new if @fieldIDValues.nil?
|
332
|
+
@fieldIDValues[@activeFieldID] = value.dup
|
333
|
+
@fieldIDProperties = Hash.new if @fieldIDProperties.nil?
|
334
|
+
@fieldIDProperties[@activeFieldID] = { "fieldAllowsNewChoices"=>@fieldAllowsNewChoices, "fieldIsMultiLineText"=>@fieldIsMultiLineText, "fieldIsValidFileAttachment"=>@fieldIsValidFileAttachment }
|
335
|
+
else
|
336
|
+
@activeField = fieldName
|
337
|
+
@activeFieldID = nil
|
338
|
+
@fieldValues = Hash.new if @fieldValues.nil?
|
339
|
+
@fieldValues[@activeField] = value.dup
|
340
|
+
@fieldProperties = Hash.new if @fieldProperties.nil?
|
341
|
+
@fieldProperties[@activeField] = { "fieldAllowsNewChoices"=>@fieldAllowsNewChoices,"fieldIsMultiLineText"=>@fieldIsMultiLineText, "fieldIsValidFileAttachment"=>@fieldIsValidFileAttachment }
|
342
|
+
end
|
343
|
+
elsif @fieldType and @fieldType[fieldName]
|
344
|
+
if @fieldProperties and @fieldProperties[fieldName]
|
345
|
+
addField(fieldName,@fieldType[fieldName],@fieldProperties[fieldName])
|
346
|
+
else
|
347
|
+
addField(fieldName,@fieldType[fieldName],nil)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def appendFieldValue(value)
|
353
|
+
if value and value.length > 0
|
354
|
+
if @activeField and @fieldProperties[@activeField]["fieldIsMultiLineText"]
|
355
|
+
@fieldValues = Hash.new if @fieldValues.nil?
|
356
|
+
@fieldValues[@activeField] << "\n"
|
357
|
+
@fieldValues[@activeField] << value.dup
|
358
|
+
elsif @activeFieldID and @fieldIDProperties[@activeFieldID]["fieldIsMultiLineText"]
|
359
|
+
@fieldIDValues = Hash.new if @fieldIDValues.nil?
|
360
|
+
@fieldIDValues[@activeFieldID] << "\n"
|
361
|
+
@fieldIDValues[@activeFieldID] << value.dup
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def addFieldValuePairs
|
367
|
+
@qbc.clearFieldValuePairList
|
368
|
+
if @fieldValues
|
369
|
+
@fieldValues.each{ |f,v|
|
370
|
+
if f and v and @fieldProperties and @fieldProperties[f] and @fieldProperties[f]["fieldAllowsNewChoices"]
|
371
|
+
@fieldChoicesToSet = Hash.new if @fieldChoicesToSet.nil?
|
372
|
+
@fieldChoicesToSet[f] = Array.new if @fieldChoicesToSet[f].nil?
|
373
|
+
@fieldChoicesToSet[f] << v
|
374
|
+
end
|
375
|
+
if f and v and @fieldProperties and @fieldProperties[f] and @fieldProperties[f]["fieldIsValidFileAttachment"]
|
376
|
+
@qbc.addFieldValuePair(f.dup,nil,v.dup,nil)
|
377
|
+
else
|
378
|
+
@qbc.addFieldValuePair(f.dup,nil,nil,v.dup)
|
379
|
+
end
|
380
|
+
}
|
381
|
+
@fieldValues = nil
|
382
|
+
end
|
383
|
+
if @fieldIDValues
|
384
|
+
@fieldIDValues.each{ |f,v|
|
385
|
+
if @fieldIDProperties and @fieldIDProperties[f] and @fieldIDProperties[f]["fieldAllowsNewChoices"]
|
386
|
+
@fieldIDChoicesToSet = Hash.new if @fieldChoicesToSet.nil?
|
387
|
+
@fieldIDChoicesToSet[f] << v
|
388
|
+
end
|
389
|
+
if @fieldIDProperties and @fieldIDProperties[f] and @fieldIDProperties[f]["fieldIsValidFileAttachment"]
|
390
|
+
@qbc.addFieldValuePair(nil,f.dup,v.dup,nil)
|
391
|
+
else
|
392
|
+
@qbc.addFieldValuePair(nil,f.dup,nil,v.dup)
|
393
|
+
end
|
394
|
+
}
|
395
|
+
@fieldIDValues = nil
|
396
|
+
end
|
397
|
+
@qbc.fvlist
|
398
|
+
end
|
399
|
+
|
400
|
+
def isValidRecord?(addingRecord)
|
401
|
+
valid = true
|
402
|
+
if addingRecord and @qbc.fields
|
403
|
+
# check that all required fields are present
|
404
|
+
requiredFieldIDs = Hash.new
|
405
|
+
requiredFields = Hash.new
|
406
|
+
fieldID = ""
|
407
|
+
fieldName = ""
|
408
|
+
findRequiredFieldsBlock = proc { |element|
|
409
|
+
if element.is_a?(REXML::Element) and element.name == "field"
|
410
|
+
fieldID = element.attributes["id"]
|
411
|
+
elsif element.is_a?(REXML::Element) and element.name == "label" and element.has_text?
|
412
|
+
fieldName = element.text
|
413
|
+
elsif element.is_a?(REXML::Element) and element.name == "required" and element.has_text?
|
414
|
+
if element.text == "1"
|
415
|
+
requiredFieldIDs[fieldID] = "1"
|
416
|
+
requiredFields[fieldName] = "1"
|
417
|
+
end
|
418
|
+
end
|
419
|
+
}
|
420
|
+
@qbc.processChildElements(@qbc.fields, false, findRequiredFieldsBlock)
|
421
|
+
|
422
|
+
if @fieldValues
|
423
|
+
@fieldValues.each{ |f,v| requiredFields[f] = nil if requiredFields[f] }
|
424
|
+
end
|
425
|
+
if @fieldIDValues
|
426
|
+
@fieldIDValues.each{ |f,v| requiredFieldIDs[f] = nil if requiredFieldIDs[f] }
|
427
|
+
end
|
428
|
+
missingFields = ""
|
429
|
+
requiredFields.each{ |f,v| missingFields << "#{f} " if v } if @fieldValues
|
430
|
+
requiredFieldIDs.each{ |f,v| missingFields << "#{f} " if v } if @fieldIDValues
|
431
|
+
raise "Required fields are missing: #{missingFields}" if missingFields.length > 0
|
432
|
+
end
|
433
|
+
valid
|
434
|
+
end
|
435
|
+
|
436
|
+
def addOrEditRecord
|
437
|
+
if @activeRecordNumber
|
438
|
+
editRecord
|
439
|
+
elsif isValidRecord?(true)
|
440
|
+
addRecord
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
def addRecord
|
445
|
+
if addFieldValuePairs
|
446
|
+
addFieldChoices
|
447
|
+
@qbc.addRecord(@qbc.dbid, @qbc.fvlist)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
def editRecord
|
452
|
+
if @activeRecordNumber and addFieldValuePairs
|
453
|
+
addFieldChoices
|
454
|
+
@qbc.editRecord(@qbc.dbid, @activeRecordNumber, @qbc.fvlist)
|
455
|
+
@activeRecordNumber = nil
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
def addFieldChoices
|
460
|
+
if @fieldIDChoicesToSet
|
461
|
+
@fieldIDChoicesToSet.each{ |f,choices|
|
462
|
+
@qbc._fieldAddChoices(f,choices)
|
463
|
+
}
|
464
|
+
@fieldIDChoicesToSet = nil
|
465
|
+
end
|
466
|
+
if @fieldChoicesToSet
|
467
|
+
@fieldChoicesToSet.each{ |f,choices|
|
468
|
+
@qbc._fieldNameAddChoices(f,choices)
|
469
|
+
}
|
470
|
+
@fieldChoicesToSet = nil
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
# Writes all records and all fields from a table to the specified
|
475
|
+
# outputFilename, in the format specified above, sorted by record ID#.
|
476
|
+
# Since they can't be sent back into QuickBase, built-in fields are excluded.
|
477
|
+
def writeDataFromQuickBase(dbid, outputFilename, errorFilename)
|
478
|
+
@errorFilename = errorFilename
|
479
|
+
if login
|
480
|
+
@qbc.getSchema(dbid)
|
481
|
+
if @qbc.dbid and @qbc.requestSucceeded
|
482
|
+
@outputFile = File.new(outputFilename,"w")
|
483
|
+
@outputFile.puts( "dbid:#{dbid}")
|
484
|
+
@isBuitlInField = false
|
485
|
+
recordIDs = @qbc.getAllValuesForFields(dbid,["Record ID#"],nil,nil,nil,"3","3")
|
486
|
+
if recordIDs
|
487
|
+
recordIDs["Record ID#"].each{ |recordID|
|
488
|
+
@outputFile.puts( "record:#{recordID}")
|
489
|
+
@qbc._getRecordInfo(recordID)
|
490
|
+
processFieldDataBlock = proc { |element|
|
491
|
+
if element.is_a?(REXML::Element)
|
492
|
+
if element.name == "fid"
|
493
|
+
@isBuitlInField = element.text.to_i < 6
|
494
|
+
elsif element.name == "name" and !@isBuitlInField
|
495
|
+
@outputFile.print("#{element.text}:")
|
496
|
+
elsif element.name == "type"
|
497
|
+
@outputFieldType = element.text.dup.downcase!
|
498
|
+
elsif element.name == "value" and !@isBuitlInField
|
499
|
+
outputFieldValue = ""
|
500
|
+
outputFieldValue = element.text.dup if element.has_text?
|
501
|
+
outputFieldValue.gsub!("<BR/>","\n ")
|
502
|
+
outputFieldValue = @qbc.formatFieldValue(outputFieldValue,@outputFieldType)
|
503
|
+
outputFieldValue = "" if outputFieldValue.nil?
|
504
|
+
@outputFile.puts(outputFieldValue)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
}
|
508
|
+
@qbc.processChildElements(@qbc.field_data_list,true,processFieldDataBlock)
|
509
|
+
}
|
510
|
+
@outputFile.close
|
511
|
+
end
|
512
|
+
else
|
513
|
+
writeError("Invalid dbid #{dbid}.")
|
514
|
+
end
|
515
|
+
logout
|
516
|
+
else
|
517
|
+
writeError("Error connecting to QuickBase.")
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
def TextData.uploadData(username,password,file)
|
522
|
+
td = QuickBase::TextData.new(username,password)
|
523
|
+
td.sendDataToQuickBase(file,"textDataUploadErrors.txt")
|
524
|
+
end
|
525
|
+
|
526
|
+
def TextData.downloadData(username,password,dbid)
|
527
|
+
td = QuickBase::TextData.new(username,password)
|
528
|
+
td.writeDataFromQuickBase(dbid,"downloadedTextData.txt","textDataDownloadErrors.txt")
|
529
|
+
end
|
530
|
+
|
531
|
+
# Sends data from file to QuickBase, then overwrites the same
|
532
|
+
# file with data from the last referenced table. It's best to
|
533
|
+
# use this for synchronizing just one table's data.
|
534
|
+
def TextData.synchDataFile(username,password,file)
|
535
|
+
td = QuickBase::TextData.new(username,password)
|
536
|
+
td.sendDataToQuickBase(file,"textDataUploadErrors.txt")
|
537
|
+
td.writeDataFromQuickBase(td.dbid,file,"textDataDownloadErrors.txt")
|
538
|
+
end
|
539
|
+
|
540
|
+
end # class TextData
|
541
|
+
|
542
|
+
end # module QuickBase
|
543
|
+
|
544
|
+
#QuickBase::TextData.uploadData(ARGV[0],ARGV[1],ARGV[2]) if ARGV[2]
|
545
|
+
#QuickBase::TextData.downloadData(ARGV[0],ARGV[1],ARGV[3]) if ARGV[3]
|