active_record_union/spec/union_spec.rb

175 lines
7.3 KiB
Ruby

require "spec_helper"
describe ActiveRecord::Relation do
TIME = Time.utc(2014, 7, 19, 0, 0, 0)
SQL_TIME = ActiveRecord::VERSION::MAJOR >= 5 ? "2014-07-19 00:00:00" : "2014-07-19 00:00:00.000000"
describe ".union" do
it "returns an ActiveRecord::Relation" do
expect(User.all.union(User.all)).to be_kind_of(ActiveRecord::Relation)
end
it "requires an argument" do
expect{User.all.union}.to raise_error(ArgumentError)
end
it "explodes if asked to union a relation with includes" do
expect{User.all.union(User.includes(:posts))}.to raise_error(ArgumentError)
expect{User.includes(:posts).union(User.all)}.to raise_error(ArgumentError)
end
it "explodes if asked to union a relation with preload values" do
expect{User.all.union(User.preload(:posts))}.to raise_error(ArgumentError)
expect{User.preload(:posts).union(User.all)}.to raise_error(ArgumentError)
end
it "explodes if asked to union a relation with eager loading" do
expect{User.all.union(User.eager_load(:posts))}.to raise_error(ArgumentError)
expect{User.eager_load(:posts).union(User.all)}.to raise_error(ArgumentError)
end
it "works" do
union = User.new(id: 1).posts.union(Post.where("created_at > ?", TIME))
expect(union.to_sql.squish).to eq(
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '#{SQL_TIME}') ) \"posts\""
)
expect(union.arel.to_sql.squish).to eq(
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = ? UNION SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '#{SQL_TIME}') ) \"posts\""
)
expect{union.to_a}.to_not raise_error
end
def bind_values_from_relation(relation)
if ActiveRecord::VERSION::MAJOR >= 5
relation.bound_attributes.map { |a| a.value_for_database }
else
(relation.arel.bind_values + relation.bind_values).map { |_column, value| value }
end
end
it "binds values properly" do
user1 = User.new(id: 1)
user2 = User.new(id: 2)
user3 = User.new(id: 3)
union = user1.posts.union(user2.posts).where.not(id: user3.posts)
# Inside ActiveRecord the bind value list is
# (union.arel.bind_values + union.bind_values)
bind_values = bind_values_from_relation union
expect(bind_values).to eq([1, 2, 3])
end
it "binds values properly on joins" do
union = User.joins(:drafts).union(User.where(id: 11))
bind_values = bind_values_from_relation union
expect(bind_values).to eq([true, 11])
expect(union.to_sql.squish).to eq(
"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\""
)
expect{union.to_a}.to_not raise_error
end
it "doesn't repeat default scopes" do
expect(Time).to receive(:now) { Time.utc(2014, 7, 24, 0, 0, 0) }
sql_now = "2014-07-24 00:00:00#{".000000" if ActiveRecord::VERSION::MAJOR < 5}"
class PublishedPost < ActiveRecord::Base
self.table_name = "posts"
default_scope { where("published_at < ?", Time.now) }
end
union = PublishedPost.where("created_at > ?", TIME).union(User.new(id: 1).posts)
expect(union.to_sql.squish).to eq(
"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\""
)
expect{union.to_a}.to_not raise_error
end
context "with ORDER BY in subselects" do
let :union do
User.new(id: 1).posts.order(:created_at).union(
Post.where("created_at > ?", TIME).order(:created_at)
).order(:created_at)
end
context "in SQLite" do
it "lets ORDER BY in query subselects throw a syntax error" do
expect(union.to_sql.squish).to eq(
"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"
)
expect{union.to_a}.to raise_error(ActiveRecord::StatementInvalid)
end
end
context "in Postgres" do
it "wraps query subselects in parentheses to allow ORDER BY clauses" do
Databases.with_postgres do
expect(union.to_sql.squish).to eq(
"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"
)
expect{union.to_a}.to_not raise_error
end
end
end
context "in MySQL" do
it "wraps query subselects in parentheses to allow ORDER BY clauses" do
Databases.with_mysql do
expect(union.to_sql.squish).to eq(
"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"
)
expect{union.to_a}.to_not raise_error
end
end
end
end
context "builds a scope when given" do
it "a hash" do
union = User.new(id: 1).posts.union(id: 2)
expect(union.to_sql.squish).to eq(
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"id\" = 2 ) \"posts\""
)
expect{union.to_a}.to_not raise_error
end
it "multiple arguments" do
union = User.new(id: 1).posts.union("created_at > ?", TIME)
expect(union.to_sql.squish).to eq(
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '#{SQL_TIME}') ) \"posts\""
)
expect{union.to_a}.to_not raise_error
end
it "arel" do
union = User.new(id: 1).posts.union(Post.arel_table[:id].eq(2).or(Post.arel_table[:id].eq(3)))
expect(union.to_sql.squish).to eq(
"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\""
)
expect{union.to_a}.to_not raise_error
end
end
end
describe ".union_all" do
it "works" do
union = User.new(id: 1).posts.union_all(Post.where("created_at > ?", TIME))
expect(union.to_sql.squish).to eq(
"SELECT \"posts\".* FROM ( SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"user_id\" = 1 UNION ALL SELECT \"posts\".* FROM \"posts\" WHERE (created_at > '#{SQL_TIME}') ) \"posts\""
)
expect{union.to_a}.to_not raise_error
end
end
end