active_record_extended 0.7.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +508 -12
- data/lib/active_record_extended.rb +1 -1
- data/lib/active_record_extended/active_record.rb +9 -0
- data/lib/active_record_extended/active_record/relation_patch.rb +33 -0
- data/lib/active_record_extended/arel/nodes.rb +20 -0
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +13 -1
- data/lib/active_record_extended/query_methods/either.rb +1 -1
- data/lib/active_record_extended/query_methods/inet.rb +0 -18
- data/lib/active_record_extended/query_methods/json.rb +223 -0
- data/lib/active_record_extended/query_methods/unionize.rb +278 -0
- data/lib/active_record_extended/query_methods/where_chain.rb +1 -1
- data/lib/active_record_extended/query_methods/with_cte.rb +0 -18
- data/lib/active_record_extended/utilities.rb +141 -0
- data/lib/active_record_extended/version.rb +1 -1
- data/spec/query_methods/inet_query_spec.rb +0 -11
- data/spec/query_methods/json_spec.rb +142 -0
- data/spec/query_methods/unionize_spec.rb +165 -0
- data/spec/sql_inspections/json_sql_spec.rb +46 -0
- data/spec/sql_inspections/unionize_sql_spec.rb +124 -0
- data/spec/support/models.rb +28 -5
- metadata +22 -4
@@ -49,7 +49,7 @@ module ActiveRecordExtended
|
|
49
49
|
when Arel::Nodes::In, Arel::Nodes::Equality
|
50
50
|
column = left_column(arel) || column_from_association(arel)
|
51
51
|
|
52
|
-
if
|
52
|
+
if [:hstore, :jsonb].include?(column.type)
|
53
53
|
Arel::Nodes::ContainsHStore.new(arel.left, arel.right)
|
54
54
|
elsif column.try(:array)
|
55
55
|
Arel::Nodes::ContainsArray.new(arel.left, arel.right)
|
@@ -2,16 +2,6 @@
|
|
2
2
|
|
3
3
|
module ActiveRecordExtended
|
4
4
|
module QueryMethods
|
5
|
-
module MergerCTE
|
6
|
-
def normal_values
|
7
|
-
super + [:with]
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
module QueryDelegationCTE
|
12
|
-
delegate :with, to: :all
|
13
|
-
end
|
14
|
-
|
15
5
|
module WithCTE
|
16
6
|
class WithChain
|
17
7
|
def initialize(scope)
|
@@ -59,12 +49,6 @@ module ActiveRecordExtended
|
|
59
49
|
self
|
60
50
|
end
|
61
51
|
|
62
|
-
def build_arel(*aliases)
|
63
|
-
super.tap do |arel|
|
64
|
-
build_with(arel) if with_values?
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
52
|
def build_with_hashed_value(with_value)
|
69
53
|
with_value.map do |name, expression|
|
70
54
|
select =
|
@@ -97,5 +81,3 @@ module ActiveRecordExtended
|
|
97
81
|
end
|
98
82
|
|
99
83
|
ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::WithCTE)
|
100
|
-
ActiveRecord::Relation::Merger.prepend(ActiveRecordExtended::QueryMethods::MergerCTE)
|
101
|
-
ActiveRecord::Querying.prepend(ActiveRecordExtended::QueryMethods::QueryDelegationCTE)
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordExtended
|
4
|
+
module Utilities
|
5
|
+
A_TO_Z_KEYS = ("a".."z").to_a.freeze
|
6
|
+
|
7
|
+
# We need to ensure we can flatten nested ActiveRecord::Relations
|
8
|
+
# that might have been nested due to the (splat)*args parameters
|
9
|
+
#
|
10
|
+
# Note: calling `Array.flatten[!]/1` will actually remove all AR relations from the array.
|
11
|
+
#
|
12
|
+
def flatten_to_sql(*values)
|
13
|
+
flatten_safely(values) do |value|
|
14
|
+
value = yield value if block_given?
|
15
|
+
to_arel_sql(value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
alias to_sql_array flatten_to_sql
|
19
|
+
|
20
|
+
def flatten_safely(values, &block)
|
21
|
+
unless values.is_a?(Array)
|
22
|
+
values = yield values if block_given?
|
23
|
+
return [values]
|
24
|
+
end
|
25
|
+
|
26
|
+
values.map { |value| flatten_safely(value, &block) }.reduce(:+)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Applies aliases to the given query
|
30
|
+
# Ex: `SELECT * FROM users` => `(SELECT * FROM users) AS "members"`
|
31
|
+
def nested_alias_escape(query, alias_name)
|
32
|
+
sql_query = Arel::Nodes::Grouping.new(to_arel_sql(query))
|
33
|
+
Arel::Nodes::As.new(sql_query, to_arel_sql(double_quote(alias_name)))
|
34
|
+
end
|
35
|
+
|
36
|
+
# Wraps subquery into an Aliased ARRAY
|
37
|
+
# Ex: `SELECT * FROM users` => (ARRAY(SELECT * FROM users)) AS "members"
|
38
|
+
def wrap_with_array(arel_or_rel_query, alias_name)
|
39
|
+
query = Arel::Nodes::NamedFunction.new("ARRAY", to_sql_array(arel_or_rel_query))
|
40
|
+
nested_alias_escape(query, alias_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Will attempt to digest and resolve the from clause
|
44
|
+
#
|
45
|
+
# If the from clause is a String, it will check to see if a table reference key has been assigned.
|
46
|
+
# - If one cannot be detected, one will be appended.
|
47
|
+
# - Rails does not allow assigning table references using the `.from/2` method, when its a string / sym type.
|
48
|
+
#
|
49
|
+
# If the from clause is an AR relation; it will duplicate the object.
|
50
|
+
# - Ensures any memorizers are reset (ex: `.to_sql` sets a memorizer on the instance)
|
51
|
+
# - Key's can be assigned using the `.from/2` method.
|
52
|
+
#
|
53
|
+
def from_clause_constructor(from, reference_key)
|
54
|
+
case from
|
55
|
+
when /\s.?#{reference_key}.?$/ # The from clause is a string and has the tbl reference key
|
56
|
+
@scope.unscoped.from(from)
|
57
|
+
when String, Symbol
|
58
|
+
@scope.unscoped.from("#{from} #{reference_key}")
|
59
|
+
else
|
60
|
+
replicate_klass = from.respond_to?(:unscoped) ? from.unscoped : @scope.unscoped
|
61
|
+
replicate_klass.from(from.dup, reference_key)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Will carry defined CTE tables from the nested sub-query and gradually pushes it up to the parents query stack
|
66
|
+
# I.E: It pushes `WITH [:cte_name:] AS(...), ..` to the top of the query structure tree
|
67
|
+
#
|
68
|
+
# SPECIAL GOTCHA NOTE: (if duplicate keys are found) This will favor the parents query `with's` over nested ones!
|
69
|
+
def pipe_cte_with!(subquery)
|
70
|
+
return self unless subquery.try(:with_values?)
|
71
|
+
|
72
|
+
cte_ary = flatten_safely(subquery.with_values)
|
73
|
+
subquery.with_values = nil # Remove nested queries with values
|
74
|
+
|
75
|
+
# Add subquery's CTE's to the parents query stack. (READ THE SPECIAL NOTE ABOVE!)
|
76
|
+
if @scope.with_values?
|
77
|
+
# combine top-level and lower level queries `.with` values into 1 structure
|
78
|
+
with_hash = cte_ary.each_with_object(@scope.with_values.first) do |from_cte, hash|
|
79
|
+
hash.reverse_merge!(from_cte)
|
80
|
+
end
|
81
|
+
|
82
|
+
@scope.with_values = [with_hash]
|
83
|
+
else
|
84
|
+
# Top level has no with values
|
85
|
+
@scope.with!(*cte_ary)
|
86
|
+
end
|
87
|
+
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
# Ensures the given value is properly double quoted.
|
92
|
+
# This also ensures we don't have conflicts with reversed keywords.
|
93
|
+
#
|
94
|
+
# IE: `user` is a reserved keyword in PG. But `"user"` is allowed and works the same
|
95
|
+
# when used as an column/tbl alias.
|
96
|
+
def double_quote(value)
|
97
|
+
return if value.nil?
|
98
|
+
|
99
|
+
case value.to_s
|
100
|
+
when "*", /^".+"$/ # Ignore keys that contain double quotes or a Arel.star (*)[all columns]
|
101
|
+
value
|
102
|
+
else
|
103
|
+
PG::Connection.quote_ident(value.to_s)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Ensures the key is properly single quoted and treated as a actual PG key reference.
|
108
|
+
def literal_key(key)
|
109
|
+
case key
|
110
|
+
when TrueClass then "'t'"
|
111
|
+
when FalseClass then "'f'"
|
112
|
+
when Numeric then key
|
113
|
+
else
|
114
|
+
key = key.to_s
|
115
|
+
key.start_with?("'") && key.end_with?("'") ? key : "'#{key}'"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Converts a potential subquery into a compatible Arel SQL node.
|
120
|
+
#
|
121
|
+
# Note:
|
122
|
+
# We convert relations to SQL to maintain compatibility with Rails 5.[0/1].
|
123
|
+
# Only Rails 5.2+ maintains bound attributes in Arel, so its better to be safe then sorry.
|
124
|
+
# When we drop support for Rails 5.[0/1], we then can then drop the '.to_sql' conversation
|
125
|
+
|
126
|
+
def to_arel_sql(value)
|
127
|
+
case value
|
128
|
+
when Arel::Node, Arel::Nodes::SqlLiteral, nil
|
129
|
+
value
|
130
|
+
when ActiveRecord::Relation
|
131
|
+
Arel.sql(value.spawn.to_sql)
|
132
|
+
else
|
133
|
+
Arel.sql(value.respond_to?(:to_sql) ? value.to_sql : value.to_s)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def key_generator
|
138
|
+
A_TO_Z_KEYS.sample
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -3,17 +3,6 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
RSpec.describe "Active Record Inet Query Methods" do
|
6
|
-
describe "Deprecation Notices" do
|
7
|
-
%i[contained_within contained_within_or_equals contains_or_equals].each do |method|
|
8
|
-
it "Should display a deprecation warning for #{method}" do
|
9
|
-
new_method = "inet_#{method}".to_sym
|
10
|
-
warning_msg = "##{method} will soon be deprecated for version 1.0 release. Please use ##{new_method} instead."
|
11
|
-
expect_any_instance_of(ActiveRecordExtended::QueryMethods::Inet).to receive(new_method)
|
12
|
-
expect { Person.where.send(method, nil) }.to output(Regexp.new(warning_msg)).to_stderr
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
6
|
describe "#inet_contained_within" do
|
18
7
|
let!(:local_1) { Person.create!(ip: "127.0.0.1") }
|
19
8
|
let!(:local_44) { Person.create!(ip: "127.0.0.44") }
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe "Active Record JSON methods" do
|
4
|
+
let!(:person_one) { Person.create! }
|
5
|
+
let!(:person_two) { Person.create! }
|
6
|
+
|
7
|
+
describe ".select_row_to_json" do
|
8
|
+
let!(:tag_one) { Tag.create!(person: person_one, tag_number: 2) }
|
9
|
+
let!(:tag_two) { Tag.create!(person: person_two, tag_number: 5) }
|
10
|
+
let(:sub_query) { Tag.select(:tag_number).where("tags.person_id = people.id") }
|
11
|
+
|
12
|
+
it "should nest a json object in the query results" do
|
13
|
+
query = Person.select(:id).select_row_to_json(sub_query, as: :results).where(id: person_one.id)
|
14
|
+
expect(query.size).to eq(1)
|
15
|
+
expect(query.take.results).to be_a(Hash).and(match("tag_number" => 2))
|
16
|
+
end
|
17
|
+
|
18
|
+
# ugh wording here sucks, brain is fried.
|
19
|
+
it "accepts a block for appending additional scopes to the middle-top level" do
|
20
|
+
query = Person.select(:id).select_row_to_json(sub_query, key: :tag_row, as: :results) do |scope|
|
21
|
+
scope.where("tag_row.tag_number = 5")
|
22
|
+
end
|
23
|
+
|
24
|
+
expect(query.size).to eq(2)
|
25
|
+
query.each do |result|
|
26
|
+
if result.id == person_one.id
|
27
|
+
expect(result.results).to be_blank
|
28
|
+
else
|
29
|
+
expect(result.results).to be_present.and(match("tag_number" => 5))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "allows for casting results in an aggregate-able Array function" do
|
35
|
+
query = Person.select(:id).select_row_to_json(sub_query, key: :tag_row, as: :results, cast_as_array: true)
|
36
|
+
expect(query.take.results).to be_a(Array).and(be_present)
|
37
|
+
expect(query.take.results.first).to be_a(Hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "raises an error if a from clause key is missing" do
|
41
|
+
expect do
|
42
|
+
Person.select(:id).select_row_to_json(key: :tag_row, as: :results)
|
43
|
+
end.to raise_error(ArgumentError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ".json_build_object" do
|
48
|
+
let(:sub_query) do
|
49
|
+
Person.select_row_to_json(from: Person.select(:id), cast_as_array: true, as: :ids).where(id: person_one.id)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "defaults the column alias if one is not provided" do
|
53
|
+
query = Person.json_build_object(:personal, sub_query)
|
54
|
+
expect(query.size).to eq(1)
|
55
|
+
expect(query.take.results).to match(
|
56
|
+
"personal" => match("ids" => match_array([{ "id" => person_one.id }, { "id" => person_two.id }])),
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "allows for re-aliasing the default 'results' column" do
|
61
|
+
query = Person.json_build_object(:personal, sub_query, as: :cool_dudes)
|
62
|
+
expect(query.take).to respond_to(:cool_dudes)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe ".jsonb_build_object" do
|
67
|
+
let(:sub_query) { Person.select(:id, :number).where(id: person_one.id) }
|
68
|
+
|
69
|
+
it "defaults the column alias if one is not provided" do
|
70
|
+
query = Person.jsonb_build_object(:personal, sub_query)
|
71
|
+
expect(query.size).to eq(1)
|
72
|
+
expect(query.take.results).to be_a(Hash).and(be_present)
|
73
|
+
expect(query.take.results).to match("personal" => match("id" => person_one.id, "number" => person_one.number))
|
74
|
+
end
|
75
|
+
|
76
|
+
it "allows for re-aliasing the default 'results' column" do
|
77
|
+
query = Person.jsonb_build_object(:personal, sub_query, as: :cool_dudes)
|
78
|
+
expect(query.take).to respond_to(:cool_dudes)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "allows for custom value statement" do
|
82
|
+
query = Person.jsonb_build_object(
|
83
|
+
:personal,
|
84
|
+
sub_query.where.not(id: person_one),
|
85
|
+
value: "COALESCE(array_agg(\"personal\"), '{}')",
|
86
|
+
as: :cool_dudes,
|
87
|
+
)
|
88
|
+
|
89
|
+
expect(query.take.cool_dudes["personal"]).to be_a(Array).and(be_empty)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "will raise a warning if the value doesn't include a double quoted input" do
|
93
|
+
expect do
|
94
|
+
Person.jsonb_build_object(
|
95
|
+
:personal,
|
96
|
+
sub_query.where.not(id: person_one),
|
97
|
+
value: "COALESCE(array_agg(personal), '{}')",
|
98
|
+
as: :cool_dudes,
|
99
|
+
)
|
100
|
+
end.to output.to_stderr
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "Json literal builds" do
|
105
|
+
let(:original_hash) { { p: 1, b: "three", x: 3.14 } }
|
106
|
+
let(:hash_as_array_objs) { original_hash.to_a.flatten }
|
107
|
+
|
108
|
+
shared_examples_for "literal builds" do
|
109
|
+
let(:method) { raise "You are expected to over ride this!" }
|
110
|
+
|
111
|
+
it "will accept a hash arguments that will return itself" do
|
112
|
+
query = Person.send(method.to_sym, original_hash)
|
113
|
+
expect(query.take.results).to be_a(Hash).and(be_present)
|
114
|
+
expect(query.take.results).to match(original_hash.stringify_keys)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "will accept a standard array of key values" do
|
118
|
+
query = Person.send(method.to_sym, hash_as_array_objs)
|
119
|
+
expect(query.take.results).to be_a(Hash).and(be_present)
|
120
|
+
expect(query.take.results).to match(original_hash.stringify_keys)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "will accept a splatted array of key-values" do
|
124
|
+
query = Person.send(method.to_sym, *hash_as_array_objs)
|
125
|
+
expect(query.take.results).to be_a(Hash).and(be_present)
|
126
|
+
expect(query.take.results).to match(original_hash.stringify_keys)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe ".json_build_literal" do
|
131
|
+
it_behaves_like "literal builds" do
|
132
|
+
let!(:method) { :json_build_literal }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe ".jsonb_build_literal" do
|
137
|
+
it_behaves_like "literal builds" do
|
138
|
+
let!(:method) { :jsonb_build_literal }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe "Active Record Union Methods" do
|
6
|
+
let!(:person_one) { Person.create!(number: 8) }
|
7
|
+
let!(:person_two) { Person.create!(number: 10) }
|
8
|
+
let!(:person_three) { Person.create!(number: 1) }
|
9
|
+
let!(:person_one_pl) { ProfileL.create!(person: person_one, likes: 100) }
|
10
|
+
let!(:person_two_pl) { ProfileL.create!(person: person_two, likes: 200) }
|
11
|
+
|
12
|
+
shared_examples_for "standard set of errors" do
|
13
|
+
let(:person_one_query) { Person.select(:id).where(id: person_one.id) }
|
14
|
+
let(:person_two_query) { Person.select(:id, :tags).where(id: person_two.id) }
|
15
|
+
let(:misaligned_cmd) { raise("required to override this 'let' statement") }
|
16
|
+
let(:lacking_union_cmd) { raise("required to override this 'let' statement") }
|
17
|
+
|
18
|
+
it "should raise an error if the select statements do not align" do
|
19
|
+
expect { misaligned_cmd.to_a }.to(
|
20
|
+
raise_error(ActiveRecord::StatementInvalid, /each [[:alpha:]]+ query must have the same number of columns/),
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should raise an argument error if there are less then two union statements" do
|
25
|
+
expect { lacking_union_cmd.to_a }.to(
|
26
|
+
raise_error(ArgumentError, "You are required to provide 2 or more unions to join!"),
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe ".union" do
|
32
|
+
it_behaves_like "standard set of errors" do
|
33
|
+
let!(:misaligned_cmd) { Person.union(person_one_query, person_two_query) }
|
34
|
+
let!(:lacking_union_cmd) { Person.union(person_one_query) }
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return two users that match the where conditions" do
|
38
|
+
query = Person.union(Person.where(id: person_one.id), Person.where(id: person_three.id))
|
39
|
+
expect(query).to match_array([person_one, person_three])
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should allow joins on union statements" do
|
43
|
+
query = Person.union(Person.where(id: person_one.id), Person.joins(:profile_l).where.not(id: person_one.id))
|
44
|
+
expect(query).to match_array([person_one, person_two])
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should eliminate duplicate results" do
|
48
|
+
expected_ids = Person.pluck(:id)
|
49
|
+
query = Person.union(Person.select(:id), Person.select(:id))
|
50
|
+
expect(query.pluck(:id)).to have_attributes(size: expected_ids.size).and(match_array(expected_ids))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe ".union.all" do
|
55
|
+
it_behaves_like "standard set of errors" do
|
56
|
+
let!(:misaligned_cmd) { Person.union.all(person_one_query, person_two_query) }
|
57
|
+
let!(:lacking_union_cmd) { Person.union.all(person_one_query) }
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should keep duplicate results from each union statement" do
|
61
|
+
expected_ids = Person.pluck(:id) * 2
|
62
|
+
query = Person.union.all(Person.select(:id), Person.select(:id))
|
63
|
+
expect(query.pluck(:id)).to have_attributes(size: expected_ids.size).and(match_array(expected_ids))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe ".union.except" do
|
68
|
+
it_behaves_like "standard set of errors" do
|
69
|
+
let!(:misaligned_cmd) { Person.union.except(person_one_query, person_two_query) }
|
70
|
+
let!(:lacking_union_cmd) { Person.union.except(person_one_query) }
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should eliminate records that match a given except statement" do
|
74
|
+
query = Person.union.except(Person.select(:id), Person.select(:id).where(id: person_one.id))
|
75
|
+
expect(query).to match_array([person_two, person_three])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "union.intersect" do
|
80
|
+
it_behaves_like "standard set of errors" do
|
81
|
+
let!(:misaligned_cmd) { Person.union.intersect(person_one_query, person_two_query) }
|
82
|
+
let!(:lacking_union_cmd) { Person.union.intersect(person_one_query) }
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should find records with similar attributes" do
|
86
|
+
ProfileL.create!(person: person_three, likes: 120)
|
87
|
+
|
88
|
+
query =
|
89
|
+
Person.union.intersect(
|
90
|
+
Person.select(:id, "profile_ls.likes").joins(:profile_l).where(profile_ls: { likes: 100 }),
|
91
|
+
Person.select(:id, "profile_ls.likes").joins(:profile_l).where("profile_ls.likes < 150"),
|
92
|
+
)
|
93
|
+
|
94
|
+
expect(query.pluck(:id)).to have_attributes(size: 1).and(eq([person_one_pl.id]))
|
95
|
+
expect(query.first.likes).to eq(person_one_pl.likes)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "union.as" do
|
100
|
+
let(:query) do
|
101
|
+
Person.select("happy_people.id")
|
102
|
+
.union(Person.where(id: person_one.id), Person.where(id: person_three.id))
|
103
|
+
.union_as(:happy_people)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should return two people" do
|
107
|
+
expect(query.size).to eq(2)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should return two peoples id's" do
|
111
|
+
expect(query.map(&:id)).to match_array([person_one.id, person_three.id])
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should alias the tables being union'd but still allow for accessing table methods" do
|
115
|
+
query.each do |happy_person|
|
116
|
+
expect(happy_person).to respond_to(:profile_l)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "union.order_union" do
|
122
|
+
it "should order the .union commands" do
|
123
|
+
query = Person.union(Person.where(id: person_one.id), Person.where(id: person_three.id)).order_union(id: :desc)
|
124
|
+
expect(query).to eq([person_three, person_one])
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should order the .union.all commands" do
|
128
|
+
query =
|
129
|
+
Person.union.all(
|
130
|
+
Person.where(id: person_one.id),
|
131
|
+
Person.where(id: person_three.id),
|
132
|
+
).order_union(id: :desc)
|
133
|
+
|
134
|
+
expect(query).to eq([person_three, person_one])
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should order the union.except commands" do
|
138
|
+
query = Person.union.except(Person.order(id: :asc), Person.where(id: person_one.id)).order_union(id: :desc)
|
139
|
+
expect(query).to eq([person_three, person_two])
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should order the .union.intersect commands" do
|
143
|
+
query =
|
144
|
+
Person.union.intersect(
|
145
|
+
Person.where("id < ?", person_three.id),
|
146
|
+
Person.where("id >= ?", person_one.id),
|
147
|
+
).order_union(id: :desc)
|
148
|
+
|
149
|
+
expect(query).to eq([person_two, person_one])
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "union.reorder_union" do
|
154
|
+
it "should replace the ordering with the new parameters" do
|
155
|
+
person_a = Person.create!(number: 1)
|
156
|
+
person_b = Person.create!(number: 10)
|
157
|
+
initial_ordering = [person_b, person_a]
|
158
|
+
query = Person.union(Person.where(id: person_a.id), Person.where(id: person_b.id))
|
159
|
+
.order_union(id: :desc)
|
160
|
+
|
161
|
+
expect(query).to eq(initial_ordering)
|
162
|
+
expect(query.reorder_union(number: :asc)).to eq(initial_ordering.reverse)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|