joiner 0.3.4 → 0.6.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
- SHA1:
3
- metadata.gz: 7249d05e24afee39fbb2547085068230f6b1d62b
4
- data.tar.gz: 941f72f5d80c3a6c0b8b88684f7a35b77e7461ee
2
+ SHA256:
3
+ metadata.gz: 50d83b6e1af0b5ef8c29fbbaabbb56a8c33ed99acbf57f507d13bcff673d92b2
4
+ data.tar.gz: 62adfb53520243ff108e85f68c61ca121cf28f1e6344fac539aa36c725efe0c1
5
5
  SHA512:
6
- metadata.gz: 0fd9651d510164f7e5843589678ba62d3f214c67dca6309e4711f71cd8b4200b3e105cd780fdfab14d3a4240d4354fcd95b6568b101d710757da1d093e96600a
7
- data.tar.gz: ff41eaace4916f013c03e7407a65709543251908daab4f3203f723282a9c38c50a35b9767a9b074bb6f7f336bb1042cf30542d8ee179cd08ca868a23228b66fe
6
+ metadata.gz: f96a6e7b1165be044ae4fba8dd71f9950f6635cbac352d7aa9d0aea9f7ead03d8bc0928f4fa69422539e4945976207394efc86704556c5fe44b8bcbda9ac2106
7
+ data.tar.gz: fa5f47d42b4d823581706dc5644b1b7b0ff4b47232f4216667c9526065e01fa13d1d1f46424e815f6df4bfc42b5659a56dc4831e5af1928ca3151d63bd0a7b5f
@@ -0,0 +1,23 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ ruby: [ '2.5', '2.6', '2.7' ]
13
+
14
+ steps:
15
+ - name: Check out code
16
+ uses: actions/checkout@v2
17
+ - name: Set up ruby
18
+ uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: ${{ matrix.ruby }}
21
+ bundler-cache: true
22
+ - name: Test
23
+ run: "bundle exec rspec"
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ dist: xenial
3
+ sudo: false
4
+ rvm:
5
+ - 2.5.8
6
+ - 2.6.6
7
+ before_install:
8
+ - gem update --system
9
+ script: bundle exec rspec
data/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  This gem, abstracted out from [Thinking Sphinx](http://pat.github.io/thinking-sphinx), turns a bunch of association trees from the perspective of a single model and builds a bunch of OUTER JOINs that can be passed into ActiveRecord::Relation's `join` method. You can also find out the generated table aliases for each join, in case you're referring to columns from those joins at some other point.
4
4
 
5
- If this gem is used by anyone other than myself/Thinking Sphinx, I'll be surprised. My reason for pulling it out is so I can more cleanly support Rails' changing approaches to join generation (see v3.1-4.0 compared to v4.1).
5
+ If this gem is used by anyone other than myself/Thinking Sphinx, I'll be surprised. My reason for pulling it out is so I can more cleanly support Rails' changing approaches to join generation (see v3.1-v4.0 compared to v4.1-v5.1 compared to v5.2).
6
6
 
7
7
  ## Installation
8
8
 
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.3.4
12
+ gem install joiner --version 0.6.0
13
13
  ```
14
14
 
15
15
  ## Usage
@@ -17,7 +17,7 @@ gem install joiner --version 0.3.4
17
17
  First, create a join collection, based on an ActiveRecord model:
18
18
 
19
19
  ```ruby
20
- joiner = Joiner::Joins.new user
20
+ joiner = Joiner::Joins.new User
21
21
  ```
22
22
 
23
23
  Then you can add joins for a given association path. For example, if User has many articles, and articles have many comments:
@@ -49,6 +49,8 @@ path.model #=> Comment
49
49
 
50
50
  ## Contributing
51
51
 
52
+ Please note that this project now has a [Contributor Code of Conduct](http://contributor-covenant.org/version/1/0/0/). By participating in this project you agree to abide by its terms.
53
+
52
54
  1. Fork it
53
55
  2. Create your feature branch (`git checkout -b my-new-feature`)
54
56
  3. Commit your changes (`git commit -am 'Add some feature'`)
@@ -57,4 +59,4 @@ path.model #=> Comment
57
59
 
58
60
  ## Licence
59
61
 
60
- Copyright (c) 2013, Joiner is developed and maintained by [Pat Allan](http://freelancing-gods.com), and is released under the open MIT Licence.
62
+ Copyright (c) 2013-2020, Joiner is developed and maintained by [Pat Allan](http://freelancing-gods.com), and is released under the open MIT Licence.
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
@@ -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.3.4'
4
+ spec.version = '0.6.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,10 +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', '>= 4.1.0'
16
+ spec.add_runtime_dependency 'activerecord', '>= 6.1.0'
17
17
 
18
- spec.add_development_dependency 'combustion', '~> 0.5.1'
19
- spec.add_development_dependency 'rails', '>= 4.1.2'
20
- spec.add_development_dependency 'rspec-rails', '~> 2.14.1'
21
- spec.add_development_dependency 'sqlite3', '~> 1.3.8'
18
+ spec.add_development_dependency 'combustion', '~> 1.1'
19
+ spec.add_development_dependency 'rails', '>= 6.1.0'
20
+ spec.add_development_dependency 'rspec-rails', '~> 4'
21
+ spec.add_development_dependency 'sqlite3', '~> 1.4'
22
22
  end
@@ -1,9 +1,13 @@
1
1
  require 'set'
2
+ require 'active_record'
2
3
 
3
4
  module Joiner
4
5
  class AssociationNotFound < StandardError
5
6
  end
6
7
  end
7
8
 
9
+ require 'joiner/alias_tracker'
10
+ require 'joiner/join_aliaser'
11
+ require 'joiner/join_dependency'
8
12
  require 'joiner/joins'
9
13
  require 'joiner/path'
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/conversions"
4
+
5
+ # This code is taken straight from Rails, prior to v6.1.0.
6
+ # I'm maintaining a copy here to save myself having to work through aliasing
7
+ # logic myself - there's a good chance I don't need all of thiis, but it'll do
8
+ # to get this gem working with Rails 6.1.
9
+
10
+ class Joiner::AliasTracker # :nodoc:
11
+ def self.create(connection, initial_table, joins, aliases = nil)
12
+ if joins.empty?
13
+ aliases ||= Hash.new(0)
14
+ elsif aliases
15
+ default_proc = aliases.default_proc || proc { 0 }
16
+ aliases.default_proc = proc { |h, k|
17
+ h[k] = initial_count_for(connection, k, joins) + default_proc.call(h, k)
18
+ }
19
+ else
20
+ aliases = Hash.new { |h, k|
21
+ h[k] = initial_count_for(connection, k, joins)
22
+ }
23
+ end
24
+ aliases[initial_table] = 1
25
+ new(connection, aliases)
26
+ end
27
+
28
+ def self.initial_count_for(connection, name, table_joins)
29
+ quoted_name = nil
30
+
31
+ counts = table_joins.map do |join|
32
+ if join.is_a?(Arel::Nodes::StringJoin)
33
+ # quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase
34
+ quoted_name ||= connection.quote_table_name(name)
35
+
36
+ # Table names + table aliases
37
+ join.left.scan(
38
+ /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i
39
+ ).size
40
+ elsif join.is_a?(Arel::Nodes::Join)
41
+ join.left.name == name ? 1 : 0
42
+ else
43
+ raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join"
44
+ end
45
+ end
46
+
47
+ counts.sum
48
+ end
49
+
50
+ # table_joins is an array of arel joins which might conflict with the aliases we assign here
51
+ def initialize(connection, aliases)
52
+ @aliases = aliases
53
+ @connection = connection
54
+ end
55
+
56
+ def aliased_table_for(table_name, aliased_name, type_caster)
57
+ if aliases[table_name].zero?
58
+ # If it's zero, we can have our table_name
59
+ aliases[table_name] = 1
60
+ Arel::Table.new(table_name, type_caster: type_caster)
61
+ else
62
+ # Otherwise, we need to use an alias
63
+ aliased_name = @connection.table_alias_for(aliased_name)
64
+
65
+ # Update the count
66
+ aliases[aliased_name] += 1
67
+
68
+ table_alias = if aliases[aliased_name] > 1
69
+ "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
70
+ else
71
+ aliased_name
72
+ end
73
+ Arel::Table.new(table_name, type_caster: type_caster).alias(table_alias)
74
+ end
75
+ end
76
+
77
+ attr_reader :aliases
78
+
79
+ private
80
+
81
+ def truncate(name)
82
+ name.slice(0, @connection.table_alias_length - 2)
83
+ end
84
+ end
@@ -0,0 +1,41 @@
1
+ # The core logic of this class is old Rails behaviour, replicated here because
2
+ # their own alias logic has evolved, but I haven't yet found a way to make use
3
+ # of it - and besides, this is only used to generate Thinking Sphinx's
4
+ # configuration rarely - not in any web requests, so performance issues are less
5
+ # critical here.
6
+
7
+ class Joiner::JoinAliaser
8
+ def self.call(join_root, alias_tracker)
9
+ new(join_root, alias_tracker).call
10
+ end
11
+
12
+ def initialize(join_root, alias_tracker)
13
+ @join_root = join_root
14
+ @alias_tracker = alias_tracker
15
+ end
16
+
17
+ def call
18
+ join_root.each_children do |parent, child|
19
+ child.table = table_aliases_for(parent, child).first
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :join_root, :alias_tracker
26
+
27
+ def table_aliases_for(parent, node)
28
+ node.reflection.chain.map { |reflection|
29
+ alias_tracker.aliased_table_for(
30
+ reflection.table_name,
31
+ table_alias_for(reflection, parent, reflection != node.reflection),
32
+ reflection.klass.type_caster
33
+ )
34
+ }
35
+ end
36
+
37
+ def table_alias_for(reflection, parent, join)
38
+ name = reflection.alias_candidate(parent.table_name)
39
+ join ? "#{name}_join" : name
40
+ end
41
+ end
@@ -0,0 +1,11 @@
1
+ class Joiner::JoinDependency < ActiveRecord::Associations::JoinDependency
2
+ def join_association_for(path, alias_tracker = nil)
3
+ @alias_tracker = alias_tracker
4
+
5
+ Joiner::JoinAliaser.call join_root, alias_tracker
6
+
7
+ path.inject(join_root) do |node, piece|
8
+ node.children.detect { |child| child.reflection.name == piece }
9
+ end
10
+ end
11
+ end
@@ -2,11 +2,7 @@ require 'active_record'
2
2
  require 'active_support/ordered_hash'
3
3
 
4
4
  class Joiner::Joins
5
- JoinDependency = ActiveRecord::Associations::JoinDependency
6
- JoinAssociation = JoinDependency::JoinAssociation
7
-
8
- attr_reader :model
9
- attr_accessor :join_association_class
5
+ attr_reader :model
10
6
 
11
7
  def initialize(model)
12
8
  @model = model
@@ -23,35 +19,34 @@ class Joiner::Joins
23
19
  return model.table_name if path.empty?
24
20
 
25
21
  add_join_to path
26
- join_association_for(path).tables.first.name
22
+ association_for(path).table.name
27
23
  end
28
24
 
29
25
  def join_values
30
- switch_join_dependency join_association_class
31
- result = JoinDependency.new model, joins_cache.to_a, []
32
- switch_join_dependency JoinAssociation
33
-
34
- result
26
+ Joiner::JoinDependency.new(
27
+ model, table, joins_cache.to_a, Arel::Nodes::OuterJoin
28
+ )
35
29
  end
36
30
 
37
31
  private
38
32
 
39
33
  attr_reader :joins_cache
40
34
 
41
- def join_association_for(path)
42
- path.inject(join_values.join_root) do |node, piece|
43
- node.children.detect { |child| child.reflection.name == piece }
44
- end
35
+ def alias_tracker
36
+ Joiner::AliasTracker.create(
37
+ model.connection, table.name, []
38
+ )
39
+ end
40
+
41
+ def association_for(path)
42
+ join_values.join_association_for path, alias_tracker
45
43
  end
46
44
 
47
45
  def path_as_hash(path)
48
46
  path[0..-2].reverse.inject(path.last) { |key, item| {item => key} }
49
47
  end
50
48
 
51
- def switch_join_dependency(klass)
52
- return unless join_association_class
53
-
54
- JoinDependency.send :remove_const, :JoinAssociation
55
- JoinDependency.const_set :JoinAssociation, klass
49
+ def table
50
+ @table ||= model.arel_table
56
51
  end
57
52
  end
@@ -3,7 +3,7 @@ require 'bundler/setup'
3
3
 
4
4
  require 'combustion'
5
5
 
6
- Combustion.initialize! :all
6
+ Combustion.initialize! :active_record
7
7
 
8
8
  require 'rspec/rails'
9
9
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: joiner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pat Allan
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-17 00:00:00.000000000 Z
11
+ date: 2020-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,70 +16,70 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.1.0
19
+ version: 6.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.1.0
26
+ version: 6.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: combustion
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.5.1
33
+ version: '1.1'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.5.1
40
+ version: '1.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rails
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 4.1.2
47
+ version: 6.1.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 4.1.2
54
+ version: 6.1.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec-rails
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 2.14.1
61
+ version: '4'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 2.14.1
68
+ version: '4'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: sqlite3
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 1.3.8
75
+ version: '1.4'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 1.3.8
82
+ version: '1.4'
83
83
  description: Builds ActiveRecord outer joins from association paths and provides references
84
84
  to table aliases.
85
85
  email:
@@ -88,13 +88,18 @@ executables: []
88
88
  extensions: []
89
89
  extra_rdoc_files: []
90
90
  files:
91
+ - ".github/workflows/ci.yml"
91
92
  - ".gitignore"
93
+ - ".travis.yml"
92
94
  - Gemfile
93
95
  - LICENSE.txt
94
96
  - README.md
95
97
  - Rakefile
96
98
  - joiner.gemspec
97
99
  - lib/joiner.rb
100
+ - lib/joiner/alias_tracker.rb
101
+ - lib/joiner/join_aliaser.rb
102
+ - lib/joiner/join_dependency.rb
98
103
  - lib/joiner/joins.rb
99
104
  - lib/joiner/path.rb
100
105
  - spec/acceptance/joiner_spec.rb
@@ -110,7 +115,7 @@ homepage: https://github.com/pat/joiner
110
115
  licenses:
111
116
  - MIT
112
117
  metadata: {}
113
- post_install_message:
118
+ post_install_message:
114
119
  rdoc_options: []
115
120
  require_paths:
116
121
  - lib
@@ -125,9 +130,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
130
  - !ruby/object:Gem::Version
126
131
  version: '0'
127
132
  requirements: []
128
- rubyforge_project:
129
- rubygems_version: 2.3.0
130
- signing_key:
133
+ rubygems_version: 3.1.2
134
+ signing_key:
131
135
  specification_version: 4
132
136
  summary: Builds ActiveRecord joins from association paths
133
137
  test_files: