Class: Reporting

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
lib/reporting/reporting.rb

Overview

Super class for reportings to be rendered with Google visualizations Encapsulates the aggregation of the reporting data based on a configuration.

The configuration is implemented by inherting from ActiveRecord and switching off the database stuff. Thus the regular ActiveRecord validators and parameter assignments including type casting cab be used

The ActiveRecord extension is copied from the ActiveForm plugin (github.com/remvee/active_form)

Direct Known Subclasses

SqlReporting

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Reporting

Returns a new instance of Reporting.



21
22
23
24
25
# File 'lib/reporting/reporting.rb', line 21

def initialize(*args)
  @required_columns = []
  @virtual_columns = {}
  super(*args)
end

Instance Attribute Details

#group_byObject

Returns the grouping columns as array



107
108
109
# File 'lib/reporting/reporting.rb', line 107

def group_by
  @group_by
end

#idObject

Returns an ID which is used by frontend components to generate unique dom ids Defaults to the underscoreized classname



35
36
37
# File 'lib/reporting/reporting.rb', line 35

def id
  @id || self.class.name.underscore.split('/').last #gsub('/', '_')
end

#limitObject

Returns the value of attribute limit.



12
13
14
# File 'lib/reporting/reporting.rb', line 12

def limit
  @limit
end

#offsetObject

Returns the value of attribute offset.



12
13
14
# File 'lib/reporting/reporting.rb', line 12

def offset
  @offset
end

#order_byObject

Returns the value of attribute order_by.



12
13
14
# File 'lib/reporting/reporting.rb', line 12

def order_by
  @order_by
end

#queryObject

Returns the value of attribute query.



12
13
14
# File 'lib/reporting/reporting.rb', line 12

def query
  @query
end

#selectObject

Returns the select columns as array



102
103
104
# File 'lib/reporting/reporting.rb', line 102

def select
  @select
end

#virtual_columnsObject (readonly)

, :required_columns



13
14
15
# File 'lib/reporting/reporting.rb', line 13

def virtual_columns
  @virtual_columns
end

Class Method Details

.abstract_classObject

:nodoc:



300
301
302
# File 'lib/reporting/reporting.rb', line 300

def abstract_class # :nodoc:
  true
end

.column(name, options = {}) ⇒ Object

Defines a displayable column of the datasource Type defaults to string



176
177
178
179
# File 'lib/reporting/reporting.rb', line 176

def column(name, options = {})
  self.datasource_columns ||= Hash.new.freeze
  self.datasource_columns = column_or_filter(name, options, self.datasource_columns)
end

.column_defaults(*args) ⇒ Object

Returns a hash with column name as key and default value as value



284
285
286
287
288
289
# File 'lib/reporting/reporting.rb', line 284

def column_defaults(*args)
  columns_hash.keys.inject({}) do |mem, var|
    mem[var] = columns_hash[var].default
    mem
  end
end

.columnsObject

TODO: merge with columns hash



292
293
294
# File 'lib/reporting/reporting.rb', line 292

def columns
  columns_hash.values
end

.columns_hashObject

ActiveRecord overrides

Needed to build column accessors



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/reporting/reporting.rb', line 257

def columns_hash
  @columns_hash ||= begin
    ret = { }
    data = { }
    data.update(self.datasource_columns) if self.datasource_columns
    data.update(self.datasource_filters) if self.datasource_filters
    
    data.each do |k, v|
      ret[k.to_s] = ActiveRecord::ConnectionAdapters::Column.new(k.to_s, 
        v[:default], 
        v[:type].to_s,
        v.key?(:null) ? v[:null] : true)
      
      # define a humanize method which returns the human name
      if v.key?(:human_name)
        ret[k.to_s].define_singleton_method(:humanize) do
          v[:human_name]
        end
      end
    end
    
    ret
  end
end

.connectionObject

Returns an instance of our own connection adapter



29
30
31
# File 'lib/reporting/reporting.rb', line 29

def self.connection
  @adapter ||= ActiveRecord::ConnectionAdapters::ReportingAdapter.new
end

.defaultsObject

Returns the defaults class variable



192
193
194
# File 'lib/reporting/reporting.rb', line 192

def defaults
  self.datasource_defaults ||= Hash.new.freeze
end

.deserialize(value) ⇒ Object

Returns a reporting from a serialized representation



207
208
209
# File 'lib/reporting/reporting.rb', line 207

def deserialize(value)
  self.new(JSON.parse(value))
end

.filter(name, options = {}) ⇒ Object

Marks a column as filterable / adds it in the background



184
185
186
187
188
189
# File 'lib/reporting/reporting.rb', line 184

def filter(name, options = {})
  self.datasource_filters ||= Hash.new.freeze
  
  # update the column options
  self.datasource_filters = column_or_filter(name, options, self.datasource_filters)
end

.from_params(params, key = self.name.underscore.gsub('/', '_')) ⇒ Object

Uses the simple_parse method of the SqlParser to setup a reporting from a query. The where clause is intepreted as reporting configuration (activerecord attributes)



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/reporting/reporting.rb', line 213

def from_params(params, key = self.name.underscore.gsub('/', '_'))
  return self.deserialize(params[key]) if params.has_key?(key) && params[key].is_a?(String)

  reporting = self.new(params[key])
  reporting = from_query_params(params["query"]) if params.has_key?("query")
  return reporting unless params.has_key?(:tq)

  query = GoogleDataSource::DataSource::SqlParser.simple_parse(params[:tq])
  attributes = Hash.new
  query.conditions.each do |k, v|
    if v.is_a?(Array)
      v.each do |condition|
        case condition.op
        when '<='
          attributes["to_#{k}"] = condition.value
        when '>='
          attributes["from_#{k}"] = condition.value
        when 'in'
          attributes["in_#{k}"] = condition.value
        else
          # raise exception for unsupported operator?
        end
      end
    else
      attributes[k] = v
    end
  end
  attributes[:group_by] = query.groupby 
  attributes[:select]   = query.select
  attributes[:order_by] = query.orderby
  attributes[:limit]    = query.limit
  attributes[:offset]   = query.offset
  attributes.merge!(params[key]) if params.has_key?(key)
  #reporting.update_attributes(attributes)
  reporting.attributes = attributes
  reporting.query = params[:tq]
  reporting
end

.group_by_default(group_by) ⇒ Object

Sets the default value for group_by



202
203
204
# File 'lib/reporting/reporting.rb', line 202

def group_by_default(group_by)
  self.datasource_defaults = defaults.merge({:group_by => group_by})
end

.primary_keyObject



296
297
298
# File 'lib/reporting/reporting.rb', line 296

def primary_key
  'id'
end

.select_default(select) ⇒ Object

Sets the default value for select



197
198
199
# File 'lib/reporting/reporting.rb', line 197

def select_default(select)
  self.datasource_defaults = defaults.merge({:select => select})
end

Instance Method Details

#add_required_columns(*columns) ⇒ Object

Adds required columns



57
58
59
# File 'lib/reporting/reporting.rb', line 57

def add_required_columns(*columns)
  @required_columns = (@required_columns + columns.flatten.collect(&:to_s)).uniq
end

#add_virtual_column(name, type = :string) ⇒ Object

add a virtual column



112
113
114
115
116
# File 'lib/reporting/reporting.rb', line 112

def add_virtual_column(name, type = :string)
  virtual_columns[name.to_s] = {
    :type => type
  }
end

#aggregateObject

‘Abstract’ method that has to be overridden by subclasses Returns an array of rows written to #rows and accessible in DataSource compatible format in #rows_as_datasource



64
65
66
# File 'lib/reporting/reporting.rb', line 64

def aggregate
  []
end

#all_columnsObject

Returns a list of all columns (real and virtual)



119
120
121
# File 'lib/reporting/reporting.rb', line 119

def all_columns
  datasource_columns.merge(virtual_columns)
end

#column_label(column, default = nil) ⇒ Object

Retrieves the I18n translation of the column label



91
92
93
94
95
96
97
98
99
# File 'lib/reporting/reporting.rb', line 91

def column_label(column, default = nil)
  return '' if column.blank?
  defaults = ['reportings.{{model}}.{{column}}', 'models.attributes.{{model}}.{{column}}'].collect do |scope|
    scope.gsub!('{{model}}', self.class.name.underscore.gsub('/', '.'))
    scope.gsub('{{column}}', column.to_s)
  end.collect(&:to_sym)
  defaults << column.to_s.humanize
  I18n.t(defaults.shift, :default => defaults)
end

#columnsObject

Lazy getter for the columns object



77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/reporting/reporting.rb', line 77

def columns
  select.inject([]) do |columns, column|
    columns << {
      :type => all_columns[column][:type]
    }.merge({
      :id    => column.to_s,
      :label => column_label(column)
    }) if all_columns.key?(column)
    
    columns
  end
end

#data(options = {}) ⇒ Object

Returns the data rows Calls the aggregate method first if rows do not exist



71
72
73
74
# File 'lib/reporting/reporting.rb', line 71

def data(options = {})
  add_required_columns(options[:required_columns])
  @rows ||= aggregate
end

#datasource_columnsObject

Attribute reader for datasource_columns



138
139
140
# File 'lib/reporting/reporting.rb', line 138

def datasource_columns
  self.class.datasource_columns || { }.freeze
end

#defaultsObject

Returns the defaults Hash Convenience wrapper for instance access



133
134
135
# File 'lib/reporting/reporting.rb', line 133

def defaults
  self.class.defaults #.merge(@defaults || {})
end

#filterable_by?(column_name) ⇒ Boolean

Returns true if the given column is available for filtering

Returns:

  • (Boolean)


169
170
171
# File 'lib/reporting/reporting.rb', line 169

def filterable_by?(column_name)
  self.class.datasource_filters.key?(column_name.to_s)
end

#groupable_columnsObject

Returns an array of columns which are allowed for grouping



125
126
127
128
129
# File 'lib/reporting/reporting.rb', line 125

def groupable_columns
  datasource_columns.collect do |key, options|
    key.to_sym if options[:grouping]
  end.compact
end

#required_columnsObject

Returns the columns that have to be selected



49
50
51
52
53
54
# File 'lib/reporting/reporting.rb', line 49

def required_columns
  (select + group_by + @required_columns).inject([]) do |columns, column|
    columns << required_columns_for(column)
    columns << column
  end.flatten.map(&:to_s).uniq
end

#required_columns_for(column, start = nil) ⇒ Object

helper method used by required_columns Returns all columns required by a certain column resolving the dependencies recursively



41
42
43
44
45
46
# File 'lib/reporting/reporting.rb', line 41

def required_columns_for(column, start = nil)
  return [] unless self.datasource_columns.has_key?(column)
  raise CircularDependencyException.new("Column #{start} has a circular dependency") if column.to_sym == start
  columns = [ self.datasource_columns[column][:requires] ].flatten.compact.collect(&:to_s)
  columns.collect { |c| [c, required_columns_for(c, start || column.to_sym)] }.flatten
end

#saveObject

ActiveRecord overrides



342
343
344
345
346
347
# File 'lib/reporting/reporting.rb', line 342

def save # :nodoc:
  if result = valid?
  end
  
  result
end

#save!Object

:nodoc:



349
350
351
# File 'lib/reporting/reporting.rb', line 349

def save! # :nodoc:
  save or raise ActiveRecord::RecordInvalid.new(self)
end

#serializeObject

Returns a serialized representation of the reporting



143
144
145
# File 'lib/reporting/reporting.rb', line 143

def serialize
  to_param.to_json
end

#to_paramObject

:nodoc:



147
148
149
150
151
152
153
154
155
# File 'lib/reporting/reporting.rb', line 147

def to_param # :nodoc:
  attributes.merge({
    :select   => select,
    :group_by => group_by,
    :order_by => order_by,
    :limit => limit,
    :offset => offset
  })
end

#to_params(key = self.class.name.underscore.gsub('/', '_')) ⇒ Object

Returns the serialized Reporting in a Hash that can be used for links and which is deserialized by from_params



163
164
165
# File 'lib/reporting/reporting.rb', line 163

def to_params(key = self.class.name.underscore.gsub('/', '_'))
  HashWithIndifferentAccess.new( key => to_param )
end