reporter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,153 @@
1
+ # The scoping class manages the active scopes applied on all datasources.
2
+ # It takes care of serialization of the scopes when they are switched between iterations of the data.
3
+ #
4
+ class Reporter::DataSource::Scoping
5
+
6
+ SUPPORTED_SCOPES = {
7
+ :date => Reporter::Scope::DateScope,
8
+ :reference => Reporter::Scope::ReferenceScope
9
+ }
10
+
11
+ def initialize data_source
12
+ @data_source = data_source
13
+ @defined_scopes = {}
14
+ @scope_serialization = {}
15
+ @scope_hash = nil
16
+ end
17
+
18
+ public
19
+
20
+ # returns an array with all possible scopes between all datasources
21
+ def possible
22
+ return @possible_scopes if @possible_scopes
23
+ results = SUPPORTED_SCOPES.collect { |type_name, scope_type| scope_type.possible_scopes data_source.sources }.flatten
24
+ #[{ :funeral => "work_area", :cbs_statistic => "work_area" }, { :funeral => "notification_date", :cbs_statistic => "month" }]
25
+ @possible_scopes = results.uniq
26
+ end
27
+
28
+ def << scope_type, name, *args
29
+ raise "Invalid scope #{scope_type}" unless SUPPORTED_SCOPES.keys.include? scope_type
30
+ @defined_scopes[name] = SUPPORTED_SCOPES[scope_type].new(self, name, data_source, *args)
31
+ #Rails.logger.info "Added scope #{name}: #{scope_type}"
32
+ self
33
+ end
34
+ alias :add :<<
35
+
36
+ def limit_scope scope, *args
37
+ get(scope).limit = *args
38
+ end
39
+
40
+ def change changes
41
+ changes.each do |scope, change|
42
+ get(scope).change change
43
+ end
44
+ self
45
+ end
46
+
47
+ def get scope
48
+ raise "Scope does not exist" unless @defined_scopes.has_key? scope
49
+ @defined_scopes[scope]
50
+ end
51
+
52
+ # internal
53
+
54
+ def apply_on source, options
55
+ ignored_scopes = options[:ignore_scopes] || []
56
+ c = @cached_scopes[source][ignored_scopes.hash][@scope_hash]
57
+ raise "create" if c.nil?
58
+ # Rails.logger.info "cache!"
59
+ c
60
+ rescue
61
+ # Rails.logger.info "scope creation!"
62
+ c_source = source
63
+ @cached_scopes ||= {}
64
+ @cached_scopes[c_source] ||= {}
65
+ @cached_scopes[c_source][ignored_scopes.hash] ||= {}
66
+ @defined_scopes.each do |name, scope|
67
+ source = scope.apply_on(source) unless ignored_scopes.include? name
68
+ end
69
+ @cached_scopes[c_source][ignored_scopes.hash][@scope_hash] = source
70
+ end
71
+
72
+ def normalize_mapping mapping
73
+ normalized = {}
74
+ mapping.each do |key, value|
75
+ key_s = key.to_s
76
+ source = data_source.sources.find { |source| source.model_name.underscore == key_s.underscore }
77
+ normalized_value = \
78
+ case value
79
+ when Array :
80
+ value.collect(&:to_s)
81
+ when Hash :
82
+ value
83
+ else
84
+ value.to_s
85
+ end
86
+ normalized[source.model_name] = normalized_value
87
+ end
88
+ normalized.freeze
89
+ end
90
+
91
+ def valid_scope? mapping
92
+ !(get_scope_type mapping == false)
93
+ end
94
+
95
+ def get_scope_type mapping
96
+ if mapping.is_a? Hash
97
+ mapping = normalize_mapping mapping
98
+ #Rails.logger.info mapping.inspect
99
+ valid = false
100
+ possible.each do |fields|
101
+ if (mapping.keys & fields.keys) == mapping.keys
102
+ combination_valid = true
103
+ mapping.each do |source, column|
104
+ columns = fields[source].is_a?(Array) ? fields[source] : [fields[source]]
105
+ #Rails.logger.info "#{columns.inspect} <=> #{column.inspect}"
106
+ combination_valid = false unless columns.include? column
107
+ end
108
+ return fields[:type] if combination_valid
109
+ end
110
+ end
111
+ elsif mapping.ancestors.include? ActiveRecord::Base
112
+ return :reference
113
+ end
114
+ false
115
+ end
116
+
117
+ # internal serialization
118
+
119
+ def current_scope
120
+ scope_serialization
121
+ end
122
+
123
+ def apply_scope scope_serialization
124
+ @scope_serialization = scope_serialization
125
+ @scope_hash = scope_serialization.hash
126
+ end
127
+
128
+ def serialize_scope(scope_name, value)
129
+ scope_serialization[scope_name] = value
130
+ @scope_hash = scope_serialization.hash
131
+ end
132
+
133
+ def unserialize_scope(scope_name)
134
+ scope_serialization[scope_name]
135
+ end
136
+
137
+ def method_missing(method_name, *args, &block)
138
+ if method_name.to_s =~ /^add_(.*)_scope$/
139
+ return send :add, $1.to_sym, *args, &block
140
+ end
141
+ super
142
+ end
143
+
144
+ def respond_to?(method_name)
145
+ return true if method_name.to_s =~ /^add_(.*)_scope$/
146
+ super
147
+ end
148
+
149
+ private
150
+
151
+ attr_reader :data_source, :scope_serialization
152
+
153
+ end
@@ -0,0 +1,30 @@
1
+
2
+ class Reporter::DataSource
3
+
4
+ def initialize *args, &block
5
+ @sources = []
6
+ @scopes = Reporter::DataSource::Scoping.new self
7
+ yield self if block_given?
8
+ end
9
+
10
+ def << source
11
+ @sources << wrap_source(source)
12
+ self
13
+ end
14
+ alias :add :<<
15
+
16
+ def get name
17
+ sources.detect { |source| source.name == name } or raise "Source #{name} not found"
18
+ end
19
+
20
+ attr_reader :scopes, :sources
21
+
22
+ private
23
+
24
+ def wrap_source source
25
+ if source.ancestors.include? ActiveRecord::Base
26
+ Reporter::DataSource::ActiveRecordSource.new(self, source)
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,41 @@
1
+ class Reporter::DataStructure
2
+
3
+ def initialize data_set, *args
4
+ @data_set = data_set
5
+ @fields = {}
6
+ end
7
+
8
+ attr_reader :fields
9
+
10
+ def << column_type, column_alias, *args, &block
11
+ klass = "reporter/field/#{column_type}".classify.constantize
12
+ column = klass.new self, column_alias, *args, &block
13
+ #TODO: Validate column
14
+
15
+ @fields[column_alias] = column
16
+ column
17
+ end
18
+ alias :add :<<
19
+
20
+ def field_value_of field, options
21
+ raise "No such field defined: #{field}" unless @fields.has_key? field
22
+ @fields[field].calculate_value(data_set.data_source, options)
23
+ end
24
+
25
+ def method_missing(method_name, *args, &block)
26
+ if method_name.to_s =~ /^add_(.*)_field$/
27
+ return send :add, "#{$1}_field", *args, &block
28
+ end
29
+ return send :add, :field, *args, &block if method_name.to_s == "add_field"
30
+ super
31
+ end
32
+
33
+ def respond_to?(method_name)
34
+ return true if method_name.to_s.starts_with? "add_" and method_name.to_s.ends_with? "_field"
35
+ super
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :data_set
41
+ end
@@ -0,0 +1,7 @@
1
+ class Reporter::Field::AverageField < Reporter::Field::CalculationField
2
+
3
+ def initialize structure, alias_name, data_source, column, options = {}, &block
4
+ super structure, alias_name, data_source, :average, column, options, &block
5
+ end
6
+
7
+ end
@@ -0,0 +1,21 @@
1
+ class Reporter::Field::Base
2
+
3
+ def initialize structure, alias_name
4
+ @structure = structure
5
+ @name = alias_name
6
+ end
7
+
8
+ def validate
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def calculate_value data_source, options
13
+ raise NotImplementedError
14
+ end
15
+
16
+ attr_reader :name
17
+
18
+ protected
19
+ attr_reader :structure
20
+
21
+ end
@@ -0,0 +1,32 @@
1
+ class Reporter::Field::CalculationField < Reporter::Field::Base
2
+
3
+ def initialize structure, alias_name, data_source, calculation, column, options = {}, &block
4
+ super structure, alias_name
5
+ @source = data_source
6
+ @column = column
7
+ @options = options
8
+ @calculation = calculation
9
+ @calculation_block = block if block_given?
10
+ end
11
+
12
+ def calculate_value data_source, calculation_options
13
+ source = data_source.get(@source)
14
+ value = source.calculate @calculation, @column, options, &calculation_block
15
+ Reporter::Value.new(name, options[:name], value, nil, options[:description], options[:source_link])
16
+ end
17
+
18
+ def preload_for_period data_source, calculation_options, period, filter, scope
19
+ source = data_source.get(@source)
20
+ values = source.calculate_for_period @calculation, period, filter, scope, @column, options, &calculation_block
21
+ results = {}
22
+ values.each do |r|
23
+ val = r.delete :value
24
+ results[r] = Reporter::Value.new(name, options[:name], val, nil, options[:description], options[:source_link])
25
+ end
26
+ results
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :options, :calculation_block
32
+ end
@@ -0,0 +1,9 @@
1
+ class Reporter::Field::CountField < Reporter::Field::CalculationField
2
+
3
+ def initialize structure, alias_name, data_source, *args, &block
4
+ options = args.extract_options!
5
+ column = args.first
6
+ super structure, alias_name, data_source, :count, column, options, &block
7
+ end
8
+
9
+ end
@@ -0,0 +1,25 @@
1
+ class Reporter::Field::Field < Reporter::Field::Base
2
+
3
+ def initialize structure, alias_name, *args, &block
4
+ super structure, alias_name
5
+ @options = args.extract_options!
6
+ @value = args.first
7
+ @calculation_block = block if block_given?
8
+ end
9
+
10
+ def calculate_value data_source, calculation_options
11
+ if calculation_block
12
+ row = Reporter::Value.new(name, options[:name], nil, nil, options[:description], options[:source_link])
13
+ calculation_block.call(data_source, options, row)
14
+ return row
15
+ end
16
+ return Reporter::Value.new(name, options[:name], value, nil, options[:description], options[:source_link]) unless value.is_a? Symbol
17
+ Reporter::Value.new(name, options[:name], data_source.scopes.get(value).value,
18
+ data_source.scopes.get(value).human_name, options[:description], options[:source_link])
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :options, :value, :calculation_block
24
+
25
+ end
@@ -0,0 +1,24 @@
1
+ class Reporter::Field::FormulaField < Reporter::Field::Base
2
+
3
+ def initialize structure, alias_name, formula, options = {}
4
+ super structure, alias_name
5
+ @formula = Reporter::Formula.new formula
6
+ @options = options
7
+ end
8
+
9
+ def calculate_value data_source, calculation_options
10
+ required_terms = {}
11
+ formula.term_list.each do |term|
12
+ required_terms[term] = nil
13
+ required_terms[term] = calculation_options[:row][term].value if calculation_options[:row] and term != name
14
+ end
15
+ value = formula.call(required_terms)
16
+
17
+ Reporter::Value.new(name, options[:name], value, nil, options[:description], options[:source_link])
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :options, :formula
23
+
24
+ end
@@ -0,0 +1,7 @@
1
+ class Reporter::Field::SumField < Reporter::Field::CalculationField
2
+
3
+ def initialize structure, alias_name, data_source, column, options = {}, &block
4
+ super structure, alias_name, data_source, :sum, column, options, &block
5
+ end
6
+
7
+ end