baby_squeel 0.3.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -2
- data/ISSUE_TEMPLATE.md +39 -0
- data/README.md +10 -11
- data/baby_squeel.gemspec +2 -1
- data/lib/baby_squeel.rb +23 -7
- data/lib/baby_squeel/active_record/base.rb +27 -0
- data/lib/baby_squeel/{active_record.rb → active_record/query_methods.rb} +10 -16
- data/lib/baby_squeel/active_record/where_chain.rb +13 -0
- data/lib/baby_squeel/association.rb +55 -1
- data/lib/baby_squeel/compat.rb +9 -0
- data/lib/baby_squeel/dsl.rb +26 -9
- data/lib/baby_squeel/errors.rb +30 -6
- data/lib/baby_squeel/join_dependency/builder.rb +79 -0
- data/lib/baby_squeel/join_dependency/finder.rb +36 -0
- data/lib/baby_squeel/join_dependency/injector.rb +27 -0
- data/lib/baby_squeel/join_expression.rb +25 -0
- data/lib/baby_squeel/nodes.rb +11 -83
- data/lib/baby_squeel/nodes/attribute.rb +30 -0
- data/lib/baby_squeel/nodes/function.rb +13 -0
- data/lib/baby_squeel/nodes/grouping.rb +16 -0
- data/lib/baby_squeel/nodes/node.rb +18 -0
- data/lib/baby_squeel/nodes/proxy.rb +36 -0
- data/lib/baby_squeel/operators.rb +1 -1
- data/lib/baby_squeel/relation.rb +9 -4
- data/lib/baby_squeel/table.rb +43 -20
- data/lib/baby_squeel/version.rb +1 -1
- metadata +32 -7
- data/lib/baby_squeel/join_dependency.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 394545743835687d87f67feb11e76e787cebc34a
|
4
|
+
data.tar.gz: 4a3f636a36f4e83a889e3948ea6acf35f235b402
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba300fa29631298a8143a580ab34bb66298b022aa9e32d0204f4855e23a464090c3ba0032f1c63152bd3de90a46f12f2ed609234b735de41cce50cd841a512e6
|
7
|
+
data.tar.gz: 6e565e66b5e8dd456f4a388f5b1e0b903ddcc2f1baaee86aed0ca0c295238a66d9a0d16f31cd6f9ce4e4679700354b2c98782284e418d2c981d7db88fab864fc
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,21 @@
|
|
1
|
-
##
|
1
|
+
## [1.0.0] - 2016-9-9
|
2
|
+
### Added
|
3
|
+
- Polyamorous. Unfortunately, this *does* monkey-patch Active Record internals, but there just isn't any other reliable way to generate outer joins. Baby Squeel, itself, will still keep monkey patching to an absolute minimum.
|
4
|
+
- Within DSL blocks, you can use `exists` and `not_exists` with Active Record relations. For example: `Post.where.has { exists Post.where(title: 'Fun') }`.`
|
5
|
+
- Support for polymorphic associations.
|
6
|
+
|
7
|
+
### Deprecations
|
8
|
+
- Removed support for Active Record 4.0.x
|
2
9
|
|
3
|
-
|
10
|
+
### Changed
|
11
|
+
- BabySqueel::JoinDependency is no longer a class responsible for creating Arel joins. It is now a namespace for utilities used when working with the ActiveRecord::Association::JoinDependency class.
|
12
|
+
- BabySqueel::Nodes::Generic is now BabySqueel::Nodes::Node.
|
13
|
+
- Arel nodes are only extended with the behaviors they need. Previously, all Arel nodes were being extended with `Arel::AliasPredication`, `Arel::OrderPredications`, and `Arel::Math`.
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
- Fixed deprecation warnings on Active Record 5 when initializing an Arel::Table without a type caster.
|
17
|
+
- No more duplicate joins. Previously, Baby Squeel did a very poor job of ensuring that you didn't join an association twice.
|
18
|
+
- Alias detection should now *actually* work. The previous implementation was naive.
|
4
19
|
|
5
20
|
## [0.3.1] - 2016-08-02
|
6
21
|
### Added
|
data/ISSUE_TEMPLATE.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#### Issue
|
2
|
+
|
3
|
+
Please explain this issue you're encountering to the best of your ability.
|
4
|
+
|
5
|
+
#### Reproduction
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
require 'bundler/inline'
|
9
|
+
require 'minitest/spec'
|
10
|
+
require 'minitest/autorun'
|
11
|
+
|
12
|
+
gemfile true do
|
13
|
+
source 'https://rubygems.org'
|
14
|
+
gem 'activerecord', '~> 5.0.0' # which Active Record version?
|
15
|
+
gem 'sqlite3'
|
16
|
+
gem 'baby_squeel', github: 'rzane/baby_squeel'
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
20
|
+
|
21
|
+
ActiveRecord::Schema.define do
|
22
|
+
create_table :dogs, force: true do |t|
|
23
|
+
t.string :name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Dog < ActiveRecord::Base
|
28
|
+
end
|
29
|
+
|
30
|
+
class BabySqueelTest < Minitest::Spec
|
31
|
+
it 'works' do
|
32
|
+
scope = Dog.where.has { name == 'Fido' }
|
33
|
+
|
34
|
+
scope.to_sql.must_equal %{
|
35
|
+
SELECT "dogs".* FROM "dogs" WHERE "dogs"."name" = 'Fido'
|
36
|
+
}.squish
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
data/README.md
CHANGED
@@ -175,6 +175,14 @@ Post.joining { author.outer.posts }
|
|
175
175
|
Post.joining { author.alias('a').on((author.id == author_id) | (author.name == title)) }
|
176
176
|
# SELECT "posts".* FROM "posts"
|
177
177
|
# INNER JOIN "authors" "a" ON ("authors"."id" = "posts"."author_id" OR "authors"."name" = "posts"."title")
|
178
|
+
|
179
|
+
Picture.joining { imageable.of(Post) }
|
180
|
+
# SELECT "pictures".* FROM "pictures"
|
181
|
+
# INNER JOIN "posts" ON "posts"."id" = "pictures"."imageable_id" AND "pictures"."imageable_type" = 'Post'
|
182
|
+
|
183
|
+
Picture.joining { imageable.of(Post).outer }
|
184
|
+
# SELECT "pictures".* FROM "pictures"
|
185
|
+
# LEFT OUTER JOIN "posts" ON "posts"."id" = "pictures"."imageable_id" AND "pictures"."imageable_type" = 'Post'
|
178
186
|
```
|
179
187
|
|
180
188
|
##### Grouping
|
@@ -279,18 +287,9 @@ The following methods give you access to BabySqueel's DSL:
|
|
279
287
|
| `where.has` | `where` |
|
280
288
|
| `when_having` | `having` |
|
281
289
|
|
282
|
-
##
|
283
|
-
|
284
|
-
If you want `select`, `order`, `joins`, `group`, and `having` to be able to accept DSL blocks, you can do so by adding the following to an initializer.
|
285
|
-
|
286
|
-
```ruby
|
287
|
-
# config/initializers/baby_squeel.rb
|
288
|
-
BabySqueel.configure do |config|
|
289
|
-
config.enable_compat!
|
290
|
-
end
|
291
|
-
```
|
290
|
+
## Migrating from Squeel
|
292
291
|
|
293
|
-
|
292
|
+
Check out the [migration guide](https://github.com/rzane/baby_squeel/wiki/Migrating-from-Squeel).
|
294
293
|
|
295
294
|
## Development
|
296
295
|
|
data/baby_squeel.gemspec
CHANGED
@@ -19,7 +19,8 @@ Gem::Specification.new do |spec|
|
|
19
19
|
|
20
20
|
spec.files = Dir.glob('{lib/**/*,*.{md,txt,gemspec}}')
|
21
21
|
|
22
|
-
spec.add_dependency 'activerecord', '>= 4.
|
22
|
+
spec.add_dependency 'activerecord', '>= 4.1.0'
|
23
|
+
spec.add_dependency 'polyamorous', '~> 1.3'
|
23
24
|
|
24
25
|
spec.add_development_dependency 'bundler', '~> 1.11'
|
25
26
|
spec.add_development_dependency 'rake', '~> 10.0'
|
data/lib/baby_squeel.rb
CHANGED
@@ -1,31 +1,47 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
require 'active_record/relation'
|
3
|
+
require 'polyamorous'
|
3
4
|
require 'baby_squeel/version'
|
4
5
|
require 'baby_squeel/errors'
|
5
|
-
require 'baby_squeel/active_record'
|
6
|
+
require 'baby_squeel/active_record/base'
|
7
|
+
require 'baby_squeel/active_record/query_methods'
|
8
|
+
require 'baby_squeel/active_record/where_chain'
|
6
9
|
|
7
10
|
module BabySqueel
|
8
11
|
class << self
|
12
|
+
# Configures BabySqueel using the given block
|
9
13
|
def configure
|
10
14
|
yield self
|
11
15
|
end
|
12
16
|
|
17
|
+
# Turn on BabySqueel's compatibility mode. This will
|
18
|
+
# make BabySqueel act more like Squeel.
|
13
19
|
def enable_compatibility!
|
14
20
|
require 'baby_squeel/compat'
|
15
21
|
BabySqueel::Compat.enable!
|
16
22
|
end
|
17
23
|
|
18
|
-
|
24
|
+
# Get a BabySqueel table instance.
|
25
|
+
#
|
26
|
+
# ==== Examples
|
27
|
+
# BabySqueel[Post]
|
28
|
+
# BabySqueel[:posts]
|
29
|
+
# BabySqueel[Post.arel_table]
|
30
|
+
#
|
31
|
+
def [](thing, **kwargs)
|
19
32
|
if thing.respond_to?(:model_name)
|
20
33
|
Relation.new(thing)
|
34
|
+
elsif thing.kind_of?(Arel::Table)
|
35
|
+
Table.new(thing)
|
21
36
|
else
|
22
|
-
Table.new(Arel::Table.new(thing))
|
37
|
+
Table.new(Arel::Table.new(thing, **kwargs))
|
23
38
|
end
|
24
39
|
end
|
25
40
|
end
|
26
41
|
end
|
27
42
|
|
28
|
-
|
29
|
-
::ActiveRecord::Base.extend BabySqueel::ActiveRecord::
|
30
|
-
::ActiveRecord::Relation.prepend BabySqueel::ActiveRecord::QueryMethods
|
31
|
-
::ActiveRecord::QueryMethods::WhereChain.prepend BabySqueel::ActiveRecord::WhereChain
|
43
|
+
ActiveSupport.on_load :active_record do
|
44
|
+
::ActiveRecord::Base.extend BabySqueel::ActiveRecord::Base
|
45
|
+
::ActiveRecord::Relation.prepend BabySqueel::ActiveRecord::QueryMethods
|
46
|
+
::ActiveRecord::QueryMethods::WhereChain.prepend BabySqueel::ActiveRecord::WhereChain
|
47
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'baby_squeel/dsl'
|
2
|
+
|
3
|
+
module BabySqueel
|
4
|
+
module ActiveRecord
|
5
|
+
module Base
|
6
|
+
delegate :joining, :joining!, :selecting, :ordering,
|
7
|
+
:grouping, :when_having, to: :all
|
8
|
+
|
9
|
+
# Define a sifter that can be used within DSL blocks.
|
10
|
+
#
|
11
|
+
# ==== Examples
|
12
|
+
# class Post < ActiveRecord::Base
|
13
|
+
# sifter :name_contains do |string|
|
14
|
+
# name =~ "%#{string}%"
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Post.where.has { sift(:name_contains, 'joe') }
|
19
|
+
#
|
20
|
+
def sifter(name, &block)
|
21
|
+
define_singleton_method "sift_#{name}" do |*args|
|
22
|
+
DSL.evaluate_sifter(self, *args, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,20 +1,12 @@
|
|
1
1
|
require 'baby_squeel/dsl'
|
2
|
+
require 'baby_squeel/join_dependency/injector'
|
2
3
|
|
3
4
|
module BabySqueel
|
4
5
|
module ActiveRecord
|
5
|
-
module Sifting
|
6
|
-
def sifter(name, &block)
|
7
|
-
define_singleton_method "sift_#{name}" do |*args|
|
8
|
-
DSL.evaluate_sifter(self, *args, &block)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
6
|
module QueryMethods
|
14
7
|
# Constructs Arel for ActiveRecord::Base#joins using the DSL.
|
15
8
|
def joining(&block)
|
16
|
-
|
17
|
-
joins(arel).tap { |s| s.bind_values += binds }
|
9
|
+
joins DSL.evaluate(self, &block)
|
18
10
|
end
|
19
11
|
|
20
12
|
# Constructs Arel for ActiveRecord::Base#select using the DSL.
|
@@ -36,13 +28,15 @@ module BabySqueel
|
|
36
28
|
def when_having(&block)
|
37
29
|
having DSL.evaluate(self, &block)
|
38
30
|
end
|
39
|
-
end
|
40
31
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
32
|
+
private
|
33
|
+
|
34
|
+
# This is a monkey patch, and I'm not happy about it.
|
35
|
+
# Active Record will call `group_by` on the `joins`. The
|
36
|
+
# Injector has a custom `group_by` method that handles
|
37
|
+
# BabySqueel::JoinExpression nodes.
|
38
|
+
def build_joins(manager, joins)
|
39
|
+
super manager, BabySqueel::JoinDependency::Injector.new(joins)
|
46
40
|
end
|
47
41
|
end
|
48
42
|
end
|
@@ -2,12 +2,57 @@ require 'baby_squeel/relation'
|
|
2
2
|
|
3
3
|
module BabySqueel
|
4
4
|
class Association < Relation
|
5
|
+
# An Active Record association reflection
|
5
6
|
attr_reader :_reflection
|
6
7
|
|
8
|
+
# Specifies the model that the polymorphic
|
9
|
+
# association should join with
|
10
|
+
attr_accessor :_polymorphic_klass
|
11
|
+
|
7
12
|
def initialize(parent, reflection)
|
8
13
|
@parent = parent
|
9
14
|
@_reflection = reflection
|
10
|
-
|
15
|
+
|
16
|
+
# In the case of a polymorphic reflection these
|
17
|
+
# attributes will be set after calling #of
|
18
|
+
unless @_reflection.polymorphic?
|
19
|
+
super @_reflection.klass
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def of(klass)
|
24
|
+
unless _reflection.polymorphic?
|
25
|
+
raise PolymorphicSpecificationError.new(_reflection.name, klass)
|
26
|
+
end
|
27
|
+
|
28
|
+
clone.of! klass
|
29
|
+
end
|
30
|
+
|
31
|
+
def of!(klass)
|
32
|
+
self._scope = klass
|
33
|
+
self._table = klass.arel_table
|
34
|
+
self._polymorphic_klass = klass
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def needs_polyamorous?
|
39
|
+
_join == Arel::Nodes::OuterJoin || _reflection.polymorphic?
|
40
|
+
end
|
41
|
+
|
42
|
+
# See JoinExpression#add_to_tree.
|
43
|
+
def add_to_tree(hash)
|
44
|
+
polyamorous = Polyamorous::Join.new(
|
45
|
+
_reflection.name,
|
46
|
+
_join,
|
47
|
+
_polymorphic_klass
|
48
|
+
)
|
49
|
+
|
50
|
+
hash[polyamorous] ||= {}
|
51
|
+
end
|
52
|
+
|
53
|
+
# See BabySqueel::Table#find_alias.
|
54
|
+
def find_alias(association, associations = [])
|
55
|
+
@parent.find_alias(association, [self, *associations])
|
11
56
|
end
|
12
57
|
|
13
58
|
# Intelligently constructs Arel nodes. There are three outcomes:
|
@@ -15,18 +60,27 @@ module BabySqueel
|
|
15
60
|
# 1. The user explicitly constructed their join using #on.
|
16
61
|
# See BabySqueel::Table#_arel.
|
17
62
|
#
|
63
|
+
# Post.joining { author.on(author_id == author.id) }
|
64
|
+
#
|
18
65
|
# 2. The user aliased an implicitly joined association. ActiveRecord's
|
19
66
|
# join dependency gives us no way of handling this, so we have to
|
20
67
|
# throw an error.
|
21
68
|
#
|
69
|
+
# Post.joining { author.as('some_alias') }
|
70
|
+
#
|
22
71
|
# 3. The user implicitly joined this association, so we pass this
|
23
72
|
# association up the tree until it hits the top-level BabySqueel::Table.
|
24
73
|
# Once it gets there, Arel join nodes will be constructed.
|
74
|
+
#
|
75
|
+
# Post.joining { author }
|
76
|
+
#
|
25
77
|
def _arel(associations = [])
|
26
78
|
if _on
|
27
79
|
super
|
28
80
|
elsif _table.is_a? Arel::Nodes::TableAlias
|
29
81
|
raise AssociationAliasingError.new(_reflection.name, _table.right)
|
82
|
+
elsif _reflection.polymorphic? && _polymorphic_klass.nil?
|
83
|
+
raise PolymorphicNotSpecifiedError.new(_reflection.name)
|
30
84
|
else
|
31
85
|
@parent._arel([self, *associations])
|
32
86
|
end
|
data/lib/baby_squeel/compat.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module BabySqueel
|
2
2
|
module Compat
|
3
|
+
# Monkey-patches BabySqueel and ActiveRecord
|
4
|
+
# in order to behave more like Squeel
|
3
5
|
def self.enable!
|
4
6
|
BabySqueel::DSL.prepend BabySqueel::Compat::DSL
|
5
7
|
::ActiveRecord::Base.singleton_class.prepend QueryMethods
|
@@ -7,10 +9,12 @@ module BabySqueel
|
|
7
9
|
end
|
8
10
|
|
9
11
|
module DSL
|
12
|
+
# An alias for BabySqueel::DSL#sql
|
10
13
|
def `(str)
|
11
14
|
sql(str)
|
12
15
|
end
|
13
16
|
|
17
|
+
# Allows you to call out of an instance_eval'd block.
|
14
18
|
def my(&block)
|
15
19
|
@caller.instance_eval(&block)
|
16
20
|
end
|
@@ -23,6 +27,7 @@ module BabySqueel
|
|
23
27
|
end
|
24
28
|
|
25
29
|
module QueryMethods
|
30
|
+
# Overrides ActiveRecord::QueryMethods#joins
|
26
31
|
def joins(*args, &block)
|
27
32
|
if block_given? && args.empty?
|
28
33
|
joining(&block)
|
@@ -47,6 +52,7 @@ module BabySqueel
|
|
47
52
|
end
|
48
53
|
end
|
49
54
|
|
55
|
+
# Overrides ActiveRecord::QueryMethods#order
|
50
56
|
def order(*args, &block)
|
51
57
|
if block_given? && args.empty?
|
52
58
|
ordering(&block)
|
@@ -55,6 +61,7 @@ module BabySqueel
|
|
55
61
|
end
|
56
62
|
end
|
57
63
|
|
64
|
+
# Overrides ActiveRecord::QueryMethods#group
|
58
65
|
def group(*args, &block)
|
59
66
|
if block_given? && args.empty?
|
60
67
|
grouping(&block)
|
@@ -63,6 +70,7 @@ module BabySqueel
|
|
63
70
|
end
|
64
71
|
end
|
65
72
|
|
73
|
+
# Overrides ActiveRecord::QueryMethods#having
|
66
74
|
def having(*args, &block)
|
67
75
|
if block_given? && args.empty?
|
68
76
|
when_having(&block)
|
@@ -71,6 +79,7 @@ module BabySqueel
|
|
71
79
|
end
|
72
80
|
end
|
73
81
|
|
82
|
+
# Overrides ActiveRecord::QueryMethods#where
|
74
83
|
def where(*args, &block)
|
75
84
|
if block_given? && args.empty?
|
76
85
|
where.has(&block)
|
data/lib/baby_squeel/dsl.rb
CHANGED
@@ -15,15 +15,6 @@ module BabySqueel
|
|
15
15
|
new(scope).evaluate(&block)
|
16
16
|
end
|
17
17
|
|
18
|
-
# Evaluates a block specifically for a join. In this
|
19
|
-
# case, we'll return an array of Arel join nodes and
|
20
|
-
# a list of bind parameters.
|
21
|
-
def evaluate_joins(scope, &block)
|
22
|
-
dependency = evaluate!(scope, &block)._arel
|
23
|
-
join_arel = Nodes.unwrap(dependency._arel)
|
24
|
-
[join_arel, dependency.bind_values]
|
25
|
-
end
|
26
|
-
|
27
18
|
# Evaluates a block in the context of a new DSL instance
|
28
19
|
# and passes all arguments to the block.
|
29
20
|
def evaluate_sifter(scope, *args, &block)
|
@@ -48,6 +39,32 @@ module BabySqueel
|
|
48
39
|
Nodes.wrap Arel::Nodes::NamedFunction.new(name.to_s, args)
|
49
40
|
end
|
50
41
|
|
42
|
+
# Generate an EXISTS subselect from an ActiveRecord::Relation
|
43
|
+
#
|
44
|
+
# ==== Arguments
|
45
|
+
#
|
46
|
+
# * +relation+ - An ActiveRecord::Relation
|
47
|
+
#
|
48
|
+
# ==== Example
|
49
|
+
# Post.where.has { exists Post.where(id: 1) }
|
50
|
+
#
|
51
|
+
def exists(relation)
|
52
|
+
func 'EXISTS', sql(relation.to_sql)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Generate a NOT EXISTS subselect from an ActiveRecord::Relation
|
56
|
+
#
|
57
|
+
# ==== Arguments
|
58
|
+
#
|
59
|
+
# * +relation+ - An ActiveRecord::Relation
|
60
|
+
#
|
61
|
+
# ==== Example
|
62
|
+
# Post.where.has { not_exists Post.where(id: 1) }
|
63
|
+
#
|
64
|
+
def not_exists(rel)
|
65
|
+
func 'NOT EXISTS', sql(rel.to_sql)
|
66
|
+
end
|
67
|
+
|
51
68
|
# See Arel::sql
|
52
69
|
def sql(value)
|
53
70
|
Nodes.wrap ::Arel.sql(value)
|
data/lib/baby_squeel/errors.rb
CHANGED
@@ -1,24 +1,48 @@
|
|
1
1
|
module BabySqueel
|
2
|
-
class NotFoundError < StandardError
|
2
|
+
class NotFoundError < StandardError # :nodoc:
|
3
3
|
def initialize(model_name, name)
|
4
4
|
super "There is no column or association named '#{name}' for #{model_name}."
|
5
5
|
end
|
6
6
|
end
|
7
7
|
|
8
|
-
class AssociationNotFoundError < StandardError
|
8
|
+
class AssociationNotFoundError < StandardError # :nodoc:
|
9
9
|
def initialize(model_name, name)
|
10
10
|
super "Association named '#{name}' was not found for #{model_name}."
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
class AssociationAliasingError < StandardError
|
14
|
+
class AssociationAliasingError < StandardError # :nodoc:
|
15
15
|
MESSAGE =
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
"Attempted to alias '%{association}' as '%{alias_name}', but the " \
|
17
|
+
"association was implicitly joined. Either join the association " \
|
18
|
+
"with `on` or remove the alias. For example:" \
|
19
|
+
"\n\n Post.joining { author }" \
|
20
|
+
"\n Post.joining { author.on(author_id == author.id) }\n\n"
|
19
21
|
|
20
22
|
def initialize(association, alias_name)
|
21
23
|
super format(MESSAGE, association: association, alias_name: alias_name)
|
22
24
|
end
|
23
25
|
end
|
26
|
+
|
27
|
+
class PolymorphicSpecificationError < StandardError # :nodoc:
|
28
|
+
MESSAGE =
|
29
|
+
"'%{association}' is not a polymorphic association, therefore " \
|
30
|
+
"the following expression is invalid:" \
|
31
|
+
"\n\n %{association}.of(%{klass})\n\n"
|
32
|
+
|
33
|
+
def initialize(association, klass)
|
34
|
+
super format(MESSAGE, association: association, klass: klass)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class PolymorphicNotSpecifiedError < StandardError # :nodoc:
|
39
|
+
MESSAGE =
|
40
|
+
"'%{association}' is a polymorphic association, therefore " \
|
41
|
+
"you must call #of when referencing the association. For example:" \
|
42
|
+
"\n\n %{association}.of(SomeModel)\n\n"
|
43
|
+
|
44
|
+
def initialize(association)
|
45
|
+
super format(MESSAGE, association: association)
|
46
|
+
end
|
47
|
+
end
|
24
48
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'baby_squeel/join_dependency/injector'
|
2
|
+
|
3
|
+
module BabySqueel
|
4
|
+
module JoinDependency
|
5
|
+
# Unfortunately, this is mostly all duplication of
|
6
|
+
# ActiveRecord::QueryMethods#build_joins
|
7
|
+
class Builder # :nodoc:
|
8
|
+
attr_reader :relation
|
9
|
+
|
10
|
+
def initialize(relation)
|
11
|
+
@relation = relation
|
12
|
+
@joins_values = relation.joins_values.dup
|
13
|
+
end
|
14
|
+
|
15
|
+
def ensure_associated(*values)
|
16
|
+
@joins_values += values
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_join_dependency
|
20
|
+
::ActiveRecord::Associations::JoinDependency.new(
|
21
|
+
relation.model,
|
22
|
+
association_joins,
|
23
|
+
join_list
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def join_list
|
30
|
+
join_nodes + join_strings_as_ast
|
31
|
+
end
|
32
|
+
|
33
|
+
def association_joins
|
34
|
+
buckets[:association_join] || []
|
35
|
+
end
|
36
|
+
|
37
|
+
def stashed_association_joins
|
38
|
+
buckets[:stashed_join] || []
|
39
|
+
end
|
40
|
+
|
41
|
+
def join_nodes
|
42
|
+
(buckets[:join_node] || []).uniq
|
43
|
+
end
|
44
|
+
|
45
|
+
def string_joins
|
46
|
+
(buckets[:string_join] || []).map(&:strip).uniq
|
47
|
+
end
|
48
|
+
|
49
|
+
if Arel::VERSION >= '7.0.0'
|
50
|
+
def join_strings_as_ast
|
51
|
+
manager = Arel::SelectManager.new(relation.table)
|
52
|
+
relation.send(:convert_join_strings_to_ast, manager, string_joins)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
def join_strings_as_ast
|
56
|
+
manager = Arel::SelectManager.new(relation.table.engine, relation.table)
|
57
|
+
relation.send(:custom_join_ast, manager, string_joins)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def buckets
|
62
|
+
@buckets ||= Injector.new(@joins_values).group_by do |join|
|
63
|
+
case join
|
64
|
+
when String
|
65
|
+
:string_join
|
66
|
+
when Hash, Symbol, Array
|
67
|
+
:association_join
|
68
|
+
when ::ActiveRecord::Associations::JoinDependency
|
69
|
+
:stashed_join
|
70
|
+
when ::Arel::Nodes::Join
|
71
|
+
:join_node
|
72
|
+
else
|
73
|
+
raise 'unknown class: %s' % join.class.name
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module BabySqueel
|
2
|
+
module JoinDependency
|
3
|
+
class Finder # :nodoc:
|
4
|
+
attr_reader :join_dependency
|
5
|
+
|
6
|
+
def initialize(join_dependency)
|
7
|
+
@join_dependency = join_dependency
|
8
|
+
end
|
9
|
+
|
10
|
+
def find_alias(reflection)
|
11
|
+
join_association = find_association(reflection)
|
12
|
+
join_association.tables.first if join_association
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def find(&block)
|
18
|
+
deeply_find(join_dependency.join_root, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_association(reflection)
|
22
|
+
find { |assoc| assoc.reflection == reflection }
|
23
|
+
end
|
24
|
+
|
25
|
+
def deeply_find(root, &block)
|
26
|
+
root.children.each do |assoc|
|
27
|
+
found = assoc if yield assoc
|
28
|
+
found ||= deeply_find(assoc, &block)
|
29
|
+
return found if found
|
30
|
+
end
|
31
|
+
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module BabySqueel
|
2
|
+
module JoinDependency
|
3
|
+
# This class allows BabySqueel to slip custom
|
4
|
+
# joins_values into Active Record's JoinDependency
|
5
|
+
class Injector < Array # :nodoc:
|
6
|
+
def initialize(joins)
|
7
|
+
@joins = joins
|
8
|
+
end
|
9
|
+
|
10
|
+
# Active Record will call group_by on this object
|
11
|
+
# in ActiveRecord::QueryMethods#build_joins. This
|
12
|
+
# allows BabySqueel::JoinExpressions to be treated
|
13
|
+
# like typical join hashes until Polyamorous can
|
14
|
+
# deal with them.
|
15
|
+
def group_by(&block)
|
16
|
+
@joins.group_by do |join|
|
17
|
+
case join
|
18
|
+
when BabySqueel::JoinExpression
|
19
|
+
:association_join
|
20
|
+
else
|
21
|
+
yield join
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'baby_squeel/join_dependency/builder'
|
2
|
+
require 'baby_squeel/join_dependency/finder'
|
3
|
+
|
4
|
+
module BabySqueel
|
5
|
+
# This is the thing that gets added to Active Record's joins_values.
|
6
|
+
# By including Polyamorous::TreeNode, when this instance is found when
|
7
|
+
# traversing joins in ActiveRecord::Associations::JoinDependency::walk_tree,
|
8
|
+
# JoinExpression#add_to_tree will be called.
|
9
|
+
class JoinExpression
|
10
|
+
include Polyamorous::TreeNode
|
11
|
+
|
12
|
+
def initialize(associations)
|
13
|
+
@associations = associations
|
14
|
+
end
|
15
|
+
|
16
|
+
# Each individual association object knows how
|
17
|
+
# to build a Polyamorous::Join. Those joins
|
18
|
+
# will be added to the hash incrementally.
|
19
|
+
def add_to_tree(hash)
|
20
|
+
@associations.inject(hash) do |acc, assoc|
|
21
|
+
assoc.add_to_tree(acc)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/baby_squeel/nodes.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
require 'baby_squeel/
|
1
|
+
require 'baby_squeel/nodes/node'
|
2
|
+
require 'baby_squeel/nodes/attribute'
|
3
|
+
require 'baby_squeel/nodes/function'
|
4
|
+
require 'baby_squeel/nodes/grouping'
|
2
5
|
|
3
6
|
module BabySqueel
|
4
7
|
module Nodes
|
@@ -6,8 +9,13 @@ module BabySqueel
|
|
6
9
|
# Wraps an Arel node in a Proxy so that it can
|
7
10
|
# be extended.
|
8
11
|
def wrap(arel)
|
9
|
-
|
10
|
-
|
12
|
+
case arel
|
13
|
+
when Arel::Nodes::Grouping
|
14
|
+
Grouping.new(arel)
|
15
|
+
when Arel::Nodes::Function
|
16
|
+
Function.new(arel)
|
17
|
+
when Arel::Nodes::Node, Arel::Nodes::SqlLiteral
|
18
|
+
Node.new(arel)
|
11
19
|
else
|
12
20
|
arel
|
13
21
|
end
|
@@ -25,85 +33,5 @@ module BabySqueel
|
|
25
33
|
end
|
26
34
|
end
|
27
35
|
end
|
28
|
-
|
29
|
-
# This proxy class allows us to quack like any arel object. When a
|
30
|
-
# method missing is hit, we'll instantiate a new proxy object.
|
31
|
-
class Proxy < ActiveSupport::ProxyObject
|
32
|
-
# Resolve constants the normal way
|
33
|
-
def self.const_missing(name)
|
34
|
-
::Object.const_get(name)
|
35
|
-
end
|
36
|
-
|
37
|
-
attr_reader :_arel
|
38
|
-
|
39
|
-
def initialize(arel)
|
40
|
-
@_arel = Nodes.unwrap(arel)
|
41
|
-
end
|
42
|
-
|
43
|
-
def respond_to?(meth, include_private = false)
|
44
|
-
meth.to_s == '_arel' || _arel.respond_to?(meth, include_private)
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def method_missing(meth, *args, &block)
|
50
|
-
if _arel.respond_to?(meth)
|
51
|
-
Nodes.wrap _arel.send(meth, *Nodes.unwrap(args), &block)
|
52
|
-
else
|
53
|
-
super
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# This is a generic proxy class that includes all possible modules.
|
59
|
-
# In the future, these proxy classes should be more specific and only
|
60
|
-
# include necessary/applicable modules.
|
61
|
-
class Generic < Proxy
|
62
|
-
extend Operators::ArelAliasing
|
63
|
-
include Operators::Comparison
|
64
|
-
include Operators::Equality
|
65
|
-
include Operators::Generic
|
66
|
-
include Operators::Grouping
|
67
|
-
include Operators::Matching
|
68
|
-
|
69
|
-
# Extend the Arel node with some extra modules. For example,
|
70
|
-
# Arel::Nodes::Grouping does not implement Math. InfixOperation doesn't
|
71
|
-
# implement AliasPredication. Without these extensions, the interface
|
72
|
-
# just seems inconsistent.
|
73
|
-
def initialize(node)
|
74
|
-
node.extend Arel::Math
|
75
|
-
node.extend Arel::AliasPredication
|
76
|
-
node.extend Arel::OrderPredications
|
77
|
-
super(node)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
class Attribute < Generic
|
82
|
-
def initialize(parent, name)
|
83
|
-
@parent = parent
|
84
|
-
@name = name
|
85
|
-
super(parent._table[name])
|
86
|
-
end
|
87
|
-
|
88
|
-
def in(rel)
|
89
|
-
if rel.is_a? ::ActiveRecord::Relation
|
90
|
-
::Arel::Nodes::In.new(self, Arel.sql(rel.to_sql))
|
91
|
-
else
|
92
|
-
super
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def _arel
|
97
|
-
parent_arel = @parent._arel
|
98
|
-
parent_arel &&= parent_arel._arel
|
99
|
-
parent_arel &&= parent_arel.last
|
100
|
-
|
101
|
-
if parent_arel
|
102
|
-
parent_arel.left[@name]
|
103
|
-
else
|
104
|
-
super
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
36
|
end
|
109
37
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'baby_squeel/nodes/node'
|
2
|
+
|
3
|
+
module BabySqueel
|
4
|
+
module Nodes
|
5
|
+
class Attribute < Node
|
6
|
+
def initialize(parent, name)
|
7
|
+
@parent = parent
|
8
|
+
@name = name
|
9
|
+
super(parent._table[name])
|
10
|
+
end
|
11
|
+
|
12
|
+
def in(rel)
|
13
|
+
if rel.is_a? ::ActiveRecord::Relation
|
14
|
+
::Arel::Nodes::In.new(self, Arel.sql(rel.to_sql))
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def _arel
|
21
|
+
if @parent.kind_of? BabySqueel::Association
|
22
|
+
table = @parent.find_alias(@parent)
|
23
|
+
table ? table[@name] : super
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'baby_squeel/nodes/node'
|
2
|
+
|
3
|
+
module BabySqueel
|
4
|
+
module Nodes
|
5
|
+
# See: https://github.com/rails/arel/pull/435
|
6
|
+
class Grouping < Node
|
7
|
+
def initialize(node)
|
8
|
+
super
|
9
|
+
node.extend Arel::AliasPredication
|
10
|
+
node.extend Arel::OrderPredications
|
11
|
+
node.extend Arel::Math
|
12
|
+
node.extend Arel::Expressions
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'baby_squeel/operators'
|
2
|
+
require 'baby_squeel/nodes/proxy'
|
3
|
+
|
4
|
+
module BabySqueel
|
5
|
+
module Nodes
|
6
|
+
# This is a generic proxy class that includes all possible modules.
|
7
|
+
# In the future, these proxy classes should be more specific and only
|
8
|
+
# include necessary/applicable modules.
|
9
|
+
class Node < Proxy
|
10
|
+
extend Operators::ArelAliasing
|
11
|
+
include Operators::Comparison
|
12
|
+
include Operators::Equality
|
13
|
+
include Operators::Generic
|
14
|
+
include Operators::Grouping
|
15
|
+
include Operators::Matching
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module BabySqueel
|
2
|
+
module Nodes
|
3
|
+
# This proxy class allows us to quack like any arel object. When a
|
4
|
+
# method missing is hit, we'll instantiate a new proxy object.
|
5
|
+
class Proxy < ActiveSupport::ProxyObject
|
6
|
+
# Resolve constants the normal way
|
7
|
+
def self.const_missing(name)
|
8
|
+
::Object.const_get(name)
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :_arel
|
12
|
+
|
13
|
+
def initialize(arel)
|
14
|
+
@_arel = Nodes.unwrap(arel)
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
"BabySqueel{#{super}}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def respond_to?(meth, include_private = false)
|
22
|
+
meth.to_s == '_arel' || _arel.respond_to?(meth, include_private)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def method_missing(meth, *args, &block)
|
28
|
+
if _arel.respond_to?(meth)
|
29
|
+
Nodes.wrap _arel.send(meth, *Nodes.unwrap(args), &block)
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -9,7 +9,7 @@ module BabySqueel
|
|
9
9
|
# * +arel_name+ - The name of the Arel method you want to alias.
|
10
10
|
#
|
11
11
|
# ==== Example
|
12
|
-
# BabySqueel::Nodes::
|
12
|
+
# BabySqueel::Nodes::Node.arel_alias :unlike, :does_not_match
|
13
13
|
# Post.where.has { title.unlike 'something' }
|
14
14
|
#
|
15
15
|
def arel_alias(operator, arel_name)
|
data/lib/baby_squeel/relation.rb
CHANGED
@@ -15,19 +15,24 @@ module BabySqueel
|
|
15
15
|
if reflection = _scope.reflect_on_association(name)
|
16
16
|
Association.new(self, reflection)
|
17
17
|
else
|
18
|
-
|
18
|
+
not_found_error! name, type: AssociationNotFoundError
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
# Invokes a sifter defined on the model
|
23
|
+
#
|
24
|
+
# ==== Examples
|
25
|
+
# Post.where.has { sift(:name_contains, 'joe') }
|
26
|
+
#
|
22
27
|
def sift(sifter_name, *args)
|
23
28
|
Nodes.wrap _scope.public_send("sift_#{sifter_name}", *args)
|
24
29
|
end
|
25
30
|
|
26
31
|
private
|
27
32
|
|
28
|
-
# @override BabySqueel::Table#
|
29
|
-
def
|
30
|
-
_scope.model_name
|
33
|
+
# @override BabySqueel::Table#not_found_error!
|
34
|
+
def not_found_error!(name, type: NotFoundError)
|
35
|
+
raise type.new(_scope.model_name, name)
|
31
36
|
end
|
32
37
|
|
33
38
|
# @override BabySqueel::Table#resolve
|
data/lib/baby_squeel/table.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
require 'baby_squeel/
|
1
|
+
require 'baby_squeel/join_expression'
|
2
2
|
|
3
3
|
module BabySqueel
|
4
4
|
class Table
|
5
|
-
attr_accessor :_on, :
|
5
|
+
attr_accessor :_on, :_table
|
6
|
+
attr_writer :_join
|
6
7
|
|
7
8
|
def initialize(arel_table)
|
8
9
|
@_table = arel_table
|
9
|
-
@_join = Arel::Nodes::InnerJoin
|
10
10
|
end
|
11
11
|
|
12
12
|
# See Arel::Table#[]
|
@@ -14,23 +14,27 @@ module BabySqueel
|
|
14
14
|
Nodes::Attribute.new(self, key)
|
15
15
|
end
|
16
16
|
|
17
|
+
def _join
|
18
|
+
@_join ||= Arel::Nodes::InnerJoin
|
19
|
+
end
|
20
|
+
|
17
21
|
# Alias a table. This is only possible when joining
|
18
22
|
# an association explicitly.
|
19
23
|
def alias(alias_name)
|
20
24
|
clone.alias! alias_name
|
21
25
|
end
|
22
26
|
|
23
|
-
def alias!(alias_name)
|
27
|
+
def alias!(alias_name) # :nodoc:
|
24
28
|
self._table = _table.alias(alias_name)
|
25
29
|
self
|
26
30
|
end
|
27
31
|
|
28
|
-
# Instruct the table to be joined with
|
32
|
+
# Instruct the table to be joined with a LEFT OUTER JOIN.
|
29
33
|
def outer
|
30
34
|
clone.outer!
|
31
35
|
end
|
32
36
|
|
33
|
-
def outer!
|
37
|
+
def outer! # :nodoc:
|
34
38
|
self._join = Arel::Nodes::OuterJoin
|
35
39
|
self
|
36
40
|
end
|
@@ -40,7 +44,7 @@ module BabySqueel
|
|
40
44
|
clone.inner!
|
41
45
|
end
|
42
46
|
|
43
|
-
def inner!
|
47
|
+
def inner! # :nodoc:
|
44
48
|
self._join = Arel::Nodes::InnerJoin
|
45
49
|
self
|
46
50
|
end
|
@@ -50,26 +54,48 @@ module BabySqueel
|
|
50
54
|
clone.on! node
|
51
55
|
end
|
52
56
|
|
53
|
-
def on!(node)
|
54
|
-
self._on =
|
57
|
+
def on!(node) # :nodoc:
|
58
|
+
self._on = node
|
55
59
|
self
|
56
60
|
end
|
57
61
|
|
62
|
+
# When referencing a joined table, the tables that
|
63
|
+
# attributes reference can change (due to aliasing).
|
64
|
+
# This method allows BabySqueel::Nodes::Attribute
|
65
|
+
# instances to find what their alias will be.
|
66
|
+
def find_alias(association, associations = [])
|
67
|
+
builder = JoinDependency::Builder.new(_scope.all)
|
68
|
+
builder.ensure_associated(_arel(associations))
|
69
|
+
|
70
|
+
finder = JoinDependency::Finder.new(builder.to_join_dependency)
|
71
|
+
finder.find_alias(association._reflection)
|
72
|
+
end
|
73
|
+
|
58
74
|
# This method will be invoked by BabySqueel::Nodes::unwrap. When called,
|
59
|
-
# there are
|
75
|
+
# there are three possible outcomes:
|
60
76
|
#
|
61
|
-
# 1. Join explicitly using an on clause.
|
62
|
-
# 2.
|
77
|
+
# 1. Join explicitly using an on clause. Just return Arel.
|
78
|
+
# 2. Implicit join without using an outer join. In this case, we'll just
|
79
|
+
# give a hash to Active Record, and join the normal way.
|
80
|
+
# 3. Implicit join using an outer join. In this case, we need to use
|
81
|
+
# Polyamorous to build the join. We'll return a JoinExpression.
|
63
82
|
#
|
64
83
|
def _arel(associations = [])
|
65
|
-
|
66
|
-
|
84
|
+
if _on
|
85
|
+
_join.new(_table, Arel::Nodes::On.new(_on))
|
86
|
+
elsif associations.any?(&:needs_polyamorous?)
|
87
|
+
JoinExpression.new(associations)
|
88
|
+
elsif associations.any?
|
89
|
+
associations.reverse.inject({}) do |names, assoc|
|
90
|
+
{ assoc._reflection.name => names }
|
91
|
+
end
|
92
|
+
end
|
67
93
|
end
|
68
94
|
|
69
95
|
private
|
70
96
|
|
71
|
-
def
|
72
|
-
|
97
|
+
def not_found_error!
|
98
|
+
raise NotImplementedError, 'BabySqueel::Table will never raise a NotFoundError'
|
73
99
|
end
|
74
100
|
|
75
101
|
def resolve(name)
|
@@ -82,10 +108,7 @@ module BabySqueel
|
|
82
108
|
|
83
109
|
def method_missing(name, *args, &block)
|
84
110
|
return super if !args.empty? || block_given?
|
85
|
-
|
86
|
-
resolve(name) || begin
|
87
|
-
raise NotFoundError.new(_table_name, name)
|
88
|
-
end
|
111
|
+
resolve(name) || not_found_error!(name)
|
89
112
|
end
|
90
113
|
end
|
91
114
|
end
|
data/lib/baby_squeel/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: baby_squeel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ray Zane
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 4.
|
19
|
+
version: 4.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.
|
26
|
+
version: 4.1.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: polyamorous
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -88,17 +102,28 @@ extensions: []
|
|
88
102
|
extra_rdoc_files: []
|
89
103
|
files:
|
90
104
|
- CHANGELOG.md
|
105
|
+
- ISSUE_TEMPLATE.md
|
91
106
|
- LICENSE.txt
|
92
107
|
- README.md
|
93
108
|
- baby_squeel.gemspec
|
94
109
|
- lib/baby_squeel.rb
|
95
|
-
- lib/baby_squeel/active_record.rb
|
110
|
+
- lib/baby_squeel/active_record/base.rb
|
111
|
+
- lib/baby_squeel/active_record/query_methods.rb
|
112
|
+
- lib/baby_squeel/active_record/where_chain.rb
|
96
113
|
- lib/baby_squeel/association.rb
|
97
114
|
- lib/baby_squeel/compat.rb
|
98
115
|
- lib/baby_squeel/dsl.rb
|
99
116
|
- lib/baby_squeel/errors.rb
|
100
|
-
- lib/baby_squeel/join_dependency.rb
|
117
|
+
- lib/baby_squeel/join_dependency/builder.rb
|
118
|
+
- lib/baby_squeel/join_dependency/finder.rb
|
119
|
+
- lib/baby_squeel/join_dependency/injector.rb
|
120
|
+
- lib/baby_squeel/join_expression.rb
|
101
121
|
- lib/baby_squeel/nodes.rb
|
122
|
+
- lib/baby_squeel/nodes/attribute.rb
|
123
|
+
- lib/baby_squeel/nodes/function.rb
|
124
|
+
- lib/baby_squeel/nodes/grouping.rb
|
125
|
+
- lib/baby_squeel/nodes/node.rb
|
126
|
+
- lib/baby_squeel/nodes/proxy.rb
|
102
127
|
- lib/baby_squeel/operators.rb
|
103
128
|
- lib/baby_squeel/relation.rb
|
104
129
|
- lib/baby_squeel/table.rb
|
@@ -123,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
148
|
version: '0'
|
124
149
|
requirements: []
|
125
150
|
rubyforge_project:
|
126
|
-
rubygems_version: 2.
|
151
|
+
rubygems_version: 2.5.1
|
127
152
|
signing_key:
|
128
153
|
specification_version: 4
|
129
154
|
summary: A tiny squeel implementation without all of the evil.
|
@@ -1,62 +0,0 @@
|
|
1
|
-
module BabySqueel
|
2
|
-
class JoinDependency
|
3
|
-
delegate :_scope, :_join, :_on, :_table, to: :@table
|
4
|
-
|
5
|
-
def initialize(table, associations = [])
|
6
|
-
@table = table
|
7
|
-
@associations = associations
|
8
|
-
end
|
9
|
-
|
10
|
-
if ActiveRecord::VERSION::STRING < '4.1.0'
|
11
|
-
def bind_values
|
12
|
-
return [] unless relation?
|
13
|
-
_scope.joins(join_names(@associations)).bind_values
|
14
|
-
end
|
15
|
-
else
|
16
|
-
def bind_values
|
17
|
-
return [] unless relation?
|
18
|
-
relation = _scope.joins(join_names(@associations))
|
19
|
-
relation.arel.bind_values + relation.bind_values
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# Converts an array of BabySqueel::Associations into an array
|
24
|
-
# of Arel join nodes.
|
25
|
-
#
|
26
|
-
# Each association is built individually so that the correct
|
27
|
-
# Arel join node will be used for each individual association.
|
28
|
-
def _arel
|
29
|
-
if _on
|
30
|
-
[_join.new(_table, _on)]
|
31
|
-
else
|
32
|
-
@associations.each.with_index.inject([]) do |joins, (assoc, i)|
|
33
|
-
construct @associations[0..i], joins, assoc._join
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def relation?
|
41
|
-
@table.kind_of? BabySqueel::Relation
|
42
|
-
end
|
43
|
-
|
44
|
-
def construct(associations, theirs, join_node)
|
45
|
-
names = join_names associations
|
46
|
-
mine = build names, join_node
|
47
|
-
theirs + mine[theirs.length..-1]
|
48
|
-
end
|
49
|
-
|
50
|
-
def build(names, join_node)
|
51
|
-
_scope.joins(names).join_sources.map do |join|
|
52
|
-
join_node.new(join.left, join.right)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def join_names(associations = [])
|
57
|
-
associations.reverse.inject({}) do |names, assoc|
|
58
|
-
{ assoc._reflection.name => names }
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|