martyr 0.1.74.pre
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.
- 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
|