arlj 0.0.2 → 0.1.0
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.
- 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
|