pivotable 0.0.2 → 0.0.3
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.
- data/lib/pivotable/expression/abstract.rb +33 -0
- data/lib/pivotable/expression/calculation.rb +39 -0
- data/lib/pivotable/expression/generic.rb +29 -0
- data/lib/pivotable/expression.rb +5 -0
- data/lib/pivotable/model.rb +23 -26
- data/lib/pivotable/pivoting.rb +5 -4
- data/lib/pivotable/rotation.rb +84 -43
- data/lib/pivotable.rb +8 -2
- metadata +8 -6
- data/lib/pivotable/collection.rb +0 -17
- data/lib/pivotable/column.rb +0 -35
@@ -0,0 +1,33 @@
|
|
1
|
+
class Pivotable::Expression::Abstract
|
2
|
+
attr_reader :name, :model, :via
|
3
|
+
|
4
|
+
def initialize(model, name, options = {})
|
5
|
+
@name = name.to_s
|
6
|
+
@model = model
|
7
|
+
@via = process_via options[:via]
|
8
|
+
|
9
|
+
raise ArgumentError, "Could not find DB attribute for '#{@name}', please specify a :via option" if via.blank?
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_select
|
13
|
+
not_implemented
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_group
|
17
|
+
not_implemented
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def process_via(value)
|
23
|
+
case value
|
24
|
+
when String # Pure SQL string
|
25
|
+
value
|
26
|
+
when Symbol
|
27
|
+
model.arel_table[value]
|
28
|
+
else
|
29
|
+
model.arel_table[name.to_sym]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Pivotable::Expression::Calculation < Pivotable::Expression::Abstract
|
2
|
+
|
3
|
+
attr_reader :function
|
4
|
+
|
5
|
+
def initialize(model, name, options = {})
|
6
|
+
super
|
7
|
+
@function = options[:function] if options[:function].is_a?(Symbol)
|
8
|
+
|
9
|
+
if via.is_a?(Arel::Attribute)
|
10
|
+
raise ArgumentError, "No calculation function provided for '#{@name}'. Please provide a :function option" unless function
|
11
|
+
@via = via.send(function)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_select
|
16
|
+
case via
|
17
|
+
when String
|
18
|
+
"#{via} AS #{model.connection.quote(name)}"
|
19
|
+
else
|
20
|
+
via.clone.as(model.connection.quote(name))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_group
|
25
|
+
raise RuntimeError, "Cannot group by calculation"
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def process_via(value)
|
31
|
+
case value
|
32
|
+
when Arel::Expression
|
33
|
+
value
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Pivotable::Expression::Generic < Pivotable::Expression::Abstract
|
2
|
+
|
3
|
+
def to_select
|
4
|
+
case via
|
5
|
+
when String
|
6
|
+
"#{via} AS #{model.connection.quote(name)}"
|
7
|
+
else
|
8
|
+
via.as(name.to_sym)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_group
|
13
|
+
via.clone
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def process_via(value)
|
19
|
+
case value
|
20
|
+
when Symbol
|
21
|
+
model.arel_table[value]
|
22
|
+
when Arel::Attribute
|
23
|
+
value
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/pivotable/model.rb
CHANGED
@@ -2,11 +2,6 @@
|
|
2
2
|
module Pivotable::Model
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
-
included do
|
6
|
-
class_attribute :_pivotable
|
7
|
-
self._pivotable = {}
|
8
|
-
end
|
9
|
-
|
10
5
|
module ClassMethods
|
11
6
|
|
12
7
|
# Pivotable definition for a model. Example:
|
@@ -14,35 +9,33 @@ module Pivotable::Model
|
|
14
9
|
# class Stat < ActiveRecord::Base
|
15
10
|
#
|
16
11
|
# # Add your rotations
|
17
|
-
# pivotable do
|
18
|
-
#
|
19
|
-
# rotation :overall do
|
20
|
-
# sum :visits
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# # Rotations can in herit parent definitions
|
24
|
-
# rotation :by_day => :overall do
|
25
|
-
# by :day
|
26
|
-
# end
|
27
|
-
#
|
12
|
+
# pivotable :overall do
|
13
|
+
# sum :visits
|
28
14
|
# end
|
29
15
|
#
|
30
|
-
# #
|
31
|
-
# pivotable :
|
16
|
+
# # Rotations can in herit parent definitions
|
17
|
+
# pivotable :daily => :overall do
|
18
|
+
# by :day
|
19
|
+
# end
|
32
20
|
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
21
|
+
# # Add "namespaced" rotations, e.g. for admins
|
22
|
+
# pivotable "admin:daily" => :daily do
|
23
|
+
# maximum :bounce_rate
|
24
|
+
# end
|
37
25
|
#
|
26
|
+
# # Can be expressed as
|
27
|
+
# pivotable :admin, :extended => [:admin, :daily] do
|
28
|
+
# sum :bot_impressions
|
38
29
|
# end
|
39
30
|
#
|
40
31
|
# end
|
41
32
|
#
|
42
|
-
def pivotable(
|
43
|
-
name =
|
44
|
-
|
45
|
-
|
33
|
+
def pivotable(*tokens, &block)
|
34
|
+
name, parent = tokens.extract_options!.to_a.first
|
35
|
+
name, parent = Pivotable.name(tokens, name), Pivotable.name(parent)
|
36
|
+
raise ArgmentError, "A name must be provided" unless name
|
37
|
+
|
38
|
+
_pivotable[name] = Pivotable::Rotation.new(self, name, parent, &block) if block
|
46
39
|
_pivotable[name]
|
47
40
|
end
|
48
41
|
|
@@ -51,5 +44,9 @@ module Pivotable::Model
|
|
51
44
|
scoped.pivot(*args)
|
52
45
|
end
|
53
46
|
|
47
|
+
def _pivotable
|
48
|
+
@_pivotable ||= {}
|
49
|
+
end
|
50
|
+
|
54
51
|
end
|
55
52
|
end
|
data/lib/pivotable/pivoting.rb
CHANGED
@@ -7,13 +7,14 @@ module Pivotable::Pivoting
|
|
7
7
|
# # Use custom (e.g. admin) rotation
|
8
8
|
# Stat.pivot(:admin, :by_day)
|
9
9
|
#
|
10
|
+
# # Same as
|
11
|
+
# Stat.pivot("admin:by_day")
|
12
|
+
#
|
10
13
|
# # Combine it with relations & scopes
|
11
14
|
# Stat.latest.order('some_column').pivot(:by_day).paginate :page => 1
|
12
15
|
#
|
13
|
-
def pivot(*
|
14
|
-
|
15
|
-
scope = args.first.is_a?(Symbol) ? args.shift : nil
|
16
|
-
klass.pivotable(scope)[rotation].merge(self)
|
16
|
+
def pivot(*parts)
|
17
|
+
klass.pivotable(*parts).merge(self)
|
17
18
|
end
|
18
19
|
|
19
20
|
end
|
data/lib/pivotable/rotation.rb
CHANGED
@@ -1,51 +1,101 @@
|
|
1
1
|
class Pivotable::Rotation
|
2
2
|
|
3
|
-
attr_reader :
|
4
|
-
delegate :model, :to => :collection
|
3
|
+
attr_reader :model, :name, :parent, :block, :selects, :groups, :joins, :loaded
|
5
4
|
alias :loaded? :loaded
|
6
5
|
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
@name
|
10
|
-
@parent
|
11
|
-
@selects
|
12
|
-
@groups
|
13
|
-
@joins
|
14
|
-
@block
|
15
|
-
@loaded
|
16
|
-
end
|
17
|
-
|
18
|
-
def sum(*cols)
|
19
|
-
calculate :sum, *cols
|
6
|
+
def initialize(model, name, parent = nil, &block)
|
7
|
+
@model = model
|
8
|
+
@name = name
|
9
|
+
@parent = parent
|
10
|
+
@selects = []
|
11
|
+
@groups = []
|
12
|
+
@joins = []
|
13
|
+
@block = block
|
14
|
+
@loaded = false
|
20
15
|
end
|
21
16
|
|
17
|
+
# Calculate minimum. Calls #calculate with :via => :minimum.
|
18
|
+
# See #calulate for examples.
|
22
19
|
def minimum(*cols)
|
23
|
-
|
20
|
+
opts = cols.extract_options!.update :function => :minimum
|
21
|
+
calculate *(cols << opts)
|
24
22
|
end
|
25
23
|
|
24
|
+
# Calculate maximum. Calls #calculate with :via => :maximum.
|
25
|
+
# See #calulate for examples.
|
26
26
|
def maximum(*cols)
|
27
|
-
|
27
|
+
opts = cols.extract_options!.update :function => :maximum
|
28
|
+
calculate *(cols << opts)
|
28
29
|
end
|
29
30
|
|
31
|
+
# Calculate sum. Calls #calculate with :via => :sum.
|
32
|
+
# See #calulate for examples.
|
33
|
+
def sum(*cols)
|
34
|
+
opts = cols.extract_options!.update :function => :sum
|
35
|
+
calculate *(cols << opts)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Calculate sum. Calls #calculate with :via => :average.
|
39
|
+
# See #calulate for examples.
|
30
40
|
def average(*cols)
|
31
|
-
|
41
|
+
opts = cols.extract_options!.update :function => :average
|
42
|
+
calculate *(cols << opts)
|
32
43
|
end
|
33
44
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
45
|
+
# Calculate value. Examples:
|
46
|
+
#
|
47
|
+
# # Simple function
|
48
|
+
# calculate :views, :function => :sum
|
49
|
+
# # => SELECT SUM(table.views) AS views FROM table
|
50
|
+
#
|
51
|
+
# # Use a special column
|
52
|
+
# calculate :page_views, :via => :views, :function => :sum
|
53
|
+
# # => SELECT SUM(table.views) AS page_views FROM table
|
54
|
+
#
|
55
|
+
# # Use a custom SQL
|
56
|
+
# calculate :page_views, :via => "SUM(table.views)"
|
57
|
+
# # => SELECT SUM(table.views) AS page_views FROM table
|
58
|
+
#
|
59
|
+
# # Use an AREL expressions
|
60
|
+
# calculate :page_views, :via => Model.arel_table[:views].sum
|
61
|
+
# # => SELECT SUM(table.views) AS page_views FROM table
|
62
|
+
#
|
63
|
+
def calculate(*cols)
|
64
|
+
opts = cols.extract_options!
|
65
|
+
cols.each do |col|
|
66
|
+
@selects << Pivotable::Expression::Calculation.new(model, col, opts)
|
67
|
+
end
|
38
68
|
end
|
39
69
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
70
|
+
# Group by a column. Examples:
|
71
|
+
#
|
72
|
+
# # Simple function
|
73
|
+
# by :page_id
|
74
|
+
# # => SELECT table.page_id FROM table GROUP BY page_id
|
75
|
+
#
|
76
|
+
# # Use a special column
|
77
|
+
# by :pageid, :via => page_id
|
78
|
+
# # => SELECT table.page_id AS pageid FROM table GROUP BY page_id
|
79
|
+
#
|
80
|
+
# # Use a custom SQL
|
81
|
+
# by :page_id, :via => "page_id * 2"
|
82
|
+
# # => SELECT page_id * 2 AS page_id FROM table GROUP BY page_id * 2
|
83
|
+
#
|
84
|
+
# # Use an AREL attributes
|
85
|
+
# by :pageid, :via => Model.arel_table[:page_id]
|
86
|
+
# # => SELECT table.page_id AS pageid FROM table GROUP BY page_id
|
87
|
+
#
|
88
|
+
def by(*cols)
|
89
|
+
opts = cols.extract_options!
|
90
|
+
cols.each do |col|
|
91
|
+
expr = Pivotable::Expression::Generic.new(model, col, opts)
|
92
|
+
@selects << expr
|
93
|
+
@groups << expr
|
44
94
|
end
|
45
95
|
end
|
46
96
|
|
47
|
-
def joins(*
|
48
|
-
@joins +=
|
97
|
+
def joins(*args)
|
98
|
+
@joins += args
|
49
99
|
end
|
50
100
|
|
51
101
|
def merge(relation)
|
@@ -66,21 +116,12 @@ class Pivotable::Rotation
|
|
66
116
|
relation
|
67
117
|
end
|
68
118
|
|
69
|
-
|
119
|
+
def load!
|
120
|
+
return if loaded?
|
70
121
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
instance_eval &block
|
76
|
-
@loaded = true
|
77
|
-
end
|
78
|
-
|
79
|
-
def columns(*cols)
|
80
|
-
opts = cols.extract_options!
|
81
|
-
cols.map do |col|
|
82
|
-
Pivotable::Column.new(model, col, opts)
|
83
|
-
end
|
84
|
-
end
|
122
|
+
instance_eval &model.pivotable(parent).block if parent
|
123
|
+
instance_eval &block
|
124
|
+
@loaded = true
|
125
|
+
end
|
85
126
|
|
86
127
|
end
|
data/lib/pivotable.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
require "active_support/core_ext"
|
2
2
|
require "active_record"
|
3
3
|
require "active_record/relation"
|
4
|
+
require "abstract"
|
4
5
|
|
5
6
|
module Pivotable
|
6
7
|
autoload :Model, "pivotable/model"
|
7
8
|
autoload :Pivoting, "pivotable/pivoting"
|
8
9
|
autoload :Rotation, "pivotable/rotation"
|
9
|
-
autoload :
|
10
|
-
|
10
|
+
autoload :Expression, "pivotable/expression"
|
11
|
+
|
12
|
+
def self.name(*tokens)
|
13
|
+
result = tokens.flatten.map {|i| i.to_s.strip }.reject(&:blank?).join(':')
|
14
|
+
result unless result.blank?
|
15
|
+
end
|
16
|
+
|
11
17
|
end
|
12
18
|
|
13
19
|
ActiveRecord::Base.class_eval do
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pivotable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Dimitrij Denissenko
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-07-
|
18
|
+
date: 2011-07-21 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -75,8 +75,10 @@ extra_rdoc_files: []
|
|
75
75
|
files:
|
76
76
|
- README.markdown
|
77
77
|
- lib/pivotable/pivoting.rb
|
78
|
-
- lib/pivotable/
|
79
|
-
- lib/pivotable/
|
78
|
+
- lib/pivotable/expression/generic.rb
|
79
|
+
- lib/pivotable/expression/calculation.rb
|
80
|
+
- lib/pivotable/expression/abstract.rb
|
81
|
+
- lib/pivotable/expression.rb
|
80
82
|
- lib/pivotable/rotation.rb
|
81
83
|
- lib/pivotable/model.rb
|
82
84
|
- lib/pivotable.rb
|
data/lib/pivotable/collection.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
class Pivotable::Collection < Hash
|
2
|
-
|
3
|
-
attr_reader :model
|
4
|
-
|
5
|
-
def initialize(model)
|
6
|
-
@model = model
|
7
|
-
super()
|
8
|
-
end
|
9
|
-
|
10
|
-
# Adds a new rotation to this collection
|
11
|
-
def rotation(name, &block)
|
12
|
-
name, parent = name.is_a?(Hash) ? name.to_a.first : [name, nil]
|
13
|
-
item = Pivotable::Rotation.new(self, name, parent, &block)
|
14
|
-
self[item.name] = item
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
data/lib/pivotable/column.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
class Pivotable::Column
|
2
|
-
|
3
|
-
attr_reader :attribute
|
4
|
-
|
5
|
-
def initialize(model, attribute, options = {})
|
6
|
-
@as = options[:as]
|
7
|
-
@attribute = case attribute
|
8
|
-
when Arel::Attribute
|
9
|
-
attribute
|
10
|
-
else
|
11
|
-
model.arel_table[attribute]
|
12
|
-
end
|
13
|
-
raise ArgumentError, "Invalid attribute #{attribute.inspect}" unless @attribute
|
14
|
-
end
|
15
|
-
|
16
|
-
def name
|
17
|
-
@as || attribute.name
|
18
|
-
end
|
19
|
-
|
20
|
-
def calculate!(function)
|
21
|
-
@as ||= attribute.name
|
22
|
-
@function = attribute.send(function)
|
23
|
-
end
|
24
|
-
|
25
|
-
def to_select
|
26
|
-
select = (@function || attribute).clone
|
27
|
-
select.as(@as.to_s) if @as
|
28
|
-
select
|
29
|
-
end
|
30
|
-
|
31
|
-
def to_group
|
32
|
-
attribute.clone
|
33
|
-
end
|
34
|
-
|
35
|
-
end
|