arlj 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a58db94fac67a5fa42539b8b1cc30beaf1134207
4
- data.tar.gz: 3ffd40c1670e27ed4e6eefe82b20df628dd7b361
3
+ metadata.gz: 8d66639a751ea47513133e0ce7d6bbde7e3ea305
4
+ data.tar.gz: 7063bd9f2a5e51647a644f0ca0b5de206195866a
5
5
  SHA512:
6
- metadata.gz: caf5c2c6a30881344c5f75074a5a1131263f68614574b28f3790a475008d638cd8745dbdbc3c0f0d7085d4bc863d112b310020b14ba06d3fa3613d133ef803d8
7
- data.tar.gz: 5276383d71fdf062b199cd3370dfac5258fccf7856f5a753e8444dd5a741ab8d64ec19ba2c34b79d005e14d0286502ee4f89108bb5e5d49a159267ab7e2237ff
6
+ metadata.gz: 148bcbd399bec222d45e3aa1abf55d1b71fc97d258237a23ee882f239d4b1f8c838fc950e01a27ed256c8aef1a18c30457a1e4acd86a827e5a59b34b8aa05a4d
7
+ data.tar.gz: c9f80b5128245a37778974f82966802cbbcbc9ae68a5eef7bf30b3b3850cb44c38481ff96648dd922bffff102837f00bce4416afef7603782750d2ce03993dd7
data/README.md CHANGED
@@ -45,30 +45,67 @@ puts Parent.left_joins(:children).group('records.id').select('COUNT(children.id)
45
45
  GROUP BY records.id
46
46
  ```
47
47
 
48
- `left_joins` is purposely low level to be extra chainable.
48
+ `left_joins` is purposely low level for maximum control.
49
49
 
50
- Arlj also adds an aggregation method:
50
+ Arlj has an aggregation method that is higher level and generally easier to use:
51
51
 
52
52
  ```ruby
53
- Parent.left_joins_aggregate(:children, 'COUNT(*)', 'SUM(col)' => 'total').select('children_count', 'total').to_sql
53
+ Parent.left_joins_aggregate(:children, 'COUNT(*)').to_sql
54
+ => SELECT "parents".*
55
+ FROM "parents"
56
+ LEFT OUTER JOIN (SELECT "children"."parent_id"
57
+ , COUNT("children"."id") AS children_count
58
+ FROM "children"
59
+ GROUP BY "children"."parent_id") arlj_aggregate_children
60
+ ON arlj_aggregate_children."parent_id" = "parents"."id"
61
+ ```
62
+
63
+ Supported aggregation functions are `COUNT()`, `SUM()`, `AVG()`, `MIN()`, and `MAX()`.
64
+
65
+ The aggregation column has a default name of `{table}_{function}_{column}` which
66
+ is easily renamed:
67
+
68
+ ```ruby
69
+ Parent.left_joins_aggregate(:children, 'SUM(age)' => 'ekkekkekkekkeptangya').to_sql
70
+ => SELECT "parents".*
71
+ FROM "parents"
72
+ LEFT OUTER JOIN (SELECT "children"."parent_id"
73
+ , SUM("children"."age") AS ekkekkekkekkeptangya
74
+ FROM "children"
75
+ GROUP BY "children"."parent_id") arlj_aggregate_children
76
+ ON arlj_aggregate_children."parent_id" = "parents"."id"
77
+ ```
78
+
79
+ Since Arlj uses a sub-select, you can easily chain additional queries:
80
+
81
+ ```ruby
82
+ Parent.left_joins_aggregate(:children, 'COUNT(*)').select('children_count').to_sql
54
83
  => SELECT children_count
55
- , total
56
84
  FROM "parents"
57
85
  LEFT OUTER JOIN (SELECT "children"."parent_id"
58
86
  , COUNT("children"."id") AS children_count
59
- , SUM("children"."col") AS total
60
87
  FROM "children"
61
88
  GROUP BY "children"."parent_id") arlj_aggregate_children
62
89
  ON arlj_aggregate_children."parent_id" = "parents"."id"
63
90
  ```
64
91
 
65
- `left_joins_aggregate` currently uses a subquery to hide its aggregation. It is
66
- not the most efficient implementation but it does offer a much better chaining
67
- experience than using `group` at the top level.
92
+ Arlj also supports some basic where clauses:
93
+
94
+ ```ruby
95
+ Parent.left_joins_aggregate(:children, 'COUNT(*)', where: {age: 1..5}).to_sql
96
+ => SELECT "parents".*
97
+ FROM "parents"
98
+ LEFT OUTER JOIN (SELECT "children"."parent_id"
99
+ , COUNT("children"."id") AS children_count
100
+ FROM "children"
101
+ WHERE ("children"."age" BETWEEN 1 AND 5)
102
+ GROUP BY "children"."parent_id") arlj_aggregate_children
103
+ ON arlj_aggregate_children."parent_id" = "parents"."id"
104
+ ```
68
105
 
69
106
  If you prefer, you may also use `arlj` and `arlj_aggregate` instead of
70
107
  `left_joins` and `left_joins_aggregate` respectively. To prevent potential
71
- naming conflicts, please use `Arlj::Base`:
108
+ naming conflicts, use `Arlj::Base` instead:
72
109
 
73
110
  ```ruby
74
111
  class Parent < ActiveRecord::Base
@@ -85,10 +122,18 @@ Arlj.memoize!
85
122
 
86
123
  This has not been proven to be faster.
87
124
 
125
+ ## Gotchas
126
+
127
+ * Since `left_joins_aggregate` uses a sub-select for its aggregation, it can
128
+ underperform a better optimized query.
129
+
130
+ * When `left_joins_aggregate` joins zero records, the aggregate column is NULL.
131
+ To operate correctly on these columns, please use `COALESCE(col, 0)`.
132
+
88
133
  ## TODO
89
134
 
90
- * Relations with conditions
91
- * `LEFT JOIN [...] ON`
135
+ * `left_joins(nested: :relations)`
136
+ * `left_joins_aggregate([...], merge: User.active)`
92
137
  * `has_and_belongs_to_many`
93
138
  * `has_many :through =>`
94
139
 
@@ -41,21 +41,26 @@ module Arlj
41
41
  refl = reflect_on_association(assoc)
42
42
  refl_arel = refl.klass.arel_table
43
43
 
44
- join_name = "arlj_aggregate_#{refl.table_name}"
44
+ subq_ar = refl.klass.group(refl_arel[refl.foreign_key])
45
45
 
46
46
  columns = [refl_arel[refl.foreign_key]]
47
- args.each do |thunk|
48
- columns << parse_thunk(refl, assoc, refl_arel, thunk)
47
+ args.each do |arg|
48
+ columns << parse_directive(refl, assoc, refl_arel, arg)
49
49
  end
50
- options.each do |thunk, name|
51
- columns << parse_thunk(refl, assoc, refl_arel, thunk, name)
50
+ options.each do |key, value|
51
+ if directive?(key)
52
+ columns << parse_directive(refl, assoc, refl_arel, key, value)
53
+ elsif key.to_s == 'where'
54
+ subq_ar = subq_ar.send(key, value)
55
+ else
56
+ raise "'#{key.inspect} => #{value.inspect}' not recognized"
57
+ end
52
58
  end
53
59
 
54
- subq_arel =
55
- refl_arel.project(columns).
56
- from(refl_arel).
57
- group(refl_arel[refl.foreign_key]).
58
- as(join_name)
60
+ subq_arel = subq_ar.arel
61
+ subq_arel.projections.clear
62
+ subq_arel = subq_arel.project(columns).
63
+ as("arlj_aggregate_#{refl.table_name}")
59
64
 
60
65
  arlj_left_join_arel(subq_arel, refl.foreign_key)
61
66
  end
@@ -66,7 +71,7 @@ module Arlj
66
71
 
67
72
  private
68
73
 
69
- THUNK_PATTERN = /^([a-zA-Z]*)\((.*)\)$/
74
+ DIRECTIVE_PATTERN = /^([a-zA-Z]*)\((.*)\)$/
70
75
  AGGREGATE_FUNCTIONS = {
71
76
  'sum' => 'sum',
72
77
  'average' => 'average',
@@ -77,10 +82,14 @@ module Arlj
77
82
  'min' => 'minimum',
78
83
  'count' => 'count',
79
84
  }.freeze
80
- def parse_thunk(refl, assoc, arel, thunk, name=nil)
81
- matchdata = THUNK_PATTERN.match(thunk)
85
+ def directive?(check)
86
+ DIRECTIVE_PATTERN =~ check
87
+ end
88
+
89
+ def parse_directive(refl, assoc, arel, directive, name=nil)
90
+ matchdata = DIRECTIVE_PATTERN.match(directive)
82
91
  if matchdata.nil?
83
- raise "'#{thunk}' not parsable - must be of format 'func(column)'"
92
+ raise "'#{directive}' not parsable - must be of format 'func(column)'"
84
93
  end
85
94
 
86
95
  func = AGGREGATE_FUNCTIONS[matchdata[1].downcase]
@@ -98,6 +107,10 @@ module Arlj
98
107
  arel[column].send(func).as(name)
99
108
  end
100
109
 
110
+ def arel_node(value)
111
+ Arel::Nodes::SqlLiteral.new(value)
112
+ end
113
+
101
114
  def arlj_left_join_arel(arel, foreign_key)
102
115
  arel_table.join(arel, Arel::Nodes::OuterJoin).
103
116
  on(arel[foreign_key].eq(arel_table[self.primary_key]))
@@ -1,3 +1,3 @@
1
1
  module Arlj
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -17,14 +17,14 @@ RSpec.describe Arlj::Base do
17
17
  Temping.create :child do
18
18
  with_columns do |t|
19
19
  t.integer :parent_id
20
- t.integer :col
20
+ t.integer :age
21
21
  end
22
22
  end
23
23
 
24
24
  before(:all) do
25
25
  @parent = Parent.create(name: 'John')
26
26
  (1..10).each do |n|
27
- @parent.children.create(col: n)
27
+ @parent.children.create(age: n)
28
28
  end
29
29
 
30
30
  @parent_no_child = Parent.create(name: 'Jane')
@@ -45,25 +45,42 @@ RSpec.describe Arlj::Base do
45
45
  assert{ children_count == @parent.children.size }
46
46
  end
47
47
 
48
- specify 'sum(col)' do
49
- children_sum_col = Parent.arlj_aggregate(:children, 'SUM(col)').pluck('children_sum_col').first
50
- assert{ children_sum_col == @parent.children.sum(:col) }
48
+ specify 'sum(age)' do
49
+ children_sum_age = Parent.arlj_aggregate(:children, 'SUM(age)').pluck('children_sum_age').first
50
+ assert{ children_sum_age == @parent.children.sum(:age) }
51
51
  end
52
52
 
53
- specify 'SUM(col) => name' do
54
- sum = Parent.arlj_aggregate(:children, 'sum(col)' => 'sum').pluck('sum').first
55
- assert{ sum == @parent.children.sum(:col) }
53
+ specify 'SUM(age) => name' do
54
+ sum = Parent.arlj_aggregate(:children, 'sum(age)' => 'sum').pluck('sum').first
55
+ assert{ sum == @parent.children.sum(:age) }
56
56
  end
57
57
 
58
- specify 'FAKE(col) raises error' do
59
- error = rescuing{ Parent.arlj_aggregate(:children, 'FAKE(col)') }
58
+ specify 'FAKE(age) raises error' do
59
+ error = rescuing{ Parent.arlj_aggregate(:children, 'FAKE(age)') }
60
60
  assert{ error }
61
61
  end
62
62
 
63
- specify 'COUNT(*) => count, SUM(col) => sum' do
64
- array = Parent.arlj_aggregate(:children, 'count(*)' => 'count', 'sum(col)' => 'sum').pluck('count', 'sum').first
63
+ specify 'COUNT(*) => count, SUM(age) => sum' do
64
+ array = Parent.arlj_aggregate(:children, 'count(*)' => 'count', 'sum(age)' => 'sum').pluck('count', 'sum').first
65
65
  assert{ array[0] == @parent.children.size }
66
- assert{ array[1] == @parent.children.sum(:col) }
66
+ assert{ array[1] == @parent.children.sum(:age) }
67
+ end
68
+
69
+ context 'where' do
70
+ specify 'COUNT(*), where: "age > 4"' do
71
+ count = Parent.arlj_aggregate(:children, 'COUNT(*)', where: 'age > 4').pluck('children_count').first
72
+ assert{ count == @parent.children.where('age > 4').count }
73
+ end
74
+
75
+ specify 'SUM(age), where: {age: 1..4}' do
76
+ count = Parent.arlj_aggregate(:children, 'SUM(age)', where: {age: 1..4}).pluck('children_sum_age').first
77
+ assert{ count == @parent.children.where(age: 1..4).sum(:age) }
78
+ end
79
+
80
+ specify 'COUNT(age), where: ["age = ?", 1]' do
81
+ count = Parent.arlj_aggregate(:children, 'SUM(age)', where: ['age = ?', 8]).pluck('children_sum_age').first
82
+ assert{ count == 8 }
83
+ end
67
84
  end
68
85
  end
69
86
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arlj
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Feng
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-05 00:00:00.000000000 Z
11
+ date: 2015-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -134,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
134
  version: '0'
135
135
  requirements: []
136
136
  rubyforge_project:
137
- rubygems_version: 2.2.2
137
+ rubygems_version: 2.4.5
138
138
  signing_key:
139
139
  specification_version: 4
140
140
  summary: ActiveRecord Left Join