baby_squeel 0.3.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|