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 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