ipp_quickbase_devkit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. data/LICENSE +87 -0
  2. data/README.rdoc +112 -0
  3. data/doc/QuickBaseClient.rb.htm +1896 -0
  4. data/doc/ReleaseNotes.txt +43 -0
  5. data/doc/qbc.makeCSVFile.qbc +4 -0
  6. data/doc/qbc.makeCSVFile.rb +4 -0
  7. data/doc/quickbase_adapter.rb.htm +399 -0
  8. data/examples/cookbookfiles/QuickBaseAPICookbook.html +2590 -0
  9. data/examples/cookbookfiles/addChangeRemoveUserRole.rb +21 -0
  10. data/examples/cookbookfiles/addOrEditRecord.rb +10 -0
  11. data/examples/cookbookfiles/application_object.rb +55 -0
  12. data/examples/cookbookfiles/applyRubyFormulas.rb +10 -0
  13. data/examples/cookbookfiles/average.rb +27 -0
  14. data/examples/cookbookfiles/backupApplication.rb +8 -0
  15. data/examples/cookbookfiles/cacheSchemas.rb +53 -0
  16. data/examples/cookbookfiles/calculateRunningTotals.rb +11 -0
  17. data/examples/cookbookfiles/copyrecords.rb +73 -0
  18. data/examples/cookbookfiles/count.rb +26 -0
  19. data/examples/cookbookfiles/createRecordNavigatorHTML.rb +42 -0
  20. data/examples/cookbookfiles/createReportDashboard.rb +35 -0
  21. data/examples/cookbookfiles/createTable.rb +12 -0
  22. data/examples/cookbookfiles/deviation.rb +25 -0
  23. data/examples/cookbookfiles/downloadCookbook.rb +97 -0
  24. data/examples/cookbookfiles/downloadFile.rb +10 -0
  25. data/examples/cookbookfiles/downloadFilesToFolder.rb +81 -0
  26. data/examples/cookbookfiles/downloadToTextFile.rb +64 -0
  27. data/examples/cookbookfiles/dumpSchema.rb +11 -0
  28. data/examples/cookbookfiles/duplicateRecord.rb +8 -0
  29. data/examples/cookbookfiles/dynamicMethods.rb +33 -0
  30. data/examples/cookbookfiles/editRecords.rb +15 -0
  31. data/examples/cookbookfiles/findJohnsLast10Records.rb +17 -0
  32. data/examples/cookbookfiles/findRubyRecords.rb +17 -0
  33. data/examples/cookbookfiles/formatCurrency.rb +24 -0
  34. data/examples/cookbookfiles/formatDate.rb +10 -0
  35. data/examples/cookbookfiles/formatDuration.rb +27 -0
  36. data/examples/cookbookfiles/formatPercent.rb +24 -0
  37. data/examples/cookbookfiles/getAllValuesForFields.rb +18 -0
  38. data/examples/cookbookfiles/getAppDTMInfo.rb +29 -0
  39. data/examples/cookbookfiles/getApplicationVariable.rb +5 -0
  40. data/examples/cookbookfiles/getChildTableDBID.rb +11 -0
  41. data/examples/cookbookfiles/getColumnListForReport.rb +6 -0
  42. data/examples/cookbookfiles/getFieldChoices.rb +13 -0
  43. data/examples/cookbookfiles/getFieldIDs.rb +6 -0
  44. data/examples/cookbookfiles/getFieldNames.rb +6 -0
  45. data/examples/cookbookfiles/getLastModTime.rb +8 -0
  46. data/examples/cookbookfiles/getLastRecModTime.rb +8 -0
  47. data/examples/cookbookfiles/getNumRecords.rb +8 -0
  48. data/examples/cookbookfiles/getNumTables.rb +4 -0
  49. data/examples/cookbookfiles/getRecord.rb +5 -0
  50. data/examples/cookbookfiles/getRecordDisplayURL.rb +13 -0
  51. data/examples/cookbookfiles/getRecordsAddedToday.rb +20 -0
  52. data/examples/cookbookfiles/getRecordsAsJSON.rb +6 -0
  53. data/examples/cookbookfiles/getReportNames.rb +25 -0
  54. data/examples/cookbookfiles/getRoleInfo.rb +48 -0
  55. data/examples/cookbookfiles/getServerStatus.rb +11 -0
  56. data/examples/cookbookfiles/getSortListForReport.rb +6 -0
  57. data/examples/cookbookfiles/getTableIDs.rb +6 -0
  58. data/examples/cookbookfiles/getTableName.rb +8 -0
  59. data/examples/cookbookfiles/getTableNames.rb +25 -0
  60. data/examples/cookbookfiles/getTimeCreated.rb +8 -0
  61. data/examples/cookbookfiles/getTimeInMilliseconds.rb +5 -0
  62. data/examples/cookbookfiles/getUserInfo.rb +26 -0
  63. data/examples/cookbookfiles/getUserRole.rb +15 -0
  64. data/examples/cookbookfiles/intranet.rb +101 -0
  65. data/examples/cookbookfiles/isAverageField.rb +17 -0
  66. data/examples/cookbookfiles/isDbidString.rb +8 -0
  67. data/examples/cookbookfiles/isTotalField.rb +16 -0
  68. data/examples/cookbookfiles/iterateDBPages.rb +8 -0
  69. data/examples/cookbookfiles/iterateFilteredRecords.rb +12 -0
  70. data/examples/cookbookfiles/iterateJoinRecords.rb +68 -0
  71. data/examples/cookbookfiles/iterateRecordInfos.rb +8 -0
  72. data/examples/cookbookfiles/iterateRecords.rb +23 -0
  73. data/examples/cookbookfiles/iterateSummaryRecords.rb +13 -0
  74. data/examples/cookbookfiles/iterateUnionRecords.rb +38 -0
  75. data/examples/cookbookfiles/listAccessibleApplications.rb +6 -0
  76. data/examples/cookbookfiles/logRequestAndResponseXML.rb +8 -0
  77. data/examples/cookbookfiles/lookupFieldPropertyByName.rb +62 -0
  78. data/examples/cookbookfiles/lookupFieldTypeByName.rb +10 -0
  79. data/examples/cookbookfiles/makeCSVFile.rb +4 -0
  80. data/examples/cookbookfiles/makeSlideShow.rb +42 -0
  81. data/examples/cookbookfiles/makerecs.rb +64 -0
  82. data/examples/cookbookfiles/max.rb +26 -0
  83. data/examples/cookbookfiles/min.rb +26 -0
  84. data/examples/cookbookfiles/percent.rb +29 -0
  85. data/examples/cookbookfiles/printChildElements.rb +54 -0
  86. data/examples/cookbookfiles/printNewRecords.rb +12 -0
  87. data/examples/cookbookfiles/processRESTRequest.rb +21 -0
  88. data/examples/cookbookfiles/provisionAndInviteNewUser.rb +13 -0
  89. data/examples/cookbookfiles/purgeRecords.rb +15 -0
  90. data/examples/cookbookfiles/quickbase_adapter.rb.htm +397 -0
  91. data/examples/cookbookfiles/quickbase_record_finder.zip +0 -0
  92. data/examples/cookbookfiles/recordAndFieldIterator.rb +24 -0
  93. data/examples/cookbookfiles/runImport.rb +9 -0
  94. data/examples/cookbookfiles/runQuickBaseTwitterConnector.rb +41 -0
  95. data/examples/cookbookfiles/sendToQuickBase.rb +33 -0
  96. data/examples/cookbookfiles/setDBvar.rb +6 -0
  97. data/examples/cookbookfiles/showRequestAndResponseXML.rb +8 -0
  98. data/examples/cookbookfiles/sqlQuery.rb +11 -0
  99. data/examples/cookbookfiles/stopOnError.rb +10 -0
  100. data/examples/cookbookfiles/sum.rb +26 -0
  101. data/examples/cookbookfiles/twitterFromQuickBase.rb +42 -0
  102. data/examples/cookbookfiles/twitterWithQuickBase.rb +36 -0
  103. data/examples/cookbookfiles/uploadCSVData.rb +20 -0
  104. data/examples/cookbookfiles/uploadExcelData.rb +22 -0
  105. data/examples/cookbookfiles/uploadFileEveryHour.rb +18 -0
  106. data/examples/cookbookfiles/uploadFileIntoNewRecord.rb +8 -0
  107. data/examples/cookbookfiles/uploadFilesFromFolder.exe +0 -0
  108. data/examples/cookbookfiles/uploadFilesFromFolder.rb +69 -0
  109. data/examples/cookbookfiles/useCompanyURL.rb +12 -0
  110. data/examples/cookbookfiles/userRoles.rb +49 -0
  111. data/examples/cookbookfiles/watchCommunityForum.rb +5 -0
  112. data/examples/cookbookfiles/wikifyTable.rb +29 -0
  113. data/examples/cookbookfiles/xmlShortcuts.rb +33 -0
  114. data/examples/pmp/app/controllers/application.rb +7 -0
  115. data/examples/pmp/app/controllers/contacts_controller.rb +8 -0
  116. data/examples/pmp/app/controllers/document_library_controller.rb +2 -0
  117. data/examples/pmp/app/controllers/issues_controller.rb +5 -0
  118. data/examples/pmp/app/controllers/projects_controller.rb +22 -0
  119. data/examples/pmp/app/controllers/resources_controller.rb +2 -0
  120. data/examples/pmp/app/controllers/tasks_controller.rb +13 -0
  121. data/examples/pmp/app/controllers/time_cards_controller.rb +5 -0
  122. data/examples/pmp/app/helpers/application_helper.rb +3 -0
  123. data/examples/pmp/app/helpers/contacts_helper.rb +2 -0
  124. data/examples/pmp/app/helpers/document_library_helper.rb +2 -0
  125. data/examples/pmp/app/helpers/issues_helper.rb +2 -0
  126. data/examples/pmp/app/helpers/projects_helper.rb +2 -0
  127. data/examples/pmp/app/helpers/resources_helper.rb +2 -0
  128. data/examples/pmp/app/helpers/tasks_helper.rb +2 -0
  129. data/examples/pmp/app/helpers/time_cards_helper.rb +2 -0
  130. data/examples/pmp/app/models/contacts.rb +26 -0
  131. data/examples/pmp/app/models/document_library.rb +2 -0
  132. data/examples/pmp/app/models/issues.rb +6 -0
  133. data/examples/pmp/app/models/projects.rb +26 -0
  134. data/examples/pmp/app/models/resources.rb +2 -0
  135. data/examples/pmp/app/models/tasks.rb +12 -0
  136. data/examples/pmp/app/models/time_cards.rb +7 -0
  137. data/examples/pmp/app/schemas/contacts.xml +1 -0
  138. data/examples/pmp/app/schemas/document_library.xml +1 -0
  139. data/examples/pmp/app/schemas/issues.xml +1 -0
  140. data/examples/pmp/app/schemas/pmp.xml +1 -0
  141. data/examples/pmp/app/schemas/projects.xml +1 -0
  142. data/examples/pmp/app/schemas/readme.txt +8 -0
  143. data/examples/pmp/app/schemas/resources.xml +1 -0
  144. data/examples/pmp/app/schemas/tasks.xml +1 -0
  145. data/examples/pmp/app/schemas/time_cards.xml +1 -0
  146. data/examples/pmp/app/views/contacts/companies.rhtml +31 -0
  147. data/examples/pmp/app/views/contacts/project_contacts.rhtml +31 -0
  148. data/examples/pmp/app/views/issues/filter_issues.rhtml +26 -0
  149. data/examples/pmp/app/views/layouts/application.rhtml +56 -0
  150. data/examples/pmp/app/views/projects/all_projects.rhtml +33 -0
  151. data/examples/pmp/app/views/projects/home.rhtml +11 -0
  152. data/examples/pmp/app/views/projects/my_open_projects.rhtml +27 -0
  153. data/examples/pmp/app/views/projects/open_projects.rhtml +44 -0
  154. data/examples/pmp/app/views/projects/project_sorted_by_company.rhtml +40 -0
  155. data/examples/pmp/app/views/projects/projects_sorted_by_priority.rhtml +30 -0
  156. data/examples/pmp/app/views/projects/updated_projects.rhtml +0 -0
  157. data/examples/pmp/app/views/tasks/all_tasks.rhtml +27 -0
  158. data/examples/pmp/app/views/tasks/search.rhtml +23 -0
  159. data/examples/pmp/app/views/tasks/search2.rhtml +23 -0
  160. data/examples/pmp/app/views/tasks/search3.rhtml +23 -0
  161. data/examples/pmp/app/views/time_cards/summary.rhtml +38 -0
  162. data/examples/pmp/config/boot.rb +45 -0
  163. data/examples/pmp/config/database.yml +30 -0
  164. data/examples/pmp/config/environment.rb +60 -0
  165. data/examples/pmp/config/environments/development.rb +21 -0
  166. data/examples/pmp/config/environments/production.rb +18 -0
  167. data/examples/pmp/config/environments/test.rb +19 -0
  168. data/examples/pmp/config/routes.rb +23 -0
  169. data/examples/pmp/db/migrate/001_create_projects.rb +10 -0
  170. data/examples/pmp/db/migrate/002_create_tasks.rb +10 -0
  171. data/examples/pmp/db/migrate/003_create_issues.rb +10 -0
  172. data/examples/pmp/db/migrate/004_create_document_libraries.rb +10 -0
  173. data/examples/pmp/db/migrate/005_create_resources.rb +10 -0
  174. data/examples/pmp/db/migrate/006_create_time_cards.rb +10 -0
  175. data/examples/pmp/db/migrate/007_create_contacts.rb +10 -0
  176. data/examples/pmp/public/404.html +30 -0
  177. data/examples/pmp/public/500.html +30 -0
  178. data/examples/pmp/public/app.index.html +277 -0
  179. data/examples/pmp/public/dispatch.cgi +10 -0
  180. data/examples/pmp/public/dispatch.fcgi +24 -0
  181. data/examples/pmp/public/dispatch.rb +10 -0
  182. data/examples/pmp/public/favicon.ico +0 -0
  183. data/examples/pmp/public/images/rails.png +0 -0
  184. data/examples/pmp/public/javascripts/application.js +2 -0
  185. data/examples/pmp/public/javascripts/controls.js +833 -0
  186. data/examples/pmp/public/javascripts/dragdrop.js +942 -0
  187. data/examples/pmp/public/javascripts/effects.js +1088 -0
  188. data/examples/pmp/public/javascripts/prototype.js +2515 -0
  189. data/examples/pmp/public/robots.txt +1 -0
  190. data/examples/pmp/test/fixtures/contacts.yml +5 -0
  191. data/examples/pmp/test/fixtures/document_libraries.yml +5 -0
  192. data/examples/pmp/test/fixtures/issues.yml +5 -0
  193. data/examples/pmp/test/fixtures/projects.yml +5 -0
  194. data/examples/pmp/test/fixtures/resources.yml +5 -0
  195. data/examples/pmp/test/fixtures/tasks.yml +5 -0
  196. data/examples/pmp/test/fixtures/time_cards.yml +5 -0
  197. data/examples/pmp/test/functional/contacts_controller_test.rb +18 -0
  198. data/examples/pmp/test/functional/document_library_controller_test.rb +18 -0
  199. data/examples/pmp/test/functional/issues_controller_test.rb +18 -0
  200. data/examples/pmp/test/functional/projects_controller_test.rb +18 -0
  201. data/examples/pmp/test/functional/resources_controller_test.rb +18 -0
  202. data/examples/pmp/test/functional/tasks_controller_test.rb +18 -0
  203. data/examples/pmp/test/functional/time_cards_controller_test.rb +18 -0
  204. data/examples/pmp/test/test_helper.rb +28 -0
  205. data/examples/pmp/test/unit/contacts_test.rb +10 -0
  206. data/examples/pmp/test/unit/document_library_test.rb +10 -0
  207. data/examples/pmp/test/unit/issues_test.rb +10 -0
  208. data/examples/pmp/test/unit/projects_test.rb +10 -0
  209. data/examples/pmp/test/unit/resources_test.rb +10 -0
  210. data/examples/pmp/test/unit/tasks_test.rb +10 -0
  211. data/examples/pmp/test/unit/time_cards_test.rb +10 -0
  212. data/lib/QuickBaseClient.rb +5054 -0
  213. data/lib/QuickBaseCommandLineClient.rb +401 -0
  214. data/lib/QuickBaseContactsAppBuilder.rb +419 -0
  215. data/lib/QuickBaseEmailer.rb +334 -0
  216. data/lib/QuickBaseEventNotifier.rb +592 -0
  217. data/lib/QuickBaseMisc.rb +96 -0
  218. data/lib/QuickBaseObjects.rb +566 -0
  219. data/lib/QuickBaseRSSGenerator.rb +286 -0
  220. data/lib/QuickBaseTextData.rb +545 -0
  221. data/lib/QuickBaseTwitterConnector.rb +300 -0
  222. data/lib/QuickBaseWebClient.rb +126 -0
  223. data/lib/WorkPlaceClient.rb +45 -0
  224. data/lib/qbc.makeCSVFile.qbc +4 -0
  225. data/lib/qbc.makeCSVFile.rb +17 -0
  226. data/lib/quickbase_adapter.rb +320 -0
  227. data/lib/runFieldEntryDialog.rb +151 -0
  228. data/lib/runOfflineFieldEntryDialog.rb +203 -0
  229. data/rakefile +100 -0
  230. data/test/run_tests.bat +7 -0
  231. data/test/spec_all_tests.rb +13 -0
  232. data/test/spec_smoke_tests.rb +58 -0
  233. data/test/spec_workplace_addrecord_test.rb +46 -0
  234. data/test/spec_workplace_base_test.rb +57 -0
  235. data/test/spec_workplace_editrecord_test.rb +38 -0
  236. data/test/spec_workplace_json_test.rb +38 -0
  237. data/test/spec_workplace_objects_test.rb +39 -0
  238. data/test/spec_workplace_smoke_tests.rb +45 -0
  239. 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]