baby_squeel 0.1.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +15 -0
- data/.travis.yml +19 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +21 -0
- data/README.md +170 -0
- data/Rakefile +32 -0
- data/baby_squeel.gemspec +31 -0
- data/bin/console +25 -0
- data/bin/setup +8 -0
- data/lib/baby_squeel.rb +10 -0
- data/lib/baby_squeel/active_record.rb +35 -0
- data/lib/baby_squeel/association.rb +43 -0
- data/lib/baby_squeel/dsl.rb +33 -0
- data/lib/baby_squeel/join_dependency.rb +55 -0
- data/lib/baby_squeel/nodes.rb +68 -0
- data/lib/baby_squeel/operators.rb +47 -0
- data/lib/baby_squeel/table.rb +99 -0
- data/lib/baby_squeel/version.rb +3 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4dec1d36d240f113d9122c83549c06601f29124c
|
4
|
+
data.tar.gz: f97d4bc708832c1895e0e612c6bd76dfe63b6877
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7be1b2e3d861f50328cb1a9b05f8e02099a1db3c33dfeda3259a77667291b8f3b373d3e0a395230b1b64af7b8d8c2d559f52e31752c7e20401371e5eaa1fecaf
|
7
|
+
data.tar.gz: 877ad6134d70a905a9142f92265e059ef48e49edd9ef513445a966b0a2a22758c0e1792be9180b749be5d2673e5e817a15550fe93e465eb61e2440709bd9b770
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
rvm:
|
4
|
+
- 2.2.4
|
5
|
+
|
6
|
+
before_install: gem install bundler -v 1.11.2
|
7
|
+
before_script: bundle exec rubocop
|
8
|
+
after_script: bundle exec rake coveralls:push
|
9
|
+
|
10
|
+
env:
|
11
|
+
global:
|
12
|
+
- COVERALLS_REPO_TOKEN=X5ZWSPW8VW2JXNJO0qNjlZCQNW4ju89gb
|
13
|
+
matrix:
|
14
|
+
- AR=4.0.0
|
15
|
+
- AR=4.1.0
|
16
|
+
- AR=4.1.15
|
17
|
+
- AR=4.2.0
|
18
|
+
- AR=4.2.6
|
19
|
+
- AR=5.0.0.beta3
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in baby_squeel.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
if ar_version = ENV['AR']
|
7
|
+
gem 'activerecord', ar_version
|
8
|
+
else
|
9
|
+
gem 'activerecord', github: 'rails/rails'
|
10
|
+
end
|
11
|
+
|
12
|
+
group :test do
|
13
|
+
gem 'pry'
|
14
|
+
gem 'coveralls'
|
15
|
+
gem 'simplecov'
|
16
|
+
end
|
17
|
+
|
18
|
+
gem 'rubocop', require: false
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Ray Zane
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# BabySqueel
|
2
|
+
|
3
|
+
[](https://travis-ci.org/rzane/baby_squeel)
|
4
|
+
[](https://codeclimate.com/github/rzane/baby_squeel)
|
5
|
+
[](https://coveralls.io/github/rzane/baby_squeel?branch=master)
|
6
|
+
|
7
|
+

|
8
|
+
|
9
|
+
Have you ever used the [squeel](https://github.com/activerecord-hackery/squeel) gem? It's a really nice way to build complex queries. However, squeel monkeypatches ActiveRecord internals, so it has a tendency to break every time a new ActiveRecord version comes out.
|
10
|
+
|
11
|
+
For me, that's a deal breaker. BabySqueel provides a query DSL for ActiveRecord without all of the evil :heart:.
|
12
|
+
|
13
|
+
It's also suprisingly uncomplicated. It's really just a layer of sugar on top of Arel.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'baby_squeel'
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install baby_squeel
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
Okay, so we have a `Post` model:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class Post < ActiveRecord::Base
|
37
|
+
belongs_to :author
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
#### Selects
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
Post.selecting { (id + 5).as('id_plus_five') }
|
45
|
+
# SELECT "posts"."id" + 5 AS id_plus_five FROM "posts"
|
46
|
+
|
47
|
+
Post.selecting { id.sum }
|
48
|
+
# SELECT SUM("posts"."id") FROM "posts"
|
49
|
+
|
50
|
+
Post.joins(:author).selecting { [id, author.id] }
|
51
|
+
# SELECT "posts"."id", "author"."id"
|
52
|
+
# FROM "posts"
|
53
|
+
# INNER JOIN "authors" ON "posts"."author_id" = "authors"."id"
|
54
|
+
```
|
55
|
+
|
56
|
+
#### Joins
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
Post.joining { author }
|
60
|
+
# SELECT "posts".* FROM "posts"
|
61
|
+
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
62
|
+
|
63
|
+
Post.joining { [author.outer, comments] }
|
64
|
+
# SELECT "posts".* FROM "posts"
|
65
|
+
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
66
|
+
# INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
67
|
+
|
68
|
+
Post.joining { author.comments }
|
69
|
+
# SELECT "posts".* FROM "posts"
|
70
|
+
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
71
|
+
# INNER JOIN "comments" ON "comments"."author_id" = "authors"."id"
|
72
|
+
|
73
|
+
Post.joining { author.outer.comments.outer }
|
74
|
+
# SELECT "posts".* FROM "posts"
|
75
|
+
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
76
|
+
# LEFT OUTER JOIN "comments" ON "comments"."author_id" = "authors"."id"
|
77
|
+
|
78
|
+
Post.joining { author.comments.outer }
|
79
|
+
# SELECT "posts".* FROM "posts"
|
80
|
+
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
81
|
+
# LEFT OUTER JOIN "comments" ON "comments"."author_id" = "authors"."id"
|
82
|
+
|
83
|
+
Post.joining { author.alias('a').on((author.id == author_id) | (author.name == title)) }
|
84
|
+
# SELECT "posts".* FROM "posts"
|
85
|
+
# INNER JOIN "authors" "a" ON (
|
86
|
+
# "authors"."id" = "posts"."author_id" OR
|
87
|
+
# "authors"."name" = "posts"."title"
|
88
|
+
# )
|
89
|
+
```
|
90
|
+
|
91
|
+
#### Wheres
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
Post.where.has { title == 'My Post' }
|
95
|
+
# SELECT "posts".* FROM "posts" WHERE "posts"."title" = 'My Post'
|
96
|
+
|
97
|
+
Post.where.has { title =~ 'My P%' }
|
98
|
+
# SELECT "posts".* FROM "posts" WHERE "posts"."title" LIKE 'My P%'
|
99
|
+
|
100
|
+
Author.where.has { (name =~ 'Ray%') & (id < 5) | (name.lower =~ 'zane%') & (id > 100) }
|
101
|
+
# SELECT "authors".* FROM "authors"
|
102
|
+
# WHERE (
|
103
|
+
# "authors"."name" LIKE 'Ray%' AND "authors"."id" < 5 OR
|
104
|
+
# LOWER("authors"."name") LIKE 'zane%' AND "authors"."id" > 100
|
105
|
+
# )
|
106
|
+
|
107
|
+
Post.joins(:author).where.has { author.name == 'Ray' }
|
108
|
+
# SELECT "posts".* FROM "posts"
|
109
|
+
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
110
|
+
# WHERE "authors"."name" = 'Ray'
|
111
|
+
```
|
112
|
+
|
113
|
+
#### Orders
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
Post.ordering { [id.desc, title.asc] }
|
117
|
+
# SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC, "posts"."title" ASC
|
118
|
+
|
119
|
+
Post.ordering { (id * 5).desc }
|
120
|
+
# SELECT "posts".* FROM "posts" ORDER BY "posts"."id" * 5 DESC
|
121
|
+
|
122
|
+
Post.select(:author_id).group(:author_id).ordering { id.count.desc }
|
123
|
+
# SELECT "posts"."author_id"
|
124
|
+
# FROM "posts" GROUP BY "posts"."author_id"
|
125
|
+
# ORDER BY COUNT("posts"."id") DESC
|
126
|
+
|
127
|
+
Post.joins(:author).ordering { author.id.desc }
|
128
|
+
# SELECT "posts".* FROM "posts"
|
129
|
+
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
130
|
+
# ORDER BY "authors"."id" DESC
|
131
|
+
```
|
132
|
+
|
133
|
+
#### Functions
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
Post.selecting { coalesce(author_id, 5).as('author_id_with_default') }
|
137
|
+
# SELECT coalesce("posts"."author_id", 5) AS author_id_with_default FROM "posts"
|
138
|
+
```
|
139
|
+
|
140
|
+
## Important Notes
|
141
|
+
|
142
|
+
While inside one of BabySqueel's blocks, `self` will be something totally different. You won't have access to your instance variables or methods.
|
143
|
+
|
144
|
+
Don't worry, there's an easy solution. Just give arity to the block:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
Post.where.has { |table| table.title == 'Test' }
|
148
|
+
# SELECT "posts".* WHERE "posts"."title" = 'Test'
|
149
|
+
```
|
150
|
+
|
151
|
+
## Development
|
152
|
+
|
153
|
+
1. Pick an ActiveRecord version to develop against, then export it: `export AR=4.2.6`.
|
154
|
+
2. Run `bin/setup` to install dependencies.
|
155
|
+
3. Run `rake` to run the specs.
|
156
|
+
|
157
|
+
You can also run `bin/console` to open up a prompt where you'll have access to some models to experiment with.
|
158
|
+
|
159
|
+
## Todo
|
160
|
+
|
161
|
+
I'd like to support complex joins with explicit outer joins and aliasing.
|
162
|
+
|
163
|
+
## Contributing
|
164
|
+
|
165
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rzane/baby_squeel.
|
166
|
+
|
167
|
+
|
168
|
+
## License
|
169
|
+
|
170
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'coveralls/rake/task'
|
5
|
+
|
6
|
+
Coveralls::RakeTask.new
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
9
|
+
|
10
|
+
def run_version(version, cmd)
|
11
|
+
display = "AR=#{version} #{cmd}"
|
12
|
+
puts display
|
13
|
+
|
14
|
+
Bundler.with_clean_env do
|
15
|
+
system({ 'AR' => version }, cmd)
|
16
|
+
end
|
17
|
+
|
18
|
+
abort "\nFAILED: #{display}" unless $CHILD_STATUS.success?
|
19
|
+
end
|
20
|
+
|
21
|
+
task 'spec:matrix' do
|
22
|
+
travis = YAML.load_file '.travis.yml'
|
23
|
+
|
24
|
+
travis['env']['matrix'].reverse.each do |variable|
|
25
|
+
version = variable[/\=(.+)$/, 1]
|
26
|
+
rm_rf 'Gemfile.lock'
|
27
|
+
run_version version, 'bundle install > /dev/null'
|
28
|
+
run_version version, 'rake spec'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
task default: :spec
|
data/baby_squeel.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'baby_squeel/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'baby_squeel'
|
8
|
+
spec.version = BabySqueel::VERSION
|
9
|
+
spec.authors = ['Ray Zane']
|
10
|
+
spec.email = ['ray@promptworks.com']
|
11
|
+
|
12
|
+
spec.summary = 'A tiny squeel implementation without all of the evil.'
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = 'https://github.com/rzane/baby_squeel'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.bindir = 'exe'
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
22
|
+
f.match(%r{^(test|spec|features)/})
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.add_dependency 'activerecord', '>= 4.0.0'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.11'
|
28
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.4.0'
|
30
|
+
spec.add_development_dependency 'sqlite3'
|
31
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'baby_squeel'
|
5
|
+
require_relative '../spec/support/schema'
|
6
|
+
require_relative '../spec/support/models'
|
7
|
+
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
10
|
+
|
11
|
+
puts 'Creating 2 authors...'
|
12
|
+
authors = [
|
13
|
+
Author.create!(name: 'Ray Zane'),
|
14
|
+
Author.create!(name: 'Jimmy Hoffa')
|
15
|
+
]
|
16
|
+
|
17
|
+
puts 'Creating 15 posts...'
|
18
|
+
15.times do |n|
|
19
|
+
Post.create! title: "Post #{n}", author: authors.sample
|
20
|
+
end
|
21
|
+
|
22
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
23
|
+
|
24
|
+
require 'pry'
|
25
|
+
Pry.start
|
data/bin/setup
ADDED
data/lib/baby_squeel.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/relation'
|
3
|
+
require 'baby_squeel/version'
|
4
|
+
require 'baby_squeel/active_record'
|
5
|
+
|
6
|
+
module BabySqueel
|
7
|
+
end
|
8
|
+
|
9
|
+
::ActiveRecord::Base.extend BabySqueel::ActiveRecord::QueryMethods
|
10
|
+
::ActiveRecord::QueryMethods::WhereChain.prepend BabySqueel::ActiveRecord::WhereChain
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'baby_squeel/dsl'
|
2
|
+
|
3
|
+
module BabySqueel
|
4
|
+
module ActiveRecord
|
5
|
+
module QueryMethods
|
6
|
+
def joining(&block)
|
7
|
+
joins DSL.evaluate(self, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def selecting(&block)
|
11
|
+
select DSL.evaluate(self, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def ordering(&block)
|
15
|
+
order DSL.evaluate(self, &block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module WhereChain
|
20
|
+
if ::ActiveRecord::VERSION::MAJOR > 4
|
21
|
+
def has(&block)
|
22
|
+
opts = DSL.evaluate(@scope, &block)
|
23
|
+
factory = @scope.send(:where_clause_factory)
|
24
|
+
@scope.where_clause += factory.build(opts, [])
|
25
|
+
@scope
|
26
|
+
end
|
27
|
+
else
|
28
|
+
def has(&block)
|
29
|
+
@scope.where_values += [DSL.evaluate(@scope, &block)]
|
30
|
+
@scope
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'baby_squeel/table'
|
2
|
+
require 'baby_squeel/join_dependency'
|
3
|
+
|
4
|
+
module BabySqueel
|
5
|
+
class AliasingError < StandardError
|
6
|
+
MESSAGE =
|
7
|
+
'Attempted to alias \'%{association}\' as \'%{alias_name}\', but the ' \
|
8
|
+
'association was implicitly joined. Either join the association ' \
|
9
|
+
'with `on` or remove the alias.'.freeze
|
10
|
+
|
11
|
+
def initialize(association, alias_name)
|
12
|
+
super format(MESSAGE, association: association, alias_name: alias_name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Association < Table
|
17
|
+
def initialize(parent, reflection)
|
18
|
+
@parent = parent
|
19
|
+
@reflection = reflection
|
20
|
+
super(@reflection.klass)
|
21
|
+
end
|
22
|
+
|
23
|
+
def _arel
|
24
|
+
props[:on] ? super : ([*@parent._arel] + join_constraints)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def join_constraints
|
30
|
+
if props[:table].is_a? Arel::Nodes::TableAlias
|
31
|
+
raise AliasingError.new(@reflection.name, props[:table].right)
|
32
|
+
end
|
33
|
+
|
34
|
+
JoinDependency.new(@scope, @reflection, props[:join]).constraints
|
35
|
+
end
|
36
|
+
|
37
|
+
def spawn
|
38
|
+
Association.new(@parent, @reflection).tap do |table|
|
39
|
+
table.props = props
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'baby_squeel/nodes'
|
2
|
+
require 'baby_squeel/table'
|
3
|
+
require 'baby_squeel/association'
|
4
|
+
|
5
|
+
module BabySqueel
|
6
|
+
class DSL < Table
|
7
|
+
def self.evaluate(scope, &block)
|
8
|
+
new(scope).evaluate(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def func(name, *args)
|
12
|
+
Nodes.wrap Arel::Nodes::NamedFunction.new(name.to_s, args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate(&block)
|
16
|
+
if block.arity.zero?
|
17
|
+
Nodes.unwrap instance_eval(&block)
|
18
|
+
else
|
19
|
+
Nodes.unwrap yield(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def method_missing(meth, *args, &block)
|
26
|
+
if !args.empty? && !block_given?
|
27
|
+
func(meth, args)
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Props to Arel Helpers
|
2
|
+
# https://github.com/camertron/arel-helpers/blob/master/lib/arel-helpers/join_association.rb
|
3
|
+
#
|
4
|
+
# This is really, really ugly.
|
5
|
+
#
|
6
|
+
module BabySqueel
|
7
|
+
class JoinDependency
|
8
|
+
def initialize(scope, reflection, join_type)
|
9
|
+
@scope = scope
|
10
|
+
@reflection = reflection
|
11
|
+
@join_type = join_type
|
12
|
+
end
|
13
|
+
|
14
|
+
if ::ActiveRecord::VERSION::MAJOR > 4
|
15
|
+
def constraints
|
16
|
+
dependency.join_constraints([], @join_type).flat_map(&:joins)
|
17
|
+
end
|
18
|
+
elsif ::ActiveRecord::VERSION::STRING >= '4.2.0'
|
19
|
+
def constraints
|
20
|
+
dependency.join_constraints([]).flat_map do |constraint|
|
21
|
+
constraint.joins.map { |join| rebuild join }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
elsif ::ActiveRecord::VERSION::STRING >= '4.1.0'
|
25
|
+
def constraints
|
26
|
+
dependency.join_constraints([]).flat_map { |join| rebuild join }
|
27
|
+
end
|
28
|
+
else
|
29
|
+
def constraints
|
30
|
+
manager = Arel::SelectManager.new(@scope)
|
31
|
+
|
32
|
+
dependency.join_associations.each do |assoc|
|
33
|
+
assoc.join_type = @join_type
|
34
|
+
assoc.join_to(manager)
|
35
|
+
end
|
36
|
+
|
37
|
+
manager.join_sources
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def rebuild(join)
|
44
|
+
@join_type.new(join.left, join.right)
|
45
|
+
end
|
46
|
+
|
47
|
+
def dependency
|
48
|
+
::ActiveRecord::Associations::JoinDependency.new(
|
49
|
+
@reflection.active_record,
|
50
|
+
[@reflection.name],
|
51
|
+
[]
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'baby_squeel/operators'
|
2
|
+
|
3
|
+
module BabySqueel
|
4
|
+
module Nodes
|
5
|
+
class << self
|
6
|
+
def wrap(arel)
|
7
|
+
if arel.class.parents.include?(Arel)
|
8
|
+
Generic.new(arel)
|
9
|
+
else
|
10
|
+
arel
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def unwrap(node)
|
15
|
+
if node.respond_to? :_arel
|
16
|
+
node._arel
|
17
|
+
elsif node.is_a? Array
|
18
|
+
node.map { |n| unwrap(n) }
|
19
|
+
else
|
20
|
+
node
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# This proxy class allows us to quack like
|
26
|
+
# any arel object. When a method missing is
|
27
|
+
# hit, we'll instantiate a new proxy object.
|
28
|
+
class Proxy < ActiveSupport::ProxyObject
|
29
|
+
# Resolve constants the normal way
|
30
|
+
def self.const_missing(name)
|
31
|
+
::Object.const_get(name)
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :_arel
|
35
|
+
|
36
|
+
def initialize(arel)
|
37
|
+
@_arel = Nodes.unwrap(arel)
|
38
|
+
end
|
39
|
+
|
40
|
+
def respond_to?(meth, include_private = false)
|
41
|
+
meth.to_s == '_arel' || _arel.respond_to?(meth, include_private)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def method_missing(meth, *args, &block)
|
47
|
+
if _arel.respond_to?(meth)
|
48
|
+
Nodes.wrap _arel.send(meth, *Nodes.unwrap(args), &block)
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# This is a generic proxy class that includes
|
56
|
+
# all possible modules. In the future, these
|
57
|
+
# proxy classes should be more specific and
|
58
|
+
# only include necessary/applicable modules.
|
59
|
+
class Generic < Proxy
|
60
|
+
include Arel::OrderPredications
|
61
|
+
include Operators::Comparison
|
62
|
+
include Operators::Equality
|
63
|
+
include Operators::Generic
|
64
|
+
include Operators::Grouping
|
65
|
+
include Operators::Matching
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module BabySqueel
|
2
|
+
module Operators
|
3
|
+
module ArelAliasing
|
4
|
+
def arel_alias(operator, arel_name)
|
5
|
+
define_method operator do |other|
|
6
|
+
send(arel_name, other)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Comparison
|
12
|
+
extend ArelAliasing
|
13
|
+
arel_alias :<, :lt
|
14
|
+
arel_alias :>, :gt
|
15
|
+
arel_alias :<=, :lteq
|
16
|
+
arel_alias :>=, :gteq
|
17
|
+
end
|
18
|
+
|
19
|
+
module Equality
|
20
|
+
extend ArelAliasing
|
21
|
+
arel_alias :==, :eq
|
22
|
+
arel_alias :'!=', :not_eq
|
23
|
+
end
|
24
|
+
|
25
|
+
module Generic
|
26
|
+
def op(operator, other)
|
27
|
+
Nodes.wrap Arel::Nodes::InfixOperation.new(operator, self, other)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Grouping
|
32
|
+
extend ArelAliasing
|
33
|
+
arel_alias :&, :and
|
34
|
+
arel_alias :|, :or
|
35
|
+
end
|
36
|
+
|
37
|
+
module Matching
|
38
|
+
extend ArelAliasing
|
39
|
+
arel_alias :=~, :matches
|
40
|
+
arel_alias :'!~', :does_not_match
|
41
|
+
arel_alias :like, :matches
|
42
|
+
arel_alias :not_like, :does_not_match
|
43
|
+
arel_alias :like_any, :matches_any
|
44
|
+
arel_alias :not_like_any, :does_not_match_any
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module BabySqueel
|
2
|
+
class AssociationNotFoundError < StandardError
|
3
|
+
def initialize(scope, name)
|
4
|
+
super "Association named '#{name}' was not found on #{scope.model_name}"
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Table
|
9
|
+
attr_writer :props
|
10
|
+
|
11
|
+
def initialize(scope)
|
12
|
+
@scope = scope
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
Nodes.wrap props[:table][key]
|
17
|
+
end
|
18
|
+
|
19
|
+
def association(name)
|
20
|
+
if reflection = @scope.reflect_on_association(name)
|
21
|
+
Association.new(self, reflection)
|
22
|
+
else
|
23
|
+
raise AssociationNotFoundError.new(@scope, name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def alias(alias_name)
|
28
|
+
spawn.alias! alias_name
|
29
|
+
end
|
30
|
+
|
31
|
+
def alias!(alias_name)
|
32
|
+
props.store :table, props[:table].alias(alias_name)
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def outer
|
37
|
+
spawn.outer!
|
38
|
+
end
|
39
|
+
|
40
|
+
def outer!
|
41
|
+
props.store :join, Arel::Nodes::OuterJoin
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def inner
|
46
|
+
spawn.inner!
|
47
|
+
end
|
48
|
+
|
49
|
+
def inner!
|
50
|
+
props.store :join, Arel::Nodes::InnerJoin
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def on(node)
|
55
|
+
spawn.on! node
|
56
|
+
end
|
57
|
+
|
58
|
+
def on!(node)
|
59
|
+
props.store :on, Arel::Nodes::On.new(node)
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def _arel
|
64
|
+
props[:join].new(props[:table], props[:on]) if props[:on]
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def props
|
70
|
+
@props ||= { table: @scope.arel_table, join: Arel::Nodes::InnerJoin }
|
71
|
+
end
|
72
|
+
|
73
|
+
def spawn
|
74
|
+
Table.new(@scope).tap do |table|
|
75
|
+
table.props = props
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def resolve(name)
|
80
|
+
if @scope.column_names.include?(name.to_s)
|
81
|
+
self[name]
|
82
|
+
elsif @scope.reflect_on_association(name)
|
83
|
+
association(name)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def respond_to_missing?(name, *)
|
88
|
+
resolve(name).present? || super
|
89
|
+
end
|
90
|
+
|
91
|
+
def method_missing(name, *args, &block)
|
92
|
+
if !args.empty? || block_given?
|
93
|
+
super
|
94
|
+
else
|
95
|
+
resolve(name) || super
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: baby_squeel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ray Zane
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.11'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.11'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.4.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.4.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: A tiny squeel implementation without all of the evil.
|
84
|
+
email:
|
85
|
+
- ray@promptworks.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".rubocop.yml"
|
93
|
+
- ".travis.yml"
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- baby_squeel.gemspec
|
99
|
+
- bin/console
|
100
|
+
- bin/setup
|
101
|
+
- lib/baby_squeel.rb
|
102
|
+
- lib/baby_squeel/active_record.rb
|
103
|
+
- lib/baby_squeel/association.rb
|
104
|
+
- lib/baby_squeel/dsl.rb
|
105
|
+
- lib/baby_squeel/join_dependency.rb
|
106
|
+
- lib/baby_squeel/nodes.rb
|
107
|
+
- lib/baby_squeel/operators.rb
|
108
|
+
- lib/baby_squeel/table.rb
|
109
|
+
- lib/baby_squeel/version.rb
|
110
|
+
homepage: https://github.com/rzane/baby_squeel
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
metadata: {}
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 2.4.8
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: A tiny squeel implementation without all of the evil.
|
134
|
+
test_files: []
|