joiner 0.2.0 → 0.3.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 +1 -1
- data/joiner.gemspec +3 -2
- data/lib/joiner/joins.rb +61 -45
- data/spec/internal/app/models/comment.rb +1 -1
- data/spec/joiner/joins_spec.rb +32 -128
- data/spec/spec_helper.rb +1 -1
- metadata +28 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa6e6cc16da43c455ac08933d6c0a0b4e27914c9
|
4
|
+
data.tar.gz: a77958f970489d51253f581afdea3317b8c764bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12d1d52dd1fcc4f91bb5f4cc0472bea0729fbbcdf3ab563bc9a7b7c1ce16db10ab77fc327ab80437e923a67ba91d7ee269022bcb46af68de6f74b558d21d54b0
|
7
|
+
data.tar.gz: 2a3d2d038dd4c725dbeadc456e1c1cd83a082abc6467ae5cfc52c71f38a8bcb292c7b7fa231d202108a53a45ce9a50da832c616b3bf0a2d4b8b754d461bff198
|
data/README.md
CHANGED
@@ -9,7 +9,7 @@ If this gem is used by anyone other than myself/Thinking Sphinx, I'll be surpris
|
|
9
9
|
It's a gem - so you can either install it yourself, or add it to the appropriate Gemfile or gemspec.
|
10
10
|
|
11
11
|
```term
|
12
|
-
gem install joiner --version 0.
|
12
|
+
gem install joiner --version 0.3.0
|
13
13
|
```
|
14
14
|
|
15
15
|
## Usage
|
data/joiner.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
Gem::Specification.new do |spec|
|
3
3
|
spec.name = 'joiner'
|
4
|
-
spec.version = '0.
|
4
|
+
spec.version = '0.3.0'
|
5
5
|
spec.authors = ['Pat Allan']
|
6
6
|
spec.email = ['pat@freelancing-gods.com']
|
7
7
|
spec.summary = %q{Builds ActiveRecord joins from association paths}
|
@@ -13,9 +13,10 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.test_files = spec.files.grep(%r{^(spec)/})
|
14
14
|
spec.require_paths = ['lib']
|
15
15
|
|
16
|
-
spec.add_runtime_dependency 'activerecord',
|
16
|
+
spec.add_runtime_dependency 'activerecord', '>= 4.1.0'
|
17
17
|
|
18
18
|
spec.add_development_dependency 'combustion', '~> 0.5.1'
|
19
|
+
spec.add_development_dependency 'rails', '>= 4.1.0'
|
19
20
|
spec.add_development_dependency 'rspec-rails', '~> 2.14.1'
|
20
21
|
spec.add_development_dependency 'sqlite3', '~> 1.3.8'
|
21
22
|
end
|
data/lib/joiner/joins.rb
CHANGED
@@ -4,78 +4,94 @@ class Joiner::Joins
|
|
4
4
|
attr_reader :model
|
5
5
|
|
6
6
|
def initialize(model)
|
7
|
-
@model
|
8
|
-
@
|
7
|
+
@model = model
|
8
|
+
@base = JoinDependency.new model, [], []
|
9
|
+
@joins_cache = ActiveSupport::OrderedHash.new
|
9
10
|
end
|
10
11
|
|
11
12
|
def add_join_to(path)
|
12
|
-
|
13
|
+
@joins_cache[path] ||= build_join(path)
|
13
14
|
end
|
14
15
|
|
15
16
|
def alias_for(path)
|
16
|
-
return model.
|
17
|
-
|
18
|
-
join_for(path).aliased_table_name
|
17
|
+
return model.table_name if path.empty?
|
18
|
+
add_join_to(path).aliased_table_name
|
19
19
|
end
|
20
20
|
|
21
21
|
def join_values
|
22
|
-
@
|
22
|
+
@base
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
26
26
|
|
27
|
-
def
|
28
|
-
|
27
|
+
def build_join(path)
|
28
|
+
if join = find_join(path)
|
29
|
+
return join
|
30
|
+
end
|
31
|
+
|
32
|
+
base_node, short_path = relative_path(path)
|
33
|
+
|
34
|
+
join = build_join_association(short_path, base_node.base_klass)
|
35
|
+
base_node.children << join
|
36
|
+
construct_tables! base_node, join
|
37
|
+
|
38
|
+
find_join(path)
|
29
39
|
end
|
30
40
|
|
31
|
-
def
|
32
|
-
|
33
|
-
reflection = reflection_for path
|
34
|
-
reflection.nil? ? nil : JoinDependency::JoinAssociation.new(
|
35
|
-
reflection, base, parent_join_for(path)
|
36
|
-
).tap { |join|
|
37
|
-
join.join_type = Arel::OuterJoin
|
41
|
+
def build_join_association(path, base_class)
|
42
|
+
return nil if path.empty?
|
38
43
|
|
39
|
-
|
40
|
-
|
41
|
-
|
44
|
+
step = path.first
|
45
|
+
reflection = find_reflection(step, base_class)
|
46
|
+
reflection.check_validity!
|
47
|
+
|
48
|
+
JoinDependency::JoinAssociation.new reflection, [build_join_association(path[1..-1], reflection.klass)].compact
|
42
49
|
end
|
43
50
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
51
|
+
def find_join(path, base = nil)
|
52
|
+
base ||= @base.join_root
|
53
|
+
|
54
|
+
return base if path.empty?
|
55
|
+
|
56
|
+
if next_step = base.children.detect{ |c| c.reflection.name == path.first }
|
57
|
+
find_join path[1..-1], next_step
|
49
58
|
end
|
50
59
|
end
|
51
60
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
61
|
+
def relative_path(path)
|
62
|
+
short_path = []
|
63
|
+
test_path = path.dup
|
64
|
+
|
65
|
+
while test_path.size > 1
|
66
|
+
short_path << test_path.pop
|
67
|
+
node = find_join(test_path)
|
68
|
+
return [node, short_path] if node
|
69
|
+
end
|
55
70
|
|
56
|
-
|
57
|
-
path.length == 1 ? base.join_base : parent_for(path)
|
71
|
+
[@base.join_root, path]
|
58
72
|
end
|
59
73
|
|
60
|
-
def
|
61
|
-
|
62
|
-
klass = parent.respond_to?(:base_klass) ? parent.base_klass :
|
63
|
-
parent.active_record
|
64
|
-
klass.reflections[path.last]
|
74
|
+
def find_reflection(name, klass)
|
75
|
+
klass.reflect_on_association(name)
|
65
76
|
end
|
66
77
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
78
|
+
def table_aliases_for(parent, node)
|
79
|
+
node.reflection.chain.map { |reflection|
|
80
|
+
@base.alias_tracker.aliased_table_for(
|
81
|
+
reflection.table_name,
|
82
|
+
table_alias_for(reflection, parent, reflection != node.reflection)
|
83
|
+
)
|
84
|
+
}
|
85
|
+
end
|
73
86
|
|
74
|
-
|
75
|
-
|
87
|
+
def construct_tables!(parent, node)
|
88
|
+
node.tables = table_aliases_for(parent, node)
|
89
|
+
node.children.each { |child| construct_tables! node, child }
|
90
|
+
end
|
76
91
|
|
77
|
-
|
78
|
-
|
79
|
-
|
92
|
+
def table_alias_for(reflection, parent, join)
|
93
|
+
name = "#{reflection.plural_name}_#{parent.table_name}"
|
94
|
+
name << "_join" if join
|
95
|
+
name
|
80
96
|
end
|
81
97
|
end
|
data/spec/joiner/joins_spec.rb
CHANGED
@@ -3,177 +3,81 @@ require 'spec_helper'
|
|
3
3
|
describe Joiner::Joins do
|
4
4
|
JoinDependency = ::ActiveRecord::Associations::JoinDependency
|
5
5
|
|
6
|
-
|
7
|
-
let(:model) { model_double 'articles' }
|
8
|
-
let(:base) {
|
9
|
-
double('base', :active_record => model, :join_base => join_base)
|
10
|
-
}
|
11
|
-
let(:join_base) { double('join base') }
|
12
|
-
let(:join) { join_double 'users' }
|
13
|
-
let(:sub_join) { join_double 'posts' }
|
14
|
-
|
15
|
-
def join_double(table_alias)
|
16
|
-
double 'join',
|
17
|
-
:join_type= => nil,
|
18
|
-
:aliased_table_name => table_alias,
|
19
|
-
:reflection => double('reflection'),
|
20
|
-
:conditions => []
|
21
|
-
end
|
22
|
-
|
23
|
-
def model_double(table_name = nil)
|
24
|
-
double 'model', :quoted_table_name => table_name, :reflections => {}
|
25
|
-
end
|
26
|
-
|
27
|
-
before :each do
|
28
|
-
JoinDependency.stub :new => base
|
29
|
-
JoinDependency::JoinAssociation.stub(:new).and_return(join, sub_join)
|
30
|
-
model.reflections[:user] = join.reflection
|
31
|
-
|
32
|
-
join.stub :active_record => model_double
|
33
|
-
join.active_record.reflections[:posts] = sub_join.reflection
|
34
|
-
end
|
6
|
+
subject { Joiner::Joins.new Article }
|
35
7
|
|
36
8
|
describe '#add_join_to' do
|
37
|
-
before :each do
|
38
|
-
JoinDependency::JoinAssociation.unstub :new
|
39
|
-
end
|
40
|
-
|
41
9
|
it "adds just one join for a stack with a single association" do
|
42
10
|
JoinDependency::JoinAssociation.should_receive(:new).
|
43
|
-
with(
|
11
|
+
with(Article.reflections[:user], []).once.and_call_original
|
44
12
|
|
45
|
-
|
13
|
+
subject.add_join_to([:user])
|
46
14
|
end
|
47
15
|
|
48
16
|
it "does not duplicate joins when given the same stack twice" do
|
49
|
-
JoinDependency::JoinAssociation.should_receive(:new).once.
|
17
|
+
JoinDependency::JoinAssociation.should_receive(:new).once.and_call_original
|
50
18
|
|
51
|
-
|
52
|
-
|
19
|
+
subject.add_join_to([:user])
|
20
|
+
subject.add_join_to([:user])
|
53
21
|
end
|
54
22
|
|
55
|
-
context '
|
23
|
+
context 'when joins are nested' do
|
56
24
|
it "adds two joins for a stack with two associations" do
|
57
25
|
JoinDependency::JoinAssociation.should_receive(:new).
|
58
|
-
with(
|
26
|
+
with(Article.reflections[:user], kind_of(Array)).once.and_call_original
|
59
27
|
JoinDependency::JoinAssociation.should_receive(:new).
|
60
|
-
with(
|
28
|
+
with(User.reflections[:comments], kind_of(Array)).once.and_call_original
|
61
29
|
|
62
|
-
|
30
|
+
subject.add_join_to([:user, :comments])
|
63
31
|
end
|
64
32
|
|
65
33
|
it "extends upon existing joins when given stacks where parts are already mapped" do
|
66
|
-
JoinDependency::JoinAssociation.should_receive(:new).twice.
|
67
|
-
and_return(join, sub_join)
|
68
|
-
|
69
|
-
joins.add_join_to([:user])
|
70
|
-
joins.add_join_to([:user, :posts])
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
context 'join with conditions' do
|
75
|
-
let(:connection) { double }
|
76
|
-
let(:parent) { double :aliased_table_name => 'qux' }
|
77
|
-
|
78
|
-
before :each do
|
79
|
-
JoinDependency::JoinAssociation.stub :new => join
|
80
|
-
|
81
|
-
join.stub :parent => parent
|
82
|
-
model.stub :connection => connection
|
83
|
-
connection.stub(:quote_table_name) { |table| "\"#{table}\"" }
|
84
|
-
end
|
85
|
-
|
86
|
-
it "leaves standard conditions untouched" do
|
87
|
-
join.stub :conditions => 'foo = bar'
|
88
|
-
|
89
|
-
joins.add_join_to [:user]
|
90
|
-
|
91
|
-
join.conditions.should == 'foo = bar'
|
92
|
-
end
|
93
|
-
|
94
|
-
it "modifies filtered polymorphic conditions" do
|
95
|
-
join.stub :conditions => '::ts_join_alias::.foo = bar'
|
96
|
-
|
97
|
-
joins.add_join_to [:user]
|
34
|
+
JoinDependency::JoinAssociation.should_receive(:new).twice.and_call_original
|
98
35
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
it "modifies filtered polymorphic conditions within arrays" do
|
103
|
-
join.stub :conditions => ['::ts_join_alias::.foo = bar']
|
104
|
-
|
105
|
-
joins.add_join_to [:user]
|
36
|
+
join1 = subject.add_join_to([:user])
|
37
|
+
join2 = subject.add_join_to([:user, :comments])
|
106
38
|
|
107
|
-
|
108
|
-
end
|
109
|
-
|
110
|
-
it "does not modify conditions as hashes" do
|
111
|
-
join.stub :conditions => [{:foo => 'bar'}]
|
112
|
-
|
113
|
-
joins.add_join_to [:user]
|
114
|
-
|
115
|
-
join.conditions.should == [{:foo => 'bar'}]
|
39
|
+
join1.children.should include(join2)
|
116
40
|
end
|
117
41
|
end
|
118
42
|
end
|
119
43
|
|
120
44
|
describe '#alias_for' do
|
121
45
|
it "returns the model's table name when no stack is given" do
|
122
|
-
|
46
|
+
subject.alias_for([]).should == 'articles'
|
123
47
|
end
|
124
48
|
|
125
|
-
it "
|
126
|
-
|
127
|
-
|
128
|
-
with(join.reflection, base, join_base).once.and_return(join)
|
129
|
-
|
130
|
-
joins.alias_for([:user])
|
49
|
+
it "gets join association using #add_join_to" do
|
50
|
+
subject.should_receive(:add_join_to).with([:user]).and_call_original
|
51
|
+
subject.alias_for([:user])
|
131
52
|
end
|
132
53
|
|
133
54
|
it "returns the aliased table name for the join" do
|
134
|
-
|
55
|
+
subject.alias_for([:user]).should == 'users'
|
135
56
|
end
|
136
57
|
|
137
58
|
it "does not duplicate joins when given the same stack twice" do
|
138
|
-
JoinDependency::JoinAssociation.
|
139
|
-
JoinDependency::JoinAssociation.should_receive(:new).once.and_return(join)
|
59
|
+
JoinDependency::JoinAssociation.should_receive(:new).once.and_call_original
|
140
60
|
|
141
|
-
|
142
|
-
|
61
|
+
subject.alias_for([:user])
|
62
|
+
subject.alias_for([:user])
|
143
63
|
end
|
144
64
|
|
145
|
-
context '
|
146
|
-
it "adds two joins for a stack with two associations" do
|
147
|
-
JoinDependency::JoinAssociation.unstub :new
|
148
|
-
JoinDependency::JoinAssociation.should_receive(:new).
|
149
|
-
with(join.reflection, base, join_base).once.and_return(join)
|
150
|
-
JoinDependency::JoinAssociation.should_receive(:new).
|
151
|
-
with(sub_join.reflection, base, join).once.and_return(sub_join)
|
152
|
-
|
153
|
-
joins.alias_for([:user, :posts])
|
154
|
-
end
|
155
|
-
|
65
|
+
context 'when joins are nested' do
|
156
66
|
it "returns the sub join's aliased table name" do
|
157
|
-
|
158
|
-
end
|
159
|
-
|
160
|
-
it "extends upon existing joins when given stacks where parts are already mapped" do
|
161
|
-
JoinDependency::JoinAssociation.unstub :new
|
162
|
-
JoinDependency::JoinAssociation.should_receive(:new).twice.
|
163
|
-
and_return(join, sub_join)
|
164
|
-
|
165
|
-
joins.alias_for([:user])
|
166
|
-
joins.alias_for([:user, :posts])
|
67
|
+
subject.alias_for([:user, :comments]).should == 'comments'
|
167
68
|
end
|
168
69
|
end
|
169
70
|
end
|
170
71
|
|
171
72
|
describe '#join_values' do
|
172
|
-
it "returns all joins that have been created" do
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
73
|
+
it "returns JoinDependency with all joins that have been created" do
|
74
|
+
join1 = subject.add_join_to([:user])
|
75
|
+
join2 = subject.add_join_to([:comments])
|
76
|
+
join3 = subject.add_join_to([:comments, :user])
|
77
|
+
|
78
|
+
join_values = subject.join_values
|
79
|
+
join_values.should be_a JoinDependency
|
80
|
+
join_values.join_root.children.should == [join1, join2]
|
177
81
|
end
|
178
82
|
end
|
179
83
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,75 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: joiner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pat Allan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 3.1.0
|
20
|
-
- - <
|
17
|
+
- - ">="
|
21
18
|
- !ruby/object:Gem::Version
|
22
19
|
version: 4.1.0
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: 3.1.0
|
30
|
-
- - <
|
24
|
+
- - ">="
|
31
25
|
- !ruby/object:Gem::Version
|
32
26
|
version: 4.1.0
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: combustion
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
36
30
|
requirements:
|
37
|
-
- - ~>
|
31
|
+
- - "~>"
|
38
32
|
- !ruby/object:Gem::Version
|
39
33
|
version: 0.5.1
|
40
34
|
type: :development
|
41
35
|
prerelease: false
|
42
36
|
version_requirements: !ruby/object:Gem::Requirement
|
43
37
|
requirements:
|
44
|
-
- - ~>
|
38
|
+
- - "~>"
|
45
39
|
- !ruby/object:Gem::Version
|
46
40
|
version: 0.5.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 4.1.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 4.1.0
|
47
55
|
- !ruby/object:Gem::Dependency
|
48
56
|
name: rspec-rails
|
49
57
|
requirement: !ruby/object:Gem::Requirement
|
50
58
|
requirements:
|
51
|
-
- - ~>
|
59
|
+
- - "~>"
|
52
60
|
- !ruby/object:Gem::Version
|
53
61
|
version: 2.14.1
|
54
62
|
type: :development
|
55
63
|
prerelease: false
|
56
64
|
version_requirements: !ruby/object:Gem::Requirement
|
57
65
|
requirements:
|
58
|
-
- - ~>
|
66
|
+
- - "~>"
|
59
67
|
- !ruby/object:Gem::Version
|
60
68
|
version: 2.14.1
|
61
69
|
- !ruby/object:Gem::Dependency
|
62
70
|
name: sqlite3
|
63
71
|
requirement: !ruby/object:Gem::Requirement
|
64
72
|
requirements:
|
65
|
-
- - ~>
|
73
|
+
- - "~>"
|
66
74
|
- !ruby/object:Gem::Version
|
67
75
|
version: 1.3.8
|
68
76
|
type: :development
|
69
77
|
prerelease: false
|
70
78
|
version_requirements: !ruby/object:Gem::Requirement
|
71
79
|
requirements:
|
72
|
-
- - ~>
|
80
|
+
- - "~>"
|
73
81
|
- !ruby/object:Gem::Version
|
74
82
|
version: 1.3.8
|
75
83
|
description: Builds ActiveRecord outer joins from association paths and provides references
|
@@ -80,7 +88,7 @@ executables: []
|
|
80
88
|
extensions: []
|
81
89
|
extra_rdoc_files: []
|
82
90
|
files:
|
83
|
-
- .gitignore
|
91
|
+
- ".gitignore"
|
84
92
|
- Gemfile
|
85
93
|
- LICENSE.txt
|
86
94
|
- README.md
|
@@ -109,17 +117,17 @@ require_paths:
|
|
109
117
|
- lib
|
110
118
|
required_ruby_version: !ruby/object:Gem::Requirement
|
111
119
|
requirements:
|
112
|
-
- -
|
120
|
+
- - ">="
|
113
121
|
- !ruby/object:Gem::Version
|
114
122
|
version: '0'
|
115
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
124
|
requirements:
|
117
|
-
- -
|
125
|
+
- - ">="
|
118
126
|
- !ruby/object:Gem::Version
|
119
127
|
version: '0'
|
120
128
|
requirements: []
|
121
129
|
rubyforge_project:
|
122
|
-
rubygems_version: 2.
|
130
|
+
rubygems_version: 2.2.2
|
123
131
|
signing_key:
|
124
132
|
specification_version: 4
|
125
133
|
summary: Builds ActiveRecord joins from association paths
|