google_data_source 0.7.6

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 (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