active_record_union 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/README.md +13 -0
- data/active_record_union.gemspec +2 -0
- data/lib/active_record_union/active_record/relation/union.rb +11 -3
- data/lib/active_record_union/version.rb +1 -1
- data/spec/spec_helper.rb +5 -5
- data/spec/support/databases.rb +47 -0
- data/spec/support/models.rb +10 -9
- data/spec/union_spec.rb +41 -0
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de7279017756c9ab6fa65a13d457b671d4d3fc1f
|
4
|
+
data.tar.gz: a059566a08659d432635c1ad707431a3b8c163a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc0aae94fd243b66458b8ad719833bea64c69be694eaa5a73e370c3aea1dd918b74e15463b8c03b7d5c7d808db526bc0de3b06f27e8d27087e1678c0d0441b70
|
7
|
+
data.tar.gz: 0481dcf3092a0e166bcca93da7e9b1634d999cdecfd7ef14d108ab95ab2c291c9d8cf8e77f232b946da5beba0aec78ecb2036abaa5536668656655af6f908a0e
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -100,6 +100,13 @@ SELECT "posts".* FROM (
|
|
100
100
|
|
101
101
|
<a name="footnote-1"></a>[1] Note: the `?` in the SQL is bound to the correct value when ActiveRecord executes the query. Also, the SQL examples here were generated for a SQLite database. The syntax generated for other databases may vary slightly.
|
102
102
|
|
103
|
+
## Caveats
|
104
|
+
|
105
|
+
There's a couple things to be aware of when using ActiveRecordUnion:
|
106
|
+
|
107
|
+
1. ActiveRecordUnion with raise an error if you try to UNION any relations that do any preloading/eager-loading. There's no sensible way to do the preloading in the subselects. If enough people complain maybe we can change ActiveRecordUnion to let the queries run anyway but without preloading any records.
|
108
|
+
2. There's no easy way to get SQLite to allow ORDER BY in the UNION subselects. If you get a syntax error, you can either write `my_relation.reorder(nil).union(other.reorder(nil))` or switch to Postgres.
|
109
|
+
|
103
110
|
## Another nifty way to reduce extra queries
|
104
111
|
|
105
112
|
ActiveRecord already supports turning scopes into nested queries in WHERE clauses. The nested relation defaults to selecting `id` by default.
|
@@ -152,6 +159,12 @@ https://github.com/yakaz/rails/commit/29b8ebd187e0888d5e71b2e1e4a12334860bc76c
|
|
152
159
|
|
153
160
|
This is a gem not a Rails pull request because the standard of code quality for a PR is a bit higher, and we'd have to wait for the PR to be merged and relased to use UNIONs. That said, the code here is fairly clean and it may end up in a PR sometime.
|
154
161
|
|
162
|
+
## Changelog
|
163
|
+
|
164
|
+
**1.0.1** - Sept 2, 2014 - Allow ORDER BY in UNION subselects for databases that support it (not SQLite).
|
165
|
+
|
166
|
+
**1.0.0** - July 24, 2014 - Initial release.
|
167
|
+
|
155
168
|
## License
|
156
169
|
|
157
170
|
ActiveRecordUnion is dedicated to the public domain by its author, Brian Hempel. No rights are reserved. No restrictions are placed on the use of ActiveRecordUnion. That freedom also means, of course, that no warrenty of fitness is claimed; use ActiveRecordUnion at your own risk.
|
data/active_record_union.gemspec
CHANGED
@@ -7,19 +7,27 @@ module ActiveRecord
|
|
7
7
|
|
8
8
|
verify_union_relations!(self, other)
|
9
9
|
|
10
|
-
|
10
|
+
# Postgres allows ORDER BY in the UNION subqueries if each subquery is surrounded by parenthesis
|
11
|
+
# but SQLite does not allow parens around the subqueries; you will have to explicitly do `relation.reorder(nil)` in SQLite
|
12
|
+
if Arel::Visitors::SQLite === self.visitor
|
13
|
+
left, right = self.ast, other.ast
|
14
|
+
else
|
15
|
+
left, right = Arel::Nodes::Grouping.new(self.ast), Arel::Nodes::Grouping.new(other.ast)
|
16
|
+
end
|
17
|
+
|
18
|
+
union = Arel::Nodes::Union.new(left, right)
|
11
19
|
from = Arel::Nodes::TableAlias.new(
|
12
20
|
union,
|
13
21
|
Arel::Nodes::SqlLiteral.new(@klass.arel_table.name)
|
14
22
|
)
|
15
23
|
|
16
24
|
relation = @klass.unscoped.from(from)
|
17
|
-
relation.bind_values = self.bind_values + other.bind_values
|
25
|
+
relation.bind_values = self.bind_values + other.bind_values
|
18
26
|
relation
|
19
27
|
end
|
20
28
|
|
21
29
|
private
|
22
|
-
|
30
|
+
|
23
31
|
def verify_union_relations!(*args)
|
24
32
|
includes_relations = args.select { |r| r.includes_values.any? }
|
25
33
|
if includes_relations.any?
|
data/spec/spec_helper.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
Bundler.require(:development)
|
1
4
|
require "active_record_union"
|
2
5
|
|
3
|
-
|
4
|
-
adapter: "sqlite3",
|
5
|
-
database: ":memory:"
|
6
|
-
)
|
6
|
+
require "support/databases"
|
7
7
|
|
8
|
-
|
8
|
+
Databases.connect_to_sqlite
|
9
9
|
|
10
10
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
11
11
|
RSpec.configure do |config|
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Databases
|
2
|
+
extend self
|
3
|
+
|
4
|
+
def connect_to_sqlite
|
5
|
+
ActiveRecord::Base.establish_connection(
|
6
|
+
adapter: "sqlite3",
|
7
|
+
database: ":memory:"
|
8
|
+
)
|
9
|
+
load("support/models.rb")
|
10
|
+
end
|
11
|
+
|
12
|
+
def connect_to_postgres
|
13
|
+
ActiveRecord::Base.establish_connection(
|
14
|
+
adapter: "postgresql"
|
15
|
+
)
|
16
|
+
ActiveRecord::Base.connection.recreate_database("test_active_record_union")
|
17
|
+
ActiveRecord::Base.establish_connection(
|
18
|
+
adapter: "postgresql",
|
19
|
+
database: "test_active_record_union"
|
20
|
+
)
|
21
|
+
load("support/models.rb")
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect_to_mysql
|
25
|
+
ActiveRecord::Base.establish_connection(
|
26
|
+
adapter: "mysql2"
|
27
|
+
)
|
28
|
+
ActiveRecord::Base.connection.recreate_database("test_active_record_union")
|
29
|
+
ActiveRecord::Base.establish_connection(
|
30
|
+
adapter: "mysql2",
|
31
|
+
database: "test_active_record_union"
|
32
|
+
)
|
33
|
+
load("support/models.rb")
|
34
|
+
end
|
35
|
+
|
36
|
+
def with_postgres(&block)
|
37
|
+
connect_to_postgres
|
38
|
+
ensure
|
39
|
+
connect_to_sqlite
|
40
|
+
end
|
41
|
+
|
42
|
+
def with_mysql(&block)
|
43
|
+
connect_to_mysql
|
44
|
+
ensure
|
45
|
+
connect_to_sqlite
|
46
|
+
end
|
47
|
+
end
|
data/spec/support/models.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
end
|
1
|
+
ActiveRecord::Base.connection.create_table :users, force: true do |t|
|
2
|
+
end
|
4
3
|
|
4
|
+
class User < ActiveRecord::Base
|
5
5
|
has_many :posts
|
6
|
+
end unless defined?(User)
|
7
|
+
|
8
|
+
ActiveRecord::Base.connection.create_table :posts, force: true do |t|
|
9
|
+
t.integer :user_id
|
10
|
+
t.timestamp :published_at
|
11
|
+
t.timestamps
|
6
12
|
end
|
7
13
|
|
8
14
|
class Post < ActiveRecord::Base
|
9
|
-
connection.create_table :posts, force: true do |t|
|
10
|
-
t.integer :user_id
|
11
|
-
t.timestamps
|
12
|
-
end
|
13
|
-
|
14
15
|
belongs_to :user
|
15
|
-
end
|
16
|
+
end unless defined?(Post)
|
data/spec/union_spec.rb
CHANGED
@@ -31,6 +31,7 @@ describe ActiveRecord::Relation do
|
|
31
31
|
expect(union.to_sql).to eq(
|
32
32
|
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = ? UNION SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '2014-07-19 00:00:00.000000') ) posts"
|
33
33
|
)
|
34
|
+
expect{union.to_a}.to_not raise_error
|
34
35
|
end
|
35
36
|
|
36
37
|
it "binds values properly" do
|
@@ -57,6 +58,46 @@ describe ActiveRecord::Relation do
|
|
57
58
|
expect(union.to_sql).to eq(
|
58
59
|
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE (published_at < '2014-07-24 00:00:00.000000') AND (created_at > '2014-07-19 00:00:00.000000') UNION SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = ? ) posts"
|
59
60
|
)
|
61
|
+
expect{union.to_a}.to_not raise_error
|
62
|
+
end
|
63
|
+
|
64
|
+
context "with ORDER BY in subselects" do
|
65
|
+
def union
|
66
|
+
User.new.posts.order(:created_at).union(
|
67
|
+
Post.where("created_at > ?", Time.utc(2014, 7, 19, 0, 0, 0)).order(:created_at)
|
68
|
+
).order(:created_at)
|
69
|
+
end
|
70
|
+
|
71
|
+
context "in SQLite" do
|
72
|
+
it "lets ORDER BY in query subselects throw a syntax error" do
|
73
|
+
expect(union.to_sql).to eq(
|
74
|
+
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = ? ORDER BY \"posts\".\"created_at\" ASC UNION SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '2014-07-19 00:00:00.000000') ORDER BY \"posts\".\"created_at\" ASC ) posts ORDER BY \"posts\".\"created_at\" ASC"
|
75
|
+
)
|
76
|
+
expect{union.to_a}.to raise_error(ActiveRecord::StatementInvalid)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "in Postgres" do
|
81
|
+
it "wraps query subselects in parentheses to allow ORDER BY clauses" do
|
82
|
+
Databases.with_postgres do
|
83
|
+
expect(union.to_sql).to eq(
|
84
|
+
"SELECT \"posts\".* FROM ( (SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = $1 ORDER BY \"posts\".\"created_at\" ASC) UNION (SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '2014-07-19 00:00:00.000000') ORDER BY \"posts\".\"created_at\" ASC) ) posts ORDER BY \"posts\".\"created_at\" ASC"
|
85
|
+
)
|
86
|
+
expect{union.to_a}.to_not raise_error
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "in MySQL" do
|
92
|
+
it "wraps query subselects in parentheses to allow ORDER BY clauses" do
|
93
|
+
Databases.with_mysql do
|
94
|
+
expect(union.to_sql).to eq(
|
95
|
+
"SELECT \"posts\".* FROM ( (SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = $1 ORDER BY \"posts\".\"created_at\" ASC) UNION (SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '2014-07-19 00:00:00.000000') ORDER BY \"posts\".\"created_at\" ASC) ) posts ORDER BY \"posts\".\"created_at\" ASC"
|
96
|
+
)
|
97
|
+
expect{union.to_a}.to_not raise_error
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
60
101
|
end
|
61
102
|
|
62
103
|
context "builds a scope when given" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_union
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Hempel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -94,6 +94,34 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pg
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: mysql2
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
97
125
|
description: UNIONs in ActiveRecord! Adds a proper union method to ActiveRecord::Relation.
|
98
126
|
email:
|
99
127
|
- plasticchicken@gmail.com
|
@@ -113,6 +141,7 @@ files:
|
|
113
141
|
- lib/active_record_union/active_record/relation/union.rb
|
114
142
|
- lib/active_record_union/version.rb
|
115
143
|
- spec/spec_helper.rb
|
144
|
+
- spec/support/databases.rb
|
116
145
|
- spec/support/models.rb
|
117
146
|
- spec/union_spec.rb
|
118
147
|
homepage: https://github.com/brianhempel/active_record_union
|
@@ -141,6 +170,7 @@ specification_version: 4
|
|
141
170
|
summary: UNIONs in ActiveRecord! Adds a proper union method to ActiveRecord::Relation.
|
142
171
|
test_files:
|
143
172
|
- spec/spec_helper.rb
|
173
|
+
- spec/support/databases.rb
|
144
174
|
- spec/support/models.rb
|
145
175
|
- spec/union_spec.rb
|
146
176
|
- bin/console
|