reporter 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.
@@ -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