active_record_extended 2.1.0 → 3.0.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 +4 -4
- data/README.md +22 -7
- data/lib/active_record_extended/arel/aggregate_function_name.rb +0 -0
- data/lib/active_record_extended/arel/nodes.rb +1 -1
- data/lib/active_record_extended/arel/predications.rb +0 -0
- data/lib/active_record_extended/arel/{sql_literal.rb → sql_literal_patch.rb} +2 -2
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +16 -12
- data/lib/active_record_extended/arel.rb +1 -1
- data/lib/active_record_extended/patch/array_handler_patch.rb +22 -0
- data/lib/active_record_extended/patch/relation_patch.rb +67 -0
- data/lib/active_record_extended/patch/where_clause_patch.rb +13 -0
- data/lib/active_record_extended/query_methods/any_of.rb +1 -1
- data/lib/active_record_extended/query_methods/either.rb +3 -3
- data/lib/active_record_extended/query_methods/{select.rb → foster_select.rb} +4 -4
- data/lib/active_record_extended/query_methods/inet.rb +0 -0
- data/lib/active_record_extended/query_methods/json.rb +2 -2
- data/lib/active_record_extended/query_methods/unionize.rb +5 -5
- data/lib/active_record_extended/query_methods/where_chain.rb +96 -92
- data/lib/active_record_extended/query_methods/window.rb +3 -3
- data/lib/active_record_extended/query_methods/with_cte.rb +3 -3
- data/lib/active_record_extended/utilities/order_by.rb +1 -1
- data/lib/active_record_extended/utilities/support.rb +1 -1
- data/lib/active_record_extended/version.rb +1 -1
- data/lib/active_record_extended.rb +55 -4
- metadata +21 -70
- data/lib/active_record_extended/active_record/relation_patch.rb +0 -50
- data/lib/active_record_extended/active_record.rb +0 -25
- data/lib/active_record_extended/patch/5_1/where_clause.rb +0 -11
- data/lib/active_record_extended/patch/5_2/where_clause.rb +0 -11
- data/lib/active_record_extended/predicate_builder/array_handler_decorator.rb +0 -20
- data/spec/active_record_extended_spec.rb +0 -7
- data/spec/query_methods/any_of_spec.rb +0 -131
- data/spec/query_methods/array_query_spec.rb +0 -64
- data/spec/query_methods/either_spec.rb +0 -70
- data/spec/query_methods/hash_query_spec.rb +0 -45
- data/spec/query_methods/inet_query_spec.rb +0 -112
- data/spec/query_methods/json_spec.rb +0 -157
- data/spec/query_methods/select_spec.rb +0 -115
- data/spec/query_methods/unionize_spec.rb +0 -165
- data/spec/query_methods/window_spec.rb +0 -51
- data/spec/query_methods/with_cte_spec.rb +0 -50
- data/spec/spec_helper.rb +0 -28
- data/spec/sql_inspections/any_of_sql_spec.rb +0 -41
- data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +0 -41
- data/spec/sql_inspections/arel/array_spec.rb +0 -63
- data/spec/sql_inspections/arel/inet_spec.rb +0 -66
- data/spec/sql_inspections/contains_sql_queries_spec.rb +0 -47
- data/spec/sql_inspections/either_sql_spec.rb +0 -71
- data/spec/sql_inspections/json_sql_spec.rb +0 -82
- data/spec/sql_inspections/unionize_sql_spec.rb +0 -124
- data/spec/sql_inspections/window_sql_spec.rb +0 -98
- data/spec/sql_inspections/with_cte_sql_spec.rb +0 -95
- data/spec/support/database_cleaner.rb +0 -15
- data/spec/support/models.rb +0 -80
@@ -1,45 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Active Record Hash Related Query Methods" do
|
6
|
-
let!(:one) { User.create!(data: { nickname: "george" }, jsonb_data: { payment: "zip" }) }
|
7
|
-
let!(:two) { User.create!(data: { nickname: "dan" }, jsonb_data: { payment: "zipper" }) }
|
8
|
-
let!(:three) { User.create!(data: { nickname: "georgey" }) }
|
9
|
-
|
10
|
-
describe "#contains" do
|
11
|
-
context "HStore Column Type" do
|
12
|
-
it "returns records that contain hash elements in joined tables" do
|
13
|
-
tag_one = Tag.create!(user_id: one.id)
|
14
|
-
tag_two = Tag.create!(user_id: two.id)
|
15
|
-
|
16
|
-
query = Tag.joins(:user).where.contains(users: { data: { nickname: "george" } })
|
17
|
-
expect(query).to include(tag_one)
|
18
|
-
expect(query).to_not include(tag_two)
|
19
|
-
end
|
20
|
-
|
21
|
-
it "returns records that contain hash value" do
|
22
|
-
query = User.where.contains(data: { nickname: "george" })
|
23
|
-
expect(query).to include(one)
|
24
|
-
expect(query).to_not include(two, three)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
context "JSONB Column Type" do
|
29
|
-
it "returns records that contains a json hashed value" do
|
30
|
-
query = User.where.contains(jsonb_data: { payment: "zip" })
|
31
|
-
expect(query).to include(one)
|
32
|
-
expect(query).to_not include(two, three)
|
33
|
-
end
|
34
|
-
|
35
|
-
it "returns records that contain jsonb elements in joined tables" do
|
36
|
-
tag_one = Tag.create!(user_id: one.id)
|
37
|
-
tag_two = Tag.create!(user_id: two.id)
|
38
|
-
|
39
|
-
query = Tag.joins(:user).where.contains(users: { jsonb_data: { payment: "zip" } })
|
40
|
-
expect(query).to include(tag_one)
|
41
|
-
expect(query).to_not include(tag_two)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,112 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Active Record Inet Query Methods" do
|
6
|
-
before { stub_const("User", Namespaced::Record) }
|
7
|
-
|
8
|
-
describe "#inet_contained_within" do
|
9
|
-
let!(:local_1) { User.create!(ip: "127.0.0.1") }
|
10
|
-
let!(:local_44) { User.create!(ip: "127.0.0.44") }
|
11
|
-
let!(:local_99_1) { User.create!(ip: "127.0.99.1") }
|
12
|
-
let!(:local_range) { User.create!(ip: "127.0.0.1/10") }
|
13
|
-
|
14
|
-
it "Should return users who have an IP within the 127.0.0.1/24 range" do
|
15
|
-
query = User.where.inet_contained_within(ip: "127.0.0.1/24")
|
16
|
-
expect(query).to include(local_1, local_44)
|
17
|
-
expect(query).to_not include(local_99_1, local_range)
|
18
|
-
end
|
19
|
-
|
20
|
-
it "Should return all users who have an IP within the 127.0.0.1/16 range" do
|
21
|
-
query = User.where.inet_contained_within(ip: "127.0.0.1/16")
|
22
|
-
expect(query).to include(local_1, local_44, local_99_1)
|
23
|
-
expect(query).to_not include(local_range)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
describe "inet_contained_within_or_equals" do
|
28
|
-
let!(:local_1) { User.create!(ip: "127.0.0.1/10") }
|
29
|
-
let!(:local_44) { User.create!(ip: "127.0.0.44/32") }
|
30
|
-
let!(:local_99_1) { User.create!(ip: "127.0.99.1") }
|
31
|
-
|
32
|
-
it "Should find records that contain a matching submask" do
|
33
|
-
query = User.where.inet_contained_within_or_equals(ip: "127.0.0.44/32")
|
34
|
-
expect(query).to include(local_44)
|
35
|
-
expect(query).to_not include(local_1, local_99_1)
|
36
|
-
end
|
37
|
-
|
38
|
-
it "Should find records that are within range of a given submask" do
|
39
|
-
query = User.where.inet_contained_within_or_equals(ip: "127.0.0.1/16")
|
40
|
-
expect(query).to include(local_44, local_99_1)
|
41
|
-
expect(query).to_not include(local_1)
|
42
|
-
|
43
|
-
query = User.where.inet_contained_within_or_equals(ip: "127.0.0.1/8")
|
44
|
-
expect(query).to include(local_1, local_44, local_99_1)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
describe "#inet_contains_or_equals" do
|
49
|
-
let!(:local_1) { User.create!(ip: "127.0.0.1/10") }
|
50
|
-
let!(:local_44) { User.create!(ip: "127.0.0.44/24") }
|
51
|
-
let!(:local_99_1) { User.create!(ip: "127.0.99.1") }
|
52
|
-
|
53
|
-
it "Should find records with submask ranges that contain a given IP" do
|
54
|
-
query = User.where.inet_contains_or_equals(ip: "127.0.255.255")
|
55
|
-
expect(query).to include(local_1)
|
56
|
-
expect(query).to_not include(local_44, local_99_1)
|
57
|
-
|
58
|
-
query = User.where.inet_contains_or_equals(ip: "127.0.0.255")
|
59
|
-
expect(query).to include(local_1, local_44)
|
60
|
-
expect(query).to_not include(local_99_1)
|
61
|
-
end
|
62
|
-
|
63
|
-
it "Finds records when querying with a submasked value" do
|
64
|
-
query = User.where.inet_contains_or_equals(ip: "127.0.0.1/10")
|
65
|
-
expect(query).to include(local_1)
|
66
|
-
expect(query).to_not include(local_44, local_99_1)
|
67
|
-
|
68
|
-
query = User.where.inet_contains_or_equals(ip: "127.0.0.1/32")
|
69
|
-
expect(query).to include(local_1, local_44)
|
70
|
-
expect(query).to_not include(local_99_1)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
describe "#inet_contains" do
|
75
|
-
let!(:local_1) { User.create!(ip: "127.0.0.1/10") }
|
76
|
-
let!(:local_44) { User.create!(ip: "127.0.0.44/24") }
|
77
|
-
|
78
|
-
it "Should find records that the given IP falls within" do
|
79
|
-
query = User.where.inet_contains(ip: "127.0.0.1")
|
80
|
-
expect(query).to include(local_1, local_44)
|
81
|
-
end
|
82
|
-
|
83
|
-
it "Should not find records when querying with a submasked value" do
|
84
|
-
query = User.where.inet_contains(ip: "127.0.0.0/8")
|
85
|
-
expect(query).to be_empty
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
describe "#inet_contains_or_is_contained_within" do
|
90
|
-
let!(:local_1) { User.create!(ip: "127.0.0.1/24") }
|
91
|
-
let!(:local_44) { User.create!(ip: "127.0.22.44/8") }
|
92
|
-
let!(:local_99_1) { User.create!(ip: "127.0.99.1") }
|
93
|
-
|
94
|
-
it "should find records where the records contain the given IP" do
|
95
|
-
query = User.where.inet_contains_or_is_contained_within(ip: "127.0.255.80")
|
96
|
-
expect(query).to include(local_44)
|
97
|
-
expect(query).to_not include(local_1, local_99_1)
|
98
|
-
|
99
|
-
query = User.where.inet_contains_or_is_contained_within(ip: "127.0.0.80")
|
100
|
-
expect(query).to include(local_1, local_44)
|
101
|
-
expect(query).to_not include(local_99_1)
|
102
|
-
end
|
103
|
-
|
104
|
-
it "Should find records that the where query contains a valid range" do
|
105
|
-
query = User.where.inet_contains_or_is_contained_within(ip: "127.0.0.80/8")
|
106
|
-
expect(query).to include(local_1, local_44, local_99_1)
|
107
|
-
|
108
|
-
query = User.where.inet_contains_or_is_contained_within(ip: "127.0.0.80/16")
|
109
|
-
expect(query).to include(local_1, local_44, local_99_1)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
@@ -1,157 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe "Active Record JSON methods" do
|
4
|
-
let!(:user_one) { User.create! }
|
5
|
-
let!(:user_two) { User.create! }
|
6
|
-
|
7
|
-
describe ".select_row_to_json" do
|
8
|
-
let!(:tag_one) { Tag.create!(user: user_one, tag_number: 2) }
|
9
|
-
let!(:tag_two) { Tag.create!(user: user_two, tag_number: 5) }
|
10
|
-
let(:sub_query) { Tag.select(:tag_number).where("tags.user_id = users.id") }
|
11
|
-
|
12
|
-
it "should nest a json object in the query results" do
|
13
|
-
query = User.select(:id).select_row_to_json(sub_query, as: :results).where(id: user_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 = User.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 == user_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 "accepts a scope-block without arguments" do
|
35
|
-
query = User.select(:id).select_row_to_json(sub_query, key: :tag_row, as: :results) do
|
36
|
-
where("tag_row.tag_number = 5")
|
37
|
-
end
|
38
|
-
|
39
|
-
expect(query.size).to eq(2)
|
40
|
-
query.each do |result|
|
41
|
-
if result.id == user_one.id
|
42
|
-
expect(result.results).to be_blank
|
43
|
-
else
|
44
|
-
expect(result.results).to be_present.and(match("tag_number" => 5))
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
it "allows for casting results in an aggregate-able Array function" do
|
50
|
-
query = User.select(:id).select_row_to_json(sub_query, key: :tag_row, as: :results, cast_with: :array)
|
51
|
-
expect(query.take.results).to be_a(Array).and(be_present)
|
52
|
-
expect(query.take.results.first).to be_a(Hash)
|
53
|
-
end
|
54
|
-
|
55
|
-
it "raises an error if a from clause key is missing" do
|
56
|
-
expect do
|
57
|
-
User.select(:id).select_row_to_json(key: :tag_row, as: :results)
|
58
|
-
end.to raise_error(ArgumentError)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
describe ".json_build_object" do
|
63
|
-
let(:sub_query) do
|
64
|
-
User.select_row_to_json(from: User.select(:id), cast_with: :array, as: :ids).where(id: user_one.id)
|
65
|
-
end
|
66
|
-
|
67
|
-
it "defaults the column alias if one is not provided" do
|
68
|
-
query = User.json_build_object(:personal, sub_query)
|
69
|
-
expect(query.size).to eq(1)
|
70
|
-
expect(query.take.results).to match(
|
71
|
-
"personal" => match("ids" => match_array([{ "id" => user_one.id }, { "id" => user_two.id }]))
|
72
|
-
)
|
73
|
-
end
|
74
|
-
|
75
|
-
it "allows for re-aliasing the default 'results' column" do
|
76
|
-
query = User.json_build_object(:personal, sub_query, as: :cool_dudes)
|
77
|
-
expect(query.take).to respond_to(:cool_dudes)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
describe ".jsonb_build_object" do
|
82
|
-
let(:sub_query) { User.select(:id, :number).where(id: user_one.id) }
|
83
|
-
|
84
|
-
it "defaults the column alias if one is not provided" do
|
85
|
-
query = User.jsonb_build_object(:personal, sub_query)
|
86
|
-
expect(query.size).to eq(1)
|
87
|
-
expect(query.take.results).to be_a(Hash).and(be_present)
|
88
|
-
expect(query.take.results).to match("personal" => match("id" => user_one.id, "number" => user_one.number))
|
89
|
-
end
|
90
|
-
|
91
|
-
it "allows for re-aliasing the default 'results' column" do
|
92
|
-
query = User.jsonb_build_object(:personal, sub_query, as: :cool_dudes)
|
93
|
-
expect(query.take).to respond_to(:cool_dudes)
|
94
|
-
end
|
95
|
-
|
96
|
-
it "allows for custom value statement" do
|
97
|
-
query = User.jsonb_build_object(
|
98
|
-
:personal,
|
99
|
-
sub_query.where.not(id: user_one),
|
100
|
-
value: "COALESCE(array_agg(\"personal\"), '{}')",
|
101
|
-
as: :cool_dudes
|
102
|
-
)
|
103
|
-
|
104
|
-
expect(query.take.cool_dudes["personal"]).to be_a(Array).and(be_empty)
|
105
|
-
end
|
106
|
-
|
107
|
-
it "will raise a warning if the value doesn't include a double quoted input" do
|
108
|
-
expect do
|
109
|
-
User.jsonb_build_object(
|
110
|
-
:personal,
|
111
|
-
sub_query.where.not(id: user_one),
|
112
|
-
value: "COALESCE(array_agg(personal), '{}')",
|
113
|
-
as: :cool_dudes
|
114
|
-
)
|
115
|
-
end.to output.to_stderr
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
describe "Json literal builds" do
|
120
|
-
let(:original_hash) { { p: 1, b: "three", x: 3.14 } }
|
121
|
-
let(:hash_as_array_objs) { original_hash.to_a.flatten }
|
122
|
-
|
123
|
-
shared_examples_for "literal builds" do
|
124
|
-
let(:method) { raise "You are expected to over ride this!" }
|
125
|
-
|
126
|
-
it "will accept a hash arguments that will return itself" do
|
127
|
-
query = User.send(method.to_sym, original_hash)
|
128
|
-
expect(query.take.results).to be_a(Hash).and(be_present)
|
129
|
-
expect(query.take.results).to match(original_hash.stringify_keys)
|
130
|
-
end
|
131
|
-
|
132
|
-
it "will accept a standard array of key values" do
|
133
|
-
query = User.send(method.to_sym, hash_as_array_objs)
|
134
|
-
expect(query.take.results).to be_a(Hash).and(be_present)
|
135
|
-
expect(query.take.results).to match(original_hash.stringify_keys)
|
136
|
-
end
|
137
|
-
|
138
|
-
it "will accept a splatted array of key-values" do
|
139
|
-
query = User.send(method.to_sym, *hash_as_array_objs)
|
140
|
-
expect(query.take.results).to be_a(Hash).and(be_present)
|
141
|
-
expect(query.take.results).to match(original_hash.stringify_keys)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
describe ".json_build_literal" do
|
146
|
-
it_behaves_like "literal builds" do
|
147
|
-
let!(:method) { :json_build_literal }
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
describe ".jsonb_build_literal" do
|
152
|
-
it_behaves_like "literal builds" do
|
153
|
-
let!(:method) { :jsonb_build_literal }
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
@@ -1,115 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Active Record Select Methods" do
|
6
|
-
let(:numbers) { (1..10).to_a }
|
7
|
-
|
8
|
-
describe ".foster_select" do
|
9
|
-
context "with an aggregate function" do
|
10
|
-
context "agg_array" do
|
11
|
-
let(:number_set) { numbers.sample(6).to_enum }
|
12
|
-
let!(:users) { Array.new(6) { User.create!(number: number_set.next, ip: "127.0.0.1") } }
|
13
|
-
let!(:tags) { users.flat_map { |u| Array.new(2) { Tag.create!(user: u, tag_number: numbers.sample) } } }
|
14
|
-
|
15
|
-
it "can accept a subquery" do
|
16
|
-
subquery = Tag.select("count(*)").joins("JOIN users u ON tags.user_id = u.id").where("u.ip = users.ip")
|
17
|
-
query =
|
18
|
-
User.foster_select(tag_count: [subquery, { cast_with: :array_agg, distinct: true }])
|
19
|
-
.joins(:hm_tags)
|
20
|
-
.group(:ip)
|
21
|
-
.take
|
22
|
-
|
23
|
-
expect(query.tag_count).to eq([tags.size])
|
24
|
-
end
|
25
|
-
|
26
|
-
it "can be ordered" do
|
27
|
-
query = User.foster_select(
|
28
|
-
asc_ordered_numbers: [:number, { cast_with: :array_agg, order_by: { number: :asc } }],
|
29
|
-
desc_ordered_numbers: [:number, { cast_with: :array_agg, order_by: { number: :desc } }]
|
30
|
-
).take
|
31
|
-
|
32
|
-
expect(query.asc_ordered_numbers).to eq(number_set.to_a.sort)
|
33
|
-
expect(query.desc_ordered_numbers).to eq(number_set.to_a.sort.reverse)
|
34
|
-
end
|
35
|
-
|
36
|
-
it "works with joined relations" do
|
37
|
-
query =
|
38
|
-
User.foster_select(tag_numbers: { tags: :tag_number, cast_with: :array_agg })
|
39
|
-
.joins(:hm_tags)
|
40
|
-
.take
|
41
|
-
expect(query.tag_numbers).to match_array(Tag.pluck(:tag_number))
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
context "bool_[and|or]" do
|
46
|
-
let!(:users) do
|
47
|
-
enum_numbers = numbers.to_enum
|
48
|
-
Array.new(6) { User.create!(number: enum_numbers.next, ip: "127.0.0.1") }
|
49
|
-
end
|
50
|
-
|
51
|
-
it "will return a boolean expression" do
|
52
|
-
query = User.foster_select(
|
53
|
-
truthly_expr: ["users.number > 0", { cast_with: :bool_and }],
|
54
|
-
falsey_expr: ["users.number > 200", { cast_with: :bool_and }],
|
55
|
-
other_true_expr: ["users.number > 4", { cast_with: :bool_or }],
|
56
|
-
other_false_expr: ["users.number > 6", { cast_with: :bool_or }]
|
57
|
-
).take
|
58
|
-
|
59
|
-
expect(query.truthly_expr).to be_truthy
|
60
|
-
expect(query.falsey_expr).to be_falsey
|
61
|
-
expect(query.other_true_expr).to be_truthy
|
62
|
-
expect(query.other_false_expr).to be_falsey
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
context "with math functions: sum|max|min|avg" do
|
67
|
-
before { 2.times.flat_map { |i| Array.new(2) { |j| User.create!(number: (i + 1) * j + 3) } } }
|
68
|
-
|
69
|
-
it "max" do
|
70
|
-
query = User.foster_select(max_num: [:number, { cast_with: :max }]).take
|
71
|
-
expect(query.max_num).to eq(5)
|
72
|
-
end
|
73
|
-
|
74
|
-
it "min" do
|
75
|
-
query = User.foster_select(max_num: [:number, { cast_with: :min }]).take
|
76
|
-
expect(query.max_num).to eq(3)
|
77
|
-
end
|
78
|
-
|
79
|
-
it "sum" do
|
80
|
-
query = User.foster_select(
|
81
|
-
num_sum: [:number, { cast_with: :sum }],
|
82
|
-
distinct_sum: [:number, { cast_with: :sum, distinct: true }]
|
83
|
-
).take
|
84
|
-
|
85
|
-
expect(query.num_sum).to eq(15)
|
86
|
-
expect(query.distinct_sum).to eq(12)
|
87
|
-
end
|
88
|
-
|
89
|
-
it "avg" do
|
90
|
-
query = User.foster_select(
|
91
|
-
num_avg: [:number, { cast_with: :avg }],
|
92
|
-
distinct_avg: [:number, { cast_with: :avg, distinct: true }]
|
93
|
-
).take
|
94
|
-
|
95
|
-
expect(query.num_avg).to eq(3.75)
|
96
|
-
expect(query.distinct_avg).to eq(4.0)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
context "with standard select items" do
|
102
|
-
let!(:user) { User.create!(name: "Test") }
|
103
|
-
|
104
|
-
it "works with no alias" do
|
105
|
-
query = User.foster_select(:name).take
|
106
|
-
expect(query.name).to eq(user.name)
|
107
|
-
end
|
108
|
-
|
109
|
-
it "works with alias" do
|
110
|
-
query = User.foster_select(my_name: :name).take
|
111
|
-
expect(query.my_name).to eq(user.name)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
@@ -1,165 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Active Record Union Methods" do
|
6
|
-
let!(:user_one) { User.create!(number: 8) }
|
7
|
-
let!(:user_two) { User.create!(number: 10) }
|
8
|
-
let!(:user_three) { User.create!(number: 1) }
|
9
|
-
let!(:user_one_pl) { ProfileL.create!(user: user_one, likes: 100) }
|
10
|
-
let!(:user_two_pl) { ProfileL.create!(user: user_two, likes: 200) }
|
11
|
-
|
12
|
-
shared_examples_for "standard set of errors" do
|
13
|
-
let(:user_one_query) { User.select(:id).where(id: user_one.id) }
|
14
|
-
let(:user_two_query) { User.select(:id, :tags).where(id: user_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) { User.union(user_one_query, user_two_query) }
|
34
|
-
let!(:lacking_union_cmd) { User.union(user_one_query) }
|
35
|
-
end
|
36
|
-
|
37
|
-
it "should return two users that match the where conditions" do
|
38
|
-
query = User.union(User.where(id: user_one.id), User.where(id: user_three.id))
|
39
|
-
expect(query).to match_array([user_one, user_three])
|
40
|
-
end
|
41
|
-
|
42
|
-
it "should allow joins on union statements" do
|
43
|
-
query = User.union(User.where(id: user_one.id), User.joins(:profile_l).where.not(id: user_one.id))
|
44
|
-
expect(query).to match_array([user_one, user_two])
|
45
|
-
end
|
46
|
-
|
47
|
-
it "should eliminate duplicate results" do
|
48
|
-
expected_ids = User.pluck(:id)
|
49
|
-
query = User.union(User.select(:id), User.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) { User.union.all(user_one_query, user_two_query) }
|
57
|
-
let!(:lacking_union_cmd) { User.union.all(user_one_query) }
|
58
|
-
end
|
59
|
-
|
60
|
-
it "should keep duplicate results from each union statement" do
|
61
|
-
expected_ids = User.pluck(:id) * 2
|
62
|
-
query = User.union.all(User.select(:id), User.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) { User.union.except(user_one_query, user_two_query) }
|
70
|
-
let!(:lacking_union_cmd) { User.union.except(user_one_query) }
|
71
|
-
end
|
72
|
-
|
73
|
-
it "should eliminate records that match a given except statement" do
|
74
|
-
query = User.union.except(User.select(:id), User.select(:id).where(id: user_one.id))
|
75
|
-
expect(query).to match_array([user_two, user_three])
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
describe "union.intersect" do
|
80
|
-
it_behaves_like "standard set of errors" do
|
81
|
-
let!(:misaligned_cmd) { User.union.intersect(user_one_query, user_two_query) }
|
82
|
-
let!(:lacking_union_cmd) { User.union.intersect(user_one_query) }
|
83
|
-
end
|
84
|
-
|
85
|
-
it "should find records with similar attributes" do
|
86
|
-
ProfileL.create!(user: user_three, likes: 120)
|
87
|
-
|
88
|
-
query =
|
89
|
-
User.union.intersect(
|
90
|
-
User.select(:id, "profile_ls.likes").joins(:profile_l).where(profile_ls: { likes: 100 }),
|
91
|
-
User.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([user_one_pl.user.id]))
|
95
|
-
expect(query.first.likes).to eq(user_one_pl.likes)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
describe "union.as" do
|
100
|
-
let(:query) do
|
101
|
-
User
|
102
|
-
.select("happy_users.id")
|
103
|
-
.union(User.where(id: user_one.id), User.where(id: user_three.id))
|
104
|
-
.union_as(:happy_users)
|
105
|
-
end
|
106
|
-
|
107
|
-
it "should return two users" do
|
108
|
-
expect(query.size).to eq(2)
|
109
|
-
end
|
110
|
-
|
111
|
-
it "should return two userss id's" do
|
112
|
-
expect(query.map(&:id)).to match_array([user_one.id, user_three.id])
|
113
|
-
end
|
114
|
-
|
115
|
-
it "should alias the tables being union'd but still allow for accessing table methods" do
|
116
|
-
query.each do |happy_person|
|
117
|
-
expect(happy_person).to respond_to(:profile_l)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
describe "union.order_union" do
|
123
|
-
it "should order the .union commands" do
|
124
|
-
query = User.union(User.where(id: user_one.id), User.where(id: user_three.id)).order_union(id: :desc)
|
125
|
-
expect(query).to eq([user_three, user_one])
|
126
|
-
end
|
127
|
-
|
128
|
-
it "should order the .union.all commands" do
|
129
|
-
query =
|
130
|
-
User.union.all(
|
131
|
-
User.where(id: user_one.id),
|
132
|
-
User.where(id: user_three.id)
|
133
|
-
).order_union(id: :desc)
|
134
|
-
|
135
|
-
expect(query).to eq([user_three, user_one])
|
136
|
-
end
|
137
|
-
|
138
|
-
it "should order the union.except commands" do
|
139
|
-
query = User.union.except(User.order(id: :asc), User.where(id: user_one.id)).order_union(id: :desc)
|
140
|
-
expect(query).to eq([user_three, user_two])
|
141
|
-
end
|
142
|
-
|
143
|
-
it "should order the .union.intersect commands" do
|
144
|
-
query =
|
145
|
-
User.union.intersect(
|
146
|
-
User.where("id < ?", user_three.id),
|
147
|
-
User.where("id >= ?", user_one.id)
|
148
|
-
).order_union(id: :desc)
|
149
|
-
|
150
|
-
expect(query).to eq([user_two, user_one])
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
describe "union.reorder_union" do
|
155
|
-
it "should replace the ordering with the new parameters" do
|
156
|
-
user_a = User.create!(number: 1)
|
157
|
-
user_b = User.create!(number: 10)
|
158
|
-
initial_ordering = [user_b, user_a]
|
159
|
-
query = User.union(User.where(id: user_a.id), User.where(id: user_b.id)).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
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe "Active Record Window Function Query Methods" do
|
6
|
-
let!(:user_one) { User.create! }
|
7
|
-
let!(:user_two) { User.create! }
|
8
|
-
|
9
|
-
let!(:tag_one) { Tag.create!(user: user_one, tag_number: 1) }
|
10
|
-
let!(:tag_two) { Tag.create!(user: user_two, tag_number: 2) }
|
11
|
-
|
12
|
-
let!(:tag_three) { Tag.create!(user: user_one, tag_number: 3) }
|
13
|
-
let!(:tag_four) { Tag.create!(user: user_two, tag_number: 4) }
|
14
|
-
|
15
|
-
let(:tag_group1) { [tag_one, tag_three] }
|
16
|
-
let(:tag_group2) { [tag_two, tag_four] }
|
17
|
-
|
18
|
-
describe ".window_select" do
|
19
|
-
context "when using ROW_NUMBER() ordered in asc" do
|
20
|
-
let(:base_query) do
|
21
|
-
Tag.define_window(:w).partition_by(:user_id, order_by: :tag_number).select(:id)
|
22
|
-
end
|
23
|
-
|
24
|
-
it "should return tag_one with r_id 1 and tag_three with r_id 2" do
|
25
|
-
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
26
|
-
tag_group1.each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
27
|
-
end
|
28
|
-
|
29
|
-
it "should return tag_two with r_id 1 and tag_four with r_id 2" do
|
30
|
-
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
31
|
-
tag_group2.each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
context "when using ROW_NUMBER() ordered in desc" do
|
36
|
-
let(:base_query) do
|
37
|
-
Tag.define_window(:w).partition_by(:user_id, order_by: { tag_number: :desc }).select(:id)
|
38
|
-
end
|
39
|
-
|
40
|
-
it "should return tag_one with r_id 2 and tag_three with r_id 1" do
|
41
|
-
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
42
|
-
tag_group1.reverse_each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
43
|
-
end
|
44
|
-
|
45
|
-
it "should return tag_two with r_id 2 and tag_four with r_id 1" do
|
46
|
-
results = base_query.select_window(:row_number, over: :w, as: :r_id).group_by(&:id)
|
47
|
-
tag_group2.reverse_each.with_index { |tag, idx| expect(results[tag.id].first.r_id).to eq(idx + 1) }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|