pivotable 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|