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 +4 -4
- data/README.md +56 -11
- data/lib/arlj/base.rb +27 -14
- data/lib/arlj/version.rb +1 -1
- data/spec/base_spec.rb +30 -13
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d66639a751ea47513133e0ce7d6bbde7e3ea305
|
4
|
+
data.tar.gz: 7063bd9f2a5e51647a644f0ca0b5de206195866a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
48
|
+
`left_joins` is purposely low level for maximum control.
|
49
49
|
|
50
|
-
Arlj
|
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(*)'
|
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
|
-
|
66
|
-
|
67
|
-
|
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,
|
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
|
-
*
|
91
|
-
* `
|
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
|
|
data/lib/arlj/base.rb
CHANGED
@@ -41,21 +41,26 @@ module Arlj
|
|
41
41
|
refl = reflect_on_association(assoc)
|
42
42
|
refl_arel = refl.klass.arel_table
|
43
43
|
|
44
|
-
|
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 |
|
48
|
-
columns <<
|
47
|
+
args.each do |arg|
|
48
|
+
columns << parse_directive(refl, assoc, refl_arel, arg)
|
49
49
|
end
|
50
|
-
options.each do |
|
51
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
81
|
-
|
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 "'#{
|
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]))
|
data/lib/arlj/version.rb
CHANGED
data/spec/base_spec.rb
CHANGED
@@ -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 :
|
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(
|
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(
|
49
|
-
|
50
|
-
assert{
|
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(
|
54
|
-
sum = Parent.arlj_aggregate(:children, 'sum(
|
55
|
-
assert{ sum == @parent.children.sum(:
|
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(
|
59
|
-
error = rescuing{ Parent.arlj_aggregate(:children, 'FAKE(
|
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(
|
64
|
-
array = Parent.arlj_aggregate(:children, 'count(*)' => 'count', 'sum(
|
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(:
|
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
|
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:
|
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.
|
137
|
+
rubygems_version: 2.4.5
|
138
138
|
signing_key:
|
139
139
|
specification_version: 4
|
140
140
|
summary: ActiveRecord Left Join
|