martyr 0.1.74.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.tags +868 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +265 -0
- data/Rakefile +1 -0
- data/TODO.txt +54 -0
- data/bin/console +62 -0
- data/bin/setup +7 -0
- data/lib/martyr/base_cube.rb +73 -0
- data/lib/martyr/cube.rb +134 -0
- data/lib/martyr/dimension_reference.rb +26 -0
- data/lib/martyr/errors.rb +20 -0
- data/lib/martyr/helpers/delegators.rb +17 -0
- data/lib/martyr/helpers/intervals.rb +222 -0
- data/lib/martyr/helpers/metric_id_standardizer.rb +47 -0
- data/lib/martyr/helpers/registrable.rb +15 -0
- data/lib/martyr/helpers/sorter.rb +79 -0
- data/lib/martyr/helpers/translations.rb +34 -0
- data/lib/martyr/level_concern/has_level_collection.rb +11 -0
- data/lib/martyr/level_concern/level.rb +45 -0
- data/lib/martyr/level_concern/level_collection.rb +60 -0
- data/lib/martyr/level_concern/level_comparator.rb +45 -0
- data/lib/martyr/level_concern/level_definitions_by_dimension.rb +24 -0
- data/lib/martyr/runtime/data_set/coordinates.rb +108 -0
- data/lib/martyr/runtime/data_set/element.rb +66 -0
- data/lib/martyr/runtime/data_set/element_common.rb +51 -0
- data/lib/martyr/runtime/data_set/element_locator.rb +143 -0
- data/lib/martyr/runtime/data_set/fact.rb +83 -0
- data/lib/martyr/runtime/data_set/fact_indexer.rb +72 -0
- data/lib/martyr/runtime/data_set/future_fact_value.rb +58 -0
- data/lib/martyr/runtime/data_set/future_metric.rb +40 -0
- data/lib/martyr/runtime/data_set/virtual_element.rb +131 -0
- data/lib/martyr/runtime/data_set/virtual_elements_builder.rb +202 -0
- data/lib/martyr/runtime/dimension_scopes/base_level_scope.rb +20 -0
- data/lib/martyr/runtime/dimension_scopes/degenerate_level_scope.rb +76 -0
- data/lib/martyr/runtime/dimension_scopes/dimension_scope_collection.rb +78 -0
- data/lib/martyr/runtime/dimension_scopes/level_scope_collection.rb +20 -0
- data/lib/martyr/runtime/dimension_scopes/query_level_scope.rb +223 -0
- data/lib/martyr/runtime/fact_scopes/base_fact_scope.rb +62 -0
- data/lib/martyr/runtime/fact_scopes/fact_scope_collection.rb +127 -0
- data/lib/martyr/runtime/fact_scopes/main_fact_scope.rb +7 -0
- data/lib/martyr/runtime/fact_scopes/null_scope.rb +7 -0
- data/lib/martyr/runtime/fact_scopes/sub_fact_scope.rb +16 -0
- data/lib/martyr/runtime/fact_scopes/wrapped_fact_scope.rb +11 -0
- data/lib/martyr/runtime/pivot/pivot_axis.rb +67 -0
- data/lib/martyr/runtime/pivot/pivot_cell.rb +54 -0
- data/lib/martyr/runtime/pivot/pivot_grain_element.rb +22 -0
- data/lib/martyr/runtime/pivot/pivot_row.rb +49 -0
- data/lib/martyr/runtime/pivot/pivot_table.rb +109 -0
- data/lib/martyr/runtime/pivot/pivot_table_builder.rb +125 -0
- data/lib/martyr/runtime/query/metric_dependency_resolver.rb +149 -0
- data/lib/martyr/runtime/query/query_context.rb +246 -0
- data/lib/martyr/runtime/query/query_context_builder.rb +215 -0
- data/lib/martyr/runtime/scope_operators/base_operator.rb +113 -0
- data/lib/martyr/runtime/scope_operators/group_operator.rb +18 -0
- data/lib/martyr/runtime/scope_operators/select_operator_for_dimension.rb +24 -0
- data/lib/martyr/runtime/scope_operators/select_operator_for_metric.rb +35 -0
- data/lib/martyr/runtime/scope_operators/where_operator_for_dimension.rb +43 -0
- data/lib/martyr/runtime/scope_operators/where_operator_for_metric.rb +25 -0
- data/lib/martyr/runtime/slices/data_slices/data_slice.rb +70 -0
- data/lib/martyr/runtime/slices/data_slices/metric_data_slice.rb +54 -0
- data/lib/martyr/runtime/slices/data_slices/plain_dimension_data_slice.rb +109 -0
- data/lib/martyr/runtime/slices/data_slices/time_dimension_data_slice.rb +9 -0
- data/lib/martyr/runtime/slices/has_scoped_levels.rb +29 -0
- data/lib/martyr/runtime/slices/memory_slices/TO_DELETE.md +188 -0
- data/lib/martyr/runtime/slices/memory_slices/memory_slice.rb +84 -0
- data/lib/martyr/runtime/slices/memory_slices/metric_memory_slice.rb +59 -0
- data/lib/martyr/runtime/slices/memory_slices/plain_dimension_memory_slice.rb +48 -0
- data/lib/martyr/runtime/slices/scopeable_slice_data.rb +73 -0
- data/lib/martyr/runtime/slices/slice_definitions/base_slice_definition.rb +30 -0
- data/lib/martyr/runtime/slices/slice_definitions/metric_slice_definition.rb +120 -0
- data/lib/martyr/runtime/slices/slice_definitions/plain_dimension_level_slice_definition.rb +26 -0
- data/lib/martyr/runtime/sub_cubes/fact_filler_strategies.rb +61 -0
- data/lib/martyr/runtime/sub_cubes/query_metrics.rb +56 -0
- data/lib/martyr/runtime/sub_cubes/sub_cube.rb +134 -0
- data/lib/martyr/runtime/sub_cubes/sub_cube_grain.rb +117 -0
- data/lib/martyr/schema/dimension_associations/dimension_association_collection.rb +33 -0
- data/lib/martyr/schema/dimension_associations/level_association.rb +37 -0
- data/lib/martyr/schema/dimension_associations/level_association_collection.rb +18 -0
- data/lib/martyr/schema/dimensions/dimension_definition_collection.rb +39 -0
- data/lib/martyr/schema/dimensions/plain_dimension_definition.rb +39 -0
- data/lib/martyr/schema/dimensions/time_dimension_definition.rb +24 -0
- data/lib/martyr/schema/facts/base_fact_definition.rb +22 -0
- data/lib/martyr/schema/facts/fact_definition_collection.rb +44 -0
- data/lib/martyr/schema/facts/main_fact_definition.rb +45 -0
- data/lib/martyr/schema/facts/sub_fact_definition.rb +44 -0
- data/lib/martyr/schema/metrics/base_metric.rb +77 -0
- data/lib/martyr/schema/metrics/built_in_metric.rb +38 -0
- data/lib/martyr/schema/metrics/count_distinct_metric.rb +172 -0
- data/lib/martyr/schema/metrics/custom_metric.rb +26 -0
- data/lib/martyr/schema/metrics/custom_rollup.rb +31 -0
- data/lib/martyr/schema/metrics/dependency_inferrer.rb +150 -0
- data/lib/martyr/schema/metrics/metric_definition_collection.rb +94 -0
- data/lib/martyr/schema/named_scopes/named_scope.rb +19 -0
- data/lib/martyr/schema/named_scopes/named_scope_collection.rb +42 -0
- data/lib/martyr/schema/plain_dimension_levels/base_level_definition.rb +39 -0
- data/lib/martyr/schema/plain_dimension_levels/degenerate_level_definition.rb +75 -0
- data/lib/martyr/schema/plain_dimension_levels/level_definition_collection.rb +15 -0
- data/lib/martyr/schema/plain_dimension_levels/query_level_definition.rb +99 -0
- data/lib/martyr/version.rb +3 -0
- data/lib/martyr/virtual_cube.rb +74 -0
- data/lib/martyr.rb +55 -0
- data/martyr.gemspec +41 -0
- metadata +296 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class MetricDefinitionCollection < HashWithIndifferentAccess
|
4
|
+
include Martyr::Registrable
|
5
|
+
include Martyr::Translations
|
6
|
+
|
7
|
+
attr_reader :cube, :dependency_inferrer, :standardizer
|
8
|
+
delegate :cube_name, to: :cube
|
9
|
+
|
10
|
+
alias_method :find_metric, :find_or_error
|
11
|
+
|
12
|
+
def initialize(cube)
|
13
|
+
super()
|
14
|
+
@cube = cube
|
15
|
+
@dependency_inferrer = cube.metric_dependency_inferrer
|
16
|
+
@standardizer = Martyr::MetricIdStandardizer.new(cube_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def supports_metric?(metric_name)
|
20
|
+
has_key? second_element_from_id(metric_name, fallback: true)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @see register_built_in_metric
|
24
|
+
def has_sum_metric(*args)
|
25
|
+
register_built_in_metric(:sum, *args)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @see register_built_in_metric
|
29
|
+
def has_min_metric(*args)
|
30
|
+
register_built_in_metric(:min, *args)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @see register_built_in_metric
|
34
|
+
def has_max_metric(*args)
|
35
|
+
register_built_in_metric(:max, *args)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param level [String] The level ID on which to perform the distinct count. The level must be connected to the
|
39
|
+
# fact with has_dimension_level.
|
40
|
+
# @option null_unless [String] an SQL fragment that creates a helper field on which the COUNT DISTINCT occurs.
|
41
|
+
# Example:
|
42
|
+
# 1. has_count_distinct_metric 'customer_count', level: 'customers.name'
|
43
|
+
# Count distinct occurs on `customers.name`
|
44
|
+
#
|
45
|
+
# 2. has_count_distinct_metric 'customer_with_property_count', level: 'customers.name', null_unless: 'customers.property'
|
46
|
+
# A helper field is created on which the COUNT DISTINCT occurs:
|
47
|
+
# CASE WHEN customers.property THEN customers.id ELSE NULL END AS customer_with_property_count_helper
|
48
|
+
#
|
49
|
+
# Since COUNT DISTINCT ignores null values, this can be an effective way to create boolean values
|
50
|
+
#
|
51
|
+
def has_count_distinct_metric(name, level:, null_unless: nil, fact_alias: name, typecast: :to_i,
|
52
|
+
sort: Sorter.identity, fact_grain: [], sub_query: [], sub_queries: [])
|
53
|
+
|
54
|
+
level_association = cube.dimension_associations.find_level_association(level)
|
55
|
+
register CountDistinctMetric.new cube_name: cube_name, name: name, fact_alias: fact_alias, typecast: typecast,
|
56
|
+
sort: sort, level: level_association, null_unless: null_unless, fact_grain: Array.wrap(fact_grain),
|
57
|
+
sub_queries: Array.wrap(sub_queries) + Array.wrap(sub_query)
|
58
|
+
end
|
59
|
+
|
60
|
+
def has_custom_metric(name, block, rollup: :sum, sort: Sorter.identity, depends_on: [], fact_grain: [])
|
61
|
+
inferrer = dependency_inferrer.infer_from_block(depends_on:
|
62
|
+
standardizer.standardize(depends_on), fact_grain: fact_grain, &block)
|
63
|
+
|
64
|
+
register CustomMetric.new cube_name: cube_name, name: name, block: block, rollup_function: rollup, sort: sort,
|
65
|
+
depends_on: inferrer.depends_on, fact_grain: inferrer.fact_grain
|
66
|
+
end
|
67
|
+
|
68
|
+
def has_custom_rollup(name, block, sort: Sorter.identity, depends_on: [], fact_grain: [])
|
69
|
+
inferrer = dependency_inferrer.infer_from_block(depends_on:
|
70
|
+
standardizer.standardize(depends_on), fact_grain: fact_grain, &block)
|
71
|
+
|
72
|
+
register CustomRollup.new cube_name: cube_name, name: name, block: block, sort: sort,
|
73
|
+
depends_on: inferrer.depends_on, fact_grain: inferrer.fact_grain
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def register(metric)
|
79
|
+
super
|
80
|
+
dependency_inferrer.add_metric(metric)
|
81
|
+
metric
|
82
|
+
end
|
83
|
+
|
84
|
+
def register_built_in_metric(rollup_function, name, statement, fact_alias: name, typecast: :to_i,
|
85
|
+
sort: Sorter.identity, fact_grain: [], sub_query: [], sub_queries: [])
|
86
|
+
|
87
|
+
register BuiltInMetric.new cube_name: cube_name, name: name, statement: statement, fact_alias: fact_alias,
|
88
|
+
rollup_function: rollup_function, typecast: typecast, sort: sort, fact_grain: Array.wrap(fact_grain),
|
89
|
+
sub_queries: Array.wrap(sub_queries) + Array.wrap(sub_query)
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class NamedScope
|
4
|
+
include ActiveModel::Model
|
5
|
+
|
6
|
+
attr_reader :name, :proc
|
7
|
+
|
8
|
+
def initialize(name, proc)
|
9
|
+
@name = name.to_s
|
10
|
+
@proc = proc.to_proc
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(query_context_builder, *args)
|
14
|
+
query_context_builder.instance_exec(*args, &@proc)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class NamedScopeCollection < HashWithIndifferentAccess
|
4
|
+
include Martyr::Registrable
|
5
|
+
|
6
|
+
# = DSL
|
7
|
+
|
8
|
+
def scope(name, proc)
|
9
|
+
named_scope = NamedScope.new(name, proc)
|
10
|
+
register(named_scope)
|
11
|
+
add_to_cube_helper_module(named_scope)
|
12
|
+
add_to_query_helper_module(named_scope)
|
13
|
+
end
|
14
|
+
|
15
|
+
def cube_helper_module
|
16
|
+
@cube_helper_module ||= Module.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def query_helper_module
|
20
|
+
@query_helper_module ||= Module.new
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Delegates all named scopes to #new_query_context_builder
|
26
|
+
def add_to_cube_helper_module(named_scope)
|
27
|
+
cube_helper_module.module_eval do
|
28
|
+
delegate named_scope.name, to: :new_query_context_builder
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_to_query_helper_module(named_scope)
|
33
|
+
query_helper_module.module_eval do
|
34
|
+
define_method(named_scope.name) do |*args|
|
35
|
+
named_scope.run(self, *args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class BaseLevelDefinition
|
4
|
+
include ActiveModel::Model
|
5
|
+
include Martyr::Level
|
6
|
+
|
7
|
+
# @attribute fact_key [String] the field in the fact where the attribute resides. E.g.:
|
8
|
+
# degenerate_level :country, fact_key: 'invoices.country'
|
9
|
+
#
|
10
|
+
# @attribute fact_alias [String] the alias to give in the `AS` part of the SQL fact statement.
|
11
|
+
#
|
12
|
+
# @attribute sort [Proc] optional lambda function for sorting.
|
13
|
+
# For query levels it accepts the record:
|
14
|
+
# ->(record) { record.custom_sort_order }
|
15
|
+
#
|
16
|
+
# For degenerates it accepts the value:
|
17
|
+
# ->(value) { value[1..2] }
|
18
|
+
#
|
19
|
+
attr_accessor :name, :fact_key, :fact_alias, :sort
|
20
|
+
|
21
|
+
delegate :dimension_name, :dimension_definition, to: :collection
|
22
|
+
alias_method :slice_id, :dimension_name
|
23
|
+
delegate :build_data_slice, :build_memory_slice, to: :dimension_definition
|
24
|
+
|
25
|
+
# This allows to ask any Martyr::Level for #level_definition
|
26
|
+
def level_definition
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def dimension_definition
|
31
|
+
collection.dimension
|
32
|
+
end
|
33
|
+
|
34
|
+
def supported?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class DegenerateLevelDefinition < BaseLevelDefinition
|
4
|
+
|
5
|
+
# @attribute query_level_key [String] the field in the query level where the degenerate attribute resides. E.g.:
|
6
|
+
# degenerate_level :country, query_level_key: 'billing_country'
|
7
|
+
#
|
8
|
+
# @attribute query_level_with_finder [Proc] the block to run on the query level's ActiveRecord scope. The block
|
9
|
+
# receives two arguments - `scope` and `values`. E.g.:
|
10
|
+
# ->(scope, values) { scope.where('billing_country' => values) }
|
11
|
+
#
|
12
|
+
attr_accessor :query_level_key, :query_level_with_finder, :value_method
|
13
|
+
attr_reader :loaded
|
14
|
+
|
15
|
+
# @param collection [DimensionDefinitionCollection]
|
16
|
+
# @param name [String, Symbol]
|
17
|
+
def initialize(collection, name, **options)
|
18
|
+
@collection = collection
|
19
|
+
hash = {name: name.to_s,
|
20
|
+
query_level_key: options[:query_level_key] || name,
|
21
|
+
value_method: options[:value_method] || "#{dimension_name}_#{name}",
|
22
|
+
fact_key: options[:fact_key] || "#{dimension_name}_#{name}",
|
23
|
+
fact_alias: options[:fact_alias] || "#{dimension_name}_#{name}",
|
24
|
+
sort: options[:sort] || Sorter.identity }
|
25
|
+
|
26
|
+
hash.merge! label_expression: options[:label_expression] if options[:label_expression]
|
27
|
+
|
28
|
+
super hash
|
29
|
+
|
30
|
+
@query_level_with_finder = options[:query_level_with_finder] || default_query_level_with_finder
|
31
|
+
@loaded = false
|
32
|
+
end
|
33
|
+
|
34
|
+
def label_key
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def label_expression
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def query?
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def degenerate?
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def build(collection)
|
51
|
+
Runtime::DegenerateLevelScope.new(collection, self)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param mod [Module]
|
55
|
+
def register_element_helper_methods(mod)
|
56
|
+
level_id = id
|
57
|
+
level_definition = self
|
58
|
+
mod.module_eval do
|
59
|
+
define_method(level_definition.value_method) { fetch(level_id) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def helper_methods
|
64
|
+
[value_method]
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def default_query_level_with_finder
|
70
|
+
->(scope, values){ scope.where query_level_key => values }
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class LevelDefinitionCollection < HashWithIndifferentAccess
|
4
|
+
include Martyr::LevelCollection
|
5
|
+
|
6
|
+
def degenerate_level(*args)
|
7
|
+
register DegenerateLevelDefinition.new(self, *args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def query_level(*args)
|
11
|
+
register QueryLevelDefinition.new(self, *args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Martyr
|
2
|
+
module Schema
|
3
|
+
class QueryLevelDefinition < BaseLevelDefinition
|
4
|
+
|
5
|
+
LABEL_EXPRESSION_ALIAS = 'martyr_label_expression'
|
6
|
+
|
7
|
+
attr_accessor :scope, :primary_key, :label_key, :label_expression, :id_method, :record_method, :value_method,
|
8
|
+
:parent_association_name
|
9
|
+
|
10
|
+
# @param collection [DimensionDefinitionCollection]
|
11
|
+
# @param name [String, Symbol]
|
12
|
+
# @param scope [Proc]
|
13
|
+
# @option primary_key [String]
|
14
|
+
# @option label_key [String]
|
15
|
+
# @option fact_key [String]
|
16
|
+
# @option fact_alias [String]
|
17
|
+
def initialize(collection, name, scope = nil, **options)
|
18
|
+
@collection = collection
|
19
|
+
@scope = scope || default_scope
|
20
|
+
super name: name.to_s,
|
21
|
+
primary_key: options[:primary_key] || 'id',
|
22
|
+
label_key: options[:label_key] || name.to_s,
|
23
|
+
label_expression: options[:label_expression],
|
24
|
+
id_method: options[:id_method] || "#{dimension_name}_#{name}_id",
|
25
|
+
record_method: options[:record_method] || "#{dimension_name}_#{name}_record",
|
26
|
+
value_method: options[:value_method] || "#{dimension_name}_#{name}",
|
27
|
+
fact_key: options[:fact_key] || "#{dimension_name}_#{name}_id",
|
28
|
+
fact_alias: options[:fact_alias] || "#{dimension_name}_#{name}_id",
|
29
|
+
parent_association_name: options[:parent_association_name]
|
30
|
+
|
31
|
+
self.sort = options[:sort] || Sorter.default_for_query(label_field)
|
32
|
+
add_label_expression_to_scope
|
33
|
+
end
|
34
|
+
|
35
|
+
def query?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
def degenerate?
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def parent_association_name_with_default
|
44
|
+
(parent_association_name || level_above.try(:name)).to_s.presence
|
45
|
+
end
|
46
|
+
|
47
|
+
def label_field
|
48
|
+
label_expression ? LABEL_EXPRESSION_ALIAS : label_key
|
49
|
+
end
|
50
|
+
|
51
|
+
def build(collection)
|
52
|
+
Runtime::QueryLevelScope.new(collection, self)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param record [ActiveRecord::Base]
|
56
|
+
def record_value(record)
|
57
|
+
record.try(label_field)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param mod [Module]
|
61
|
+
def register_element_helper_methods(mod)
|
62
|
+
level_id = id
|
63
|
+
level_definition = self
|
64
|
+
mod.module_eval do
|
65
|
+
define_method(level_definition.id_method) { key_for(level_id) }
|
66
|
+
define_method(level_definition.record_method) { record_for(level_id) }
|
67
|
+
define_method(level_definition.value_method) { fetch(level_id) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def helper_methods
|
72
|
+
[id_method, record_method, value_method]
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# @return [Proc] a lambda object representing running #all on the guessed-class
|
78
|
+
def default_scope
|
79
|
+
begin
|
80
|
+
klass = dimension_name.classify.constantize
|
81
|
+
->{ klass.all }
|
82
|
+
rescue => e
|
83
|
+
raise Schema::Error.new(e)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_label_expression_to_scope
|
88
|
+
return unless label_expression
|
89
|
+
original_scope = @scope.call
|
90
|
+
if original_scope.select_values.present?
|
91
|
+
@scope = -> { original_scope.select("#{label_expression} AS #{LABEL_EXPRESSION_ALIAS}") }
|
92
|
+
else
|
93
|
+
@scope = -> { original_scope.select("#{original_scope.klass.table_name}.*", "#{label_expression} AS #{LABEL_EXPRESSION_ALIAS}") }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Martyr
|
2
|
+
class VirtualCube < BaseCube
|
3
|
+
|
4
|
+
def self.contained_cube_classes
|
5
|
+
@contained_cube_classes ||= []
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.metric_definitions
|
9
|
+
@metric_definitions ||= Schema::MetricDefinitionCollection.new(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
# = DSL
|
13
|
+
|
14
|
+
def self.use_cube(class_name)
|
15
|
+
contained_cube_classes << class_name.constantize
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.dimension_definitions
|
19
|
+
merge_from_cubes(Schema::DimensionDefinitionCollection.new, &:dimension_definitions)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.supported_dimension_definitions
|
23
|
+
merge_from_cubes(Schema::DimensionDefinitionCollection.new, &:supported_dimension_definitions)
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
delegate :find_metric, :has_custom_rollup, to: :metric_definitions
|
28
|
+
delegate :select, :slice, :granulate, :pivot, to: :new_query_context_builder
|
29
|
+
alias_method :all, :new_query_context_builder
|
30
|
+
alias_method :metrics, :metric_definitions
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param mergeable [#merge!]
|
34
|
+
def self.merge_from_cubes(mergeable)
|
35
|
+
contained_cube_classes.inject(mergeable) do |merged_object, contained_cube|
|
36
|
+
merged_object.merge! yield(contained_cube)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.virtual?
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
# @override
|
45
|
+
def self.find_metric_id(metric_id)
|
46
|
+
cube_name, metric_name = id_components(metric_id)
|
47
|
+
if cube_name == self.cube_name
|
48
|
+
find_metric(metric_name)
|
49
|
+
else
|
50
|
+
find_cube(cube_name).find_metric(metric_name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.find_cube(cube_name)
|
55
|
+
contained_cube_classes.find{ |cube| cube.cube_name == cube_name } ||
|
56
|
+
raise(Schema::Error.new "Could not find `#{cube_name}`")
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Schema::DependencyInferrer]
|
60
|
+
def self.metric_dependency_inferrer
|
61
|
+
return @metric_dependency_inferrer if @metric_dependency_inferrer
|
62
|
+
inferrer = Schema::DependencyInferrer.new
|
63
|
+
contained_cube_classes.each do |contained_cube|
|
64
|
+
inferrer.add_cube_levels(contained_cube)
|
65
|
+
end
|
66
|
+
@metric_dependency_inferrer = inferrer
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.standardizer
|
70
|
+
@standardizer ||= Martyr::MetricIdStandardizer.new(cube_name, raise_if_not_ok: true)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
data/lib/martyr.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'martyr/version'
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/core_ext'
|
6
|
+
require 'active_model'
|
7
|
+
require 'active_record'
|
8
|
+
|
9
|
+
require 'martyr/helpers/translations'
|
10
|
+
require 'martyr/runtime/slices/has_scoped_levels'
|
11
|
+
require 'martyr/runtime/data_set/element_common'
|
12
|
+
|
13
|
+
Dir.glob(File.expand_path '../martyr/helpers/*.rb', __FILE__).each{|x| require File.expand_path(x).split('.rb').first}
|
14
|
+
Dir.glob(File.expand_path '../martyr/level_concern/*.rb', __FILE__).each{|x| require File.expand_path(x).split('.rb').first}
|
15
|
+
Dir.glob(File.expand_path '../martyr/runtime/scope_operators/*.rb', __FILE__).each{|x| require File.expand_path(x).split('.rb').first}
|
16
|
+
Dir.glob(File.expand_path '../martyr/**/*.rb', __FILE__).each{|x| require File.expand_path(x).split('.rb').first}
|
17
|
+
|
18
|
+
# require 'martyr/base'
|
19
|
+
# require 'martyr/errors'
|
20
|
+
#
|
21
|
+
# require 'martyr/schema/helpers/registrable'
|
22
|
+
# require 'martyr/schema/helpers/has_scope'
|
23
|
+
#
|
24
|
+
# require 'martyr/schema/dimensions/dimension_definition'
|
25
|
+
# require 'martyr/schema/dimensions/degenerate_dimension'
|
26
|
+
# require 'martyr/schema/dimensions/query_dimension'
|
27
|
+
# require 'martyr/schema/dimensions/time_dimension'
|
28
|
+
# require 'martyr/schema/dimensions/dimension_definition_collection'
|
29
|
+
# require 'martyr/schema/dimensions/shared_dimension_wrapper'
|
30
|
+
# require 'martyr/schema/dimensions/level_collection'
|
31
|
+
# require 'martyr/schema/dimensions/level'
|
32
|
+
#
|
33
|
+
# require 'martyr/schema/facts/fact_definition_collection'
|
34
|
+
# require 'martyr/schema/facts/main_fact_scope'
|
35
|
+
# require 'martyr/schema/facts/sub_fact_scope'
|
36
|
+
#
|
37
|
+
# require 'martyr/schema/metrics/built_in_metric'
|
38
|
+
# require 'martyr/schema/metrics/custom_metric'
|
39
|
+
# require 'martyr/schema/metrics/metric_definition_collection'
|
40
|
+
#
|
41
|
+
# require 'martyr/schema/rollups/custom_rollup'
|
42
|
+
# require 'martyr/schema/rollups/rollup_definition_collection'
|
43
|
+
#
|
44
|
+
# require 'martyr/runtime/query/query_context'
|
45
|
+
#
|
46
|
+
# require 'martyr/runtime/slices/base_dimension_slice'
|
47
|
+
# require 'martyr/runtime/slices/compound_slice'
|
48
|
+
# require 'martyr/runtime/slices/degenerate_dimension_slice'
|
49
|
+
# require 'martyr/runtime/slices/metric_slice'
|
50
|
+
# require 'martyr/runtime/slices/query_dimension_slice'
|
51
|
+
# require 'martyr/runtime/slices/time_dimension_slice'
|
52
|
+
|
53
|
+
module Martyr
|
54
|
+
# Your code goes here...
|
55
|
+
end
|
data/martyr.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'martyr/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "martyr"
|
8
|
+
spec.version = Martyr::VERSION
|
9
|
+
spec.authors = ["Amit Aharoni"]
|
10
|
+
spec.email = ["amit.sites@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Add data mart and pivoting functionality to Active Record models}
|
13
|
+
spec.description = %q{A multi-dimensional semantic layer on top of ActiveRecord that allows running pivot table queries and rendering them as CSV, HTML, or KickChart-ready hashes. Supports time dimensions, cohort analysis, custom rollups, and drilling through to the underlying ActiveRecord objects.}
|
14
|
+
spec.homepage = "https://github.com/vaharoni/martyr"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
|
+
# delete this section to allow pushing this gem to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
33
|
+
spec.add_development_dependency "sqlite3", "~> 1.3"
|
34
|
+
spec.add_development_dependency "activerecord", "~> 4.2"
|
35
|
+
spec.add_development_dependency "chinook_database", "~> 0.1"
|
36
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
37
|
+
spec.add_development_dependency "pry-byebug", "~> 3.3"
|
38
|
+
|
39
|
+
spec.add_runtime_dependency "activesupport", "~> 4.2"
|
40
|
+
spec.add_runtime_dependency "activemodel", "~> 4.2"
|
41
|
+
end
|