pivotable 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,5 @@
1
+ module Pivotable::Expression
2
+ autoload :Abstract, "pivotable/expression/abstract"
3
+ autoload :Generic, "pivotable/expression/generic"
4
+ autoload :Calculation, "pivotable/expression/calculation"
5
+ end
@@ -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
- # # Add your custom rotations, e.g. for admins
31
- # pivotable :admin do
16
+ # # Rotations can in herit parent definitions
17
+ # pivotable :daily => :overall do
18
+ # by :day
19
+ # end
32
20
  #
33
- # rotation :overall do
34
- # sum :visits
35
- # maximum :bounce_rate
36
- # end
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(name = nil, &block)
43
- name = name.present? ? name.to_sym : :_default
44
- _pivotable[name] ||= Pivotable::Collection.new(self)
45
- _pivotable[name.to_sym].instance_eval(&block) if block
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
@@ -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(*args)
14
- rotation = args.pop
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
@@ -1,51 +1,101 @@
1
1
  class Pivotable::Rotation
2
2
 
3
- attr_reader :collection, :name, :parent, :block, :selects, :groups, :joins, :loaded
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(collection, name, parent = nil, &block)
8
- @collection = collection
9
- @name = name.to_sym
10
- @parent = parent.to_sym if parent
11
- @selects = []
12
- @groups = []
13
- @joins = []
14
- @block = block
15
- @loaded = false
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
- calculate :minimum, *cols
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
- calculate :maximum, *cols
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
- calculate :average, *cols
41
+ opts = cols.extract_options!.update :function => :average
42
+ calculate *(cols << opts)
32
43
  end
33
44
 
34
- def by(*cols)
35
- cols = columns(*cols)
36
- @selects += cols
37
- @groups += cols
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
- def calculate(function, *cols)
41
- columns(*cols).each do |col|
42
- col.calculate!(function)
43
- @selects << col
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(*names)
48
- @joins += names
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
- private
119
+ def load!
120
+ return if loaded?
70
121
 
71
- def load!
72
- return if loaded?
73
-
74
- instance_eval &collection[parent].block if parent
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 :Column, "pivotable/column"
10
- autoload :Collection, "pivotable/collection"
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: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 2
10
- version: 0.0.2
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-05 00:00:00 +01:00
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/column.rb
79
- - lib/pivotable/collection.rb
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
@@ -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
@@ -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