arlj 0.0.1 → 0.0.2
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/.travis.yml +5 -0
- data/LICENSE.txt +1 -1
- data/README.md +19 -12
- data/Rakefile +5 -1
- data/arlj.gemspec +1 -1
- data/lib/arlj/base.rb +106 -0
- data/lib/arlj/version.rb +1 -1
- data/lib/arlj.rb +9 -92
- data/spec/{arlj_spec.rb → base_spec.rb} +2 -2
- data/spec/left_joins_spec.rb +3 -3
- metadata +20 -19
- data/lib/arlj/left_joins.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a58db94fac67a5fa42539b8b1cc30beaf1134207
|
4
|
+
data.tar.gz: 3ffd40c1670e27ed4e6eefe82b20df628dd7b361
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: caf5c2c6a30881344c5f75074a5a1131263f68614574b28f3790a475008d638cd8745dbdbc3c0f0d7085d4bc863d112b310020b14ba06d3fa3613d133ef803d8
|
7
|
+
data.tar.gz: 5276383d71fdf062b199cd3370dfac5258fccf7856f5a753e8444dd5a741ab8d64ec19ba2c34b79d005e14d0286502ee4f89108bb5e5d49a159267ab7e2237ff
|
data/.travis.yml
ADDED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Arlj - ActiveRecord Left Join
|
1
|
+
# Arlj - ActiveRecord Left Join [](https://travis-ci.org/fengb/arlj)
|
2
2
|
|
3
3
|
Make left joins feel like first-class citizens in ActiveRecord.
|
4
4
|
|
@@ -37,7 +37,7 @@ ActiveRecord::Base.extend Arlj
|
|
37
37
|
Then begin to left join!
|
38
38
|
|
39
39
|
```ruby
|
40
|
-
puts Parent.
|
40
|
+
puts Parent.left_joins(:children).group('records.id').select('COUNT(children.id)').to_sql
|
41
41
|
=> SELECT COUNT(children.id)
|
42
42
|
FROM "parents"
|
43
43
|
LEFT OUTER JOIN "children"
|
@@ -45,12 +45,12 @@ puts Parent.arlj(:children).group('records.id').select('COUNT(children.id)').to_
|
|
45
45
|
GROUP BY records.id
|
46
46
|
```
|
47
47
|
|
48
|
-
`
|
48
|
+
`left_joins` is purposely low level to be extra chainable.
|
49
49
|
|
50
|
-
Arlj also adds an aggregation
|
50
|
+
Arlj also adds an aggregation method:
|
51
51
|
|
52
52
|
```ruby
|
53
|
-
Parent.
|
53
|
+
Parent.left_joins_aggregate(:children, 'COUNT(*)', 'SUM(col)' => 'total').select('children_count', 'total').to_sql
|
54
54
|
=> SELECT children_count
|
55
55
|
, total
|
56
56
|
FROM "parents"
|
@@ -62,22 +62,29 @@ Parent.arlj_aggregate(:children, 'COUNT(*)', 'SUM(col)' => 'total').select('chil
|
|
62
62
|
ON arlj_aggregate_children."parent_id" = "parents"."id"
|
63
63
|
```
|
64
64
|
|
65
|
-
`
|
66
|
-
most efficient implementation but it does offer a much better chaining
|
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
67
|
experience than using `group` at the top level.
|
68
68
|
|
69
|
-
|
70
|
-
`left_joins_aggregate` respectively
|
69
|
+
If you prefer, you may also use `arlj` and `arlj_aggregate` instead of
|
70
|
+
`left_joins` and `left_joins_aggregate` respectively. To prevent potential
|
71
|
+
naming conflicts, please use `Arlj::Base`:
|
71
72
|
|
72
73
|
```ruby
|
73
74
|
class Parent < ActiveRecord::Base
|
74
|
-
extend Arlj::
|
75
|
+
extend Arlj::Base
|
75
76
|
end
|
77
|
+
```
|
78
|
+
|
79
|
+
**Arlj** has an experimental flag that uses the **memoist** gem to memoize the
|
80
|
+
generated join SQL:
|
76
81
|
|
77
|
-
|
78
|
-
|
82
|
+
```ruby
|
83
|
+
Arlj.memoize!
|
79
84
|
```
|
80
85
|
|
86
|
+
This has not been proven to be faster.
|
87
|
+
|
81
88
|
## TODO
|
82
89
|
|
83
90
|
* Relations with conditions
|
data/Rakefile
CHANGED
data/arlj.gemspec
CHANGED
@@ -19,10 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'activerecord', '>= 3.1'
|
22
|
-
spec.add_runtime_dependency 'memoist', '~> 0.11.0'
|
23
22
|
|
24
23
|
spec.add_development_dependency 'bundler', '~> 1.7'
|
25
24
|
spec.add_development_dependency 'rake', '~> 10.0'
|
26
25
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
27
26
|
spec.add_development_dependency 'temping', '~> 3.2'
|
27
|
+
spec.add_development_dependency 'wrong', '~> 0.7'
|
28
28
|
end
|
data/lib/arlj/base.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
module Arlj
|
2
|
+
module Base
|
3
|
+
def self.memoize!
|
4
|
+
require 'memoist'
|
5
|
+
self.extend Memoist
|
6
|
+
self.memoize :arlj_sql, :arlj_aggregate_sql
|
7
|
+
end
|
8
|
+
|
9
|
+
def arlj(assoc)
|
10
|
+
# Example snippet:
|
11
|
+
# LEFT JOIN [assoc]
|
12
|
+
# ON [assoc].source_id = source.id
|
13
|
+
|
14
|
+
joins(arlj_sql(assoc))
|
15
|
+
end
|
16
|
+
|
17
|
+
# Example usage:
|
18
|
+
# arlj_aggregate(other, 'count(*)', 'sum(col)' => target_name)
|
19
|
+
def arlj_aggregate(assoc, *args)
|
20
|
+
# Example snippet:
|
21
|
+
# LEFT JOIN(SELECT source_id, [func]([column]) AS [target_name]
|
22
|
+
# FROM [assoc]
|
23
|
+
# GROUP BY [assoc].source_id) arlj_aggregate_[assoc]
|
24
|
+
# ON [assoc].source_id = source.id
|
25
|
+
|
26
|
+
joins(arlj_aggregate_sql(assoc, *args))
|
27
|
+
end
|
28
|
+
|
29
|
+
def arlj_arel(assoc)
|
30
|
+
refl = reflect_on_association(assoc)
|
31
|
+
arlj_left_join_arel(refl.klass.arel_table, refl.foreign_key)
|
32
|
+
end
|
33
|
+
|
34
|
+
def arlj_sql(assoc)
|
35
|
+
arlj_arel(assoc).join_sources
|
36
|
+
end
|
37
|
+
|
38
|
+
def arlj_aggregate_arel(assoc, *args)
|
39
|
+
options = args.extract_options!
|
40
|
+
|
41
|
+
refl = reflect_on_association(assoc)
|
42
|
+
refl_arel = refl.klass.arel_table
|
43
|
+
|
44
|
+
join_name = "arlj_aggregate_#{refl.table_name}"
|
45
|
+
|
46
|
+
columns = [refl_arel[refl.foreign_key]]
|
47
|
+
args.each do |thunk|
|
48
|
+
columns << parse_thunk(refl, assoc, refl_arel, thunk)
|
49
|
+
end
|
50
|
+
options.each do |thunk, name|
|
51
|
+
columns << parse_thunk(refl, assoc, refl_arel, thunk, name)
|
52
|
+
end
|
53
|
+
|
54
|
+
subq_arel =
|
55
|
+
refl_arel.project(columns).
|
56
|
+
from(refl_arel).
|
57
|
+
group(refl_arel[refl.foreign_key]).
|
58
|
+
as(join_name)
|
59
|
+
|
60
|
+
arlj_left_join_arel(subq_arel, refl.foreign_key)
|
61
|
+
end
|
62
|
+
|
63
|
+
def arlj_aggregate_sql(assoc, *args)
|
64
|
+
arlj_aggregate_arel(assoc, *args).join_sources
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
THUNK_PATTERN = /^([a-zA-Z]*)\((.*)\)$/
|
70
|
+
AGGREGATE_FUNCTIONS = {
|
71
|
+
'sum' => 'sum',
|
72
|
+
'average' => 'average',
|
73
|
+
'avg' => 'average',
|
74
|
+
'maximum' => 'maximum',
|
75
|
+
'max' => 'maximum',
|
76
|
+
'minimum' => 'minimum',
|
77
|
+
'min' => 'minimum',
|
78
|
+
'count' => 'count',
|
79
|
+
}.freeze
|
80
|
+
def parse_thunk(refl, assoc, arel, thunk, name=nil)
|
81
|
+
matchdata = THUNK_PATTERN.match(thunk)
|
82
|
+
if matchdata.nil?
|
83
|
+
raise "'#{thunk}' not parsable - must be of format 'func(column)'"
|
84
|
+
end
|
85
|
+
|
86
|
+
func = AGGREGATE_FUNCTIONS[matchdata[1].downcase]
|
87
|
+
if func.nil?
|
88
|
+
raise "'#{matchdata[1]}' not recognized - must be one of #{AGGREGATE_FUNCTIONS.keys}"
|
89
|
+
end
|
90
|
+
|
91
|
+
if matchdata[2] == '*'
|
92
|
+
column = refl.active_record_primary_key
|
93
|
+
name ||= "#{assoc}_#{func}"
|
94
|
+
else
|
95
|
+
column = matchdata[2]
|
96
|
+
name ||= "#{assoc}_#{func}_#{column}"
|
97
|
+
end
|
98
|
+
arel[column].send(func).as(name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def arlj_left_join_arel(arel, foreign_key)
|
102
|
+
arel_table.join(arel, Arel::Nodes::OuterJoin).
|
103
|
+
on(arel[foreign_key].eq(arel_table[self.primary_key]))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/arlj/version.rb
CHANGED
data/lib/arlj.rb
CHANGED
@@ -1,98 +1,15 @@
|
|
1
|
-
require '
|
1
|
+
require 'arlj/base'
|
2
2
|
|
3
3
|
module Arlj
|
4
|
-
autoload :
|
5
|
-
|
4
|
+
autoload :Version, 'arlj/version'
|
5
|
+
include Arlj::Base
|
6
6
|
|
7
|
-
|
7
|
+
alias_method :left_joins, :arlj
|
8
|
+
alias_method :left_joins_aggregate, :arlj_aggregate
|
9
|
+
alias_method :left_joins_arel, :arlj_arel
|
10
|
+
alias_method :left_joins_aggregate_arel, :arlj_aggregate_arel
|
8
11
|
|
9
|
-
def
|
10
|
-
|
11
|
-
# LEFT JOIN [assoc]
|
12
|
-
# ON [assoc].source_id = source.id
|
13
|
-
|
14
|
-
refl = reflect_on_association(assoc)
|
15
|
-
sources = arlj_arel_sources(refl.klass.arel_table, refl.foreign_key)
|
16
|
-
joins(sources)
|
17
|
-
end
|
18
|
-
|
19
|
-
# Example usage:
|
20
|
-
# arlj_aggregate(other, 'count(*)', 'sum(col)' => target_name)
|
21
|
-
def arlj_aggregate(assoc, *args)
|
22
|
-
# Example snippet:
|
23
|
-
# LEFT JOIN(SELECT source_id, [func]([column]) AS [target_name]
|
24
|
-
# FROM [assoc]
|
25
|
-
# GROUP BY [assoc].source_id) arlj_aggregate_[assoc]
|
26
|
-
# ON [assoc].source_id = source.id
|
27
|
-
|
28
|
-
sources = arlj_aggregate_sources(assoc, *args)
|
29
|
-
joins(sources)
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
THUNK_PATTERN = /^([a-zA-Z]*)\((.*)\)$/
|
35
|
-
AGGREGATE_FUNCTIONS = {
|
36
|
-
'sum' => 'sum',
|
37
|
-
'average' => 'average',
|
38
|
-
'avg' => 'average',
|
39
|
-
'maximum' => 'maximum',
|
40
|
-
'max' => 'maximum',
|
41
|
-
'minimum' => 'minimum',
|
42
|
-
'min' => 'minimum',
|
43
|
-
'count' => 'count',
|
44
|
-
}.freeze
|
45
|
-
def parse_thunk(refl, assoc, arel, thunk, name=nil)
|
46
|
-
matchdata = THUNK_PATTERN.match(thunk)
|
47
|
-
if matchdata.nil?
|
48
|
-
raise "'#{thunk}' not parsable - must be of format 'func(column)'"
|
49
|
-
end
|
50
|
-
|
51
|
-
func = AGGREGATE_FUNCTIONS[matchdata[1].downcase]
|
52
|
-
if func.nil?
|
53
|
-
raise "'#{matchdata[1]}' not recognized - must be one of #{AGGREGATE_FUNCTIONS.keys}"
|
54
|
-
end
|
55
|
-
|
56
|
-
if matchdata[2] == '*'
|
57
|
-
column = refl.active_record_primary_key
|
58
|
-
name ||= "#{assoc}_#{func}"
|
59
|
-
else
|
60
|
-
column = matchdata[2]
|
61
|
-
name ||= "#{assoc}_#{func}_#{column}"
|
62
|
-
end
|
63
|
-
arel[column].send(func).as(name)
|
64
|
-
end
|
65
|
-
|
66
|
-
def arlj_aggregate_sources(assoc, *args)
|
67
|
-
options = args.extract_options!
|
68
|
-
|
69
|
-
refl = reflect_on_association(assoc)
|
70
|
-
refl_arel = refl.klass.arel_table
|
71
|
-
|
72
|
-
join_name = "arlj_aggregate_#{refl.table_name}"
|
73
|
-
|
74
|
-
columns = [refl_arel[refl.foreign_key]]
|
75
|
-
args.each do |thunk|
|
76
|
-
columns << parse_thunk(refl, assoc, refl_arel, thunk)
|
77
|
-
end
|
78
|
-
options.each do |thunk, name|
|
79
|
-
columns << parse_thunk(refl, assoc, refl_arel, thunk, name)
|
80
|
-
end
|
81
|
-
|
82
|
-
subq_arel =
|
83
|
-
refl_arel.project(columns).
|
84
|
-
from(refl_arel).
|
85
|
-
group(refl_arel[refl.foreign_key]).
|
86
|
-
as(join_name)
|
87
|
-
|
88
|
-
arlj_arel_sources(subq_arel, refl.foreign_key)
|
89
|
-
end
|
90
|
-
memoize :arlj_aggregate_sources
|
91
|
-
|
92
|
-
def arlj_arel_sources(arel, foreign_key)
|
93
|
-
arel_join =
|
94
|
-
arel_table.join(arel, Arel::Nodes::OuterJoin).
|
95
|
-
on(arel[foreign_key].eq(arel_table[self.primary_key]))
|
96
|
-
arel_join.join_sources
|
12
|
+
def self.memoize!
|
13
|
+
Arlj::Base.memoize!
|
97
14
|
end
|
98
15
|
end
|
@@ -3,13 +3,13 @@ require 'spec_helper'
|
|
3
3
|
require 'arlj'
|
4
4
|
require 'temping'
|
5
5
|
|
6
|
-
RSpec.describe Arlj do
|
6
|
+
RSpec.describe Arlj::Base do
|
7
7
|
Temping.create :parent do
|
8
8
|
with_columns do |t|
|
9
9
|
t.string :name
|
10
10
|
end
|
11
11
|
|
12
|
-
extend Arlj
|
12
|
+
extend Arlj::Base
|
13
13
|
|
14
14
|
has_many :children
|
15
15
|
end
|
data/spec/left_joins_spec.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
require 'arlj
|
3
|
+
require 'arlj'
|
4
4
|
|
5
|
-
RSpec.describe Arlj
|
5
|
+
RSpec.describe Arlj, 'left_joins' do
|
6
6
|
class LjParent < Parent
|
7
|
-
extend Arlj
|
7
|
+
extend Arlj
|
8
8
|
end
|
9
9
|
|
10
10
|
specify '#left_joins does arlj stuff' do
|
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.0.2
|
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-
|
11
|
+
date: 2014-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.1'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: memoist
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 0.11.0
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 0.11.0
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: bundler
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +80,20 @@ dependencies:
|
|
94
80
|
- - "~>"
|
95
81
|
- !ruby/object:Gem::Version
|
96
82
|
version: '3.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: wrong
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.7'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.7'
|
97
97
|
description: Make left joins feel like first-class citizens in ActiveRecord.
|
98
98
|
email:
|
99
99
|
- contact@fengb.info
|
@@ -102,15 +102,16 @@ extensions: []
|
|
102
102
|
extra_rdoc_files: []
|
103
103
|
files:
|
104
104
|
- ".gitignore"
|
105
|
+
- ".travis.yml"
|
105
106
|
- Gemfile
|
106
107
|
- LICENSE.txt
|
107
108
|
- README.md
|
108
109
|
- Rakefile
|
109
110
|
- arlj.gemspec
|
110
111
|
- lib/arlj.rb
|
111
|
-
- lib/arlj/
|
112
|
+
- lib/arlj/base.rb
|
112
113
|
- lib/arlj/version.rb
|
113
|
-
- spec/
|
114
|
+
- spec/base_spec.rb
|
114
115
|
- spec/left_joins_spec.rb
|
115
116
|
- spec/spec_helper.rb
|
116
117
|
homepage: https://github.com/fengb/arlj
|
@@ -138,6 +139,6 @@ signing_key:
|
|
138
139
|
specification_version: 4
|
139
140
|
summary: ActiveRecord Left Join
|
140
141
|
test_files:
|
141
|
-
- spec/
|
142
|
+
- spec/base_spec.rb
|
142
143
|
- spec/left_joins_spec.rb
|
143
144
|
- spec/spec_helper.rb
|