arlj 0.0.1
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 +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +2 -0
- data/arlj.gemspec +28 -0
- data/lib/arlj/left_joins.rb +10 -0
- data/lib/arlj/version.rb +3 -0
- data/lib/arlj.rb +98 -0
- data/spec/arlj_spec.rb +69 -0
- data/spec/left_joins_spec.rb +23 -0
- data/spec/spec_helper.rb +8 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 674e9b2df96b4850dc128bc5de58252833a7ba95
|
4
|
+
data.tar.gz: aa92466cd390f09ad567acbad0a05a13cdfa995f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f6498872bd416cb091ab5a69ded518af390097483583a72bb00217598373da17f608f2234ce49376978673acbd7b5e479120f1567f261ba0270ad43e449b1253
|
7
|
+
data.tar.gz: 6ed666eb60114b8225dd5ad04e51cc07094cbd81e3be9f8170af3c38e30606c2c7424f2c58aa651dcb17ac6bc4488b52547a9dbbd3f6737260c72c7ffd1ff368
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 fengb
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# Arlj - ActiveRecord Left Join
|
2
|
+
|
3
|
+
Make left joins feel like first-class citizens in ActiveRecord.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'arlj'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install arlj
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Load Arlj into your class:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class Parent < ActiveRecord::Base
|
27
|
+
extend Arlj
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
Or extend all of ActiveRecord models:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
ActiveRecord::Base.extend Arlj
|
35
|
+
```
|
36
|
+
|
37
|
+
Then begin to left join!
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
puts Parent.arlj(:children).group('records.id').select('COUNT(children.id)').to_sql
|
41
|
+
=> SELECT COUNT(children.id)
|
42
|
+
FROM "parents"
|
43
|
+
LEFT OUTER JOIN "children"
|
44
|
+
ON "children"."parent_id" = "parents"."id"
|
45
|
+
GROUP BY records.id
|
46
|
+
```
|
47
|
+
|
48
|
+
`arlj` is purposely low level to be extra chainable.
|
49
|
+
|
50
|
+
Arlj also adds an aggregation syntax:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
Parent.arlj_aggregate(:children, 'COUNT(*)', 'SUM(col)' => 'total').select('children_count', 'total').to_sql
|
54
|
+
=> SELECT children_count
|
55
|
+
, total
|
56
|
+
FROM "parents"
|
57
|
+
LEFT OUTER JOIN (SELECT "children"."parent_id"
|
58
|
+
, COUNT("children"."id") AS children_count
|
59
|
+
, SUM("children"."col") AS total
|
60
|
+
FROM "children"
|
61
|
+
GROUP BY "children"."parent_id") arlj_aggregate_children
|
62
|
+
ON arlj_aggregate_children."parent_id" = "parents"."id"
|
63
|
+
```
|
64
|
+
|
65
|
+
`arlj_aggregate` currently uses a subquery to hide its aggregation. It's not the
|
66
|
+
most efficient implementation but it does offer a much better chaining
|
67
|
+
experience than using `group` at the top level.
|
68
|
+
|
69
|
+
Arlj can also alias `arlj` and `arlj_aggregate` to `left_joins` and
|
70
|
+
`left_joins_aggregate` respectively:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class Parent < ActiveRecord::Base
|
74
|
+
extend Arlj::LeftJoins
|
75
|
+
end
|
76
|
+
|
77
|
+
Parent.left_joins(:children).group('records.id').select('COUNT(children.id)')
|
78
|
+
Parent.left_joins_aggregate(:children, 'COUNT(*)', 'SUM(col)' => 'total')
|
79
|
+
```
|
80
|
+
|
81
|
+
## TODO
|
82
|
+
|
83
|
+
* Relations with conditions
|
84
|
+
* `LEFT JOIN [...] ON`
|
85
|
+
* `has_and_belongs_to_many`
|
86
|
+
* `has_many :through =>`
|
87
|
+
|
88
|
+
## Contributing
|
89
|
+
|
90
|
+
1. Fork it ( https://github.com/fengb/arlj/fork )
|
91
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
92
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
93
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
94
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/arlj.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'arlj/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'arlj'
|
8
|
+
spec.version = Arlj::VERSION
|
9
|
+
spec.authors = ['Benjamin Feng']
|
10
|
+
spec.email = ['contact@fengb.info']
|
11
|
+
spec.summary = %q{ActiveRecord Left Join}
|
12
|
+
spec.description = %q{Make left joins feel like first-class citizens in ActiveRecord.}
|
13
|
+
spec.homepage = 'https://github.com/fengb/arlj'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'activerecord', '>= 3.1'
|
22
|
+
spec.add_runtime_dependency 'memoist', '~> 0.11.0'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
27
|
+
spec.add_development_dependency 'temping', '~> 3.2'
|
28
|
+
end
|
data/lib/arlj/version.rb
ADDED
data/lib/arlj.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'memoist'
|
2
|
+
|
3
|
+
module Arlj
|
4
|
+
autoload :LeftJoins, 'arlj/left_joins'
|
5
|
+
autoload :VERSION, 'arlj/version'
|
6
|
+
|
7
|
+
extend Memoist
|
8
|
+
|
9
|
+
def arlj(assoc)
|
10
|
+
# Example snippet:
|
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
|
97
|
+
end
|
98
|
+
end
|
data/spec/arlj_spec.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'arlj'
|
4
|
+
require 'temping'
|
5
|
+
|
6
|
+
RSpec.describe Arlj do
|
7
|
+
Temping.create :parent do
|
8
|
+
with_columns do |t|
|
9
|
+
t.string :name
|
10
|
+
end
|
11
|
+
|
12
|
+
extend Arlj
|
13
|
+
|
14
|
+
has_many :children
|
15
|
+
end
|
16
|
+
|
17
|
+
Temping.create :child do
|
18
|
+
with_columns do |t|
|
19
|
+
t.integer :parent_id
|
20
|
+
t.integer :col
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
before(:all) do
|
25
|
+
@parent = Parent.create(name: 'John')
|
26
|
+
(1..10).each do |n|
|
27
|
+
@parent.children.create(col: n)
|
28
|
+
end
|
29
|
+
|
30
|
+
@parent_no_child = Parent.create(name: 'Jane')
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#arlj' do
|
34
|
+
it 'joins the other table' do
|
35
|
+
counts = Parent.arlj(:children).
|
36
|
+
group('parents.id').
|
37
|
+
pluck('COUNT(children.id)')
|
38
|
+
assert{ counts.sort == [0, 10] }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#arlj_aggregate' do
|
43
|
+
specify 'COUNT(*)' do
|
44
|
+
children_count = Parent.arlj_aggregate(:children, 'count(*)').pluck('children_count').first
|
45
|
+
assert{ children_count == @parent.children.size }
|
46
|
+
end
|
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) }
|
51
|
+
end
|
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) }
|
56
|
+
end
|
57
|
+
|
58
|
+
specify 'FAKE(col) raises error' do
|
59
|
+
error = rescuing{ Parent.arlj_aggregate(:children, 'FAKE(col)') }
|
60
|
+
assert{ error }
|
61
|
+
end
|
62
|
+
|
63
|
+
specify 'COUNT(*) => count, SUM(col) => sum' do
|
64
|
+
array = Parent.arlj_aggregate(:children, 'count(*)' => 'count', 'sum(col)' => 'sum').pluck('count', 'sum').first
|
65
|
+
assert{ array[0] == @parent.children.size }
|
66
|
+
assert{ array[1] == @parent.children.sum(:col) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'arlj/left_joins'
|
4
|
+
|
5
|
+
RSpec.describe Arlj::LeftJoins do
|
6
|
+
class LjParent < Parent
|
7
|
+
extend Arlj::LeftJoins
|
8
|
+
end
|
9
|
+
|
10
|
+
specify '#left_joins does arlj stuff' do
|
11
|
+
counts = LjParent.left_joins(:children).
|
12
|
+
group('parents.id').
|
13
|
+
pluck('COUNT(children.id)')
|
14
|
+
assert{ counts.sort == [0, 10] }
|
15
|
+
end
|
16
|
+
|
17
|
+
specify '#left_joins_aggregate does arlj_aggregate stuff' do
|
18
|
+
children_count = LjParent.left_joins_aggregate(:children, 'count(*)').
|
19
|
+
pluck('children_count').
|
20
|
+
first
|
21
|
+
assert{ children_count == 10 }
|
22
|
+
end
|
23
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: arlj
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Benjamin Feng
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
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
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.7'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: temping
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.2'
|
97
|
+
description: Make left joins feel like first-class citizens in ActiveRecord.
|
98
|
+
email:
|
99
|
+
- contact@fengb.info
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- Gemfile
|
106
|
+
- LICENSE.txt
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- arlj.gemspec
|
110
|
+
- lib/arlj.rb
|
111
|
+
- lib/arlj/left_joins.rb
|
112
|
+
- lib/arlj/version.rb
|
113
|
+
- spec/arlj_spec.rb
|
114
|
+
- spec/left_joins_spec.rb
|
115
|
+
- spec/spec_helper.rb
|
116
|
+
homepage: https://github.com/fengb/arlj
|
117
|
+
licenses:
|
118
|
+
- MIT
|
119
|
+
metadata: {}
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 2.2.2
|
137
|
+
signing_key:
|
138
|
+
specification_version: 4
|
139
|
+
summary: ActiveRecord Left Join
|
140
|
+
test_files:
|
141
|
+
- spec/arlj_spec.rb
|
142
|
+
- spec/left_joins_spec.rb
|
143
|
+
- spec/spec_helper.rb
|