joiner 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7b7b02b7622a82eeefbf7a3abca876d752e809d0
4
- data.tar.gz: 9b6809d20db5b15abb97f40a06349a8186d3f863
3
+ metadata.gz: fa6e6cc16da43c455ac08933d6c0a0b4e27914c9
4
+ data.tar.gz: a77958f970489d51253f581afdea3317b8c764bc
5
5
  SHA512:
6
- metadata.gz: 33aac1b4d121b51ed4a23f314478135f0514e583c880c06c4b8931ef1e0fb07211ee142813e13e6d230a94096dfa486c31f60fe48bcd22df6583c8d96d29177c
7
- data.tar.gz: 8ca6904f2f5e37fbf49eeee879af83e261a650fae726a1263a46a3013105de0a1da8ccfe2665cac1cfe22ab9d227cc520319f8d965ad98ae416e0b8ce8f670a8
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.2.0
12
+ gem install joiner --version 0.3.0
13
13
  ```
14
14
 
15
15
  ## Usage
@@ -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.2.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', ['>= 3.1.0', '< 4.1.0']
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
@@ -4,78 +4,94 @@ class Joiner::Joins
4
4
  attr_reader :model
5
5
 
6
6
  def initialize(model)
7
- @model = model
8
- @joins = ActiveSupport::OrderedHash.new
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
- join_for(path)
13
+ @joins_cache[path] ||= build_join(path)
13
14
  end
14
15
 
15
16
  def alias_for(path)
16
- return model.quoted_table_name if path.empty?
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
- @joins.values.compact
22
+ @base
23
23
  end
24
24
 
25
25
  private
26
26
 
27
- def base
28
- @base ||= JoinDependency.new model, [], []
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 join_for(path)
32
- @joins[path] ||= begin
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
- rewrite_conditions_for join
40
- }
41
- end
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 joins_for(path)
45
- if path.length == 1
46
- [join_for(path)]
47
- else
48
- [joins_for(path[0..-2]), join_for(path)].flatten
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 parent_for(path)
53
- path.length == 1 ? base : join_for(path[0..-2])
54
- end
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
- def parent_join_for(path)
57
- path.length == 1 ? base.join_base : parent_for(path)
71
+ [@base.join_root, path]
58
72
  end
59
73
 
60
- def reflection_for(path)
61
- parent = parent_for(path)
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 rewrite_conditions_for(join)
68
- if join.respond_to?(:scope_chain)
69
- conditions = Array(join.scope_chain).flatten
70
- else
71
- conditions = Array(join.conditions).flatten
72
- end
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
- conditions.each do |condition|
75
- next unless condition.is_a?(String)
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
- condition.gsub! /::ts_join_alias::/,
78
- model.connection.quote_table_name(join.parent.aliased_table_name)
79
- end
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
@@ -1,4 +1,4 @@
1
1
  class Comment < ActiveRecord::Base
2
2
  belongs_to :article
3
- belongs_to :comments
3
+ belongs_to :user
4
4
  end
@@ -3,177 +3,81 @@ require 'spec_helper'
3
3
  describe Joiner::Joins do
4
4
  JoinDependency = ::ActiveRecord::Associations::JoinDependency
5
5
 
6
- let(:joins) { Joiner::Joins.new model }
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(join.reflection, base, join_base).once.and_return(join)
11
+ with(Article.reflections[:user], []).once.and_call_original
44
12
 
45
- joins.add_join_to([:user])
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.and_return(join)
17
+ JoinDependency::JoinAssociation.should_receive(:new).once.and_call_original
50
18
 
51
- joins.add_join_to([:user])
52
- joins.add_join_to([:user])
19
+ subject.add_join_to([:user])
20
+ subject.add_join_to([:user])
53
21
  end
54
22
 
55
- context 'multiple joins' do
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(join.reflection, base, join_base).once.and_return(join)
26
+ with(Article.reflections[:user], kind_of(Array)).once.and_call_original
59
27
  JoinDependency::JoinAssociation.should_receive(:new).
60
- with(sub_join.reflection, base, join).once.and_return(sub_join)
28
+ with(User.reflections[:comments], kind_of(Array)).once.and_call_original
61
29
 
62
- joins.add_join_to([:user, :posts])
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
- join.conditions.should == '"qux".foo = bar'
100
- end
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
- join.conditions.should == ['"qux".foo = bar']
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
- joins.alias_for([]).should == 'articles'
46
+ subject.alias_for([]).should == 'articles'
123
47
  end
124
48
 
125
- it "adds just one join for a stack with a single association" do
126
- JoinDependency::JoinAssociation.unstub :new
127
- JoinDependency::JoinAssociation.should_receive(:new).
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
- joins.alias_for([:user]).should == 'users'
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.unstub :new
139
- JoinDependency::JoinAssociation.should_receive(:new).once.and_return(join)
59
+ JoinDependency::JoinAssociation.should_receive(:new).once.and_call_original
140
60
 
141
- joins.alias_for([:user])
142
- joins.alias_for([:user])
61
+ subject.alias_for([:user])
62
+ subject.alias_for([:user])
143
63
  end
144
64
 
145
- context 'multiple joins' do
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
- joins.alias_for([:user, :posts]).should == 'posts'
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
- joins.alias_for([:user])
174
- joins.alias_for([:user, :posts])
175
-
176
- joins.join_values.should == [join, sub_join]
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
@@ -3,7 +3,7 @@ require 'bundler/setup'
3
3
 
4
4
  require 'combustion'
5
5
 
6
- Combustion.initialize! :active_record
6
+ Combustion.initialize! :all
7
7
 
8
8
  require 'rspec/rails'
9
9
 
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.2.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-01-11 00:00:00.000000000 Z
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.1.11
130
+ rubygems_version: 2.2.2
123
131
  signing_key:
124
132
  specification_version: 4
125
133
  summary: Builds ActiveRecord joins from association paths