active_record_union 1.1.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +8 -5
- data/README.md +33 -7
- data/Rakefile +57 -4
- data/lib/active_record_union/active_record/relation/union.rb +57 -24
- data/lib/active_record_union/version.rb +1 -1
- data/rails_4_2.gemfile +11 -0
- data/rails_5_0.gemfile +11 -0
- data/rails_5_1.gemfile +11 -0
- data/rails_5_2.gemfile +7 -0
- data/spec/support/databases.rb +2 -0
- data/spec/support/models.rb +2 -0
- data/spec/union_spec.rb +53 -18
- metadata +7 -4
- data/Gemfile +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b6908e8b32e304532e3b8405712ade67fab7e52
|
4
|
+
data.tar.gz: 937298c6d470715b18510f2e317a7f3d735b2cf6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8466c15bb3c268bbea28f49b3a85915e91f72bd43997462a4bd09a1da14ec9c85d5ca9621a6b7316ac04fe6949ccf4284bce854ed5b33c71bc492ca9bbd0cc9f
|
7
|
+
data.tar.gz: a99a9d9c0c19e332d9b0c357310d886c72c5b7f629be97a0d8e2dcdc8d90dcb1605960f3a44a8dacdfe0749dc7dfe6f2571ebe27d63a6bcb1970e7798447a88f
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
language: ruby
|
2
2
|
addons:
|
3
|
-
postgresql: "9.
|
4
|
-
before_script:
|
5
|
-
- psql -c 'create database travis;' -U postgres # "travis" is the UNIX username, and therefore the default database name on connection
|
3
|
+
postgresql: "9.4"
|
6
4
|
rvm:
|
7
|
-
- 2.
|
8
|
-
|
5
|
+
- 2.3.6
|
6
|
+
gemfile:
|
7
|
+
- rails_4_2.gemfile
|
8
|
+
- rails_5_0.gemfile
|
9
|
+
- rails_5_1.gemfile
|
10
|
+
- rails_5_2.gemfile
|
11
|
+
script: bundle exec rspec
|
data/README.md
CHANGED
@@ -7,6 +7,18 @@ Use unions on ActiveRecord scopes without ugliness.
|
|
7
7
|
|
8
8
|
If you find yourself writing `pluck(:id)` and then feeding that into another query, you may be able to reduce the number of database requests by using a nested query or a UNION without writing crazy JOIN statements.
|
9
9
|
|
10
|
+
Quick usage examples:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
current_user.posts.union(Post.published)
|
14
|
+
current_user.posts.union(Post.published).where(id: [6, 7])
|
15
|
+
current_user.posts.union("published_at < ?", Time.now)
|
16
|
+
user_1.posts.union(user_2.posts).union(Post.published)
|
17
|
+
user_1.posts.union_all(user_2.posts)
|
18
|
+
```
|
19
|
+
|
20
|
+
ActiveRecordUnion is tested against Rails 4.2 and Rails 5.0. It may or may not work on Rails 4.0/4.1.
|
21
|
+
|
10
22
|
## Installation
|
11
23
|
|
12
24
|
Add this line to your application's Gemfile:
|
@@ -55,7 +67,7 @@ SELECT "posts".* FROM (
|
|
55
67
|
SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 1
|
56
68
|
UNION
|
57
69
|
SELECT "posts".* FROM "posts" WHERE (published_at < '2014-07-19 16:04:21.918366')
|
58
|
-
) posts
|
70
|
+
) "posts"
|
59
71
|
```
|
60
72
|
|
61
73
|
Because the `union` method returns another `ActiveRecord::Relation`, we can run further queries on the union.
|
@@ -68,7 +80,7 @@ SELECT "posts".* FROM (
|
|
68
80
|
SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 1
|
69
81
|
UNION
|
70
82
|
SELECT "posts".* FROM "posts" WHERE (published_at < '2014-07-19 16:06:04.460771')
|
71
|
-
) posts WHERE "posts"."id" IN (6, 7)
|
83
|
+
) "posts" WHERE "posts"."id" IN (6, 7)
|
72
84
|
```
|
73
85
|
|
74
86
|
The `union` method can also accept anything that `where` does.
|
@@ -92,10 +104,10 @@ SELECT "posts".* FROM (
|
|
92
104
|
SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 1
|
93
105
|
UNION
|
94
106
|
SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 2
|
95
|
-
) posts
|
107
|
+
) "posts"
|
96
108
|
UNION
|
97
109
|
SELECT "posts".* FROM "posts" WHERE (published_at < '2014-07-19 16:12:45.882648')
|
98
|
-
) posts
|
110
|
+
) "posts"
|
99
111
|
```
|
100
112
|
|
101
113
|
### UNION ALL
|
@@ -110,14 +122,14 @@ SELECT "posts".* FROM (
|
|
110
122
|
SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 1
|
111
123
|
UNION ALL
|
112
124
|
SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 2
|
113
|
-
) posts
|
125
|
+
) "posts"
|
114
126
|
```
|
115
127
|
|
116
128
|
## Caveats
|
117
129
|
|
118
130
|
There's a couple things to be aware of when using ActiveRecordUnion:
|
119
131
|
|
120
|
-
1. ActiveRecordUnion
|
132
|
+
1. ActiveRecordUnion will 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.
|
121
133
|
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.
|
122
134
|
|
123
135
|
## Another nifty way to reduce extra queries
|
@@ -174,6 +186,16 @@ This is a gem not a Rails pull request because the standard of code quality for
|
|
174
186
|
|
175
187
|
## Changelog
|
176
188
|
|
189
|
+
**1.3.0** - January 14, 2018
|
190
|
+
- Ready for Rails 5.2! Updates provided by [@glebm](https://github.com/glebm).
|
191
|
+
|
192
|
+
**1.2.0** - June 26, 2016
|
193
|
+
- Ready for Rails 5.0! Updates provided by [@glebm](https://github.com/glebm).
|
194
|
+
|
195
|
+
**1.1.1** - Mar 19, 2016
|
196
|
+
- Fix broken polymorphic associations and joins due to improper handling of bind values. Fix by [@efradelos](https://github.com/efradelos), reported by [@Machiaweliczny](https://github.com/Machiaweliczny) and [@seandougall](https://github.com/seandougall).
|
197
|
+
- Quote table name aliases properly. Reported by [@odedniv](https://github.com/odedniv).
|
198
|
+
|
177
199
|
**1.1.0** - Mar 29, 2015 - Add UNION ALL support, courtesy of [@pic](https://github.com/pic).
|
178
200
|
|
179
201
|
**1.0.1** - Sept 2, 2014 - Allow ORDER BY in UNION subselects for databases that support it (not SQLite).
|
@@ -190,7 +212,11 @@ This public domain dedication follows the the CC0 1.0 at https://creativecommons
|
|
190
212
|
|
191
213
|
1. Fork it ( https://github.com/brianhempel/active_record_union/fork )
|
192
214
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
193
|
-
3. Run the tests
|
215
|
+
3. Run the tests:
|
216
|
+
1. Install MySQL and PostgreSQL.
|
217
|
+
2. You need to be able to connect to a local MySQL and Postgres database as the default user, so the specs can create a `test_active_record_union` database. From a vanilla install of MariaDB from Homebrew, this just works. For Postgres installed by Homebrew, you may need to run `$ echo "create database my_computer_user_name;" | psql postgres` since the initial database created by Homebrew is named "postgres" but PG defaults to connecting to a database named after your username.
|
218
|
+
3. Run `rake` to test with all supported Rails versions. All needed dependencies will be installed via Bundler (`gem install bundler` if you happen not to have Bundler yet).
|
219
|
+
4. Run `rake test_rails_4_2` or `rake test_rails_5_2` etc. to test a specific Rails version.
|
194
220
|
4. There is also a `bin/console` command to load up a REPL for playing around
|
195
221
|
5. Commit your changes (`git commit -am 'Add some feature'`)
|
196
222
|
6. Push to the branch (`git push origin my-new-feature`)
|
data/Rakefile
CHANGED
@@ -1,8 +1,61 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
|
3
|
-
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
task :default => :test_all_gemfiles
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
module TestTasks
|
8
|
+
module_function
|
9
|
+
|
10
|
+
TEST_CMD = 'bundle exec rspec'
|
11
|
+
|
12
|
+
def run_all(envs, cmd = "bundle install && #{TEST_CMD}", success_message)
|
13
|
+
statuses = envs.map { |env| run(env, cmd) }
|
14
|
+
failed = statuses.reject(&:first).map(&:last)
|
15
|
+
if failed.empty?
|
16
|
+
$stderr.puts success_message
|
17
|
+
else
|
18
|
+
$stderr.puts "❌ FAILING (#{failed.size}):\n#{failed.map { |env| to_bash_cmd_with_env(cmd, env) } * "\n"}"
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run_one(env, cmd = "bundle install && #{TEST_CMD}")
|
24
|
+
full_cmd = to_bash_cmd_with_env(cmd, env)
|
25
|
+
exec(full_cmd)
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(env, cmd)
|
29
|
+
Bundler.with_clean_env do
|
30
|
+
full_cmd = to_bash_cmd_with_env(cmd, env)
|
31
|
+
$stderr.puts full_cmd
|
32
|
+
isSuccess = system(full_cmd)
|
33
|
+
[isSuccess, env]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def gemfiles
|
38
|
+
Dir.glob('*.gemfile').sort
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_bash_cmd_with_env(cmd, env)
|
42
|
+
"(export #{env.map { |k, v| "#{k}=#{v}" }.join(' ')}; #{cmd})"
|
43
|
+
end
|
8
44
|
end
|
45
|
+
|
46
|
+
desc 'Test all Gemfiles'
|
47
|
+
task :test_all_gemfiles do
|
48
|
+
envs = TestTasks.gemfiles.map { |gemfile| { 'BUNDLE_GEMFILE' => gemfile } }
|
49
|
+
TestTasks.run_all envs, "✓ Tests pass with all #{envs.size} gemfiles"
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
TestTasks.gemfiles.each do |gemfile|
|
54
|
+
rails_version_underscored = gemfile[/rails_(.+)\.gemfile/, 1]
|
55
|
+
|
56
|
+
desc "Test Rails #{rails_version_underscored.gsub("_", ".")}"
|
57
|
+
task :"test_rails_#{rails_version_underscored}" do
|
58
|
+
env = { 'BUNDLE_GEMFILE' => gemfile }
|
59
|
+
TestTasks.run_one(env)
|
60
|
+
end
|
61
|
+
end
|
@@ -3,46 +3,79 @@ module ActiveRecord
|
|
3
3
|
module Union
|
4
4
|
|
5
5
|
SET_OPERATION_TO_AREL_CLASS = {
|
6
|
-
|
7
|
-
|
6
|
+
union: Arel::Nodes::Union,
|
7
|
+
union_all: Arel::Nodes::UnionAll
|
8
8
|
}
|
9
9
|
|
10
|
-
def
|
11
|
-
set_operation(
|
10
|
+
def union(relation_or_where_arg, *args)
|
11
|
+
set_operation(:union, relation_or_where_arg, *args)
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
set_operation(
|
14
|
+
def union_all(relation_or_where_arg, *args)
|
15
|
+
set_operation(:union_all, relation_or_where_arg, *args)
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def set_operation(operation, relation_or_where_arg, *args)
|
21
|
-
other = if args.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
other = if args.empty? && relation_or_where_arg.is_a?(Relation)
|
22
|
+
relation_or_where_arg
|
23
|
+
else
|
24
|
+
@klass.where(relation_or_where_arg, *args)
|
25
|
+
end
|
26
26
|
|
27
27
|
verify_relations_for_set_operation!(operation, self, other)
|
28
28
|
|
29
|
+
left = self.arel.ast
|
30
|
+
right = other.arel.ast
|
31
|
+
|
29
32
|
# Postgres allows ORDER BY in the UNION subqueries if each subquery is surrounded by parenthesis
|
30
|
-
# but SQLite does not allow parens around the subqueries
|
31
|
-
|
32
|
-
left
|
33
|
-
|
34
|
-
left, right = Arel::Nodes::Grouping.new(self.ast), Arel::Nodes::Grouping.new(other.ast)
|
33
|
+
# but SQLite does not allow parens around the subqueries
|
34
|
+
unless self.connection.visitor.is_a?(Arel::Visitors::SQLite)
|
35
|
+
left = Arel::Nodes::Grouping.new(left)
|
36
|
+
right = Arel::Nodes::Grouping.new(right)
|
35
37
|
end
|
36
38
|
|
37
|
-
set
|
38
|
-
from = Arel::Nodes::TableAlias.new(
|
39
|
-
|
40
|
-
|
41
|
-
)
|
39
|
+
set = SET_OPERATION_TO_AREL_CLASS[operation].new(left, right)
|
40
|
+
from = Arel::Nodes::TableAlias.new(set, @klass.arel_table.name)
|
41
|
+
build_union_relation(from, other)
|
42
|
+
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
if ActiveRecord.gem_version >= Gem::Version.new('5.2.0.beta2')
|
45
|
+
# Since Rails 5.2, binds are maintained only in the Arel AST.
|
46
|
+
def build_union_relation(arel_table_alias, _other)
|
47
|
+
@klass.unscoped.from(arel_table_alias)
|
48
|
+
end
|
49
|
+
elsif ActiveRecord::VERSION::MAJOR >= 5
|
50
|
+
# In Rails >= 5.0, < 5.2, binds are maintained only in ActiveRecord
|
51
|
+
# relations and clauses.
|
52
|
+
def build_union_relation(arel_table_alias, other)
|
53
|
+
relation = @klass.unscoped.spawn
|
54
|
+
relation.from_clause =
|
55
|
+
UnionFromClause.new(arel_table_alias, nil,
|
56
|
+
self.bound_attributes + other.bound_attributes)
|
57
|
+
relation
|
58
|
+
end
|
59
|
+
|
60
|
+
class UnionFromClause < ActiveRecord::Relation::FromClause
|
61
|
+
def initialize(value, name, bound_attributes)
|
62
|
+
super(value, name)
|
63
|
+
@bound_attributes = bound_attributes
|
64
|
+
end
|
65
|
+
|
66
|
+
def binds
|
67
|
+
@bound_attributes
|
68
|
+
end
|
69
|
+
end
|
70
|
+
else
|
71
|
+
# In Rails 4.x, binds are maintained in both ActiveRecord relations and
|
72
|
+
# clauses and also in their Arel ASTs.
|
73
|
+
def build_union_relation(arel_table_alias, other)
|
74
|
+
relation = @klass.unscoped.from(arel_table_alias)
|
75
|
+
relation.bind_values = self.arel.bind_values + self.bind_values +
|
76
|
+
other.arel.bind_values + other.bind_values
|
77
|
+
relation
|
78
|
+
end
|
46
79
|
end
|
47
80
|
|
48
81
|
def verify_relations_for_set_operation!(operation, *relations)
|
data/rails_4_2.gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in active_record_union.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'rails', '~> 4.2.7'
|
7
|
+
|
8
|
+
# On Rails < 5.2, only pg < v1 is supported. See:
|
9
|
+
# https://github.com/rails/rails/pull/31671
|
10
|
+
# https://bitbucket.org/ged/ruby-pg/issues/270/pg-100-x64-mingw32-rails-server-not-start
|
11
|
+
gem 'pg', '~> 0.21'
|
data/rails_5_0.gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in active_record_union.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'rails', '~> 5.0.0'
|
7
|
+
|
8
|
+
# On Rails < 5.2, only pg < v1 is supported. See:
|
9
|
+
# https://github.com/rails/rails/pull/31671
|
10
|
+
# https://bitbucket.org/ged/ruby-pg/issues/270/pg-100-x64-mingw32-rails-server-not-start
|
11
|
+
gem 'pg', '~> 0.21'
|
data/rails_5_1.gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in active_record_union.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'rails', '~> 5.1.0'
|
7
|
+
|
8
|
+
# On Rails < 5.2, only pg < v1 is supported. See:
|
9
|
+
# https://github.com/rails/rails/pull/31671
|
10
|
+
# https://bitbucket.org/ged/ruby-pg/issues/270/pg-100-x64-mingw32-rails-server-not-start
|
11
|
+
gem 'pg', '~> 0.21'
|
data/rails_5_2.gemfile
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in active_record_union.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
# pg v1.0+ compatibility, https://github.com/rails/rails/pull/31671:
|
7
|
+
gem 'rails', '~> 5.2.0.beta2', git: 'https://github.com/rails/rails', ref: 'f1af27fd9d9101684b26d0dcf2028859d67bec1f'
|
data/spec/support/databases.rb
CHANGED
data/spec/support/models.rb
CHANGED
@@ -3,10 +3,12 @@ end
|
|
3
3
|
|
4
4
|
class User < ActiveRecord::Base
|
5
5
|
has_many :posts
|
6
|
+
has_many :drafts, -> { where draft: true }, class_name: "Post"
|
6
7
|
end unless defined?(User)
|
7
8
|
|
8
9
|
ActiveRecord::Base.connection.create_table :posts, force: true do |t|
|
9
10
|
t.integer :user_id
|
11
|
+
t.boolean :draft
|
10
12
|
t.timestamp :published_at
|
11
13
|
t.timestamps null: false
|
12
14
|
end
|
data/spec/union_spec.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe ActiveRecord::Relation do
|
4
|
+
TIME = Time.utc(2014, 7, 19, 0, 0, 0)
|
5
|
+
SQL_TIME = ActiveRecord::VERSION::MAJOR >= 5 ? "2014-07-19 00:00:00" : "2014-07-19 00:00:00.000000"
|
6
|
+
|
4
7
|
describe ".union" do
|
5
8
|
it "returns an ActiveRecord::Relation" do
|
6
9
|
expect(User.all.union(User.all)).to be_kind_of(ActiveRecord::Relation)
|
@@ -26,55 +29,84 @@ describe ActiveRecord::Relation do
|
|
26
29
|
end
|
27
30
|
|
28
31
|
it "works" do
|
29
|
-
union = User.new(id: 1).posts.union(Post.where("created_at > ?",
|
32
|
+
union = User.new(id: 1).posts.union(Post.where("created_at > ?", TIME))
|
30
33
|
|
31
34
|
expect(union.to_sql.squish).to eq(
|
32
|
-
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '
|
35
|
+
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '#{SQL_TIME}') ) \"posts\""
|
33
36
|
)
|
34
37
|
expect(union.arel.to_sql.squish).to eq(
|
35
|
-
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = ? UNION SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '
|
38
|
+
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = ? UNION SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '#{SQL_TIME}') ) \"posts\""
|
36
39
|
)
|
37
40
|
expect{union.to_a}.to_not raise_error
|
38
41
|
end
|
39
42
|
|
43
|
+
def bind_values_from_relation(relation)
|
44
|
+
if ActiveRecord.gem_version >= Gem::Version.new('5.2.0.beta2')
|
45
|
+
relation.arel_table.class.engine.connection.visitor.accept(
|
46
|
+
relation.arel.ast, Arel::Collectors::Bind.new
|
47
|
+
).value.map(&:value)
|
48
|
+
elsif ActiveRecord::VERSION::MAJOR >= 5
|
49
|
+
relation.bound_attributes.map { |a| a.value_for_database }
|
50
|
+
else
|
51
|
+
(relation.arel.bind_values + relation.bind_values).map { |_column, value| value }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
40
55
|
it "binds values properly" do
|
41
56
|
user1 = User.new(id: 1)
|
42
57
|
user2 = User.new(id: 2)
|
43
58
|
user3 = User.new(id: 3)
|
44
59
|
|
45
60
|
union = user1.posts.union(user2.posts).where.not(id: user3.posts)
|
46
|
-
|
61
|
+
|
62
|
+
# Inside ActiveRecord the bind value list is
|
63
|
+
# (union.arel.bind_values + union.bind_values)
|
64
|
+
bind_values = bind_values_from_relation union
|
47
65
|
|
48
66
|
expect(bind_values).to eq([1, 2, 3])
|
49
67
|
end
|
50
68
|
|
69
|
+
it "binds values properly on joins" do
|
70
|
+
union = User.joins(:drafts).union(User.where(id: 11))
|
71
|
+
|
72
|
+
bind_values = bind_values_from_relation union
|
73
|
+
expect(bind_values).to eq([true, 11])
|
74
|
+
|
75
|
+
|
76
|
+
expect(union.to_sql.squish).to eq(
|
77
|
+
"SELECT \"users\".* FROM ( SELECT \"users\".* FROM \"users\" INNER JOIN \"posts\" ON \"posts\".\"user_id\" = \"users\".\"id\" AND \"posts\".\"draft\" = 't' UNION SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" = 11 ) \"users\""
|
78
|
+
)
|
79
|
+
expect{union.to_a}.to_not raise_error
|
80
|
+
end
|
81
|
+
|
51
82
|
it "doesn't repeat default scopes" do
|
52
83
|
expect(Time).to receive(:now) { Time.utc(2014, 7, 24, 0, 0, 0) }
|
84
|
+
sql_now = "2014-07-24 00:00:00#{".000000" if ActiveRecord::VERSION::MAJOR < 5}"
|
53
85
|
|
54
86
|
class PublishedPost < ActiveRecord::Base
|
55
87
|
self.table_name = "posts"
|
56
88
|
default_scope { where("published_at < ?", Time.now) }
|
57
89
|
end
|
58
90
|
|
59
|
-
union = PublishedPost.where("created_at > ?",
|
91
|
+
union = PublishedPost.where("created_at > ?", TIME).union(User.new(id: 1).posts)
|
60
92
|
|
61
93
|
expect(union.to_sql.squish).to eq(
|
62
|
-
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE (published_at < '
|
94
|
+
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE (published_at < '#{sql_now}') AND (created_at > '#{SQL_TIME}') UNION SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 ) \"posts\""
|
63
95
|
)
|
64
96
|
expect{union.to_a}.to_not raise_error
|
65
97
|
end
|
66
98
|
|
67
99
|
context "with ORDER BY in subselects" do
|
68
|
-
|
100
|
+
let :union do
|
69
101
|
User.new(id: 1).posts.order(:created_at).union(
|
70
|
-
Post.where("created_at > ?",
|
102
|
+
Post.where("created_at > ?", TIME).order(:created_at)
|
71
103
|
).order(:created_at)
|
72
104
|
end
|
73
105
|
|
74
106
|
context "in SQLite" do
|
75
107
|
it "lets ORDER BY in query subselects throw a syntax error" do
|
76
108
|
expect(union.to_sql.squish).to eq(
|
77
|
-
"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 > '
|
109
|
+
"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 > '#{SQL_TIME}') ORDER BY \"posts\".\"created_at\" ASC ) \"posts\" ORDER BY \"posts\".\"created_at\" ASC"
|
78
110
|
)
|
79
111
|
expect{union.to_a}.to raise_error(ActiveRecord::StatementInvalid)
|
80
112
|
end
|
@@ -84,7 +116,7 @@ describe ActiveRecord::Relation do
|
|
84
116
|
it "wraps query subselects in parentheses to allow ORDER BY clauses" do
|
85
117
|
Databases.with_postgres do
|
86
118
|
expect(union.to_sql.squish).to eq(
|
87
|
-
"SELECT \"posts\".* FROM ( (SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" =
|
119
|
+
"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 > '#{SQL_TIME}') ORDER BY \"posts\".\"created_at\" ASC) ) \"posts\" ORDER BY \"posts\".\"created_at\" ASC"
|
88
120
|
)
|
89
121
|
expect{union.to_a}.to_not raise_error
|
90
122
|
end
|
@@ -95,7 +127,7 @@ describe ActiveRecord::Relation do
|
|
95
127
|
it "wraps query subselects in parentheses to allow ORDER BY clauses" do
|
96
128
|
Databases.with_mysql do
|
97
129
|
expect(union.to_sql.squish).to eq(
|
98
|
-
"SELECT
|
130
|
+
"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 > '#{SQL_TIME}') ORDER BY `posts`.`created_at` ASC) ) `posts` ORDER BY `posts`.`created_at` ASC"
|
99
131
|
)
|
100
132
|
expect{union.to_a}.to_not raise_error
|
101
133
|
end
|
@@ -108,34 +140,37 @@ describe ActiveRecord::Relation do
|
|
108
140
|
union = User.new(id: 1).posts.union(id: 2)
|
109
141
|
|
110
142
|
expect(union.to_sql.squish).to eq(
|
111
|
-
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"id\" = 2 ) posts"
|
143
|
+
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"id\" = 2 ) \"posts\""
|
112
144
|
)
|
145
|
+
expect{union.to_a}.to_not raise_error
|
113
146
|
end
|
114
147
|
|
115
148
|
it "multiple arguments" do
|
116
|
-
union = User.new(id: 1).posts.union("created_at > ?",
|
149
|
+
union = User.new(id: 1).posts.union("created_at > ?", TIME)
|
117
150
|
|
118
151
|
expect(union.to_sql.squish).to eq(
|
119
|
-
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '
|
152
|
+
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '#{SQL_TIME}') ) \"posts\""
|
120
153
|
)
|
154
|
+
expect{union.to_a}.to_not raise_error
|
121
155
|
end
|
122
156
|
|
123
157
|
it "arel" do
|
124
158
|
union = User.new(id: 1).posts.union(Post.arel_table[:id].eq(2).or(Post.arel_table[:id].eq(3)))
|
125
159
|
|
126
160
|
expect(union.to_sql.squish).to eq(
|
127
|
-
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION SELECT \"posts\".* FROM \"posts\" WHERE (\"posts\".\"id\" = 2 OR \"posts\".\"id\" = 3) ) posts"
|
161
|
+
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION SELECT \"posts\".* FROM \"posts\" WHERE (\"posts\".\"id\" = 2 OR \"posts\".\"id\" = 3) ) \"posts\""
|
128
162
|
)
|
163
|
+
expect{union.to_a}.to_not raise_error
|
129
164
|
end
|
130
165
|
end
|
131
166
|
end
|
132
167
|
|
133
168
|
describe ".union_all" do
|
134
169
|
it "works" do
|
135
|
-
union = User.new(id: 1).posts.union_all(Post.where("created_at > ?",
|
170
|
+
union = User.new(id: 1).posts.union_all(Post.where("created_at > ?", TIME))
|
136
171
|
|
137
172
|
expect(union.to_sql.squish).to eq(
|
138
|
-
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION ALL SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '
|
173
|
+
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION ALL SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '#{SQL_TIME}') ) \"posts\""
|
139
174
|
)
|
140
175
|
expect{union.to_a}.to_not raise_error
|
141
176
|
end
|
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.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Hempel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -131,7 +131,6 @@ extra_rdoc_files: []
|
|
131
131
|
files:
|
132
132
|
- ".gitignore"
|
133
133
|
- ".travis.yml"
|
134
|
-
- Gemfile
|
135
134
|
- LICENSE.txt
|
136
135
|
- README.md
|
137
136
|
- Rakefile
|
@@ -140,6 +139,10 @@ files:
|
|
140
139
|
- lib/active_record_union.rb
|
141
140
|
- lib/active_record_union/active_record/relation/union.rb
|
142
141
|
- lib/active_record_union/version.rb
|
142
|
+
- rails_4_2.gemfile
|
143
|
+
- rails_5_0.gemfile
|
144
|
+
- rails_5_1.gemfile
|
145
|
+
- rails_5_2.gemfile
|
143
146
|
- spec/spec_helper.rb
|
144
147
|
- spec/support/databases.rb
|
145
148
|
- spec/support/models.rb
|
@@ -164,7 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
164
167
|
version: '0'
|
165
168
|
requirements: []
|
166
169
|
rubyforge_project:
|
167
|
-
rubygems_version: 2.
|
170
|
+
rubygems_version: 2.6.8
|
168
171
|
signing_key:
|
169
172
|
specification_version: 4
|
170
173
|
summary: UNIONs in ActiveRecord! Adds proper union and union_all methods to ActiveRecord::Relation.
|
data/Gemfile
DELETED