google_data_source 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.document +5 -0
  2. data/.gitignore +7 -0
  3. data/Gemfile +11 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +25 -0
  6. data/Rakefile +31 -0
  7. data/google_data_source.gemspec +32 -0
  8. data/lib/assets/images/google_data_source/chart_bar_add.png +0 -0
  9. data/lib/assets/images/google_data_source/chart_bar_delete.png +0 -0
  10. data/lib/assets/images/google_data_source/loader.gif +0 -0
  11. data/lib/assets/javascripts/google_data_source/data_source_init.js +3 -0
  12. data/lib/assets/javascripts/google_data_source/extended_data_table.js +76 -0
  13. data/lib/assets/javascripts/google_data_source/filter_form.js +180 -0
  14. data/lib/assets/javascripts/google_data_source/google_visualization/combo_table.js.erb +113 -0
  15. data/lib/assets/javascripts/google_data_source/google_visualization/table.js +116 -0
  16. data/lib/assets/javascripts/google_data_source/google_visualization/timeline.js +13 -0
  17. data/lib/assets/javascripts/google_data_source/google_visualization/visualization.js.erb +141 -0
  18. data/lib/assets/javascripts/google_data_source/index.js +7 -0
  19. data/lib/dummy_engine.rb +5 -0
  20. data/lib/google_data_source.rb +33 -0
  21. data/lib/google_data_source/base.rb +281 -0
  22. data/lib/google_data_source/column.rb +31 -0
  23. data/lib/google_data_source/csv_data.rb +23 -0
  24. data/lib/google_data_source/data_date.rb +17 -0
  25. data/lib/google_data_source/data_date_time.rb +17 -0
  26. data/lib/google_data_source/helper.rb +69 -0
  27. data/lib/google_data_source/html_data.rb +6 -0
  28. data/lib/google_data_source/invalid_data.rb +14 -0
  29. data/lib/google_data_source/json_data.rb +78 -0
  30. data/lib/google_data_source/railtie.rb +36 -0
  31. data/lib/google_data_source/sql/models.rb +266 -0
  32. data/lib/google_data_source/sql/parser.rb +239 -0
  33. data/lib/google_data_source/sql_parser.rb +82 -0
  34. data/lib/google_data_source/template_handler.rb +31 -0
  35. data/lib/google_data_source/test_helper.rb +26 -0
  36. data/lib/google_data_source/version.rb +3 -0
  37. data/lib/google_data_source/xml_data.rb +25 -0
  38. data/lib/locale/de.yml +5 -0
  39. data/lib/reporting/action_controller_extension.rb +19 -0
  40. data/lib/reporting/grouped_set.rb +58 -0
  41. data/lib/reporting/helper.rb +110 -0
  42. data/lib/reporting/reporting.rb +352 -0
  43. data/lib/reporting/reporting_adapter.rb +27 -0
  44. data/lib/reporting/reporting_entry.rb +147 -0
  45. data/lib/reporting/sql_reporting.rb +220 -0
  46. data/test/lib/empty_reporting.rb +2 -0
  47. data/test/lib/test_reporting.rb +33 -0
  48. data/test/lib/test_reporting_b.rb +9 -0
  49. data/test/lib/test_reporting_c.rb +3 -0
  50. data/test/locales/en.models.yml +6 -0
  51. data/test/locales/en.reportings.yml +5 -0
  52. data/test/rails/reporting_renderer_test.rb +47 -0
  53. data/test/test_helper.rb +50 -0
  54. data/test/units/base_test.rb +340 -0
  55. data/test/units/csv_data_test.rb +36 -0
  56. data/test/units/grouped_set_test.rb +60 -0
  57. data/test/units/json_data_test.rb +68 -0
  58. data/test/units/reporting_adapter_test.rb +20 -0
  59. data/test/units/reporting_entry_test.rb +149 -0
  60. data/test/units/reporting_test.rb +374 -0
  61. data/test/units/sql_parser_test.rb +111 -0
  62. data/test/units/sql_reporting_test.rb +307 -0
  63. data/test/units/xml_data_test.rb +32 -0
  64. metadata +286 -0
@@ -0,0 +1,116 @@
1
+
2
+ /**
3
+ * Google DataTable
4
+ */
5
+ DataSource.Table = function(query, container, options) {
6
+ DataSource.Visualization.call(this, query, container, options);
7
+ };
8
+
9
+
10
+ DataSource.Table.prototype = new DataSource.Visualization();
11
+
12
+ DataSource.Table.prototype.initialize = function(){
13
+ this.initialized = true;
14
+ this.visualization = new google.visualization.Table(this.container);
15
+
16
+ this.options['allowHtml'] = true;
17
+
18
+ var self = this;
19
+
20
+ this.csvSize = (this.options['csvSize'] > 0) ? this.options['csvSize'] : 1000;
21
+ if (this.options['serverPaging']) {
22
+ this.options['pageSize'] = (this.options['pageSize'] > 0) ? this.options['pageSize'] : 10;
23
+ this.pageSize = this.options['pageSize'];
24
+
25
+ var addListener = google.visualization.events.addListener;
26
+ addListener(this.visualization, 'page', function(e) {self.handlePage(e)});
27
+ addListener(this.visualization, 'sort', function(e) {self.handleSort(e)});
28
+
29
+ this.options['sort'] = 'event';
30
+ this.options['page'] = 'event';
31
+
32
+ var buttonConfig = 'pagingButtonsConfiguration';
33
+ this.options[buttonConfig] = this.options[buttonConfig] || 'both';
34
+
35
+ this.currentPageIndex = 0;
36
+ this.setPageQueryClause(0);
37
+ // TODO default sorting???
38
+ }
39
+ else {
40
+ this.options['sortColumn'] = 0;
41
+ }
42
+ }
43
+
44
+ DataSource.Table.prototype.beforeSendAndDraw = function() {
45
+ if (typeof this.initialized == 'undefined') {
46
+ this.initialize();
47
+ }
48
+
49
+ this.visualization.setSelection([]);
50
+ };
51
+
52
+ DataSource.Table.prototype.beforeSubmit = function() {
53
+ if (typeof this.initialized == 'undefined') {
54
+ this.initialize();
55
+ }
56
+
57
+ if (this.options['serverPaging']) this.setPageQueryClause(0);
58
+ };
59
+
60
+ /** Handles a sort event with the given properties. Will page to page=0. */
61
+ DataSource.Table.prototype.handleSort = function(properties) {
62
+ var columnIndex = properties['column'];
63
+ var isAscending = properties['ascending'];
64
+ this.options['sortColumn'] = columnIndex;
65
+ this.options['sortAscending'] = isAscending;
66
+
67
+ // dataTable exists since the user clicked the table.
68
+ var colID = this.currentDataTable.getColumnId(columnIndex);
69
+ this.queryClauses.orderby = 'order by `' + colID + (!isAscending ? '` desc' : '`');
70
+
71
+ // Calls sendAndDraw internally.
72
+ this.handlePage({'page': 0});
73
+ };
74
+
75
+
76
+ /** Handles a page event with the given properties. */
77
+ DataSource.Table.prototype.handlePage = function(properties) {
78
+ var localTableNewPage = properties['page']; // 1, -1 or 0
79
+ var newPage = 0;
80
+ if (localTableNewPage != 0) {
81
+ newPage = this.currentPageIndex + localTableNewPage;
82
+ }
83
+ if (this.setPageQueryClause(newPage)) {
84
+ this.sendAndDraw();
85
+ }
86
+ };
87
+
88
+ /**
89
+ * Sets the pageQueryClause and table options for a new page request.
90
+ * In case the next page is requested - checks that another page exists
91
+ * based on the previous request.
92
+ * Returns true if a new page query clause was set, false otherwise.
93
+ */
94
+ DataSource.Table.prototype.setPageQueryClause = function(pageIndex) {
95
+ var pageSize = this.pageSize;
96
+
97
+ if (pageIndex < 0) {
98
+ return false;
99
+ }
100
+ var dataTable = this.currentDataTable;
101
+ if ((pageIndex == this.currentPageIndex + 1) && dataTable) {
102
+ if (dataTable.getNumberOfRows() <= pageSize) {
103
+ return false;
104
+ }
105
+ }
106
+ this.currentPageIndex = pageIndex;
107
+ var newStartRow = this.currentPageIndex * pageSize;
108
+
109
+ // Get the pageSize + 1 so that we can know when the last page is reached.
110
+ this.queryClauses.limit = 'limit ' + (pageSize + 1);
111
+ this.queryClauses.offset = 'offset ' + newStartRow;
112
+
113
+ // Note: row numbers are 1-based yet dataTable rows are 0-based.
114
+ this.options['firstRowNumber'] = newStartRow + 1;
115
+ return true;
116
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * TimeLine Chart
3
+ */
4
+ //google.load("visualization", "1", {packages:["corechart"]});
5
+
6
+
7
+ DataSource.TimeLine = function(query, container, options) {
8
+ DataSource.Visualization.call(this, query, container, options);
9
+ //this.visualization = new google.visualization.LineChart(this.container);
10
+ this.visualization = new google.visualization.AnnotatedTimeLine(this.container);
11
+ }
12
+
13
+ DataSource.TimeLine.prototype = new DataSource.Visualization();
@@ -0,0 +1,141 @@
1
+
2
+ /**
3
+ * Google DataSource Visualization
4
+ * SuperClass for all Google Visualizations
5
+ * */
6
+ DataSource.Visualization = function(query, container, options) {
7
+ // TODO throw exception rename query to url and restrict to strings
8
+ this.query = (typeof query == 'string') ? new google.visualization.Query(query) : query;
9
+ this.baseurl = query;
10
+ this.container = (typeof container == 'string') ? document.getElementById(container) : container;
11
+ this.currentDataTable = null;
12
+ this.options = options;
13
+ this.queryClauses = {};
14
+ }
15
+ DataSource.Visualization.prototype = {
16
+ draw: function() {
17
+ this.visualization.draw(this.currentDataTable, this.options);
18
+ },
19
+
20
+ // TODO make configurable
21
+ showBusyStatus: function() {
22
+ $(this.container).prepend('<img src="<%= asset_path('google_data_source/loader.gif') %>" class="loader" />');
23
+ },
24
+
25
+ hideBusyStatus: function() {
26
+ $(this.container).children('.loader').remove();
27
+ },
28
+
29
+ addParamsToUrl: function(url, params) {
30
+ var separator = '?';
31
+ if (url.match(/\?/)) separator = '&';
32
+ return url + separator + params;
33
+ },
34
+
35
+ download: function(format) {
36
+ format = format || 'csv';
37
+ var self = this;
38
+ this.queryClauses.limit = 'limit ' + this.csvSize;
39
+ // prepare query
40
+ var queryClause = '';
41
+ $.each(['select', 'where', 'groupby', 'orderby', 'limit'], function(i, key) {
42
+ if (self.queryClauses[key]) queryClause += self.queryClauses[key] + ' ';
43
+ });
44
+ var url = this.addParamsToUrl(this.baseurl, $.param({tqx: "out:csv", tq: queryClause}));
45
+ location.href = url;
46
+ },
47
+
48
+ sendAndDraw: function(params) {
49
+ var self = this;
50
+ this.query.abort();
51
+
52
+ this.query = params ? new google.visualization.Query(this.addParamsToUrl(this.baseurl, params)) : this.query;
53
+ this.showBusyStatus();
54
+
55
+ // prepare query
56
+ var queryClause = '';
57
+ $.each(['select', 'where', 'groupby', 'orderby', 'limit', 'offset'], function(i, key) {
58
+ if (self.queryClauses[key]) queryClause += self.queryClauses[key] + ' ';
59
+ });
60
+ this.lastQueryClause = queryClause;
61
+ this.query.setQuery(queryClause);
62
+
63
+ this.beforeSendAndDraw();
64
+ this.query.send(function(response) {self.handleResponse(response)});
65
+ },
66
+
67
+ handleResponse: function(response) {
68
+ this.hideBusyStatus();
69
+ this.currentDataTable = null;
70
+ if (response.isError()) {
71
+ google.visualization.errors.addError(this.container, response.getMessage(),
72
+ response.getDetailedMessage(), {'showInTooltip': false});
73
+ } else {
74
+ this.setDataTableFromResponse(response);
75
+
76
+ this.draw();
77
+ }
78
+ },
79
+
80
+ beforeSendAndDraw: function() {
81
+ // called by sendAndDraw.
82
+ // Can be overridden to preare a query etc.
83
+ },
84
+
85
+ beforeSubmit: function() {
86
+ // called before form submission
87
+ },
88
+
89
+ // Sets the instances datatable from a query response
90
+ setDataTableFromResponse: function(response) {
91
+ // Set rows in a data view.
92
+ this.currentDataTable = new ExtendedDataTable(response.getDataTable(), this.options);
93
+ }
94
+ };
95
+
96
+ DataSource.Visualization.create = function(type, query, container, options) {
97
+ if(!options['asyncLoading'])
98
+ google.setOnLoadCallback(DataSource.Visualization.onloadsetup(type, query, container, options));
99
+ else
100
+ DataSource.Visualization.onloadsetup(type, query, container, options);
101
+ };
102
+
103
+ DataSource.Visualization.onloadsetup = function(type, query, container, options) {
104
+ var klass = eval("DataSource." + type);
105
+ var visualization = new klass(query, container, options);
106
+ // store visualization in container data
107
+ $('#' + container).data('visualization', visualization);
108
+
109
+ // TODO: Fix this dirty issue
110
+ window.setTimeout(function(){
111
+ $('#' + container + '_controls a.export_as_csv').click(function() {
112
+ visualization.download();
113
+ return false;
114
+ });
115
+ }, 500);
116
+
117
+ var onSubmit = function() {
118
+ var form = new DataSource.FilterForm(this);
119
+
120
+ visualization.queryClauses['select'] = form.toSelectClause();
121
+ visualization.queryClauses['where'] = form.toWhereClause();
122
+ visualization.queryClauses['groupby'] = form.toGroupByClause();
123
+
124
+ visualization.beforeSubmit();
125
+
126
+ visualization.sendAndDraw(form.getNoQueryValues());
127
+ return false;
128
+ };
129
+
130
+ // Submit form to display results
131
+ if (options['form']) {
132
+ $('#' + options['form']).submit(onSubmit);
133
+
134
+ if (options['autosubmit']) {
135
+ onSubmit.call($('#' + options['form']));
136
+ }
137
+ } else {
138
+ // If no form is defined, just get the redular results
139
+ visualization.sendAndDraw();
140
+ }
141
+ }
@@ -0,0 +1,7 @@
1
+ //= require './data_source_init'
2
+ //= require './extended_data_table'
3
+ //= require './filter_form'
4
+ //= require './google_visualization/visualization'
5
+ //= require './google_visualization/table'
6
+ //= require './google_visualization/combo_table'
7
+ //= require './google_visualization/timeline'
@@ -0,0 +1,5 @@
1
+ module GoogleDataSource
2
+ class DummyEngine < ::Rails::Engine
3
+ # just a marker to make Rails include our assets in searchpath
4
+ end
5
+ end
@@ -0,0 +1,33 @@
1
+ require 'ostruct'
2
+ require 'rparsec'
3
+ require 'active_support/all'
4
+ require 'action_controller'
5
+ require 'active_record'
6
+
7
+ require 'google_data_source/base'
8
+ require 'google_data_source/version'
9
+ require 'google_data_source/json_data'
10
+ require 'google_data_source/csv_data'
11
+ require 'google_data_source/xml_data'
12
+ require 'google_data_source/html_data'
13
+ require 'google_data_source/invalid_data'
14
+ require 'google_data_source/column'
15
+ require 'google_data_source/data_date'
16
+ require 'google_data_source/data_date_time'
17
+ require 'google_data_source/template_handler'
18
+
19
+ require 'google_data_source/sql/models'
20
+ require 'google_data_source/sql/parser'
21
+ require 'google_data_source/sql_parser'
22
+
23
+ # the reporting models
24
+ require 'reporting/reporting'
25
+ require 'reporting/reporting_adapter'
26
+ require 'reporting/sql_reporting'
27
+ require 'reporting/reporting_entry'
28
+ require 'reporting/grouped_set'
29
+
30
+ if defined?(::Rails::Railtie)
31
+ require File.join(File.dirname(__FILE__), *%w[google_data_source railtie])
32
+ require 'dummy_engine'
33
+ end
@@ -0,0 +1,281 @@
1
+ module GoogleDataSource
2
+ module DataSource
3
+ # Superclass for all data source implementations
4
+ # Offers methods for getting and setting the data and column definitions of
5
+ # the data source
6
+ class Base
7
+ # Callback defines a JavaScript snippet that is appended to the regular
8
+ # data-source reponse. This is currently used to refresh the form in
9
+ # reportings (validation)
10
+ attr_accessor :callback, :reporting, :column_labels, :formatters, :virtual_columns, :export_filename, :xml_class
11
+
12
+ # Define accessors for the data source data, columns and errors
13
+ attr_reader :errors
14
+
15
+ # Creates a new instance and validates it.
16
+ # Protected method so it can be used from the subclasses
17
+ def initialize(gdata_params)
18
+ @virtual_columns = HashWithIndifferentAccess.new
19
+ @formatters = HashWithIndifferentAccess.new
20
+ @column_labels = HashWithIndifferentAccess.new
21
+ @required_columns = {}
22
+
23
+ @raw_data = []
24
+ @columns = []
25
+
26
+ @params = gdata_params
27
+ @errors = {}
28
+ @version = "0.6"
29
+
30
+ validate
31
+ end
32
+ protected :initialize
33
+
34
+
35
+ # Returns true if formatter formatter for a certain column is defined
36
+ def has_formatter?(column_name)
37
+ @formatters.has_key?(column_name.to_sym)
38
+ end
39
+
40
+ # Convenience method for formatter definition
41
+ def formatter(column, options = {}, &block)
42
+ set_required_columns(column.to_sym, options[:requires]) if options.has_key?(:requires)
43
+ formatters[column.to_sym] = block
44
+ end
45
+
46
+ # Return true if virtual column with name exists
47
+ def is_virtual_column?(name)
48
+ virtual_columns.has_key?(name)
49
+ end
50
+
51
+ # Sets up a virtual column with name and block with is called with a row
52
+ # and returns a string or a hash like {v: "real value", f: "formatted value"}
53
+ def virtual_column(name, options = {}, &block)
54
+ @raw_data.add_virtual_column(name.to_sym, options[:type] || :string) if @raw_data.respond_to?(:add_virtual_column)
55
+ set_required_columns(name.to_sym, options[:requires]) if options.has_key?(:requires)
56
+ virtual_columns[name.to_sym] = {
57
+ :type => options[:type] || :string,
58
+ :proc => block
59
+ }
60
+ end
61
+
62
+ # Getter with empty array default
63
+ def required_columns
64
+ required = column_ids.inject([]) { |columns, column| columns << @required_columns[column.to_sym] }.flatten.compact
65
+ (required + column_ids).map(&:to_s).uniq
66
+ end
67
+
68
+ # Add a list of columns to the list of required columns (columns that have to be fetched)
69
+ def set_required_columns(column, requires = [])
70
+ @required_columns[column.to_sym] = requires
71
+ end
72
+
73
+ # Returns the filename for export without extension.
74
+ # Defaults to "export"
75
+ def export_filename
76
+ @export_filename || 'export'
77
+ end
78
+
79
+ def xml_class
80
+ @xml_class || 'item'
81
+ end
82
+
83
+ # Creates a new data source object from the get parameters of the data-source
84
+ # request.
85
+ def self.from_params(params)
86
+ # Exract GDataSource params from the request.
87
+ gdata_params = {}
88
+ tqx = params[:tqx]
89
+ unless tqx.blank?
90
+ gdata_params[:tqx] = true
91
+ tqx.split(';').each do |kv|
92
+ key, value = kv.split(':')
93
+ gdata_params[key.to_sym] = value
94
+ end
95
+ end
96
+
97
+ # Create the appropriate GDataSource instance from the gdata-specific parameters
98
+ gdata_params[:out] ||= "json"
99
+ gdata = from_gdata_params(gdata_params)
100
+ end
101
+
102
+ # Factory method to create a GDataSource instance from a serie of valid GData
103
+ # parameters, as described in the official documentation (see above links).
104
+ #
105
+ # +gdata_params+ can be any map-like object that maps keys (like +:out+, +:reqId+
106
+ # and so forth) to their values. Keys must be symbols.
107
+ def self.from_gdata_params(gdata_params)
108
+ case gdata_params[:out]
109
+ when "json"
110
+ JsonData.new(gdata_params)
111
+ when "html"
112
+ HtmlData.new(gdata_params)
113
+ when "csv"
114
+ CsvData.new(gdata_params)
115
+ when "xml"
116
+ XmlData.new(gdata_params)
117
+ else
118
+ InvalidData.new(gdata_params)
119
+ end
120
+ end
121
+
122
+ # Access a GData parameter. +k+ must be symbols, like +:out+, +:reqId+.
123
+ def [](k)
124
+ @params[k]
125
+ end
126
+
127
+ # Sets a GData parameter. +k+ must be symbols, like +:out+, +:reqId+.
128
+ # The instance is re-validated afterward.
129
+ def []=(k, v)
130
+ @params[k] = v
131
+ validate
132
+ end
133
+
134
+ # Checks whether this instance is valid (in terms of configuration parameters)
135
+ # or not.
136
+ def valid?
137
+ @errors.size == 0
138
+ end
139
+
140
+ # Manually adds a new validation error. +key+ should be a symbol pointing
141
+ # to the invalid parameter or element.
142
+ def add_error(key, message)
143
+ @errors[key] = message
144
+ return self
145
+ end
146
+
147
+ # Sets the unformatted data of the datasource
148
+ # +data+ is an array either of Hash objects or arbitrary objects
149
+ # * Hashes must have keys as column ids
150
+ # * Arbitrary object must repond to column id named methods
151
+ #
152
+ def data=(data)
153
+ # reset formatted data
154
+ @data = nil
155
+
156
+ # set unformatted data
157
+ @raw_data = data
158
+ validate
159
+
160
+ # register virtual columns
161
+ if data.respond_to?(:add_virtual_column)
162
+ @virtual_columns.each { |k, v| data.add_virtual_column(k.to_sym, v[:type]) }
163
+ end
164
+ end
165
+
166
+ # Returns the formatted data in the datasource format
167
+ #
168
+ def data
169
+ @data unless @data.nil?
170
+
171
+ # get data from object (eg. Reporting)
172
+ data = @raw_data.respond_to?(:data) ?
173
+ @raw_data.data(:required_columns => required_columns) :
174
+ @raw_data
175
+
176
+ # Run formatters and virtual columns
177
+ @data = data.collect do |row|
178
+ row = OpenStruct.new(row) if row.is_a?(Hash)
179
+
180
+ column_ids.inject([]) do |columns, column|
181
+ if is_virtual_column?(column)
182
+ columns << virtual_columns[column][:proc].call(row)
183
+ elsif has_formatter?(column)
184
+ columns << {
185
+ :f => formatters[column.to_sym].call(row),
186
+ :v => row.send(column)
187
+ }
188
+ else
189
+ columns << row.send(column)
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ # Returns the ids of the columns
196
+ #
197
+ def column_ids
198
+ columns.collect(&:id)
199
+ end
200
+
201
+ # Sets the columns which should be sent by the datasource
202
+ # +columns+ is an array of either Hashes with keys (:id, :type, :label)
203
+ # or +Column+ objects
204
+ #
205
+ def columns=(columns)
206
+ @columns = columns.map { |c| c.is_a?(Column) ? c : Column.new(c) }
207
+ @columns.each do |col|
208
+ raise ArgumentError, "Invalid column type: #{col.type}" unless col.valid?
209
+ end
210
+ end
211
+
212
+ # Returns the columns in a datasource compatible format
213
+ # Applies all labels set
214
+ def columns
215
+ @columns.each { |c| c.label = column_labels.delete(c.id) if column_labels.has_key?(c.id) }
216
+ @columns
217
+ end
218
+
219
+ # Make cols alias for json ... classes
220
+ alias_method :cols, :columns
221
+ deprecate :cols
222
+
223
+ # Sets the raw data and the columns simultaniously and tries to guess the
224
+ # columns if not set.
225
+ # +data+ may be an array of rows or an object of a class that support a data(options)
226
+ # and a columns method
227
+ #
228
+ def set(data, columns = nil)
229
+ self.data = data
230
+ self.columns = columns || guess_columns(@raw_data)
231
+ end
232
+
233
+ # Tries to get a clever column selection from the items collection.
234
+ # Currently only accounts for ActiveRecord objects
235
+ # +items+ is an arbitrary collection of items as passed to the +set+ method
236
+ def guess_columns(data)
237
+ return data.columns if data.respond_to? :columns
238
+
239
+ columns = []
240
+ klass = data.first.class
241
+ klass.columns.each do |column|
242
+ columns << Column.new({
243
+ :id => column.name,
244
+ :label => column.name.humanize,
245
+ :type => 'string' # TODO get the right type
246
+ })
247
+ end
248
+ columns
249
+ end
250
+
251
+ # Validates this instance by checking that the configuration parameters
252
+ # conform to the official specs.
253
+ def validate
254
+ @errors.clear
255
+
256
+ # check validity
257
+ if @raw_data.respond_to?(:valid?) && ! @raw_data.valid?
258
+ add_error(:reqId, "Form validation failed")
259
+ end
260
+
261
+ if @params[:tqx]
262
+ add_error(:reqId, "Missing required parameter reqId") unless @params[:reqId]
263
+
264
+ if @params[:version] && @params[:version] != @version
265
+ add_error(:version, "Unsupported version #{@params[:version]}")
266
+ end
267
+ end
268
+ end
269
+
270
+ # Empty method. This is a placeholder implemented by subclasses that
271
+ # produce the response according to a given format.
272
+ def response
273
+ end
274
+
275
+ # Empty method. This is a placeholder implemented by subclasses that return the correct format
276
+ def format
277
+ self[:out]
278
+ end
279
+ end
280
+ end
281
+ end